- File model + migration: file metadata, content lives in object storage - IFileStorage port with local disk and S3-compatible (RustFS/MinIO) adapters - POST /files, GET /files/:id/download, DELETE /files/:id - MIME sniffed from content; only safe images served inline, nosniff header - FileOwnerRegistry: owner modules register to grant visibility and keep their files from GC; FileGcService removes orphaned files hourly - TransformResponseInterceptor passes StreamableFile through Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
27 lines
1.2 KiB
TypeScript
27 lines
1.2 KiB
TypeScript
import { ConfigService } from '@nestjs/config';
|
|
import type { IFileStorage } from '../../domain/ports/file-storage.port.js';
|
|
import { LocalFileStorage } from './local-file-storage.js';
|
|
import { S3FileStorage } from './s3-file-storage.js';
|
|
|
|
/** FILE_STORAGE=s3 — S3-совместимое хранилище, иначе локальный диск */
|
|
export function createFileStorage(config: ConfigService): IFileStorage {
|
|
const driver = config.get<string>('FILE_STORAGE', 'local');
|
|
|
|
if (driver === 's3') {
|
|
return new S3FileStorage({
|
|
endpoint: config.get<string>('S3_ENDPOINT') || undefined,
|
|
region: config.get<string>('S3_REGION', 'us-east-1'),
|
|
bucket: config.getOrThrow<string>('S3_BUCKET'),
|
|
accessKeyId: config.getOrThrow<string>('S3_ACCESS_KEY_ID'),
|
|
secretAccessKey: config.getOrThrow<string>('S3_SECRET_ACCESS_KEY'),
|
|
forcePathStyle: config.get<string>('S3_FORCE_PATH_STYLE', 'true') !== 'false',
|
|
});
|
|
}
|
|
|
|
if (driver !== 'local') {
|
|
throw new Error(`Неизвестный FILE_STORAGE=${driver}, ожидается local | s3`);
|
|
}
|
|
|
|
return new LocalFileStorage(config.get<string>('FILE_STORAGE_LOCAL_DIR', './storage'));
|
|
}
|