/* SPDX-FileCopyrightText: © 2025 Hornwitser SPDX-License-Identifier: AGPL-3.0-or-later */ import { z } from "zod/v4-mini"; import { idSchema, type Id } from "~/shared/types/common"; export const apiEntitySchema = z.object({ id: idSchema, updatedAt: z.string(), deleted: z.optional(z.literal(false)), }); export type ApiEntity = z.infer; export function defineApiEntity(fields: T) { return z.extend(apiEntitySchema, fields); } export function tombstonable(entitySchema: T) { return z.discriminatedUnion("deleted", [entitySchema, apiTombstoneSchema]); } export const apiTombstoneSchema = z.object({ id: idSchema, updatedAt: z.string(), deleted: z.literal(true), }); export type ApiTombstone = z.infer; export const apiUserTypeSchema = z.union([ z.literal("anonymous"), z.literal("regular"), z.literal("crew"), z.literal("admin"), ]) export type ApiUserType = z.infer; export interface ApiAccount { id: Id, updatedAt: string, type: ApiUserType, /** Name of the account. Not present on anonymous accounts */ name?: string, interestedEventIds?: number[], interestedEventSlotIds?: number[], timezone?: string, locale?: string, } export const apiAccountPatchSchema = z.object({ name: z.optional(z.string()), interestedEventIds: z.optional(z.array(z.number())), interestedEventSlotIds: z.optional(z.array(z.number())), timezone: z.optional(z.string()), locale: z.optional(z.string()), }); export type ApiAccountPatch = z.infer; export const apiSubscriptionSchema = z.object({ type: z.literal("push"), sessionId: z.number(), push: z.object({ endpoint: z.optional(z.string()), expirationTime: z.nullish(z.number()), keys: z.record(z.string(), z.string()), }), }); export type ApiSubscription = z.infer; export interface ApiSession { id: Id, account?: ApiAccount, push: boolean, } export const apiScheduleLocationSchema = defineApiEntity({ name: z.string(), description: z.optional(z.string()), }); export type ApiScheduleLocation = z.infer; export const apiScheduleEventSlotSchema = z.object({ id: idSchema, start: z.string(), end: z.string(), locationIds: z.array(idSchema), assigned: z.optional(z.array(z.number())), interested: z.optional(z.number()), }); export type ApiScheduleEventSlot = z.infer; export const apiScheduleEventSchema = defineApiEntity({ name: z.string(), crew: z.optional(z.boolean()), host: z.optional(z.string()), cancelled: z.optional(z.boolean()), description: z.optional(z.string()), interested: z.optional(z.number()), slots: z.array(apiScheduleEventSlotSchema), }); export type ApiScheduleEvent = z.infer; export const apiScheduleRoleSchema = defineApiEntity({ name: z.string(), description: z.optional(z.string()), }); export type ApiScheduleRole = z.infer; export const apiScheduleShiftSlotSchema = z.object({ id: idSchema, start: z.string(), end: z.string(), assigned: z.optional(z.array(z.number())), }); export type ApiScheduleShiftSlot = z.infer; export const apiScheduleShiftSchema = defineApiEntity({ roleId: z.optional(idSchema), name: z.string(), description: z.optional(z.string()), slots: z.array(apiScheduleShiftSlotSchema), }); export type ApiScheduleShift = z.infer; export const apiScheduleSchema = defineApiEntity({ id: z.literal(111), locations: z.optional(z.array(tombstonable(apiScheduleLocationSchema))), events: z.optional(z.array(tombstonable(apiScheduleEventSchema))), roles: z.optional(z.array(tombstonable(apiScheduleRoleSchema))), shifts: z.optional(z.array(tombstonable(apiScheduleShiftSchema))), }); export type ApiSchedule = z.infer; export const apiUserSchema = defineApiEntity({ type: apiUserTypeSchema, name: z.optional(z.string()), }); export type ApiUser = z.infer; export const apiUserPatchSchema = z.object({ id: idSchema, type: z.optional(apiUserTypeSchema), name: z.optional(z.string()), }); export type ApiUserPatch = z.infer; export interface ApiAccountUpdate { type: "account-update", data: ApiAccount, } export interface ApiConnected { type: "connected", session?: ApiSession, } export interface ApiScheduleUpdate { type: "schedule-update", updatedFrom?: string, data: ApiSchedule | ApiTombstone, } export interface ApiSessionExpired { type: "session-expired", sessionId: Id, } export interface ApiUserUpdate { type: "user-update", updatedFrom?: string, data: ApiUser | ApiTombstone, } export type ApiEvent = | ApiAccountUpdate | ApiConnected | ApiScheduleUpdate | ApiSessionExpired | ApiUserUpdate ;