import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; import { DEFAULT_PROJECT_SETTINGS, StatusCategory, } from '@task-stream/api/tracker/domain'; import { FindIssuesRepository, IssueSort, } from '../ports/find-issues.repository'; import { FindProjectsRepository } from '../ports/find-projects.repository'; import { BoardColumn, GetBoardQuery } from './get-board.query'; import { ReadAccess } from './read-access'; @QueryHandler(GetBoardQuery) export class GetBoardQueryHandler implements IQueryHandler { constructor( private readonly findIssues: FindIssuesRepository, private readonly findProjects: FindProjectsRepository, private readonly access: ReadAccess, ) {} /** * Columns follow the project's statuses in order. Cancelled ones stay off the * board; archived ones only show while they still hold tickets. */ async execute(query: GetBoardQuery): Promise { await this.access.assertCanView(query.filter.projectId, query.viewerId); const participantId = (await this.access.participantScope( query.filter.projectId, query.viewerId, )) ?? undefined; const project = await this.findProjects.findById(query.filter.projectId); const statuses = ( project?.settings ?? DEFAULT_PROJECT_SETTINGS ).statuses.filter((status) => status.category !== StatusCategory.CANCELLED); const columns = await Promise.all( statuses.map(async (status) => { const page = await this.findIssues.search({ ...query.filter, participantId, statuses: [status.id], // Each column follows the backlog order. sort: IssueSort.RANK, limit: Math.min(query.perColumn, 200), offset: 0, }); return { column: { status: status.id, issues: page.items, total: page.total }, archived: status.archived, }; }), ); return columns .filter(({ column, archived }) => !archived || column.total > 0) .map(({ column }) => column); } }