Spaces:
Sleeping
Sleeping
| import { | |
| Body, | |
| Controller, | |
| Delete, | |
| Get, | |
| Headers, | |
| HttpException, | |
| Param, | |
| Post, | |
| Put, | |
| UseGuards, | |
| } from '@nestjs/common'; | |
| import type { User } from '../../types'; | |
| import { DaysService } from './days.service'; | |
| import { DayReorderError } from '../../services/dayService'; | |
| import { JwtAuthGuard } from '../auth/jwt-auth.guard'; | |
| import { CurrentUser } from '../auth/current-user.decorator'; | |
| /** | |
| * /api/trips/:tripId/days — trip itinerary days. | |
| * | |
| * Byte-identical to the legacy Express route (server/src/routes/days.ts): trip | |
| * access (404 "Trip not found"), the 'day_edit' permission on mutations (403), | |
| * create 201 / rest 200, the bespoke 404 "Day not found", and WebSocket | |
| * broadcasts with the forwarded X-Socket-Id. | |
| */ | |
| ('api/trips/:tripId/days') | |
| (JwtAuthGuard) | |
| export class DaysController { | |
| constructor(private readonly days: DaysService) {} | |
| private requireTrip(tripId: string, user: User) { | |
| const trip = this.days.verifyTripAccess(tripId, user.id); | |
| if (!trip) { | |
| throw new HttpException({ error: 'Trip not found' }, 404); | |
| } | |
| return trip; | |
| } | |
| private requireEdit(trip: NonNullable<ReturnType<DaysService['verifyTripAccess']>>, user: User): void { | |
| if (!this.days.canEdit(trip, user)) { | |
| throw new HttpException({ error: 'No permission' }, 403); | |
| } | |
| } | |
| () | |
| list(() user: User, ('tripId') tripId: string) { | |
| this.requireTrip(tripId, user); | |
| return this.days.list(tripId); | |
| } | |
| () | |
| create( | |
| () user: User, | |
| ('tripId') tripId: string, | |
| () body: { date?: string; notes?: string; position?: number }, | |
| ('x-socket-id') socketId?: string, | |
| ) { | |
| const trip = this.requireTrip(tripId, user); | |
| this.requireEdit(trip, user); | |
| // A `position` means "insert a new empty day here" (which on a dated trip | |
| // extends the trip and re-pins dates); without it, the legacy append. | |
| const day = body.position !== undefined | |
| ? this.days.insert(tripId, body.position) | |
| : this.days.create(tripId, body.date, body.notes); | |
| // An insert can shuffle dates/positions of other days, so collaborators | |
| // refetch the whole list; a plain append only needs the new day. | |
| const event = body.position !== undefined ? 'day:reordered' : 'day:created'; | |
| this.days.broadcast(tripId, event, { day }, socketId); | |
| return { day }; | |
| } | |
| ('reorder') | |
| reorder( | |
| () user: User, | |
| ('tripId') tripId: string, | |
| () body: { orderedIds?: number[] }, | |
| ('x-socket-id') socketId?: string, | |
| ) { | |
| const trip = this.requireTrip(tripId, user); | |
| this.requireEdit(trip, user); | |
| if (!Array.isArray(body.orderedIds)) { | |
| throw new HttpException({ error: 'orderedIds must be an array' }, 400); | |
| } | |
| try { | |
| this.days.reorder(tripId, body.orderedIds); | |
| } catch (err) { | |
| if (err instanceof DayReorderError) { | |
| throw new HttpException({ error: err.message }, 400); | |
| } | |
| throw err; | |
| } | |
| this.days.broadcast(tripId, 'day:reordered', { orderedIds: body.orderedIds }, socketId); | |
| return { success: true }; | |
| } | |
| (':id') | |
| update( | |
| () user: User, | |
| ('tripId') tripId: string, | |
| ('id') id: string, | |
| () body: { notes?: string; title?: string | null }, | |
| ('x-socket-id') socketId?: string, | |
| ) { | |
| const trip = this.requireTrip(tripId, user); | |
| this.requireEdit(trip, user); | |
| const current = this.days.getDay(id, tripId); | |
| if (!current) { | |
| throw new HttpException({ error: 'Day not found' }, 404); | |
| } | |
| const day = this.days.update(id, current as never, { notes: body.notes, title: body.title }); | |
| this.days.broadcast(tripId, 'day:updated', { day }, socketId); | |
| return { day }; | |
| } | |
| (':id') | |
| remove( | |
| () user: User, | |
| ('tripId') tripId: string, | |
| ('id') id: string, | |
| ('x-socket-id') socketId?: string, | |
| ) { | |
| const trip = this.requireTrip(tripId, user); | |
| this.requireEdit(trip, user); | |
| if (!this.days.getDay(id, tripId)) { | |
| throw new HttpException({ error: 'Day not found' }, 404); | |
| } | |
| this.days.remove(id); | |
| this.days.broadcast(tripId, 'day:deleted', { dayId: Number(id) }, socketId); | |
| return { success: true }; | |
| } | |
| } | |