import { Injectable } from '@nestjs/common'; import { CommandBus, QueryBus } from '@nestjs/cqrs'; import { ReleaseBurnupReadModel, ReleaseProgressReadModel, ReleaseReadModel, } from '@task-stream/api/tracker/domain'; import { ArchiveReleaseCommand } from './commands/archive-release.command'; import { CommandResult } from './commands/command-result'; import { CreateReleaseCommand } from './commands/create-release.command'; import { PublishReleaseCommand } from './commands/publish-release.command'; import { UpdateReleaseCommand } from './commands/update-release.command'; import { WriteReleaseNotesCommand } from './commands/write-release-notes.command'; import { GetReleaseNotesDraftQuery } from './queries/get-release-notes-draft.query'; import { GetReleaseProgressQuery } from './queries/get-release-progress.query'; import { GetReleaseQuery } from './queries/get-release.query'; import { GetUpcomingReleasesQuery } from './queries/get-upcoming-releases.query'; import { GetReleaseBurnupQuery } from './queries/get-release-burnup.query'; interface ReleaseDetails { name?: string; description?: string | null; targetDate?: string | null; } @Injectable() export class ReleasesService { constructor( private readonly commandBus: CommandBus, private readonly queryBus: QueryBus, ) {} create( actorId: string, input: ReleaseDetails & { projectId: string; name: string }, ): Promise { return this.commandBus.execute( new CreateReleaseCommand( actorId, input.projectId, input.name, input.description, input.targetDate, ), ); } update( actorId: string, releaseId: string, changes: ReleaseDetails, ): Promise { return this.commandBus.execute( new UpdateReleaseCommand(actorId, releaseId, changes), ); } /** Ships the release; fails with unfinished tickets unless `force`. */ publish( actorId: string, releaseId: string, force = false, ): Promise { return this.commandBus.execute( new PublishReleaseCommand(actorId, releaseId, force), ); } archive(actorId: string, releaseId: string): Promise { return this.commandBus.execute( new ArchiveReleaseCommand(actorId, releaseId), ); } findOne( viewerId: string | null, releaseId: string, ): Promise { return this.queryBus.execute(new GetReleaseQuery(viewerId, releaseId)); } /** Planned releases of all my projects, soonest target date first. */ upcoming(viewerId: string, limit: number): Promise { return this.queryBus.execute(new GetUpcomingReleasesQuery(viewerId, limit)); } /** Writes the notes (null removes them); also after the release ships. */ writeNotes( actorId: string, releaseId: string, notes: string | null, ): Promise { return this.commandBus.execute( new WriteReleaseNotesCommand(actorId, releaseId, notes), ); } /** A Markdown draft from the release's finished tickets (not saved). */ notesDraft(viewerId: string | null, releaseId: string): Promise { return this.queryBus.execute( new GetReleaseNotesDraftQuery(viewerId, releaseId), ); } progress( viewerId: string | null, releaseId: string, ): Promise { return this.queryBus.execute( new GetReleaseProgressQuery(viewerId, releaseId), ); } /** Scope vs finished tickets of the release, day by day. */ burnup( viewerId: string | null, releaseId: string, ): Promise { return this.queryBus.execute( new GetReleaseBurnupQuery(viewerId, releaseId), ); } }