diff --git a/apps/rpc/src/index.ts b/apps/rpc/src/index.ts index 10ea713eff..296a372342 100644 --- a/apps/rpc/src/index.ts +++ b/apps/rpc/src/index.ts @@ -2,6 +2,7 @@ export type { AppRouter } from "./app-router" export type { Pageable } from "./query" export type * from "./modules/article/article-router" +export type * from "./modules/event/attendance-router" export type * from "./modules/event/event-router" export type * from "./modules/audit-log/audit-log-router" export type * from "./modules/company/company-router" diff --git a/apps/rpc/src/modules/event/attendance-router.ts b/apps/rpc/src/modules/event/attendance-router.ts index 32bfd73403..4a46ca2a70 100644 --- a/apps/rpc/src/modules/event/attendance-router.ts +++ b/apps/rpc/src/modules/event/attendance-router.ts @@ -13,350 +13,411 @@ import { } from "@dotkomonline/types" import { getCurrentUTC } from "@dotkomonline/utils" import { TRPCError } from "@trpc/server" +import type { inferProcedureInput, inferProcedureOutput } from "@trpc/server" import { addHours, max } from "date-fns" import { z } from "zod" +import { isEditor } from "../../authorization" import { FailedPreconditionError } from "../../error" -import { authenticatedProcedure, procedure, staffProcedure, t } from "../../trpc" +import { withAuditLogEntry, withAuthentication, withAuthorization, withDatabaseTransaction } from "../../middlewares" +import { procedure, t } from "../../trpc" -export const attendanceRouter = t.router({ - createPool: staffProcedure - .input( - z.object({ - id: AttendanceSchema.shape.id, - input: AttendancePoolWriteSchema, - }) - ) - .mutation(async ({ input, ctx }) => { - return ctx.executeAuditedTransaction(async (handle) => - ctx.attendanceService.createAttendancePool(handle, input.id, input.input) - ) - }), +export type CreatePoolInput = inferProcedureInput +export type CreatePoolOutput = inferProcedureOutput +const createPoolProcedure = procedure + .input( + z.object({ + id: AttendanceSchema.shape.id, + input: AttendancePoolWriteSchema, + }) + ) + .use(withAuthentication()) + .use(withAuthorization(isEditor())) + .use(withDatabaseTransaction()) + .use(withAuditLogEntry()) + .mutation(async ({ input, ctx }) => { + return ctx.attendanceService.createAttendancePool(ctx.handle, input.id, input.input) + }) - updatePool: staffProcedure - .input( - z.object({ - id: AttendancePoolSchema.shape.id, - input: AttendancePoolWriteSchema.partial(), - }) - ) - .mutation(async ({ input, ctx }) => { - return ctx.executeAuditedTransaction(async (handle) => { - const attendance = await ctx.attendanceService.getAttendanceByPoolId(handle, input.id) - const pool = attendance.pools.find((pool) => pool.id === input.id) - if (pool === undefined) { - throw new TRPCError({ code: "NOT_FOUND" }) - } - await ctx.attendanceService.updateAttendancePool(handle, input.id, { ...pool, ...input.input }) - }) - }), +export type UpdatePoolInput = inferProcedureInput +export type UpdatePoolOutput = inferProcedureOutput +const updatePoolProcedure = procedure + .input( + z.object({ + id: AttendancePoolSchema.shape.id, + input: AttendancePoolWriteSchema.partial(), + }) + ) + .use(withAuthentication()) + .use(withAuthorization(isEditor())) + .use(withDatabaseTransaction()) + .use(withAuditLogEntry()) + .mutation(async ({ input, ctx }) => { + const attendance = await ctx.attendanceService.getAttendanceByPoolId(ctx.handle, input.id) + const pool = attendance.pools.find((pool) => pool.id === input.id) + if (pool === undefined) { + throw new TRPCError({ code: "NOT_FOUND" }) + } + await ctx.attendanceService.updateAttendancePool(ctx.handle, input.id, { ...pool, ...input.input }) + }) - deletePool: staffProcedure - .input( - z.object({ - id: AttendancePoolSchema.shape.id, - }) - ) - .mutation(async ({ input, ctx }) => { - return ctx.executeAuditedTransaction(async (handle) => - ctx.attendanceService.deleteAttendancePool(handle, input.id) - ) - }), +export type DeletePoolInput = inferProcedureInput +export type DeletePoolOutput = inferProcedureOutput +const deletePoolProcedure = procedure + .input( + z.object({ + id: AttendancePoolSchema.shape.id, + }) + ) + .use(withAuthentication()) + .use(withAuthorization(isEditor())) + .use(withDatabaseTransaction()) + .use(withAuditLogEntry()) + .mutation(async ({ input, ctx }) => { + return ctx.attendanceService.deleteAttendancePool(ctx.handle, input.id) + }) - adminRegisterForEvent: staffProcedure - .input( - z.object({ - attendanceId: AttendanceSchema.shape.id, - attendancePoolId: AttendancePoolSchema.shape.id, - userId: UserSchema.shape.id, - }) +export type AdminRegisterForEventInput = inferProcedureInput +export type AdminRegisterForEventOutput = inferProcedureOutput +const adminRegisterForEventProcedure = procedure + .input( + z.object({ + attendanceId: AttendanceSchema.shape.id, + attendancePoolId: AttendancePoolSchema.shape.id, + userId: UserSchema.shape.id, + }) + ) + .use(withAuthentication()) + .use(withAuthorization(isEditor())) + .use(withDatabaseTransaction()) + .use(withAuditLogEntry()) + .mutation(async ({ input, ctx }) => { + const result = await ctx.attendanceService.getRegistrationAvailability( + ctx.handle, + input.attendanceId, + input.userId, + { + ignoreRegistrationWindow: true, + immediateReservation: true, + immediatePayment: false, + overriddenAttendancePoolId: input.attendancePoolId, + ignoreRegisteredToParent: true, + } ) - .mutation(async ({ input, ctx }) => { - return ctx.executeAuditedTransaction(async (handle) => { - const result = await ctx.attendanceService.getRegistrationAvailability( - handle, - input.attendanceId, - input.userId, - { - ignoreRegistrationWindow: true, - immediateReservation: true, - immediatePayment: false, - overriddenAttendancePoolId: input.attendancePoolId, - ignoreRegisteredToParent: true, - } - ) - if (!result.success) { - throw new FailedPreconditionError(`Failed to register: ${result.cause}`) - } - return await ctx.attendanceService.registerAttendee(handle, result) - }) - }), + if (!result.success) { + throw new FailedPreconditionError(`Failed to register: ${result.cause}`) + } + return await ctx.attendanceService.registerAttendee(ctx.handle, result) + }) - updateAttendancePayment: staffProcedure - .input( - z.object({ - id: AttendanceSchema.shape.id, - price: z.number().int().nullable(), - }) - ) - .mutation(async ({ input, ctx }) => { - return ctx.executeAuditedTransaction(async (handle) => { - const attendance = await ctx.attendanceService.getAttendanceById(handle, input.id) - if (attendance === undefined) { - throw new TRPCError({ code: "NOT_FOUND" }) - } - return ctx.attendanceService.updateAttendancePaymentPrice(handle, input.id, input.price) - }) - }), +export type UpdateAttendancePaymentInput = inferProcedureInput +export type UpdateAttendancePaymentOutput = inferProcedureOutput +const updateAttendancePaymentProcedure = procedure + .input( + z.object({ + id: AttendanceSchema.shape.id, + price: z.number().int().nullable(), + }) + ) + .use(withAuthentication()) + .use(withAuthorization(isEditor())) + .use(withDatabaseTransaction()) + .use(withAuditLogEntry()) + .mutation(async ({ input, ctx }) => { + const attendance = await ctx.attendanceService.getAttendanceById(ctx.handle, input.id) + if (attendance === undefined) { + throw new TRPCError({ code: "NOT_FOUND" }) + } + return ctx.attendanceService.updateAttendancePaymentPrice(ctx.handle, input.id, input.price) + }) - getSelectionsResults: staffProcedure - .input( - z.object({ - attendanceId: AttendanceSchema.shape.id, - }) - ) - .query(async ({ input, ctx }) => { - return ctx.executeTransaction(async (handle) => { - const attendance = await ctx.attendanceService.getAttendanceById(handle, input.attendanceId) - const allSelectionResponses = attendance.attendees.flatMap((attendee) => attendee.selections) +export type GetSelectionsResultsInput = inferProcedureInput +export type GetSelectionsResultsOutput = inferProcedureOutput +const getSelectionsResultsProcedure = procedure + .input(z.object({ attendanceId: AttendanceSchema.shape.id })) + .use(withAuthentication()) + .use(withAuthorization(isEditor())) + .use(withDatabaseTransaction()) + .query(async ({ input, ctx }) => { + const attendance = await ctx.attendanceService.getAttendanceById(ctx.handle, input.attendanceId) + const allSelectionResponses = attendance.attendees.flatMap((attendee) => attendee.selections) - return attendance.selections.map((selection) => { - const selectionResponses = allSelectionResponses.filter((response) => response.selectionId === selection.id) + return attendance.selections.map((selection) => { + const selectionResponses = allSelectionResponses.filter((response) => response.selectionId === selection.id) - return { - id: selection.id, - name: selection.name, - totalCount: selectionResponses.length, - options: selection.options.map((option) => ({ - id: option.id, - name: option.name, - count: selectionResponses.filter((response) => response.optionId === option.id).length, - })), - } - }) - }) - }), + return { + id: selection.id, + name: selection.name, + totalCount: selectionResponses.length, + options: selection.options.map((option) => ({ + id: option.id, + name: option.name, + count: selectionResponses.filter((response) => response.optionId === option.id).length, + })), + } + }) + }) - getRegistrationAvailability: authenticatedProcedure - .input( - z.object({ - attendanceId: AttendanceSchema.shape.id, - }) +export type GetRegistrationAvailabilityInput = inferProcedureInput +export type GetRegistrationAvailabilityOutput = inferProcedureOutput +const getRegistrationAvailabilityProcedure = procedure + .input(z.object({ attendanceId: AttendanceSchema.shape.id })) + .use(withAuthentication()) + .use(withDatabaseTransaction()) + .query(async ({ input, ctx }) => { + return await ctx.attendanceService.getRegistrationAvailability( + ctx.handle, + input.attendanceId, + ctx.principal.subject, + { + ignoreRegistrationWindow: false, + immediateReservation: false, + immediatePayment: true, + overriddenAttendancePoolId: null, + ignoreRegisteredToParent: false, + } ) - .query(async ({ input, ctx }) => - ctx.executeTransaction(async (handle) => { - return await ctx.attendanceService.getRegistrationAvailability( - handle, - input.attendanceId, - ctx.principal.subject, - { - ignoreRegistrationWindow: false, - immediateReservation: false, - immediatePayment: true, - overriddenAttendancePoolId: null, - ignoreRegisteredToParent: false, - } - ) - }) - ), + }) - registerForEvent: authenticatedProcedure - .input( - z.object({ - attendanceId: AttendanceSchema.shape.id, - }) +export type RegisterForEventInput = inferProcedureInput +export type RegisterForEventOutput = inferProcedureOutput +const registerForEventProcedure = procedure + .input(z.object({ attendanceId: AttendanceSchema.shape.id })) + .use(withAuthentication()) + .use(withDatabaseTransaction()) + .use(withAuditLogEntry()) + .mutation(async ({ input, ctx }) => { + const result = await ctx.attendanceService.getRegistrationAvailability( + ctx.handle, + input.attendanceId, + ctx.principal.subject, + { + ignoreRegistrationWindow: false, + immediateReservation: false, + immediatePayment: true, + overriddenAttendancePoolId: null, + ignoreRegisteredToParent: false, + } ) - .mutation(async ({ input, ctx }) => - ctx.executeAuditedTransaction(async (handle) => { - const result = await ctx.attendanceService.getRegistrationAvailability( - handle, - input.attendanceId, - ctx.principal.subject, - { - ignoreRegistrationWindow: false, - immediateReservation: false, - immediatePayment: true, - overriddenAttendancePoolId: null, - ignoreRegisteredToParent: false, - } - ) - if (!result.success) { - throw new FailedPreconditionError(`Failed to register: ${result.cause}`) - } - return await ctx.attendanceService.registerAttendee(handle, result) - }) - ), + if (!result.success) { + throw new FailedPreconditionError(`Failed to register: ${result.cause}`) + } + return await ctx.attendanceService.registerAttendee(ctx.handle, result) + }) - onRegisterChange: procedure - .input(z.object({ attendanceId: AttendanceSchema.shape.id })) - .subscription(async function* ({ input, ctx, signal }) { - for await (const [data] of on(ctx.eventEmitter, "attendance:register-change", { signal })) { - const attendeeUpdateData = data as { attendee: Attendee; status: "registered" | "deregistered" } +export type OnRegisterChangeInput = inferProcedureInput +export type OnRegisterChangeOutput = inferProcedureOutput +const onRegisterChangeProcedure = procedure + .input(z.object({ attendanceId: AttendanceSchema.shape.id })) + .use(withDatabaseTransaction()) + .subscription(async function* ({ input, ctx, signal }) { + for await (const [data] of on(ctx.eventEmitter, "attendance:register-change", { signal })) { + const attendeeUpdateData = data as { attendee: Attendee; status: "registered" | "deregistered" } - if (attendeeUpdateData.attendee.attendanceId !== input.attendanceId) { - continue - } - - yield attendeeUpdateData + if (attendeeUpdateData.attendee.attendanceId !== input.attendanceId) { + continue } - }), - cancelAttendeePayment: staffProcedure - .input( - z.object({ - attendeeId: AttendeeSchema.shape.id, - }) - ) - .mutation(async ({ input: { attendeeId }, ctx }) => { - return ctx.executeAuditedTransaction(async (handle) => - ctx.attendanceService.cancelAttendeePayment(handle, attendeeId, ctx.principal.subject) - ) - }), - startAttendeePayment: staffProcedure - .input( - z.object({ - attendeeId: AttendeeSchema.shape.id, - }) - ) - .mutation(async ({ input: { attendeeId }, ctx }) => { - return ctx.executeAuditedTransaction(async (handle) => { - let deadline = addHours(getCurrentUTC(), 24) + yield attendeeUpdateData + } + }) - // DELETE THIS START - // turn deadline back to const after this is removed - const julebordAttendanceId = "b470eda0-4650-4dbd-bb6e-7bcf9c7757f9" - const arbitraryJulebordDeadline = new TZDate("2025-11-07T19:00:00Z") // this is 20:00 local time in norway - const attendee = await ctx.attendanceService.getAttendeeById(handle, attendeeId) - if (attendee.attendanceId === julebordAttendanceId) { - deadline = max([arbitraryJulebordDeadline, deadline]) - } - // DELETE THIS END +export type CancelAttendeePaymentInput = inferProcedureInput +export type CancelAttendeePaymentOutput = inferProcedureOutput +const cancelAttendeePaymentProcedure = procedure + .input(z.object({ attendeeId: AttendeeSchema.shape.id })) + .use(withAuthentication()) + .use(withAuthorization(isEditor())) + .use(withDatabaseTransaction()) + .use(withAuditLogEntry()) + .mutation(async ({ input: { attendeeId }, ctx }) => { + return ctx.attendanceService.cancelAttendeePayment(ctx.handle, attendeeId, ctx.principal.subject) + }) - return ctx.attendanceService.startAttendeePayment(handle, attendeeId, deadline) - }) - }), - deregisterForEvent: authenticatedProcedure - .input( - z.object({ - attendanceId: AttendancePoolSchema.shape.id, - deregisterReason: z.object({ - type: DeregisterReasonTypeSchema, - details: z.string().nullable(), - }), - }) - ) - .mutation(async ({ input, ctx }) => { - return ctx.executeAuditedTransaction(async (handle) => { - const attendance = await ctx.attendanceService.getAttendanceById(handle, input.attendanceId) - const attendee = attendance.attendees.find((attendee) => attendee.user.id === ctx.principal.subject) - if (attendee === undefined) { - throw new TRPCError({ code: "NOT_FOUND" }) - } - await ctx.attendanceService.deregisterAttendee(handle, attendee.id, { - ignoreDeregistrationWindow: false, - }) +export type StartAttendeePaymentInput = inferProcedureInput +export type StartAttendeePaymentOutput = inferProcedureOutput +const startAttendeePaymentProcedure = procedure + .input(z.object({ attendeeId: AttendeeSchema.shape.id })) + .use(withAuthentication()) + .use(withAuthorization(isEditor())) + .use(withDatabaseTransaction()) + .use(withAuditLogEntry()) + .mutation(async ({ input: { attendeeId }, ctx }) => { + let deadline = addHours(getCurrentUTC(), 24) - const event = await ctx.eventService.getByAttendanceId(handle, attendance.id) - await ctx.eventService.createDeregisterReason(handle, { - ...input.deregisterReason, - userId: ctx.principal.subject, - eventId: event.id, - registeredAt: attendee.createdAt, - userGrade: attendee.userGrade, - }) - }) - }), + // DELETE THIS START + // turn deadline back to const after this is removed + const julebordAttendanceId = "b470eda0-4650-4dbd-bb6e-7bcf9c7757f9" + const arbitraryJulebordDeadline = new TZDate("2025-11-07T19:00:00Z") // this is 20:00 local time in norway + const attendee = await ctx.attendanceService.getAttendeeById(ctx.handle, attendeeId) + if (attendee.attendanceId === julebordAttendanceId) { + deadline = max([arbitraryJulebordDeadline, deadline]) + } + // DELETE THIS END - adminDeregisterForEvent: staffProcedure - .input( - z.object({ - attendeeId: AttendeeSchema.shape.id, - }) - ) - .mutation(async ({ input, ctx }) => { - return ctx.executeAuditedTransaction(async (handle) => { - const attendance = await ctx.attendanceService.getAttendanceByAttendeeId(handle, input.attendeeId) - const attendee = attendance.attendees.find((attendee) => attendee.id === input.attendeeId) - if (attendee === undefined) { - throw new TRPCError({ code: "NOT_FOUND" }) - } - return await ctx.attendanceService.deregisterAttendee(handle, attendee.id, { - ignoreDeregistrationWindow: true, - }) - }) - }), + return ctx.attendanceService.startAttendeePayment(ctx.handle, attendeeId, deadline) + }) - adminUpdateAtteendeeReserved: staffProcedure - .input( - z.object({ - attendeeId: AttendeeSchema.shape.id, - reserved: AttendeeSchema.shape.reserved, - }) - ) - .mutation(async ({ input, ctx }) => { - return ctx.executeAuditedTransaction(async (handle) => { - await ctx.attendanceService.updateAttendeeById(handle, input.attendeeId, { reserved: input.reserved }) - }) - }), +export type DeregisterForEventInput = inferProcedureInput +export type DeregisterForEventOutput = inferProcedureOutput +const deregisterForEventProcedure = procedure + .input( + z.object({ + attendanceId: AttendancePoolSchema.shape.id, + deregisterReason: z.object({ + type: DeregisterReasonTypeSchema, + details: z.string().nullable(), + }), + }) + ) + .use(withAuthentication()) + .use(withDatabaseTransaction()) + .use(withAuditLogEntry()) + .mutation(async ({ input, ctx }) => { + const attendance = await ctx.attendanceService.getAttendanceById(ctx.handle, input.attendanceId) + const attendee = attendance.attendees.find((attendee) => attendee.user.id === ctx.principal.subject) + if (attendee === undefined) { + throw new TRPCError({ code: "NOT_FOUND" }) + } + await ctx.attendanceService.deregisterAttendee(ctx.handle, attendee.id, { + ignoreDeregistrationWindow: false, + }) - registerAttendance: staffProcedure - .input( - z.object({ - id: AttendeeSchema.shape.id, - at: z.coerce.date().nullable(), - }) - ) - .mutation(async ({ input, ctx }) => { - return ctx.executeAuditedTransaction(async (handle) => { - await ctx.attendanceService.registerAttendance(handle, input.id, input.at ? new TZDate(input.at) : null) - }) - }), + const event = await ctx.eventService.getByAttendanceId(ctx.handle, attendance.id) + await ctx.eventService.createDeregisterReason(ctx.handle, { + ...input.deregisterReason, + userId: ctx.principal.subject, + eventId: event.id, + registeredAt: attendee.createdAt, + userGrade: attendee.userGrade, + }) + }) - updateSelectionResponses: authenticatedProcedure - .input( - z.object({ - attendeeId: AttendeeSchema.shape.id, - options: AttendeeSelectionResponseSchema.array(), - }) - ) - .mutation(async ({ input, ctx }) => { - return ctx.executeAuditedTransaction(async (handle) => { - await ctx.attendanceService.updateAttendeeById(handle, input.attendeeId, { selections: input.options }) - }) - }), +export type AdminDeregisterForEventInput = inferProcedureInput +export type AdminDeregisterForEventOutput = inferProcedureOutput +const adminDeregisterForEventProcedure = procedure + .input(z.object({ attendeeId: AttendeeSchema.shape.id })) + .use(withAuthentication()) + .use(withAuthorization(isEditor())) + .use(withDatabaseTransaction()) + .use(withAuditLogEntry()) + .mutation(async ({ input, ctx }) => { + const attendance = await ctx.attendanceService.getAttendanceByAttendeeId(ctx.handle, input.attendeeId) + const attendee = attendance.attendees.find((attendee) => attendee.id === input.attendeeId) + if (attendee === undefined) { + throw new TRPCError({ code: "NOT_FOUND" }) + } + return await ctx.attendanceService.deregisterAttendee(ctx.handle, attendee.id, { + ignoreDeregistrationWindow: true, + }) + }) - getAttendance: procedure - .input( - z.object({ - id: AttendanceSchema.shape.id, - }) - ) - .query(async ({ input, ctx }) => - ctx.executeTransaction(async (handle) => ctx.attendanceService.getAttendanceById(handle, input.id)) - ), +export type AdminUpdateAtteendeeReservedInput = inferProcedureInput +export type AdminUpdateAtteendeeReservedOutput = inferProcedureOutput +const adminUpdateAtteendeeReservedProcedure = procedure + .input( + z.object({ + attendeeId: AttendeeSchema.shape.id, + reserved: AttendeeSchema.shape.reserved, + }) + ) + .use(withAuthentication()) + .use(withAuthorization(isEditor())) + .use(withDatabaseTransaction()) + .use(withAuditLogEntry()) + .mutation(async ({ input, ctx }) => { + await ctx.attendanceService.updateAttendeeById(ctx.handle, input.attendeeId, { reserved: input.reserved }) + }) - updateAttendance: staffProcedure - .input( - z.object({ - id: AttendanceSchema.shape.id, - attendance: AttendanceWriteSchema.partial(), - }) - ) - .mutation(async ({ input, ctx }) => - ctx.executeAuditedTransaction(async (handle) => - ctx.attendanceService.updateAttendanceById(handle, input.id, input.attendance) - ) - ), +export type RegisterAttendanceInput = inferProcedureInput +export type RegisterAttendanceOutput = inferProcedureOutput +const registerAttendanceProcedure = procedure + .input( + z.object({ + id: AttendeeSchema.shape.id, + at: z.coerce.date().nullable(), + }) + ) + .use(withAuthentication()) + .use(withAuthorization(isEditor())) + .use(withDatabaseTransaction()) + .use(withAuditLogEntry()) + .mutation(async ({ input, ctx }) => { + await ctx.attendanceService.registerAttendance(ctx.handle, input.id, input.at ? new TZDate(input.at) : null) + }) + +export type UpdateSelectionResponsesInput = inferProcedureInput +export type UpdateSelectionResponsesOutput = inferProcedureOutput +const updateSelectionResponsesProcedure = procedure + .input( + z.object({ + attendeeId: AttendeeSchema.shape.id, + options: AttendeeSelectionResponseSchema.array(), + }) + ) + .use(withAuthentication()) + .use(withDatabaseTransaction()) + .use(withAuditLogEntry()) + .mutation(async ({ input, ctx }) => { + await ctx.attendanceService.updateAttendeeById(ctx.handle, input.attendeeId, { selections: input.options }) + }) + +export type GetAttendanceInput = inferProcedureInput +export type GetAttendanceOutput = inferProcedureOutput +const getAttendanceProcedure = procedure + .input(z.object({ id: AttendanceSchema.shape.id })) + .use(withDatabaseTransaction()) + .query(async ({ input, ctx }) => ctx.attendanceService.getAttendanceById(ctx.handle, input.id)) - findChargeAttendeeScheduleDate: authenticatedProcedure - .input(z.object({ attendeeId: AttendeeSchema.shape.id })) - .output(z.date().nullable()) - .query(async ({ input, ctx }) => { - return ctx.executeTransaction(async (handle) => { - const attendee = await ctx.attendanceService.getAttendeeById(handle, input.attendeeId) +export type UpdateAttendanceInput = inferProcedureInput +export type UpdateAttendanceOutput = inferProcedureOutput +const updateAttendanceProcedure = procedure + .input( + z.object({ + id: AttendanceSchema.shape.id, + attendance: AttendanceWriteSchema.partial(), + }) + ) + .use(withAuthentication()) + .use(withAuthorization(isEditor())) + .use(withDatabaseTransaction()) + .use(withAuditLogEntry()) + .mutation(async ({ input, ctx }) => + ctx.attendanceService.updateAttendanceById(ctx.handle, input.id, input.attendance) + ) - ctx.authorize.requireMeOrEditorRole(attendee.userId, ["dotkom", "hs"]) +export type FindChargeAttendeeScheduleDateInput = inferProcedureInput +export type FindChargeAttendeeScheduleDateOutput = inferProcedureOutput +const findChargeAttendeeScheduleDateProcedure = procedure + .input(z.object({ attendeeId: AttendeeSchema.shape.id })) + .output(z.date().nullable()) + .use(withAuthentication()) + .use(withDatabaseTransaction()) + .query(async ({ input, ctx }) => { + const attendee = await ctx.attendanceService.getAttendeeById(ctx.handle, input.attendeeId) - return await ctx.attendanceService.findChargeAttendeeScheduleDate(handle, attendee.id) - }) - }), + ctx.authorize.requireMeOrEditorRole(attendee.userId, ["dotkom", "hs"]) + + return await ctx.attendanceService.findChargeAttendeeScheduleDate(ctx.handle, attendee.id) + }) + +export const attendanceRouter = t.router({ + createPool: createPoolProcedure, + updatePool: updatePoolProcedure, + deletePool: deletePoolProcedure, + adminRegisterForEvent: adminRegisterForEventProcedure, + updateAttendancePayment: updateAttendancePaymentProcedure, + getSelectionsResults: getSelectionsResultsProcedure, + getRegistrationAvailability: getRegistrationAvailabilityProcedure, + registerForEvent: registerForEventProcedure, + onRegisterChange: onRegisterChangeProcedure, + cancelAttendeePayment: cancelAttendeePaymentProcedure, + startAttendeePayment: startAttendeePaymentProcedure, + deregisterForEvent: deregisterForEventProcedure, + adminDeregisterForEvent: adminDeregisterForEventProcedure, + adminUpdateAtteendeeReserved: adminUpdateAtteendeeReservedProcedure, + registerAttendance: registerAttendanceProcedure, + updateSelectionResponses: updateSelectionResponsesProcedure, + getAttendance: getAttendanceProcedure, + updateAttendance: updateAttendanceProcedure, + findChargeAttendeeScheduleDate: findChargeAttendeeScheduleDateProcedure, })