mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
feat(server): adopt zod DTOs for the day and accommodation body contracts
This commit is contained in:
@@ -8,8 +8,6 @@
|
||||
* here — the gate throws on stale entries, so removal is enforced too.
|
||||
*/
|
||||
export const BODY_CONTRACT_ALLOW_LIST: string[] = [
|
||||
'AccommodationsController.create',
|
||||
'AccommodationsController.update',
|
||||
'AdminController.createInvite',
|
||||
'AdminController.createPackingTemplate',
|
||||
'AdminController.createTemplateCategory',
|
||||
@@ -64,10 +62,6 @@ export const BODY_CONTRACT_ALLOW_LIST: string[] = [
|
||||
'CategoriesController.update',
|
||||
'CollectionsController.deleteMany',
|
||||
'CollectionsController.reorder',
|
||||
'DaysController.create',
|
||||
'DaysController.reorder',
|
||||
'DaysController.transport',
|
||||
'DaysController.update',
|
||||
'ImmichMemoriesController.putSettings',
|
||||
'ImmichMemoriesController.search',
|
||||
'ImmichMemoriesController.test',
|
||||
|
||||
@@ -11,8 +11,8 @@ import {
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import type { User } from '../../types';
|
||||
import { DaysService } from './days.service';
|
||||
import { DayReorderError } from '../../services/dayService';
|
||||
import { DaysService, DayReorderError } from './days.service';
|
||||
import { DayCreateDto, DayReorderDto, DayTransportDto, DayUpdateDto } from './days.dto';
|
||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import { CurrentUser } from '../auth/current-user.decorator';
|
||||
|
||||
@@ -53,7 +53,7 @@ export class DaysController {
|
||||
create(
|
||||
@CurrentUser() user: User,
|
||||
@Param('tripId') tripId: string,
|
||||
@Body() body: { date?: string; notes?: string; position?: number },
|
||||
@Body() body: DayCreateDto,
|
||||
@Headers('x-socket-id') socketId?: string,
|
||||
) {
|
||||
const trip = this.requireTrip(tripId, user);
|
||||
@@ -74,7 +74,7 @@ export class DaysController {
|
||||
reorder(
|
||||
@CurrentUser() user: User,
|
||||
@Param('tripId') tripId: string,
|
||||
@Body() body: { orderedIds?: number[] },
|
||||
@Body() body: DayReorderDto,
|
||||
@Headers('x-socket-id') socketId?: string,
|
||||
) {
|
||||
const trip = this.requireTrip(tripId, user);
|
||||
@@ -99,7 +99,7 @@ export class DaysController {
|
||||
@CurrentUser() user: User,
|
||||
@Param('tripId') tripId: string,
|
||||
@Param('id') id: string,
|
||||
@Body() body: { notes?: string; title?: string | null },
|
||||
@Body() body: DayUpdateDto,
|
||||
@Headers('x-socket-id') socketId?: string,
|
||||
) {
|
||||
const trip = this.requireTrip(tripId, user);
|
||||
@@ -118,7 +118,7 @@ export class DaysController {
|
||||
@CurrentUser() user: User,
|
||||
@Param('tripId') tripId: string,
|
||||
@Param('id') id: string,
|
||||
@Body() body: { transport_mode?: string | null },
|
||||
@Body() body: DayTransportDto,
|
||||
@Headers('x-socket-id') socketId?: string,
|
||||
) {
|
||||
const trip = this.requireTrip(tripId, user);
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { createZodDto } from 'nestjs-zod';
|
||||
import {
|
||||
dayCreateRequestSchema,
|
||||
dayReorderRequestSchema,
|
||||
dayTransportRequestSchema,
|
||||
dayUpdateRequestSchema,
|
||||
} from '@trek/shared';
|
||||
|
||||
/**
|
||||
* Server-side createZodDto wrappers over the @trek/shared day 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 DayCreateDto extends createZodDto(dayCreateRequestSchema) {}
|
||||
export class DayReorderDto extends createZodDto(dayReorderRequestSchema) {}
|
||||
export class DayUpdateDto extends createZodDto(dayUpdateRequestSchema) {}
|
||||
export class DayTransportDto extends createZodDto(dayTransportRequestSchema) {}
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import type { User } from '../../types';
|
||||
import { AccommodationsService } from './accommodations.service';
|
||||
import { AccommodationCreateDto, AccommodationUpdateDto } from './reservations.dto';
|
||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import { CurrentUser } from '../auth/current-user.decorator';
|
||||
|
||||
@@ -65,9 +66,10 @@ export class AccommodationsController {
|
||||
create(
|
||||
@CurrentUser() user: User,
|
||||
@Param('tripId') tripId: string,
|
||||
@Body() body: AccommodationBody,
|
||||
@Body() rawBody: AccommodationCreateDto,
|
||||
@Headers('x-socket-id') socketId?: string,
|
||||
) {
|
||||
const body = rawBody as AccommodationBody;
|
||||
const trip = this.requireTrip(tripId, user);
|
||||
this.requireEdit(trip, user);
|
||||
const { place_id, start_day_id, end_day_id, check_in, check_in_end, check_out, confirmation, notes } = body;
|
||||
@@ -89,9 +91,10 @@ export class AccommodationsController {
|
||||
@CurrentUser() user: User,
|
||||
@Param('tripId') tripId: string,
|
||||
@Param('id') id: string,
|
||||
@Body() body: AccommodationBody,
|
||||
@Body() rawBody: AccommodationUpdateDto,
|
||||
@Headers('x-socket-id') socketId?: string,
|
||||
) {
|
||||
const body = rawBody as AccommodationBody;
|
||||
const trip = this.requireTrip(tripId, user);
|
||||
this.requireEdit(trip, user);
|
||||
const existing = this.accommodations.get(id, tripId);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { createZodDto } from 'nestjs-zod';
|
||||
import {
|
||||
accommodationCreateBodySchema,
|
||||
accommodationUpdateRequestSchema,
|
||||
reservationCreateRequestSchema,
|
||||
reservationUpdateRequestSchema,
|
||||
reservationPositionsRequestSchema,
|
||||
@@ -19,3 +21,5 @@ export class ReservationCreateDto extends createZodDto(reservationCreateRequestS
|
||||
export class ReservationUpdateDto extends createZodDto(reservationUpdateRequestSchema) {}
|
||||
export class ReservationPositionsDto extends createZodDto(reservationPositionsRequestSchema) {}
|
||||
export class ReservationTravelersDto extends createZodDto(reservationTravelersRequestSchema) {}
|
||||
export class AccommodationCreateDto extends createZodDto(accommodationCreateBodySchema) {}
|
||||
export class AccommodationUpdateDto extends createZodDto(accommodationUpdateRequestSchema) {}
|
||||
|
||||
@@ -6,11 +6,11 @@ import { z } from 'zod';
|
||||
* Day + day-note API contract — single source of truth for the
|
||||
* /api/trips/:tripId/days and /api/trips/:tripId/days/:dayId/notes endpoints.
|
||||
*
|
||||
* Trip-scoped, both gated by the 'day_edit' permission. The legacy routes
|
||||
* (server/src/routes/days.ts + routes/dayNotes.ts) wrap dayService /
|
||||
* dayNoteService. Day rows (with their assignments) are wide and DB-derived, so
|
||||
* list responses stay open. Day notes cap text at 500 and time at 150 chars
|
||||
* (the legacy validateStringLengths middleware) — reproduced in the controller.
|
||||
* Trip-scoped, both gated by the 'day_edit' permission. Served by the Nest
|
||||
* DaysController / DayNotesController (server/src/nest/days/). Day rows (with
|
||||
* their assignments) are wide and DB-derived, so list responses stay open. Day
|
||||
* notes cap text at 500 and time at 150 chars (the legacy
|
||||
* validateStringLengths middleware) — reproduced in the controller.
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -68,6 +68,16 @@ export const dayUpdateRequestSchema = z.object({
|
||||
});
|
||||
export type DayUpdateRequest = z.infer<typeof dayUpdateRequestSchema>;
|
||||
|
||||
/**
|
||||
* Whole-day default route mode (#1281). The client always sends
|
||||
* `{ transport_mode: string | null }`; null clears the default. Kept loose (no
|
||||
* enum, no max) — the legacy raw-body route accepted any string.
|
||||
*/
|
||||
export const dayTransportRequestSchema = z.object({
|
||||
transport_mode: z.string().nullable().optional(),
|
||||
});
|
||||
export type DayTransportRequest = z.infer<typeof dayTransportRequestSchema>;
|
||||
|
||||
// `time`/`icon` accept null on the wire: the client's moveDayNote re-sends the
|
||||
// entity fields (both nullable on dayNoteSchema) through create, and an explicit
|
||||
// null time on update means "clear" (the legacy raw-body route accepted both).
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
reservationCreateRequestSchema,
|
||||
reservationPositionsRequestSchema,
|
||||
accommodationCreateBodySchema,
|
||||
accommodationCreateRequestSchema,
|
||||
} from './reservation.schema';
|
||||
|
||||
@@ -45,4 +46,11 @@ describe('accommodationCreateRequestSchema', () => {
|
||||
).toBe(true);
|
||||
expect(accommodationCreateRequestSchema.safeParse({ place_id: 2 }).success).toBe(false);
|
||||
});
|
||||
|
||||
it('body variant keeps missing refs schema-valid (bespoke controller 400)', () => {
|
||||
// The REST endpoint requires all three refs, but their absence is answered
|
||||
// by the controller's bespoke 400 body — the pipe must not pre-empt it.
|
||||
expect(accommodationCreateBodySchema.safeParse({ place_id: 2 }).success).toBe(true);
|
||||
expect(accommodationCreateBodySchema.safeParse({ check_in: 5 }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -182,6 +182,20 @@ export const accommodationCreateRequestSchema = z.object({
|
||||
});
|
||||
export type AccommodationCreateRequest = z.infer<typeof accommodationCreateRequestSchema>;
|
||||
|
||||
/**
|
||||
* REST body variant of the create contract: the three refs are required by the
|
||||
* endpoint but optional on the wire — their absence answers with the bespoke
|
||||
* controller 400 ('place_id, start_day_id, and end_day_id are required'),
|
||||
* which the validation pipe must not pre-empt. The plugin RPC host keeps the
|
||||
* strict schema above (missing refs are BAD_PARAMS there).
|
||||
*/
|
||||
export const accommodationCreateBodySchema = accommodationCreateRequestSchema.partial({
|
||||
place_id: true,
|
||||
start_day_id: true,
|
||||
end_day_id: true,
|
||||
});
|
||||
export type AccommodationCreateBody = z.infer<typeof accommodationCreateBodySchema>;
|
||||
|
||||
export const accommodationUpdateRequestSchema = open;
|
||||
export type AccommodationUpdateRequest = z.infer<typeof accommodationUpdateRequestSchema>;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user