- CommentAttachment link table (comment <-> file) + migration
- fileIds in create/update comment; update replaces the whole set,
synced in one transaction with the comment
- CommentDto.attachments; empty for deleted comments
- Soft delete drops attachments so GC can collect the files
- Max 10 files per comment, ordered by upload time (uuid v7)
- Registers comments as a file owner
- Rename CommentAuthorExpanderService -> CommentExpanderService
(expands authors and files)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 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>
- 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>
Switch issue.assigneeId/reporterId FKs from CASCADE to SET NULL and make
reporter nullable through the entity, DTOs and GraphQL types.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>