create issues fix
Gitea Actions / Deploy (push) Canceled after 5m13s

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>
This commit is contained in:
vadimandClaude Opus 5.5 committed 2026-10-25 22:23:49 +03:00
1 parent 46903e9ec5
commit b1f6b61994
54 files changed
+901 -69

No files matched your search

@@ -1,15 +1,13 @@
import { Inject, Injectable } from '@nestjs/common';
import { ISSUE_REPOSITORY, type IIssueRepository } from '../../../domain/repositories/issue.repository.interface.js';
import {
PROJECT_REPOSITORY,
type IProjectRepository,
} from '../../../../project/domain/repositories/project.repository.interface.js';
import { IssueNotFoundException } from '../../../domain/exceptions/issue-not-found.exception.js';
import { IssueEditForbiddenException } from '../../../domain/exceptions/issue-edit-forbidden.exception.js';
import { UpdateIssueCommand } from './update-issue.command.js';
import { IssueWithUsersDto } from '../../dto/issue-with-users.dto.js';
import { IssueEventsService } from '../../services/issue-events.service.js';
import { IssueUserExpanderService } from '../../services/issue-user-expander.service.js';
import { IssueAssignmentNotifierService } from '../../services/issue-assignment-notifier.service.js';
import { IssueParticipantsService } from '../../services/issue-participants.service.js';
import { IssueUpdatedEvent } from '../../../domain/events/issue-updated.event.js';
@Injectable()
@@ -17,11 +15,10 @@ export class UpdateIssueUseCase {
constructor(
@Inject(ISSUE_REPOSITORY)
private readonly issueRepository: IIssueRepository,
@Inject(PROJECT_REPOSITORY)
private readonly projectRepository: IProjectRepository,
private readonly issueEventsService: IssueEventsService,
private readonly issueUserExpander: IssueUserExpanderService,
private readonly issueAssignmentNotifier: IssueAssignmentNotifierService,
private readonly issueParticipants: IssueParticipantsService,
) {}
async execute(command: UpdateIssueCommand): Promise<IssueWithUsersDto> {
@@ -31,6 +28,12 @@ export class UpdateIssueUseCase {
throw new IssueNotFoundException(command.id);
}
if (!issue.canBeEditedBy(command.userId)) {
throw new IssueEditForbiddenException(command.id);
}
const observersBefore = [...issue.observers];
if (command.title !== undefined) {
issue.updateTitle(command.title);
}
@@ -63,12 +66,16 @@ export class UpdateIssueUseCase {
issue.updateDueDate(command.dueDate);
}
// После assignee: новый исполнитель отбрасывается из наблюдателей
if (command.observers !== undefined) {
issue.setObservers(command.observers);
}
await this.issueParticipants.ensureObserversExist(issue.observers.filter((id) => !observersBefore.includes(id)));
await this.issueRepository.update(issue, command.userId);
const dto = await this.issueUserExpander.expand(issue);
if (issue.assignee) {
await this.projectRepository.addMember(issue.projectId, issue.assignee);
}
await this.issueParticipants.addToProject(issue);
const domainEvents = issue.pullDomainEvents();
this.issueAssignmentNotifier.notify(issue, domainEvents);