import { Injectable, NotFoundException } from "@nestjs/common"; import { Readable } from "stream"; import { MinioService } from "../minio/minio.service"; import { FilesRepository } from "./files.repository"; import { FileRecord } from "./entities/file.entity"; export interface CreateFileInput { resourceId: string; resource: string; code: string; file: Express.Multer.File; } @Injectable() export class FilesService { constructor( private readonly filesRepository: FilesRepository, private readonly minioService: MinioService, ) {} async upload(input: CreateFileInput): Promise { const { resourceId, resource, code, file } = input; const objectName = `${resource}/${resourceId}/${Date.now()}_${file.originalname}`; const url = await this.minioService.uploadFile(objectName, file.buffer, file.mimetype); return this.filesRepository.create({ resourceId, resource, code, name: file.originalname, url, size: file.size, mimeType: file.mimetype, }); } /** Replace existing file row for the same resource + code (e.g. contract PDF). */ async upsertByCode(input: CreateFileInput): Promise { const { resourceId, resource, code } = input; await this.filesRepository.deleteByCode(resourceId, resource, code); return this.upload(input); } async uploadMany( resourceId: string, resource: string, files: Express.Multer.File[], ): Promise { return Promise.all( files.map((file) => this.upload({ resourceId, resource, code: file.fieldname, file }), ), ); } async findById(id: string): Promise { const record = await this.filesRepository.findById(id); if (!record) throw new NotFoundException(`File ${id} not found`); return record; } findByResource(resourceId: string, resource: string): Promise { return this.filesRepository.findByResource(resourceId, resource); } async findByCode( resourceId: string, resource: string, code: string, ): Promise { const record = await this.filesRepository.findByCode(resourceId, resource, code); if (!record) throw new NotFoundException( `File with code "${code}" not found for ${resource} ${resourceId}`, ); return record; } async streamById(id: string): Promise<{ stream: Readable; record: FileRecord }> { const record = await this.findById(id); const objectName = this.extractObjectName(record.url); const stream = await this.minioService.getFileStream(objectName); return { stream, record }; } private extractObjectName(url: string): string { const parts = url.split("/"); return parts.slice(4).join("/"); } }