import { and, asc, eq, inArray, sql } from "drizzle-orm"; import { HTTPException } from "hono/http-exception"; import db from "../../database"; import { columnTable, externalLinkTable, labelTable, projectTable, taskTable, userTable, } from "../../database/schema"; type GetTasksOptions = { assigneeId?: string; limit?: number; page?: number; priority?: string; status?: string; }; async function getTasks(projectId: string, options: GetTasksOptions = {}) { const project = await db.query.projectTable.findFirst({ where: eq(projectTable.id, projectId), }); if (!project) { throw new HTTPException(404, { message: "Project not found", }); } const conditions = [eq(taskTable.projectId, projectId)]; if (options.status) { conditions.push(eq(taskTable.status, options.status)); } if (options.priority) { conditions.push(eq(taskTable.priority, options.priority)); } if (options.assigneeId) { conditions.push(eq(taskTable.userId, options.assigneeId)); } const whereClause = and(...conditions); const page = options.page && options.page > 0 ? options.page : 1; const limit = options.limit && options.limit > 0 ? Math.min(options.limit, 100) : null; const offset = limit ? (page - 1) * limit : 0; const [taskCount] = await db .select({ count: sql`count(*)` }) .from(taskTable) .where(whereClause); const taskSelection = { id: taskTable.id, title: taskTable.title, number: taskTable.number, description: taskTable.description, status: taskTable.status, priority: taskTable.priority, dueDate: taskTable.dueDate, position: taskTable.position, createdAt: taskTable.createdAt, userId: taskTable.userId, assigneeName: userTable.name, assigneeId: userTable.id, assigneeImage: userTable.image, projectId: taskTable.projectId, }; const paginatedTasks = limit ? await db .select(taskSelection) .from(taskTable) .leftJoin(userTable, eq(taskTable.userId, userTable.id)) .leftJoin(projectTable, eq(taskTable.projectId, projectTable.id)) .where(whereClause) .orderBy(taskTable.position) .limit(limit) .offset(offset) : await db .select(taskSelection) .from(taskTable) .leftJoin(userTable, eq(taskTable.userId, userTable.id)) .leftJoin(projectTable, eq(taskTable.projectId, projectTable.id)) .where(whereClause) .orderBy(taskTable.position); const taskIds = paginatedTasks.map((task) => task.id); const labelsData = taskIds.length > 0 ? await db .select({ id: labelTable.id, name: labelTable.name, color: labelTable.color, taskId: labelTable.taskId, }) .from(labelTable) .where(inArray(labelTable.taskId, taskIds)) : []; const externalLinksData = taskIds.length > 0 ? await db .select() .from(externalLinkTable) .where(inArray(externalLinkTable.taskId, taskIds)) : []; const taskLabelsMap = new Map< string, Array<{ id: string; name: string; color: string }> >(); for (const label of labelsData) { if (label.taskId) { if (!taskLabelsMap.has(label.taskId)) { taskLabelsMap.set(label.taskId, []); } taskLabelsMap.get(label.taskId)?.push({ id: label.id, name: label.name, color: label.color, }); } } const taskExternalLinksMap = new Map< string, Array<{ id: string; taskId: string; integrationId: string; resourceType: string; externalId: string; url: string; title: string | null; metadata: Record | null; }> >(); for (const externalLink of externalLinksData) { if (!taskExternalLinksMap.has(externalLink.taskId)) { taskExternalLinksMap.set(externalLink.taskId, []); } taskExternalLinksMap.get(externalLink.taskId)?.push({ ...externalLink, metadata: externalLink.metadata ? JSON.parse(externalLink.metadata) : null, }); } const projectColumns = await db .select() .from(columnTable) .where(eq(columnTable.projectId, projectId)) .orderBy(asc(columnTable.position)); const columns = projectColumns.map((column) => ({ id: column.slug, name: column.name, isFinal: column.isFinal, tasks: paginatedTasks .filter((task) => task.status === column.slug) .map((task) => ({ ...task, labels: taskLabelsMap.get(task.id) || [], externalLinks: taskExternalLinksMap.get(task.id) || [], })), })); const archivedTasks = paginatedTasks .filter((task) => task.status === "archived") .map((task) => ({ ...task, labels: taskLabelsMap.get(task.id) || [], externalLinks: taskExternalLinksMap.get(task.id) || [], })); const plannedTasks = paginatedTasks .filter((task) => task.status === "planned") .map((task) => ({ ...task, labels: taskLabelsMap.get(task.id) || [], externalLinks: taskExternalLinksMap.get(task.id) || [], })); return { id: project.id, name: project.name, slug: project.slug, icon: project.icon, description: project.description, isPublic: project.isPublic, workspaceId: project.workspaceId, columns, archivedTasks, plannedTasks, pagination: limit ? { page, limit, total: taskCount?.count ?? 0, totalPages: Math.max(1, Math.ceil((taskCount?.count ?? 0) / limit)), hasNextPage: offset + paginatedTasks.length < (taskCount?.count ?? 0), hasPreviousPage: page > 1, } : undefined, }; } export default getTasks;