import { Injectable, Inject, NotFoundException, BadRequestException } from '@nestjs/common'; import { eq, and, isNull } from 'drizzle-orm'; import { DATABASE_CONNECTION } from '../db/database.module'; import { Database } from '../db/connection'; import { files, fileVersions } from '../db/schema'; import type { File, NewFile, NewFileVersion } from '../db/schema'; import { StorageService } from '../storage/storage.service'; import { CreateFileDto, UpdateFileDto, MoveFileDto } from './dto/create-file.dto'; @Injectable() export class FileService { constructor( @Inject(DATABASE_CONNECTION) private db: Database, private storageService: StorageService ) {} async findAll(userId: string, parentFolderId?: string): Promise { if (parentFolderId) { return this.db .select() .from(files) .where( and( eq(files.userId, userId), eq(files.parentFolderId, parentFolderId), eq(files.isDeleted, false) ) ); } // Root files (no parent folder) return this.db .select() .from(files) .where( and(eq(files.userId, userId), isNull(files.parentFolderId), eq(files.isDeleted, false)) ); } async findOne(userId: string, id: string): Promise { const result = await this.db .select() .from(files) .where(and(eq(files.id, id), eq(files.userId, userId), eq(files.isDeleted, false))); if (result.length === 0) { throw new NotFoundException('File not found'); } return result[0]; } async upload(userId: string, file: Express.Multer.File, dto: CreateFileDto): Promise { if (!file) { throw new BadRequestException('No file provided'); } // Upload to S3 const uploadResult = await this.storageService.uploadFile( userId, file.buffer, file.originalname, file.mimetype ); // Create file record const newFile: NewFile = { userId, name: file.originalname, originalName: file.originalname, mimeType: file.mimetype, size: file.size, storagePath: uploadResult.storagePath, storageKey: uploadResult.storageKey, parentFolderId: dto.parentFolderId || null, currentVersion: 1, }; const result = await this.db.insert(files).values(newFile).returning(); const createdFile = result[0]; // Create initial version record const version: NewFileVersion = { fileId: createdFile.id, versionNumber: 1, storagePath: uploadResult.storagePath, storageKey: uploadResult.storageKey, size: file.size, createdBy: userId, }; await this.db.insert(fileVersions).values(version); return createdFile; } async update(userId: string, id: string, dto: UpdateFileDto): Promise { await this.findOne(userId, id); const result = await this.db .update(files) .set({ ...dto, updatedAt: new Date(), }) .where(and(eq(files.id, id), eq(files.userId, userId))) .returning(); return result[0]; } async move(userId: string, id: string, dto: MoveFileDto): Promise { await this.findOne(userId, id); const result = await this.db .update(files) .set({ parentFolderId: dto.parentFolderId || null, updatedAt: new Date(), }) .where(and(eq(files.id, id), eq(files.userId, userId))) .returning(); return result[0]; } async delete(userId: string, id: string): Promise { await this.findOne(userId, id); // Soft delete await this.db .update(files) .set({ isDeleted: true, deletedAt: new Date(), updatedAt: new Date(), }) .where(and(eq(files.id, id), eq(files.userId, userId))); } async toggleFavorite(userId: string, id: string): Promise { const file = await this.findOne(userId, id); const result = await this.db .update(files) .set({ isFavorite: !file.isFavorite, updatedAt: new Date(), }) .where(and(eq(files.id, id), eq(files.userId, userId))) .returning(); return result[0]; } async download(userId: string, id: string): Promise<{ buffer: Buffer; file: File }> { const file = await this.findOne(userId, id); const buffer = await this.storageService.downloadFile(file.storageKey); return { buffer, file }; } async getDownloadUrl(userId: string, id: string): Promise { const file = await this.findOne(userId, id); return this.storageService.getDownloadUrl(file.storageKey); } }