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 requireAccountSession(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); })