/* SPDX-FileCopyrightText: © 2025 Hornwitser SPDX-License-Identifier: AGPL-3.0-or-later */ import { readFileSync, writeFileSync, unlinkSync } from "node:fs"; import type { ApiAuthenticationProvider, ApiEvent, ApiSchedule, ApiSubscription, ApiUserType } from "~/shared/types/api"; import type { Id } from "~/shared/types/common"; export interface ServerSession { id: Id, access: ApiUserType, accountId?: number, authenticationProvider?: ApiAuthenticationProvider, authenticationSlug?: string, authenticationName?: string, rotatesAtMs: number, expiresAtMs?: number, discardAtMs: number, successor?: Id, }; export interface ServerUser { id: Id, updatedAt: string, deleted?: boolean, type: ApiUserType, name?: string, interestedEventIds?: number[], interestedEventSlotIds?: number[], timezone?: string, locale?: string, } export interface ServerAuthenticationMethod { id: Id, provider: ApiAuthenticationProvider, slug: string, name: string, userId: Id, } // 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"; const authMethodPath = "data/auth-method.json"; const nextAuthenticationMethodIdPath = "data/auth-method-id.json" const nextEventIdPath = "data/next-event-id.json"; const eventsPath = "data/events.json"; function remove(path: string) { try { unlinkSync(path); } catch (err: any) { if (err.code !== "ENOENT") { throw err; } } } export function deleteDatabase() { remove(schedulePath); remove(subscriptionsPath); remove(usersPath); remove(sessionsPath); } function readJson(filePath: string, fallback: T) { let data: T extends () => infer R ? R : T; try { data = JSON.parse(readFileSync(filePath, "utf-8")); } catch (err: any) { if (err.code !== "ENOENT") throw err; data = typeof fallback === "function" ? fallback() : fallback; } return data; } export function readSchedule() { return readJson(schedulePath, (): ApiSchedule => ({ id: 111, updatedAt: new Date().toISOString(), })); } export function writeSchedule(schedule: ApiSchedule) { writeFileSync(schedulePath, JSON.stringify(schedule, undefined, "\t") + "\n", "utf-8"); } export function readSubscriptions() { let subscriptions = readJson(subscriptionsPath, []); if (subscriptions.length && "keys" in subscriptions[0]) { // Discard old format subscriptions = []; } return subscriptions; } export function writeSubscriptions(subscriptions: ApiSubscription[]) { writeFileSync(subscriptionsPath, JSON.stringify(subscriptions, undefined, "\t") + "\n", "utf-8"); } export function readNextUserId() { return readJson(nextUserIdPath, 0); } export function writeNextUserId(nextId: number) { writeFileSync(nextUserIdPath, String(nextId), "utf-8"); } export function nextUserId() { let nextId = readJson(nextUserIdPath, 0); if (nextId === 0) { nextId = Math.max(...(readUsers()).map(user => user.id), -1) + 1; } writeFileSync(nextUserIdPath, String(nextId + 1), "utf-8"); return nextId; } export function readUsers() { return readJson(usersPath, (): ServerUser[] => []); } export function writeUsers(users: ServerUser[]) { writeFileSync(usersPath, JSON.stringify(users, undefined, "\t") + "\n", "utf-8"); } export function readNextSessionId() { return readJson(nextSessionIdPath, 0); } export function writeNextSessionId(nextId: number) { writeFileSync(nextSessionIdPath, String(nextId), "utf-8"); } export function nextSessionId() { const nextId = readJson(nextSessionIdPath, 0); writeFileSync(nextSessionIdPath, String(nextId + 1), "utf-8"); return nextId; } export function readSessions() { return readJson(sessionsPath, []) } export function writeSessions(sessions: ServerSession[]) { writeFileSync(sessionsPath, JSON.stringify(sessions, undefined, "\t") + "\n", "utf-8"); } export function nextAuthenticationMethodId() { const nextId = readJson(nextAuthenticationMethodIdPath, 0); writeFileSync(nextAuthenticationMethodIdPath, String(nextId + 1), "utf-8"); return nextId; } export function writeNextAuthenticationMethodId(nextId: number) { writeFileSync(nextAuthenticationMethodIdPath, String(nextId), "utf-8"); } export function readAuthenticationMethods() { return readJson(authMethodPath, []) } export function writeAuthenticationMethods(authMethods: ServerAuthenticationMethod[]) { writeFileSync(authMethodPath, JSON.stringify(authMethods, undefined, "\t") + "\n", "utf-8"); } export function nextEventId() { const nextId = readJson(nextEventIdPath, 0); writeFileSync(nextEventIdPath, String(nextId + 1), "utf-8"); return nextId; } export function writeNextEventId(nextId: number) { writeFileSync(nextEventIdPath, String(nextId), "utf-8"); } export function readEvents() { return readJson(eventsPath, []) } export function writeEvents(events: ApiEvent[]) { writeFileSync(eventsPath, JSON.stringify(events, undefined, "\t") + "\n", "utf-8"); }