mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-22 23:16:46 +00:00
feat(server): adopt zod DTOs for the assignment body contracts
This commit is contained in:
@@ -12,6 +12,14 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import type { User } from '../../types';
|
||||
import { AssignmentsService } from './assignments.service';
|
||||
import {
|
||||
AssignmentCreateDto,
|
||||
AssignmentReorderDto,
|
||||
AssignmentMoveDto,
|
||||
AssignmentTimeDto,
|
||||
AssignmentTransportDto,
|
||||
AssignmentParticipantsDto,
|
||||
} from './assignments.dto';
|
||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import { CurrentUser } from '../auth/current-user.decorator';
|
||||
|
||||
@@ -35,10 +43,11 @@ function requireEdit(svc: AssignmentsService, trip: Trip, user: User): void {
|
||||
/**
|
||||
* /api/trips/:tripId/days/:dayId/assignments — the day's ordered itinerary items.
|
||||
*
|
||||
* Byte-identical to the legacy Express route (server/src/routes/assignments.ts):
|
||||
* trip access (404), 'day_edit' on mutations (403, GET is access-only), create
|
||||
* 201 / rest 200, the bespoke "Day not found" / "Place not found" / "Assignment
|
||||
* not found" bodies, the journey skeleton reconcile, and WebSocket broadcasts.
|
||||
* Parity with the retired legacy Express route: trip access (404), 'day_edit'
|
||||
* on mutations (403, GET is access-only), create 201 / rest 200, the bespoke
|
||||
* "Day not found" / "Place not found" / "Assignment not found" bodies, the
|
||||
* journey skeleton reconcile, and WebSocket broadcasts. Bodies are validated
|
||||
* by the @trek/shared Zod contracts via assignments.dto.ts (global pipe).
|
||||
*/
|
||||
@Controller('api/trips/:tripId/days/:dayId/assignments')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@@ -59,7 +68,7 @@ export class DayAssignmentsController {
|
||||
@CurrentUser() user: User,
|
||||
@Param('tripId') tripId: string,
|
||||
@Param('dayId') dayId: string,
|
||||
@Body() body: { place_id?: unknown; notes?: string | null },
|
||||
@Body() body: AssignmentCreateDto,
|
||||
@Headers('x-socket-id') socketId?: string,
|
||||
) {
|
||||
const trip = requireTrip(this.assignments, tripId, user);
|
||||
@@ -81,7 +90,7 @@ export class DayAssignmentsController {
|
||||
@CurrentUser() user: User,
|
||||
@Param('tripId') tripId: string,
|
||||
@Param('dayId') dayId: string,
|
||||
@Body('orderedIds') orderedIds: number[],
|
||||
@Body() body: AssignmentReorderDto,
|
||||
@Headers('x-socket-id') socketId?: string,
|
||||
) {
|
||||
const trip = requireTrip(this.assignments, tripId, user);
|
||||
@@ -89,8 +98,8 @@ export class DayAssignmentsController {
|
||||
if (!this.assignments.dayExists(dayId, tripId)) {
|
||||
throw new HttpException({ error: 'Day not found' }, 404);
|
||||
}
|
||||
this.assignments.reorderAssignments(dayId, orderedIds);
|
||||
this.assignments.broadcast(tripId, 'assignment:reordered', { dayId: Number(dayId), orderedIds }, socketId);
|
||||
this.assignments.reorderAssignments(dayId, body.orderedIds);
|
||||
this.assignments.broadcast(tripId, 'assignment:reordered', { dayId: Number(dayId), orderedIds: body.orderedIds }, socketId);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
@@ -128,20 +137,18 @@ export class AssignmentOpsController {
|
||||
@CurrentUser() user: User,
|
||||
@Param('tripId') tripId: string,
|
||||
@Param('id') id: string,
|
||||
@Body() body: { new_day_id?: unknown; order_index?: number },
|
||||
@Body() body: AssignmentMoveDto,
|
||||
@Headers('x-socket-id') socketId?: string,
|
||||
) {
|
||||
const trip = requireTrip(this.assignments, tripId, user);
|
||||
requireEdit(this.assignments, trip, user);
|
||||
const existing = this.assignments.getAssignmentForTrip(id, tripId);
|
||||
if (!existing) {
|
||||
if (!this.assignments.getAssignmentForTrip(id, tripId)) {
|
||||
throw new HttpException({ error: 'Assignment not found' }, 404);
|
||||
}
|
||||
if (!this.assignments.dayExists(String(body.new_day_id), tripId)) {
|
||||
throw new HttpException({ error: 'Target day not found' }, 404);
|
||||
}
|
||||
const oldDayId = (existing as { day_id: number }).day_id;
|
||||
const { assignment } = this.assignments.moveAssignment(id, body.new_day_id, body.order_index, oldDayId);
|
||||
const { assignment, oldDayId } = this.assignments.moveAssignment(id, body.new_day_id, body.order_index);
|
||||
this.assignments.broadcast(tripId, 'assignment:moved', { assignment, oldDayId: Number(oldDayId), newDayId: Number(body.new_day_id) }, socketId);
|
||||
this.assignments.reconcile(tripId, socketId);
|
||||
return { assignment };
|
||||
@@ -158,7 +165,7 @@ export class AssignmentOpsController {
|
||||
@CurrentUser() user: User,
|
||||
@Param('tripId') tripId: string,
|
||||
@Param('id') id: string,
|
||||
@Body() body: { place_time?: string | null; end_time?: string | null },
|
||||
@Body() body: AssignmentTimeDto,
|
||||
@Headers('x-socket-id') socketId?: string,
|
||||
) {
|
||||
const trip = requireTrip(this.assignments, tripId, user);
|
||||
@@ -177,7 +184,7 @@ export class AssignmentOpsController {
|
||||
@CurrentUser() user: User,
|
||||
@Param('tripId') tripId: string,
|
||||
@Param('id') id: string,
|
||||
@Body() body: { transport_mode?: string | null },
|
||||
@Body() body: AssignmentTransportDto,
|
||||
@Headers('x-socket-id') socketId?: string,
|
||||
) {
|
||||
const trip = requireTrip(this.assignments, tripId, user);
|
||||
@@ -195,15 +202,12 @@ export class AssignmentOpsController {
|
||||
@CurrentUser() user: User,
|
||||
@Param('tripId') tripId: string,
|
||||
@Param('id') id: string,
|
||||
@Body('user_ids') userIds: unknown,
|
||||
@Body() body: AssignmentParticipantsDto,
|
||||
@Headers('x-socket-id') socketId?: string,
|
||||
) {
|
||||
const trip = requireTrip(this.assignments, tripId, user);
|
||||
requireEdit(this.assignments, trip, user);
|
||||
if (!Array.isArray(userIds)) {
|
||||
throw new HttpException({ error: 'user_ids must be an array' }, 400);
|
||||
}
|
||||
const participants = this.assignments.setParticipants(id, userIds);
|
||||
const participants = this.assignments.setParticipants(id, body.user_ids);
|
||||
this.assignments.broadcast(tripId, 'assignment:participants', { assignmentId: Number(id), participants }, socketId);
|
||||
return { participants };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { createZodDto } from 'nestjs-zod';
|
||||
import {
|
||||
assignmentCreateRequestSchema,
|
||||
assignmentReorderRequestSchema,
|
||||
assignmentMoveRequestSchema,
|
||||
assignmentTimeRequestSchema,
|
||||
assignmentTransportRequestSchema,
|
||||
assignmentParticipantsRequestSchema,
|
||||
} from '@trek/shared';
|
||||
|
||||
/**
|
||||
* Server-side createZodDto wrappers over the @trek/shared assignment
|
||||
* contracts. The global ZodValidationPipe (APP_PIPE in app.module.ts)
|
||||
* validates any @Body() parameter typed with one of these classes by
|
||||
* metatype — the Zod schemas in shared/ remain the single source of truth for
|
||||
* the wire contract.
|
||||
*/
|
||||
export class AssignmentCreateDto extends createZodDto(assignmentCreateRequestSchema) {}
|
||||
export class AssignmentReorderDto extends createZodDto(assignmentReorderRequestSchema) {}
|
||||
export class AssignmentMoveDto extends createZodDto(assignmentMoveRequestSchema) {}
|
||||
export class AssignmentTimeDto extends createZodDto(assignmentTimeRequestSchema) {}
|
||||
export class AssignmentTransportDto extends createZodDto(assignmentTransportRequestSchema) {}
|
||||
export class AssignmentParticipantsDto extends createZodDto(assignmentParticipantsRequestSchema) {}
|
||||
@@ -30,10 +30,6 @@ export const BODY_CONTRACT_ALLOW_LIST: string[] = [
|
||||
'AdminController.updateTemplateCategory',
|
||||
'AdminController.updateTemplateItem',
|
||||
'AdminController.updateUser',
|
||||
'AssignmentOpsController.move',
|
||||
'AssignmentOpsController.setParticipants',
|
||||
'AssignmentOpsController.time',
|
||||
'AssignmentOpsController.transport',
|
||||
'AtlasController.createBucketItem',
|
||||
'AtlasController.markRegion',
|
||||
'AtlasController.updateBucketItem',
|
||||
@@ -74,8 +70,6 @@ export const BODY_CONTRACT_ALLOW_LIST: string[] = [
|
||||
'CollabController.votePoll',
|
||||
'CollectionsController.deleteMany',
|
||||
'CollectionsController.reorder',
|
||||
'DayAssignmentsController.create',
|
||||
'DayAssignmentsController.reorder',
|
||||
'DaysController.create',
|
||||
'DaysController.reorder',
|
||||
'DaysController.transport',
|
||||
|
||||
Reference in New Issue
Block a user