import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; import { DEFAULT_PROJECT_SETTINGS, IssueReadModel, StatusCategory, } from '@task-stream/api/tracker/domain'; import { FindIssuesRepository, Page } from '../ports/find-issues.repository'; import { FindProjectsRepository } from '../ports/find-projects.repository'; import { SearchMyIssuesQuery } from './search-my-issues.query'; @QueryHandler(SearchMyIssuesQuery) export class SearchMyIssuesQueryHandler implements IQueryHandler> { constructor( private readonly findProjects: FindProjectsRepository, private readonly findIssues: FindIssuesRepository, ) {} /** Membership is the access rule: only projects the viewer belongs to. */ async execute(query: SearchMyIssuesQuery): Promise> { const { filter } = query; const projects = ( await this.findProjects.findByMember(query.viewerId) ).filter( (p) => !filter.projectIds?.length || filter.projectIds.includes(p.id), ); // Bugs are each project's types drawn with the bug icon. const bugTypes = filter.bugsOnly ? Object.fromEntries( projects.map((p) => [ p.id, (p.settings ?? DEFAULT_PROJECT_SETTINGS).issueTypes .filter((t) => t.icon === 'bug') .map((t) => t.id), ]), ) : undefined; const open = [ StatusCategory.TODO, StatusCategory.IN_PROGRESS, StatusCategory.QA, ]; return this.findIssues.searchAcross({ projectIds: projects.map((project) => project.id), priorities: filter.priorities?.length ? filter.priorities : undefined, typesByProject: bugTypes, dueBefore: filter.overdue ? new Date().toISOString().slice(0, 10) : undefined, assigneeId: query.filter.assignedToMe ? query.viewerId : undefined, watcherId: query.filter.watching ? query.viewerId : undefined, statuses: query.filter.statuses, categories: filter.overdue ? (filter.categories?.length ? filter.categories : open).filter((c) => open.includes(c), ) : query.filter.categories, search: query.filter.search?.trim().slice(0, 200) || undefined, limit: Math.min(query.filter.limit, 200), offset: query.filter.offset, }); } }