refactor: changed the findTasksByTaskPhase to include task info like remark, etc

This commit is contained in:
2026-07-14 21:28:09 +03:30
parent f35df6492c
commit 4263d7118e
@@ -49,26 +49,21 @@ export class TaskRepository extends Repository<TMTask> {
.getOne();
}
async findTasksByTaskPhase(
taskPhaseId: string,
pagination: CursorPaginationDto,
): Promise<{ items: TMTask[]; hasMore: boolean }> {
async findTasksByTaskPhase(taskPhaseId: string, pagination: CursorPaginationDto): Promise<{ items: TMTask[]; hasMore: boolean }> {
const limit = pagination.limit || 10;
const isBackward = pagination.direction === CursorDirection.PREV;
// truncated to milliseconds because a JS Date (and therefore the encoded cursor) only
// has millisecond precision, while Postgres timestamptz stores microseconds - comparing
// the raw column against the cursor would otherwise re-match the boundary row itself.
const createdAtExpr = `date_trunc('milliseconds', task.createdAt)`;
const queryBuilder = this.createQueryBuilder("task")
const seekQuery = this.createQueryBuilder("task")
.select(["task.id", "task.priority", "task.createdAt"])
.where("task.taskPhaseId = :taskPhaseId", { taskPhaseId })
.limit(limit + 1); // fetch one extra to check whether there's a further page
if (isBackward) {
queryBuilder.orderBy("task.priority", "ASC").addOrderBy(createdAtExpr, "DESC").addOrderBy("task.id", "DESC");
seekQuery.orderBy("task.priority", "ASC").addOrderBy(createdAtExpr, "DESC").addOrderBy("task.id", "DESC");
} else {
queryBuilder.orderBy("task.priority", "DESC").addOrderBy(createdAtExpr, "ASC").addOrderBy("task.id", "ASC");
seekQuery.orderBy("task.priority", "DESC").addOrderBy(createdAtExpr, "ASC").addOrderBy("task.id", "ASC");
}
if (pagination.cursor) {
@@ -76,7 +71,7 @@ export class TaskRepository extends Repository<TMTask> {
const priorityOp = isBackward ? ">" : "<";
const seekOp = isBackward ? "<" : ">";
queryBuilder.andWhere(
seekQuery.andWhere(
`(task.priority ${priorityOp} :cursorPriority
OR (task.priority = :cursorPriority AND ${createdAtExpr} ${seekOp} :cursorCreatedAt)
OR (task.priority = :cursorPriority AND ${createdAtExpr} = :cursorCreatedAt AND task.id ${seekOp} :cursorId))`,
@@ -84,13 +79,32 @@ export class TaskRepository extends Repository<TMTask> {
);
}
const rows = await queryBuilder.getMany();
const hasMore = rows.length > limit;
if (hasMore) rows.pop();
const seekRows = await seekQuery.getMany();
const hasMore = seekRows.length > limit;
if (hasMore) seekRows.pop();
// backward queries fetch in reverse order to seek from the cursor, so flip back to
// the normal display order before returning
const items = isBackward ? rows.reverse() : rows;
const orderedRows = isBackward ? seekRows.reverse() : seekRows;
const ids = orderedRows.map((row) => row.id);
if (!ids.length) return { items: [], hasMore };
// Detail query: hydrates exactly the ids picked above with the relations/counts the
// response needs. IN (:...ids) doesn't preserve order, so re-order to match seekQuery.
const detailRows = await this.createQueryBuilder("task")
.leftJoinAndSelect("task.remarks", "remark")
.loadRelationCountAndMap("task.attachmentCount", "task.attachments")
.loadRelationCountAndMap("task.checkListItemCount", "task.checkListItems")
.loadRelationCountAndMap("task.completedCheckListItemCount", "task.checkListItems", "completedCheckListItems", (qb) =>
qb.where("completedCheckListItems.isDone = :isDone", { isDone: true }),
)
.select(["task", "remark.id", "remark.title", "remark.color"])
.where("task.id IN (:...ids)", { ids })
.getMany();
const detailById = new Map(detailRows.map((task) => [task.id, task]));
const items = ids.map((id) => detailById.get(id)).filter((task): task is TMTask => task !== undefined);
return { items, hasMore };
}