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