/* SPDX-FileCopyrightText: © 2025 Hornwitser SPDX-License-Identifier: AGPL-3.0-or-later */ import { readFile, unlink, writeFile } from "node:fs/promises"; import type { ApiSchedule, ApiSubscription, ApiUserType } from "~/shared/types/api"; import type { Id } from "~/shared/types/common"; export interface ServerSession { id: number, account: ServerUser, }; interface StoredSession { id: number, accountId: number, } export interface ServerUser { id: Id, updatedAt: string, deleted?: boolean, type: ApiUserType, name?: string, interestedEventIds?: number[], interestedEventSlotIds?: number[], timezone?: string, locale?: string, } // For this demo I'm just storing the runtime data in JSON files. When making // this into proper application this should be replaced with an actual database. const schedulePath = "data/schedule.json"; const subscriptionsPath = "data/subscriptions.json"; const usersPath = "data/users.json"; const nextUserIdPath = "data/next-user-id.json"; const sessionsPath = "data/sessions.json"; const nextSessionIdPath = "data/next-session-id.json"; async function remove(path: string) { try { await unlink(path); } catch (err: any) { if (err.code !== "ENOENT") { throw err; } } } export async function deleteDatabase() { await remove(schedulePath); await remove(subscriptionsPath); await remove(usersPath); await remove(sessionsPath); } async function readJson(filePath: string, fallback: T) { let data: T extends () => infer R ? R : T; try { data = JSON.parse(await readFile(filePath, "utf-8")); } catch (err: any) { if (err.code !== "ENOENT") throw err; data = typeof fallback === "function" ? fallback() : fallback; } return data; } export async function readSchedule() { return readJson(schedulePath, (): ApiSchedule => ({ id: 111, updatedAt: new Date().toISOString(), })); } export async function writeSchedule(schedule: ApiSchedule) { await writeFile(schedulePath, JSON.stringify(schedule, undefined, "\t") + "\n", "utf-8"); } export async function readSubscriptions() { let subscriptions = await readJson(subscriptionsPath, []); if (subscriptions.length && "keys" in subscriptions[0]) { // Discard old format subscriptions = []; } return subscriptions; } export async function writeSubscriptions(subscriptions: ApiSubscription[]) { await writeFile(subscriptionsPath, JSON.stringify(subscriptions, undefined, "\t") + "\n", "utf-8"); } export async function readNextUserId() { return await readJson(nextUserIdPath, 0); } export async function writeNextUserId(nextId: number) { await writeFile(nextUserIdPath, String(nextId), "utf-8"); } export async function nextUserId() { let nextId = await readJson(nextUserIdPath, 0); if (nextId === 0) { nextId = Math.max(...(await readUsers()).map(user => user.id), -1) + 1; } await writeFile(nextUserIdPath, String(nextId + 1), "utf-8"); return nextId; } export async function readUsers() { return await readJson(usersPath, (): ServerUser[] => []); } export async function writeUsers(users: ServerUser[]) { await writeFile(usersPath, JSON.stringify(users, undefined, "\t") + "\n", "utf-8"); } export async function readNextSessionId() { return await readJson(nextSessionIdPath, 0); } export async function writeNextSessionId(nextId: number) { await writeFile(nextSessionIdPath, String(nextId), "utf-8"); } export async function nextSessionId() { const nextId = await readJson(nextSessionIdPath, 0); await writeFile(nextSessionIdPath, String(nextId + 1), "utf-8"); return nextId; } export async function readSessions() { const users = await readUsers(); const sessions: ServerSession[] = []; for (const stored of await readJson(sessionsPath, [])) { const user = users.find(user => user.id === stored.accountId); if (user) { sessions.push({ id: stored.id, account: user, }); } } return sessions; } export async function writeSessions(sessions: ServerSession[]) { const stored: StoredSession[] = sessions.map( session => ({ id: session.id, accountId: session.account.id, }), ); await writeFile(sessionsPath, JSON.stringify(stored, undefined, "\t") + "\n", "utf-8"); }