Add issue attachments

- IssueAttachment link table (issue <-> file) + migration
- GET/POST /issues/:issueId/attachments, DELETE .../attachments/:fileId
- Only own uploaded files can be attached; attach is idempotent
- Registers issues as a file owner: attached files stay visible to issue
  participants and are not collected by GC
- IssueDetail.attachments GraphQL field via @ResolveField, issue module untouched

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
vadimandClaude Opus 5.5 committed 2026-10-02 09:18:02 +03:00
1 parent f32c6e348d
commit 423afa658d
28 files changed
+828 -16

No files matched your search

@@ -0,0 +1,24 @@
import { Injectable } from '@nestjs/common';
import { IssueAttachment } from '../../domain/entities/issue-attachment.entity.js';
import { IssueAttachmentDto } from '../dto/issue-attachment.dto.js';
import { GetFilesUseCase } from '../../../file/application/use-cases/get-files/get-files.use-case.js';
import { GetFilesQuery } from '../../../file/application/use-cases/get-files/get-files.query.js';
/** Раскрывает вложения в метаданные файлов из file-модуля */
@Injectable()
export class IssueAttachmentExpanderService {
constructor(private readonly getFilesUseCase: GetFilesUseCase) {}
async expandAll(attachments: IssueAttachment[]): Promise<IssueAttachmentDto[]> {
if (attachments.length === 0) return [];
const files = await this.getFilesUseCase.execute(new GetFilesQuery(attachments.map((a) => a.fileId)));
const filesById = new Map(files.map((file) => [file.id, file]));
// Файл могли удалить между запросами — такое вложение просто не показываем
return attachments.flatMap((attachment) => {
const file = filesById.get(attachment.fileId);
return file ? [{ issueId: attachment.issueId, file, attachedAt: attachment.createdAt }] : [];
});
}
}