File size: 3,641 Bytes
57a889c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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<ReturnType<typeof svc.verifyTripAccess>>;

/**
 * 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<string, unknown>, socketId: string | undefined): void {
    broadcast(tripId, event, payload, socketId);
  }

  listNotes(tripId: string) { return svc.listNotes(tripId); }
  createNote(tripId: string, userId: number, data: Parameters<typeof svc.createNote>[2]) { return svc.createNote(tripId, userId, data); }
  updateNote(tripId: string, id: string, data: Parameters<typeof svc.updateNote>[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<typeof svc.addNoteFile>[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<typeof svc.createPoll>[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<string, string> = { 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(() => {});
    });
  }
}