Files
aurak/server/src/note/note.service.ts
T
Developer 0a9588abb7 feat: implement QuestionBank CRUD with pagination and template query
- Add pagination support to findAll (page, limit query params)
- Add findByTemplateId method to service
- Add GET /by-template/:templateId endpoint to controller
- Service already includes CRUD for QuestionBank and QuestionBankItem
2026-04-23 17:19:11 +08:00

219 lines
5.7 KiB
TypeScript

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<Note>,
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<Note>,
): Promise<Note> {
// 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<Note[]> {
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<Note> {
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<Note>,
isAdmin: boolean,
): Promise<Note> {
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<Note> {
// 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<void> {
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 }),
);
}
}
}