Rename and refactor the types passed over the API to be based on an entity that's either living or a tombstone. A living entity has a deleted property that's either undefined or false, while a tombstone has a deleted property set to true. All entities have a numeric id and an updatedAt timestamp. To sync entities, an array of replacements are passed around. Living entities are replaced with tombstones when they're deleted. And tombstones are replaced with living entities when restored.
95 lines
2.7 KiB
TypeScript
95 lines
2.7 KiB
TypeScript
import { z } from "zod/v4-mini";
|
|
import { readAccounts, readSchedule, writeSchedule } from "~/server/database";
|
|
import { broadcastEvent } from "~/server/streams";
|
|
import { apiScheduleSchema } from "~/shared/types/api";
|
|
import { applyUpdatesToArray } from "~/shared/utils/update";
|
|
|
|
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 { success, error, data: update } = apiScheduleSchema.safeParse(await readBody(event));
|
|
if (!success) {
|
|
throw createError({
|
|
status: 400,
|
|
statusText: "Bad Request",
|
|
message: z.prettifyError(error),
|
|
});
|
|
}
|
|
|
|
if (update.deleted) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
statusMessage: "Not implemented",
|
|
});
|
|
}
|
|
|
|
const schedule = await readSchedule();
|
|
|
|
if (schedule.deleted) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
statusMessage: "Not implemented",
|
|
});
|
|
}
|
|
|
|
// Validate edit restrictions for crew
|
|
if (account.type === "crew") {
|
|
if (update.locations?.length) {
|
|
throw createError({
|
|
status: 403,
|
|
statusMessage: "Forbidden",
|
|
message: "Only admin accounts can edit locations.",
|
|
});
|
|
}
|
|
for (const event of update.events ?? []) {
|
|
const original = schedule.events?.find(e => e.id === event.id);
|
|
if (original && !original.deleted && !original.crew) {
|
|
throw createError({
|
|
status: 403,
|
|
statusMessage: "Forbidden",
|
|
message: "Only admin accounts can edit public events.",
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update schedule
|
|
const updatedFrom = schedule.updatedAt;
|
|
update.updatedAt = new Date().toISOString();
|
|
if (update.events) {
|
|
for (const event of update.events) event.updatedAt = update.updatedAt;
|
|
applyUpdatesToArray(update.events, schedule.events = schedule.events ?? []);
|
|
}
|
|
if (update.locations) {
|
|
for (const location of update.locations) location.updatedAt = update.updatedAt;
|
|
applyUpdatesToArray(update.locations, schedule.locations = schedule.locations ?? []);
|
|
}
|
|
if (update.roles) {
|
|
for (const role of update.roles) role.updatedAt = update.updatedAt;
|
|
applyUpdatesToArray(update.roles, schedule.roles = schedule.roles ?? []);
|
|
}
|
|
if (update.shifts) {
|
|
for (const shift of update.shifts) shift.updatedAt = update.updatedAt;
|
|
applyUpdatesToArray(update.shifts, schedule.shifts = schedule.shifts ?? []);
|
|
}
|
|
|
|
await writeSchedule(schedule);
|
|
await broadcastEvent({
|
|
type: "schedule-update",
|
|
updatedFrom,
|
|
data: update,
|
|
});
|
|
})
|