import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Note } from './note.entity'; import { KnowledgeGroup } from '../knowledge-group/knowledge-group.entity'; import { OcrService } from '../ocr/ocr.service'; import * as fs from 'fs/promises'; import * as path from 'path'; import { v4 as uuidv4 } from 'uuid'; import { I18nService } from '../i18n/i18n.service'; @Injectable() export class NoteService { // Directory will be created dynamically per user private getScreenshotsDir(userId: string) { return path.join(process.cwd(), 'uploads', 'notes-screenshots', userId); } constructor( @InjectRepository(Note) private readonly noteRepository: Repository, private readonly ocrService: OcrService, private readonly i18nService: I18nService, ) {} private async ensureScreenshotsDir(userId: string) { const dir = this.getScreenshotsDir(userId); try { await fs.access(dir); } catch { await fs.mkdir(dir, { recursive: true }); } } async create( userId: string, data: Partial, ): Promise { // Handle empty strings for foreign keys if (data.groupId === '') { data.groupId = null as any; } if (data.categoryId === '') { data.categoryId = null as any; } const note = this.noteRepository.create({ ...data, userId, }); return this.noteRepository.save(note); } async findAll( userId: string, isAdmin: boolean, groupId?: string, categoryId?: string, ): Promise { const query = this.noteRepository .createQueryBuilder('note') .leftJoinAndSelect('note.user', 'user'); if (!isAdmin) { query.where('note.userId = :userId', { userId }); } if (groupId) { query.andWhere('note.groupId = :groupId', { groupId }); } if (categoryId) { query.andWhere('note.categoryId = :categoryId', { categoryId }); } return query.getMany(); } async findOne( userId: string, id: string, isAdmin: boolean, ): Promise { let note; if (isAdmin) { note = await this.noteRepository.findOne({ where: { id }, relations: ['user'], }); } else { note = await this.noteRepository.findOne({ where: { id, userId }, relations: ['user'], }); } if (!note) { throw new NotFoundException( this.i18nService.formatMessage('noteNotFound', { id }), ); } return note; } async update( userId: string, id: string, data: Partial, isAdmin: boolean, ): Promise { const note = await this.findOne(userId, id, isAdmin); // Remove protected fields delete (data as any).id; delete (data as any).userId; delete (data as any).createdAt; // Handle empty strings for foreign keys if (data.groupId === '') { data.groupId = null as any; } if (data.categoryId === '') { data.categoryId = null as any; } Object.assign(note, data); return this.noteRepository.save(note); } async createFromPDFSelection( userId: string, fileId: string, screenshot: Express.Multer.File, groupId?: string, categoryId?: string, pageNumber?: number, ): Promise { // If groupId is provided, verify that the group exists // We'll directly query the group to ensure it exists, regardless of user permissions // Since all groups are accessible to all users anyway if (groupId) { const groupRepo = this.noteRepository.manager.getRepository(KnowledgeGroup); const group = await groupRepo.findOne({ where: { id: groupId }, }); if (!group) { throw new NotFoundException( this.i18nService.formatMessage('knowledgeGroupNotFound', { id: groupId, }), ); } // Optional: Add logging to help debug permission issues console.log(`User ${userId} attempting to add note to group ${groupId}`); } if (categoryId === '') { categoryId = null as any; } // Save screenshot to disk await this.ensureScreenshotsDir(userId); const filename = `${uuidv4()}-${Date.now()}.png`; const screenshotPath = path.join( this.getScreenshotsDir(userId), filename, ); await fs.writeFile(screenshotPath, screenshot.buffer); // Extract text using OCR let extractedText = ''; try { extractedText = await this.ocrService.extractTextFromImage( screenshot.buffer, ); } catch (error) { console.error('OCR extraction failed:', error); // Continue without OCR text if extraction fails } // Create note with screenshot and extracted text const note = this.noteRepository.create({ userId, groupId: groupId || (null as any), categoryId: categoryId || (null as any), title: this.i18nService.formatMessage('pdfNoteTitle', { date: new Date().toLocaleString(), }), content: extractedText || this.i18nService.getMessage('noTextExtracted'), screenshotPath: `notes-screenshots/${userId}/${filename}`, sourceFileId: fileId, sourcePageNumber: pageNumber, }); return this.noteRepository.save(note); } async remove( userId: string, id: string, isAdmin: boolean, ): Promise { let result; if (isAdmin) { result = await this.noteRepository.delete({ id }); } else { result = await this.noteRepository.delete({ id, userId }); } if (result.affected === 0) { throw new NotFoundException( this.i18nService.formatMessage('noteNotFound', { id }), ); } } }