owltide/server/database.ts

157 lines
4.1 KiB
TypeScript
Raw Normal View History

/*
SPDX-FileCopyrightText: © 2025 Hornwitser <code@hornwitser.no>
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;
}
}
}
2025-06-23 12:48:34 +02:00
export async function deleteDatabase() {
await remove(schedulePath);
await remove(subscriptionsPath);
await remove(usersPath);
await remove(sessionsPath);
}
async function readJson<T>(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<ApiSubscription[]>(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);
2025-03-07 23:53:57 +01:00
if (nextId === 0) {
nextId = Math.max(...(await readUsers()).map(user => user.id), -1) + 1;
2025-03-07 23:53:57 +01:00
}
await writeFile(nextUserIdPath, String(nextId + 1), "utf-8");
2025-03-07 23:53:57 +01:00
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<StoredSession[]>(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");
}