import { Injectable } from '@nestjs/common'; import { db } from '../../db/database'; import { broadcast } from '../../websocket'; import { checkPermission } from '../../services/permissions'; import type { User } from '../../types'; import * as svc from '../../services/collabService'; type Trip = NonNullable>; /** * Thin Nest wrapper around the existing collab service. Trip access, the * 'collab_edit' / 'file_upload' permissions, the SQL and the WebSocket * broadcasts reuse the legacy code unchanged. */ @Injectable() export class CollabService { verifyTripAccess(tripId: string, userId: number) { return svc.verifyTripAccess(tripId, userId); } canEdit(trip: Trip, user: User): boolean { return checkPermission('collab_edit', user.role, trip.user_id, user.id, trip.user_id !== user.id); } canUploadFiles(trip: Trip, user: User): boolean { return checkPermission('file_upload', user.role, trip.user_id, user.id, trip.user_id !== user.id); } broadcast(tripId: string, event: string, payload: Record, socketId: string | undefined): void { broadcast(tripId, event, payload, socketId); } listNotes(tripId: string) { return svc.listNotes(tripId); } createNote(tripId: string, userId: number, data: Parameters[2]) { return svc.createNote(tripId, userId, data); } updateNote(tripId: string, id: string, data: Parameters[2]) { return svc.updateNote(tripId, id, data); } deleteNote(tripId: string, id: string): boolean { return svc.deleteNote(tripId, id); } addNoteFile(tripId: string, id: string, file: Parameters[2]) { return svc.addNoteFile(tripId, id, file); } getFormattedNoteById(id: string) { return svc.getFormattedNoteById(id); } deleteNoteFile(id: string, fileId: string): boolean { return svc.deleteNoteFile(id, fileId); } listPolls(tripId: string) { return svc.listPolls(tripId); } createPoll(tripId: string, userId: number, data: Parameters[2]) { return svc.createPoll(tripId, userId, data); } votePoll(tripId: string, id: string, userId: number, optionIndex: number) { return svc.votePoll(tripId, id, userId, optionIndex); } closePoll(tripId: string, id: string) { return svc.closePoll(tripId, id); } deletePoll(tripId: string, id: string): boolean { return svc.deletePoll(tripId, id); } listMessages(tripId: string, before?: string) { return svc.listMessages(tripId, before); } createMessage(tripId: string, userId: number, text: string, replyTo?: number | null) { return svc.createMessage(tripId, userId, text, replyTo); } deleteMessage(tripId: string, id: string, userId: number) { return svc.deleteMessage(tripId, id, userId); } reactMessage(id: string, tripId: string, userId: number, emoji: string) { return svc.addOrRemoveReaction(id, tripId, userId, emoji); } linkPreview(url: string) { return svc.fetchLinkPreview(url); } /** Fire-and-forget collab notification (mirrors the route's dynamic import). */ notifyCollab(tripId: string, actor: User, preview?: string): void { import('../../services/notificationService').then(({ send }) => { const tripInfo = db.prepare('SELECT title FROM trips WHERE id = ?').get(tripId) as { title: string } | undefined; const params: Record = { trip: tripInfo?.title || 'Untitled', actor: actor.email, tripId: String(tripId) }; if (preview !== undefined) params.preview = preview; send({ event: 'collab_message', actorId: actor.id, scope: 'trip', targetId: Number(tripId), params }).catch(() => {}); }); } }