/* SPDX-FileCopyrightText: © 2025 Hornwitser SPDX-License-Identifier: AGPL-3.0-or-later */ import { readSchedule, type ServerUser, writeSchedule } from '~/server/database'; import { broadcastEvent } from '~/server/streams'; import type { ApiSchedule, ApiTombstone } from '~/shared/types/api'; export async function updateScheduleInterestedCounts(users: ServerUser[]) { const eventCounts = new Map(); const eventSlotCounts = new Map(); for (const user of users) { if (user.deleted) continue; if (user.interestedEventIds) for (const id of user.interestedEventIds) eventCounts.set(id, (eventCounts.get(id) ?? 0) + 1); if (user.interestedEventSlotIds) for (const id of user.interestedEventSlotIds) eventSlotCounts.set(id, (eventSlotCounts.get(id) ?? 0) + 1); } const schedule = await readSchedule(); if (schedule.deleted) { throw new Error("Deleted schedule not implemented"); } const update: ApiSchedule = { id: schedule.id, updatedAt: new Date().toISOString(), events: [], }; const updatedFrom = schedule.updatedAt; for (const event of schedule.events ?? []) { let modified = false; if (event.deleted) continue; let count = eventCounts.get(event.id); if (count !== event.interested) { event.interested = eventCounts.get(event.id); modified = true; } for (const slot of event.slots) { let slotCount = eventSlotCounts.get(slot.id); if (slotCount !== slot.interested) { slot.interested = slotCount; modified = true; } } if (modified) { event.updatedAt = update.updatedAt; update.events!.push(event); } } if (!update.events!.length) { return; // No changes } schedule.updatedAt = updatedFrom; await writeSchedule(schedule); await broadcastEvent({ type: "schedule-update", updatedFrom, data: update, }); } export function canSeeAnonymous(userType: string | undefined) { return userType === "admin"; } export function canSeeCrew(userType: string | undefined) { return userType === "crew" || userType === "admin"; } /** Filters out crew visible only parts of schedule */ export function filterSchedule(schedule: ApiSchedule | ApiTombstone): ApiSchedule | ApiTombstone { if (schedule.deleted) { return schedule; } return { id: schedule.id, updatedAt: schedule.updatedAt, locations: schedule.locations, events: (schedule.events ?? []) .map(event => ( event.deleted ? event : event.crew // Pretend crew events are deleted. ? { id: event.id, deleted: true, updatedAt: event.updatedAt } : { ...event, slots: event.slots.map(slot => ({ ...slot, assigned: undefined, } )), })), } }