Spaces:
Sleeping
Sleeping
| import { | |
| Body, | |
| Controller, | |
| Delete, | |
| Get, | |
| Headers, | |
| HttpException, | |
| Param, | |
| Post, | |
| Put, | |
| UseGuards, | |
| } from '@nestjs/common'; | |
| import type { User } from '../../types'; | |
| import { TodoService } from './todo.service'; | |
| import { JwtAuthGuard } from '../auth/jwt-auth.guard'; | |
| import { CurrentUser } from '../auth/current-user.decorator'; | |
| /** | |
| * /api/trips/:tripId/todo — trip-scoped task list. | |
| * | |
| * Byte-identical to the legacy Express route (server/src/routes/todo.ts): every | |
| * handler verifies trip access (404); mutations check the 'packing_edit' | |
| * permission (403); create is 201, the rest 200; the bespoke 400/404 bodies are | |
| * reproduced; mutations broadcast over WebSocket with the forwarded X-Socket-Id. | |
| * /reorder is declared before /:id so it wins over the param. | |
| */ | |
| ('api/trips/:tripId/todo') | |
| (JwtAuthGuard) | |
| export class TodoController { | |
| constructor(private readonly todo: TodoService) {} | |
| private requireTrip(tripId: string, user: User) { | |
| const trip = this.todo.verifyTripAccess(tripId, user.id); | |
| if (!trip) { | |
| throw new HttpException({ error: 'Trip not found' }, 404); | |
| } | |
| return trip; | |
| } | |
| private requireEdit(trip: ReturnType<TodoService['verifyTripAccess']>, user: User): void { | |
| if (!this.todo.canEdit(trip!, user)) { | |
| throw new HttpException({ error: 'No permission' }, 403); | |
| } | |
| } | |
| () | |
| list(() user: User, ('tripId') tripId: string) { | |
| this.requireTrip(tripId, user); | |
| return { items: this.todo.listItems(tripId) }; | |
| } | |
| () | |
| create( | |
| () user: User, | |
| ('tripId') tripId: string, | |
| () body: { name?: string; category?: string; due_date?: string; description?: string; assigned_user_id?: number; priority?: number }, | |
| ('x-socket-id') socketId?: string, | |
| ) { | |
| const trip = this.requireTrip(tripId, user); | |
| this.requireEdit(trip, user); | |
| if (!body.name) { | |
| throw new HttpException({ error: 'Item name is required' }, 400); | |
| } | |
| const { name, category, due_date, description, assigned_user_id, priority } = body; | |
| const item = this.todo.createItem(tripId, { name, category, due_date, description, assigned_user_id, priority }); | |
| this.todo.broadcast(tripId, 'todo:created', { item }, socketId); | |
| return { item }; | |
| } | |
| ('reorder') | |
| reorder( | |
| () user: User, | |
| ('tripId') tripId: string, | |
| ('orderedIds') orderedIds: number[], | |
| ) { | |
| const trip = this.requireTrip(tripId, user); | |
| this.requireEdit(trip, user); | |
| this.todo.reorderItems(tripId, orderedIds); | |
| return { success: true }; | |
| } | |
| (':id') | |
| update( | |
| () user: User, | |
| ('tripId') tripId: string, | |
| ('id') id: string, | |
| () body: Record<string, unknown>, | |
| ('x-socket-id') socketId?: string, | |
| ) { | |
| const trip = this.requireTrip(tripId, user); | |
| this.requireEdit(trip, user); | |
| const { name, checked, category, due_date, description, assigned_user_id, priority } = body as Record<string, never>; | |
| const updated = this.todo.updateItem(tripId, id, { name, checked, category, due_date, description, assigned_user_id, priority }, Object.keys(body)); | |
| if (!updated) { | |
| throw new HttpException({ error: 'Item not found' }, 404); | |
| } | |
| this.todo.broadcast(tripId, 'todo:updated', { item: updated }, socketId); | |
| return { item: updated }; | |
| } | |
| (':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.todo.deleteItem(tripId, id)) { | |
| throw new HttpException({ error: 'Item not found' }, 404); | |
| } | |
| this.todo.broadcast(tripId, 'todo:deleted', { itemId: Number(id) }, socketId); | |
| return { success: true }; | |
| } | |
| ('category-assignees') | |
| categoryAssignees(() user: User, ('tripId') tripId: string) { | |
| this.requireTrip(tripId, user); | |
| return { assignees: this.todo.getCategoryAssignees(tripId) }; | |
| } | |
| ('category-assignees/:categoryName') | |
| updateCategoryAssignees( | |
| () user: User, | |
| ('tripId') tripId: string, | |
| ('categoryName') categoryName: string, | |
| ('user_ids') userIds: number[], | |
| ('x-socket-id') socketId?: string, | |
| ) { | |
| const trip = this.requireTrip(tripId, user); | |
| this.requireEdit(trip, user); | |
| const category = decodeURIComponent(categoryName); | |
| const rows = this.todo.updateCategoryAssignees(tripId, category, userIds); | |
| this.todo.broadcast(tripId, 'todo:assignees', { category, assignees: rows }, socketId); | |
| return { assignees: rows }; | |
| } | |
| } | |