import { Command, CommandBus, CommandHandler, ICommandHandler, } from '@nestjs/cqrs'; import { SYSTEM_ACTOR_ID } from '@task-stream/api/tracker/domain'; import { FindIssuesRepository } from '../ports/find-issues.repository'; import { CommandResult, ack } from './command-result'; import { DeleteIssueCommand } from './delete-issue.command'; import { WriteAccess } from './write-access'; /** Days a ticket stays in the trash before it is deleted for good. */ export const TRASH_DAYS = 30; export class TrashIssueCommand extends Command { constructor( public readonly actorId: string, public readonly issueId: string, ) { super(); } } export class RestoreIssueCommand extends Command { constructor( public readonly actorId: string, public readonly issueId: string, ) { super(); } } /** Scheduler only: deletes for good what went to the trash before `before`. */ export class EmptyTrashCommand extends Command { constructor(public readonly before: string) { super(); } } @CommandHandler(TrashIssueCommand) export class TrashIssueCommandHandler implements ICommandHandler { constructor(private readonly access: WriteAccess) {} async execute(command: TrashIssueCommand): Promise { const { issue, project } = await this.access.issueForContributor( command.issueId, command.actorId, ); const role = project.roleOf(command.actorId); issue.trash(command.actorId, role === 'OWNER' || role === 'ADMIN'); await issue.commitAndWait(); return ack(issue); } } @CommandHandler(RestoreIssueCommand) export class RestoreIssueCommandHandler implements ICommandHandler { constructor(private readonly access: WriteAccess) {} async execute(command: RestoreIssueCommand): Promise { const issue = await this.access.issue(command.issueId); const project = await this.access.project(issue.projectId); project.assertCanContribute(command.actorId); const role = project.roleOf(command.actorId); issue.restore(command.actorId, role === 'OWNER' || role === 'ADMIN'); await issue.commitAndWait(); return ack(issue); } } /** * Each deletion is the usual one: comments purged, links dropped and * subtasks back to the top, by the sagas. */ @CommandHandler(EmptyTrashCommand) export class EmptyTrashCommandHandler implements ICommandHandler { constructor( private readonly findIssues: FindIssuesRepository, private readonly commandBus: CommandBus, ) {} async execute(command: EmptyTrashCommand): Promise { const old = await this.findIssues.findTrashedBefore(command.before); for (const issue of old) await this.commandBus.execute( new DeleteIssueCommand(SYSTEM_ACTOR_ID, issue.id), ); return old.length; } } export const TRASH_HANDLERS = [ TrashIssueCommandHandler, RestoreIssueCommandHandler, EmptyTrashCommandHandler, ];