- 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>
35 lines
1.2 KiB
TypeScript
35 lines
1.2 KiB
TypeScript
import type { Readable } from 'node:stream';
|
|
import { Inject, Injectable } from '@nestjs/common';
|
|
import { FILE_STORAGE, type IFileStorage } from '../../../domain/ports/file-storage.port.js';
|
|
import { DownloadFileQuery } from './download-file.query.js';
|
|
import { FileDto } from '../../dto/file.dto.js';
|
|
import { FileApplicationMapper } from '../../mappers/file-application.mapper.js';
|
|
import { FileAccessService } from '../../services/file-access.service.js';
|
|
|
|
export interface DownloadFileResult {
|
|
file: FileDto;
|
|
/** Можно ли показывать в браузере inline, иначе — только скачивание */
|
|
inline: boolean;
|
|
stream: Readable;
|
|
}
|
|
|
|
@Injectable()
|
|
export class DownloadFileUseCase {
|
|
constructor(
|
|
@Inject(FILE_STORAGE)
|
|
private readonly fileStorage: IFileStorage,
|
|
private readonly fileAccess: FileAccessService,
|
|
) {}
|
|
|
|
async execute(query: DownloadFileQuery): Promise<DownloadFileResult> {
|
|
const file = await this.fileAccess.getVisibleFile(query.id, query.userId);
|
|
const stream = await this.fileStorage.get(file.storageKey);
|
|
|
|
return {
|
|
file: FileApplicationMapper.toDto(file),
|
|
inline: file.isInlineViewable,
|
|
stream,
|
|
};
|
|
}
|
|
}
|