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,21 @@
import { IssueAttachment } from '../entities/issue-attachment.entity.js';
/** DI-токен, т.к. интерфейсы стираются при компиляции TS */
export const ISSUE_ATTACHMENT_REPOSITORY = Symbol('ISSUE_ATTACHMENT_REPOSITORY');
export interface IIssueAttachmentRepository {
/** Вложения задачи от старых к новым */
findByIssue(issueId: number): Promise<IssueAttachment[]>;
/** Задачи, к которым прикреплён файл */
findIssueIdsByFile(fileId: string): Promise<number[]>;
/** Подмножество fileIds, прикреплённых хотя бы к одной задаче */
findReferencedFileIds(fileIds: string[]): Promise<string[]>;
/**
* Идемпотентно: повторное прикрепление возвращает существующее вложение.
* Бросает IssueNotFoundException / FileNotFoundException, если задачу
* или файл удалили между проверкой и вставкой.
*/
create(attachment: IssueAttachment): Promise<IssueAttachment>;
/** false — такого вложения не было */
delete(issueId: number, fileId: string): Promise<boolean>;
}