| 8 | * Adapter that implements IFileStorageService using MinIO + PostgreSQL |
| 9 | */ |
| 10 | export class FileStorageAdapter implements IFileStorageService { |
| 11 | constructor( |
| 12 | private minioClient: Client, |
| 13 | private db: NodePgDatabase<typeof schema>, |
| 14 | private bucketName: string, |
| 15 | ) {} |
| 16 | |
| 17 | async downloadFile(fileId: string): Promise<{ |
| 18 | buffer: Buffer; |
| 19 | metadata: { |
| 20 | id: string; |
| 21 | fileName: string; |
| 22 | mimeType: string; |
| 23 | size: number; |
| 24 | }; |
| 25 | }> { |
| 26 | // Get metadata from database |
| 27 | const [file] = await this.db |
| 28 | .select() |
| 29 | .from(schema.files) |
| 30 | .where(eq(schema.files.id, fileId)) |
| 31 | .limit(1); |
| 32 | |
| 33 | if (!file) { |
| 34 | throw new NotFoundError(`File not found: ${fileId}`, { |
| 35 | resourceType: 'file', |
| 36 | resourceId: fileId, |
| 37 | }); |
| 38 | } |
| 39 | |
| 40 | // Download from MinIO |
| 41 | const stream = await this.minioClient.getObject(this.bucketName, file.storageKey); |
| 42 | |
| 43 | // Convert stream to buffer |
| 44 | const chunks: Buffer[] = []; |
| 45 | for await (const chunk of stream) { |
| 46 | chunks.push(Buffer.from(chunk)); |
| 47 | } |
| 48 | const buffer = Buffer.concat(chunks); |
| 49 | |
| 50 | return { |
| 51 | buffer, |
| 52 | metadata: { |
| 53 | id: file.id, |
| 54 | fileName: file.fileName, |
| 55 | mimeType: file.mimeType, |
| 56 | size: file.size, |
| 57 | }, |
| 58 | }; |
| 59 | } |
| 60 | |
| 61 | async getFileMetadata(fileId: string): Promise<{ |
| 62 | id: string; |
| 63 | fileName: string; |
| 64 | mimeType: string; |
| 65 | size: number; |
| 66 | uploadedAt: Date; |
| 67 | }> { |
nothing calls this directly
no outgoing calls
no test coverage detected