issue observers
Наблюдатели задачи видят её и комментируют, но не редактируют.
- таблица issue_observer, наблюдатели — часть сущности Issue
- видимость: reporter, assignee или наблюдатель (задачи, история, SSE)
- правка/удаление задачи и вложений — только reporter/assignee (403 наблюдателю)
- observers в create/update, DELETE /issues/:id/observers/me, фильтр role
- история изменений и уведомления новым наблюдателям
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Issue changes are recorded per field in the issue_change table, in the same
transaction as the issue itself. The new history module exposes:
- GET /api/issues/:issueId/history — change timeline grouped by save
- GET /api/issues/stats/daily — created, status transitions and status
snapshot per day, with time zone support
The migration backfills history for existing issues. Setting a field to its
current value no longer bumps updatedAt or emits issue.updated.
- 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>