Spaces:
Sleeping
Sleeping
| import { | |
| Body, | |
| Controller, | |
| Delete, | |
| Get, | |
| Headers, | |
| HttpCode, | |
| HttpException, | |
| Param, | |
| Post, | |
| Put, | |
| UseGuards, | |
| } from '@nestjs/common'; | |
| import type { User } from '../../types'; | |
| import { VacayService } from './vacay.service'; | |
| import { JwtAuthGuard } from '../auth/jwt-auth.guard'; | |
| import { CurrentUser } from '../auth/current-user.decorator'; | |
| /** | |
| * /api/addons/vacay — shared vacation-day planner. | |
| * | |
| * Byte-identical to the legacy Express route (server/src/routes/vacay.ts): all | |
| * endpoints require auth; the X-Socket-Id header is forwarded to the services so | |
| * the originating client is excluded from the broadcast; POSTs answer 200 (the | |
| * legacy route uses res.json, not 201); and the bespoke 400/403/404/502 bodies | |
| * are reproduced exactly. No addon gate — the legacy mount has none. | |
| */ | |
| ('api/addons/vacay') | |
| (JwtAuthGuard) | |
| export class VacayController { | |
| constructor(private readonly vacay: VacayService) {} | |
| ('plan') | |
| getPlan(() user: User) { | |
| return this.vacay.getPlanData(user.id); | |
| } | |
| ('plan') | |
| async updatePlan(() user: User, () body: Record<string, unknown>, ('x-socket-id') socketId?: string) { | |
| const planId = this.vacay.getActivePlanId(user.id); | |
| return this.vacay.updatePlan(planId, body, socketId); | |
| } | |
| ('plan/holiday-calendars') | |
| (200) | |
| addHolidayCalendar( | |
| () user: User, | |
| () body: { region?: string; label?: string | null; color?: string; sort_order?: number }, | |
| ('x-socket-id') socketId?: string, | |
| ) { | |
| if (!body.region) { | |
| throw new HttpException({ error: 'region required' }, 400); | |
| } | |
| const planId = this.vacay.getActivePlanId(user.id); | |
| const calendar = this.vacay.addHolidayCalendar(planId, body.region, body.label ?? null, body.color, body.sort_order, socketId); | |
| return { calendar }; | |
| } | |
| ('plan/holiday-calendars/:id') | |
| updateHolidayCalendar( | |
| () user: User, | |
| ('id') idParam: string, | |
| () body: Record<string, unknown>, | |
| ('x-socket-id') socketId?: string, | |
| ) { | |
| const id = parseInt(idParam); | |
| const planId = this.vacay.getActivePlanId(user.id); | |
| const calendar = this.vacay.updateHolidayCalendar(id, planId, body, socketId); | |
| if (!calendar) { | |
| throw new HttpException({ error: 'Calendar not found' }, 404); | |
| } | |
| return { calendar }; | |
| } | |
| ('plan/holiday-calendars/:id') | |
| deleteHolidayCalendar(() user: User, ('id') idParam: string, ('x-socket-id') socketId?: string) { | |
| const id = parseInt(idParam); | |
| const planId = this.vacay.getActivePlanId(user.id); | |
| if (!this.vacay.deleteHolidayCalendar(id, planId, socketId)) { | |
| throw new HttpException({ error: 'Calendar not found' }, 404); | |
| } | |
| return { success: true }; | |
| } | |
| ('color') | |
| setColor( | |
| () user: User, | |
| () body: { color?: string; target_user_id?: number | string }, | |
| ('x-socket-id') socketId?: string, | |
| ) { | |
| const planId = this.vacay.getActivePlanId(user.id); | |
| const userId = body.target_user_id ? parseInt(String(body.target_user_id)) : user.id; | |
| if (!this.vacay.getPlanUsers(planId).find((u) => u.id === userId)) { | |
| throw new HttpException({ error: 'User not in plan' }, 403); | |
| } | |
| this.vacay.setUserColor(userId, planId, body.color, socketId); | |
| return { success: true }; | |
| } | |
| ('invite') | |
| (200) | |
| invite(() user: User, ('user_id') userIdInput?: number | string) { | |
| if (!userIdInput) { | |
| throw new HttpException({ error: 'user_id required' }, 400); | |
| } | |
| const plan = this.vacay.getActivePlan(user.id); | |
| const result = this.vacay.sendInvite(plan.id, user.id, user.username, user.email, userIdInput as number); | |
| if (result.error) { | |
| throw new HttpException({ error: result.error }, result.status!); | |
| } | |
| return { success: true }; | |
| } | |
| ('invite/accept') | |
| (200) | |
| acceptInvite(() user: User, ('plan_id') planId: number, ('x-socket-id') socketId?: string) { | |
| const result = this.vacay.acceptInvite(user.id, planId, socketId); | |
| if (result.error) { | |
| throw new HttpException({ error: result.error }, result.status!); | |
| } | |
| return { success: true }; | |
| } | |
| ('invite/decline') | |
| (200) | |
| declineInvite(() user: User, ('plan_id') planId: number, ('x-socket-id') socketId?: string) { | |
| this.vacay.declineInvite(user.id, planId, socketId); | |
| return { success: true }; | |
| } | |
| ('invite/cancel') | |
| (200) | |
| cancelInvite(() user: User, ('user_id') targetUserId: number) { | |
| const plan = this.vacay.getActivePlan(user.id); | |
| this.vacay.cancelInvite(plan.id, targetUserId); | |
| return { success: true }; | |
| } | |
| ('dissolve') | |
| (200) | |
| dissolve(() user: User, ('x-socket-id') socketId?: string) { | |
| this.vacay.dissolvePlan(user.id, socketId); | |
| return { success: true }; | |
| } | |
| ('available-users') | |
| availableUsers(() user: User) { | |
| const planId = this.vacay.getActivePlanId(user.id); | |
| return { users: this.vacay.getAvailableUsers(user.id, planId) }; | |
| } | |
| ('years') | |
| years(() user: User) { | |
| const planId = this.vacay.getActivePlanId(user.id); | |
| return { years: this.vacay.listYears(planId) }; | |
| } | |
| ('years') | |
| (200) | |
| addYear(() user: User, ('year') year?: number, ('x-socket-id') socketId?: string) { | |
| if (!year) { | |
| throw new HttpException({ error: 'Year required' }, 400); | |
| } | |
| const planId = this.vacay.getActivePlanId(user.id); | |
| return { years: this.vacay.addYear(planId, year, socketId) }; | |
| } | |
| ('years/:year') | |
| deleteYear(() user: User, ('year') yearParam: string, ('x-socket-id') socketId?: string) { | |
| const year = parseInt(yearParam); | |
| const planId = this.vacay.getActivePlanId(user.id); | |
| return { years: this.vacay.deleteYear(planId, year, socketId) }; | |
| } | |
| ('entries/:year') | |
| entries(() user: User, ('year') year: string) { | |
| const planId = this.vacay.getActivePlanId(user.id); | |
| return this.vacay.getEntries(planId, year); | |
| } | |
| ('entries/toggle') | |
| (200) | |
| toggleEntry( | |
| () user: User, | |
| () body: { date?: string; target_user_id?: number | string }, | |
| ('x-socket-id') socketId?: string, | |
| ) { | |
| if (!body.date) { | |
| throw new HttpException({ error: 'date required' }, 400); | |
| } | |
| const planId = this.vacay.getActivePlanId(user.id); | |
| let userId = user.id; | |
| if (body.target_user_id && parseInt(String(body.target_user_id)) !== user.id) { | |
| const tid = parseInt(String(body.target_user_id)); | |
| if (!this.vacay.getPlanUsers(planId).find((u) => u.id === tid)) { | |
| throw new HttpException({ error: 'User not in plan' }, 403); | |
| } | |
| userId = tid; | |
| } | |
| return this.vacay.toggleEntry(userId, planId, body.date, socketId); | |
| } | |
| ('entries/company-holiday') | |
| (200) | |
| companyHoliday( | |
| () user: User, | |
| () body: { date?: string; note?: string }, | |
| ('x-socket-id') socketId?: string, | |
| ) { | |
| const planId = this.vacay.getActivePlanId(user.id); | |
| return this.vacay.toggleCompanyHoliday(planId, body.date as string, body.note, socketId); | |
| } | |
| ('stats/:year') | |
| stats(() user: User, ('year') yearParam: string) { | |
| const year = parseInt(yearParam); | |
| const planId = this.vacay.getActivePlanId(user.id); | |
| return { stats: this.vacay.getStats(planId, year) }; | |
| } | |
| ('stats/:year') | |
| updateStats( | |
| () user: User, | |
| ('year') yearParam: string, | |
| () body: { vacation_days?: number; target_user_id?: number | string }, | |
| ('x-socket-id') socketId?: string, | |
| ) { | |
| const year = parseInt(yearParam); | |
| const planId = this.vacay.getActivePlanId(user.id); | |
| const userId = body.target_user_id ? parseInt(String(body.target_user_id)) : user.id; | |
| if (!this.vacay.getPlanUsers(planId).find((u) => u.id === userId)) { | |
| throw new HttpException({ error: 'User not in plan' }, 403); | |
| } | |
| this.vacay.updateStats(userId, planId, year, body.vacation_days as number, socketId); | |
| return { success: true }; | |
| } | |
| ('holidays/countries') | |
| async holidayCountries() { | |
| const result = await this.vacay.getCountries(); | |
| if (result.error) { | |
| throw new HttpException({ error: result.error }, 502); | |
| } | |
| return result.data; | |
| } | |
| ('holidays/:year/:country') | |
| async holidays(('year') year: string, ('country') country: string) { | |
| const result = await this.vacay.getHolidays(year, country); | |
| if (result.error) { | |
| throw new HttpException({ error: result.error }, 502); | |
| } | |
| return result.data; | |
| } | |
| } | |