owltide/server/api/schedule.patch.ts
Hornwitser 251e83f640
All checks were successful
/ build (push) Successful in 1m12s
/ deploy (push) Successful in 16s
Rename AcountSession to ServerSession
Start the work of clearly distingushing client side types, server side
types and types shared over the API by renaming "AccountSession" and
"Session" names used on the server to "ServerSession".
2025-06-09 16:51:05 +02:00

88 lines
2.7 KiB
TypeScript

import type { SchedulePatch } from "~/shared/types/schedule";
import { readAccounts, readSchedule, writeSchedule } from "~/server/database";
import { broadcastUpdate } from "~/server/streams";
import { applyChangeArray } from "~/shared/utils/changes";
function isChange(change: unknown) {
return (
typeof change === "object"
&& change !== null
&& "op" in change
&& (
change.op === "set" || change.op === "del"
)
&& "data" in change
&& typeof change.data === "object"
&& change.data !== null
&& "id" in change.data
&& typeof change.data.id === "string"
)
}
function isChangeArray(data: unknown) {
return data instanceof Array && data.every(item => isChange(item));
}
function isPatch(data: unknown): SchedulePatch {
if (
typeof data !== "object"
|| data === null
|| data instanceof Array
|| "locations" in data && !isChangeArray(data.locations)
|| "events" in data && !isChangeArray(data.events)
|| "roles" in data && !isChangeArray(data.roles)
|| "rota" in data && !isChangeArray(data.rota)
)
throw new Error("Invalid patch data")
return data // TODO: Actually validate the whole structure with e.g ajv or zod
}
export default defineEventHandler(async (event) => {
const session = await requireServerSession(event);
const accounts = await readAccounts();
const account = accounts.find(a => a.id === session.accountId);
if (!account) {
throw new Error("Account does not exist");
}
if (account.type !== "admin" && account.type !== "crew") {
throw createError({
status: 403,
statusMessage: "Forbidden",
message: "Only crew and admin accounts can edit the schedule.",
});
}
const schedule = await readSchedule();
const patch = await readValidatedBody(event, isPatch);
// Validate edit restrictions for crew
if (account.type === "crew") {
if (patch.locations?.length) {
throw createError({
status: 403,
statusMessage: "Forbidden",
message: "Only admin accounts can edit locations.",
});
}
for (const event of patch.events ?? []) {
const id = event.op === "set" ? event.data.id : event.id;
const original = schedule.events.find(e => e.id === id);
if (original && !original.crew) {
throw createError({
status: 403,
statusMessage: "Forbidden",
message: "Only admin accounts can edit public events.",
});
}
}
}
if (patch.events) applyChangeArray(patch.events, schedule.events);
if (patch.locations) applyChangeArray(patch.locations, schedule.locations);
if (patch.roles) applyChangeArray(patch.roles, schedule.roles = schedule.roles ?? []);
if (patch.rota) applyChangeArray(patch.rota, schedule.rota = schedule.rota ?? []);
await writeSchedule(schedule);
await broadcastUpdate(schedule);
})