import { prisma } from '../../database/prisma.js'; export interface Note { id?: number; userId: string; orgId: string; source: string; title: string; body: string; tags: string; owner: string; compliance: 'clean' | 'review' | 'restricted'; retention: '30d' | '90d' | '1y' | 'archive'; riskScore: number; attachments: number; createdAt?: string; updatedAt?: string; } export class NotesRepository { async createNote(note: Note): Promise { const created = await prisma.note.create({ data: { userId: note.userId, orgId: note.orgId, source: note.source, title: note.title, body: note.body, tags: note.tags || '', owner: note.owner, compliance: note.compliance || 'clean', retention: note.retention || '90d', riskScore: note.riskScore || 0, attachments: note.attachments || 0, }, }); return created.id; } async getNotes(userId: string, orgId: string, filters?: { source?: string; compliance?: string; retention?: string; query?: string; limit?: number; }): Promise { const where: any = { userId, orgId, }; if (filters?.source) { where.source = filters.source; } if (filters?.compliance) { where.compliance = filters.compliance; } if (filters?.retention) { where.retention = filters.retention; } if (filters?.query) { where.OR = [ { title: { contains: filters.query, mode: 'insensitive' } }, { body: { contains: filters.query, mode: 'insensitive' } }, { tags: { contains: filters.query, mode: 'insensitive' } }, ]; } const notes = await prisma.note.findMany({ where, orderBy: { updatedAt: 'desc' }, take: filters?.limit, }); return notes.map(this.mapToNote); } async getNoteById(id: number, userId: string, orgId: string): Promise { const note = await prisma.note.findFirst({ where: { id, userId, orgId }, }); return note ? this.mapToNote(note) : null; } async updateNote(id: number, userId: string, orgId: string, updates: Partial): Promise { const data: any = {}; if (updates.title !== undefined) data.title = updates.title; if (updates.body !== undefined) data.body = updates.body; if (updates.tags !== undefined) data.tags = updates.tags; if (updates.source !== undefined) data.source = updates.source; if (updates.compliance !== undefined) data.compliance = updates.compliance; if (updates.retention !== undefined) data.retention = updates.retention; if (updates.riskScore !== undefined) data.riskScore = updates.riskScore; if (updates.attachments !== undefined) data.attachments = updates.attachments; if (Object.keys(data).length === 0) { return false; } await prisma.note.updateMany({ where: { id, userId, orgId }, data, }); return true; } async deleteNote(id: number, userId: string, orgId: string): Promise { await prisma.note.deleteMany({ where: { id, userId, orgId }, }); return true; } async getStatistics(userId: string, orgId: string) { const notes = await this.getNotes(userId, orgId); const sourceDistribution: Record = {}; let flaggedCount = 0; let totalRisk = 0; notes.forEach(note => { sourceDistribution[note.source] = (sourceDistribution[note.source] || 0) + 1; if (note.compliance !== 'clean') { flaggedCount++; } totalRisk += note.riskScore; }); return { total: notes.length, flagged: flaggedCount, avgRisk: notes.length > 0 ? Math.round(totalRisk / notes.length) : 0, sourceDistribution, }; } // Seed sample data for demo purposes async seedSampleData(userId: string, orgId: string) { const sampleNotes = [ { userId, orgId, source: 'Microsoft OneNote', title: 'Q4 Strategy Workshop', body: 'Collected workshop notes covering AI procurement roadmap, stakeholder interviews and deliverables for DG CONNECT.', tags: 'strategy,ai,eu', owner: 'A. Rossi', compliance: 'clean' as const, retention: '1y' as const, riskScore: 12, attachments: 3, }, { userId, orgId, source: 'Google Keep', title: 'Privacy Task Force recap', body: 'Summary of Schrems II mitigation controls, DPA template updates and DPIA workflow automation ideas.', tags: 'privacy,gdpr', owner: 'K. Jensen', compliance: 'review' as const, retention: '90d' as const, riskScore: 58, attachments: 1, }, { userId, orgId, source: 'Local Files', title: 'Field research scans', body: 'Offline PDFs captured from site inspections in Munich and Ghent. Contains photos, transcripts and checklists.', tags: 'inspection,pdf', owner: 'M. Novak', compliance: 'restricted' as const, retention: '30d' as const, riskScore: 83, attachments: 12, }, { userId, orgId, source: 'Email', title: 'Bid coaching thread', body: 'Email notes exchanged with supplier consortium clarifying KPIs, SOW split and legal guardrails.', tags: 'bid,procurement', owner: 'L. Ruíz', compliance: 'review' as const, retention: '1y' as const, riskScore: 46, attachments: 2, }, { userId, orgId, source: 'Evernote', title: 'Incident Post-Mortem', body: 'Voice memo transcription summarizing containment timeline, threat intel links and RCA decisions.', tags: 'security,incident', owner: 'J. Olofsson', compliance: 'clean' as const, retention: '1y' as const, riskScore: 28, attachments: 0, }, ]; for (const note of sampleNotes) { await this.createNote(note); } } private mapToNote(row: any): Note { return { id: row.id, userId: row.userId, orgId: row.orgId, source: row.source, title: row.title, body: row.body, tags: row.tags, owner: row.owner, compliance: row.compliance as 'clean' | 'review' | 'restricted', retention: row.retention as '30d' | '90d' | '1y' | 'archive', riskScore: row.riskScore, attachments: row.attachments, createdAt: row.createdAt?.toISOString(), updatedAt: row.updatedAt?.toISOString(), }; } }