owltide/shared/types/api.ts
Hornwitser 5144bf2b37 Allow shifts without a role
Allow a shift to have no role associated with it in order to simplify
conflict resolution around situations like a shift being created while
the role it was assoiated with was deleted. This also allows for shifts
that are freestanding to be created in case having a role doesn't make
sense for it.
2025-06-30 16:36:47 +02:00

164 lines
4.5 KiB
TypeScript

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<typeof apiEntitySchema>;
export function defineApiEntity<T extends {}>(fields: T) {
return z.extend(apiEntitySchema, fields);
}
export function tombstonable<T extends z.ZodMiniObject>(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<typeof apiTombstoneSchema>;
export const apiUserTypeSchema = z.union([
z.literal("anonymous"),
z.literal("regular"),
z.literal("crew"),
z.literal("admin"),
])
export type ApiUserType = z.infer<typeof apiUserTypeSchema>;
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<typeof apiAccountPatchSchema>;
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<typeof apiSubscriptionSchema>;
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<typeof apiScheduleLocationSchema>;
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<typeof apiScheduleEventSlotSchema>;
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<typeof apiScheduleEventSchema>;
export const apiScheduleRoleSchema = defineApiEntity({
name: z.string(),
description: z.optional(z.string()),
});
export type ApiScheduleRole = z.infer<typeof apiScheduleRoleSchema>;
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<typeof apiScheduleShiftSlotSchema>;
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<typeof apiScheduleShiftSchema>;
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<typeof apiScheduleSchema>;
export const apiUserSchema = defineApiEntity({
type: apiUserTypeSchema,
name: z.optional(z.string()),
});
export type ApiUser = z.infer<typeof apiUserSchema>;
export const apiUserPatchSchema = z.object({
id: idSchema,
type: z.optional(apiUserTypeSchema),
name: z.optional(z.string()),
});
export type ApiUserPatch = z.infer<typeof apiUserPatchSchema>;
export interface ApiAccountUpdate {
type: "account-update",
data: ApiAccount,
}
export interface ApiScheduleUpdate {
type: "schedule-update",
updatedFrom?: string,
data: ApiSchedule | ApiTombstone,
}
export interface ApiUserUpdate {
type: "user-update",
updatedFrom?: string,
data: ApiUser | ApiTombstone,
}
export type ApiEvent =
| ApiAccountUpdate
| ApiScheduleUpdate
| ApiUserUpdate
;