import { AuthenticatedUser } from "../auth"; import { createNote } from "../note/note.service"; import { getFilterObject, getSortObject, PageQueryParams } from "../pagination"; import { generateId } from "../utils/id"; import { taskPipeline } from "../utils/pipeline"; import { CreateTaskInput, taskFields, taskModel, UpdateTaskInput, } from "./ctask.schema"; const InvalidOrg = new Error("invalid orgId"); export async function createTask( input: CreateTaskInput, user: AuthenticatedUser ) { if (!input.stage) { input.stage = { pipeline: taskPipeline, currentStage: 0, }; } if (input.orgId && !user.orgId.includes(input.orgId)) { throw InvalidOrg; } const task = await taskModel.create({ ...input, tenantId: user.tenantId, orgId: input.orgId ?? user.orgId[0], pid: generateId(), createdAt: new Date(), createdBy: user.userId ?? null, }); if (input.note) { await createNote( { content: input.note, }, task.pid, "ctasks", user ); } return await taskModel .findOne({ pid: task.pid }) .populate({ path: "createdBy", select: "pid name avatar" }) .populate({ path: "assignedTo", select: "pid name avatar" }); } export async function updateTask( taskId: string, input: UpdateTaskInput, user: AuthenticatedUser ) { if (input.orgId && !user.orgId.includes(input.orgId)) { throw InvalidOrg; } const updatedTask = await taskModel .findOneAndUpdate( { tenantId: user.tenantId, orgId: { $in: user.orgId }, pid: taskId }, input, { new: true } ) .populate({ path: "createdBy", select: "pid name avatar" }) .populate({ path: "assignedTo", select: "pid name avatar" }); return updatedTask; } export async function getTask(taskId: string, user: AuthenticatedUser) { return await taskModel .findOne({ tenantId: user.tenantId, orgId: { $in: user.orgId }, pid: taskId, }) .populate({ path: "createdBy", select: "pid name avatar" }) .populate({ path: "assignedTo", select: "pid name avatar" }); } export async function listTasks( params: PageQueryParams, user: AuthenticatedUser ) { const page = params.page || 1; const pageSize = params.pageSize || 10; const sortObj = getSortObject(params, taskFields); let filterObj = getFilterObject(params) || []; const taggedUserFilterIndex = filterObj.findIndex((item) => Object.keys(item).includes("taggedUsers") ); let taggedFilter = []; if (taggedUserFilterIndex != -1) { const filterItem = filterObj[taggedUserFilterIndex].taggedUsers; let filterValues = null; if (filterItem["$eq"]) filterValues = [filterItem["$eq"]]; else filterValues = filterItem["$in"]; filterValues = filterValues.map((item) => item.toString()); taggedFilter = [ { $addFields: { taggedUsers: { $filter: { input: "$taggedUsers", as: "user", cond: { $in: ["$$user.userId", filterValues] }, }, }, }, }, { $match: { "taggedUsers.0": { $exists: true } } }, { $sort: { "taggedUsers.taggedAt": sortObj.taggedUsers ?? -1 } }, ]; filterObj.splice(taggedUserFilterIndex, 1); } const taskList = await taskModel.aggregate([ { $match: { $and: [ { tenantId: user.tenantId }, { orgId: { $in: user.orgId } }, ...filterObj, ], }, }, ...taggedFilter, { $lookup: { from: "users", localField: "createdBy", foreignField: "_id", as: "createdBy", }, }, { $lookup: { from: "users", localField: "assignedTo", foreignField: "_id", as: "assignedTo", }, }, { $project: { _id: 1, pid: 1, title: 1, dueDate: 1, labels: 1, priority: 1, documents: 1, stage: 1, createdAt: 1, taggedUsers: 1, createdBy: { $let: { vars: { createdBy: { $arrayElemAt: ["$createdBy", 0] } }, in: { _id: "$$createdBy._id", pid: "$$createdBy.pid", name: "$$createdBy.name", }, }, }, assignedTo: { $map: { input: "$assignedTo", as: "user", in: { _id: "$$user._id", pid: "$$user.pid", name: "$$user.name", avatar: "$$user.avatar", }, }, }, }, }, { $facet: { metadata: [{ $count: "count" }], data: [ { $sort: sortObj }, { $skip: (page - 1) * pageSize }, { $limit: pageSize }, ], }, }, ]); if (taskList[0].data.length === 0) return { tasks: [], metadata: { count: 0, page, pageSize } }; return { tasks: taskList[0]?.data, metadata: { count: taskList[0].metadata[0].count, page, pageSize, }, }; } export async function deleteTask(taskId: string, user: AuthenticatedUser) { return await taskModel.deleteOne({ pid: taskId, orgId: { $in: user.orgId }, tenantId: user.tenantId, }); } export async function searchTasks( params: PageQueryParams, user: AuthenticatedUser ) { const page = params.page || 1; const pageSize = params.pageSize || 10; const sortObj = getSortObject(params, taskFields); const filterObj = getFilterObject(params) || []; const regex = new RegExp(params.searchToken, "i"); const taskList = await taskModel.aggregate([ { $match: { $and: [ { tenantId: user.tenantId }, { orgId: { $in: user.orgId } }, ...filterObj, ], }, }, { $match: { $or: [{ title: { $regex: regex } }], }, }, { $lookup: { from: "users", localField: "createdBy", foreignField: "_id", as: "createdBy", }, }, { $lookup: { from: "users", localField: "assignedTo", foreignField: "_id", as: "assignedTo", }, }, { $project: { _id: 1, pid: 1, title: 1, dueDate: 1, labels: 1, priority: 1, documents: 1, stage: 1, createdAt: 1, taggedUsers: 1, createdBy: { $let: { vars: { createdBy: { $arrayElemAt: ["$createdBy", 0] } }, in: { _id: "$$createdBy._id", pid: "$$createdBy.pid", name: "$$createdBy.name", }, }, }, assignedTo: { $map: { input: "$assignedTo", as: "user", in: { _id: "$$user._id", pid: "$$user.pid", name: "$$user.name", avatar: "$$user.avatar", }, }, }, }, }, { $facet: { metadata: [{ $count: "count" }], data: [ { $sort: sortObj }, { $skip: (page - 1) * pageSize }, { $limit: pageSize }, ], }, }, ]); if (taskList[0].data.length === 0) return { tasks: [], metadata: { count: 0, page, pageSize } }; return { tasks: taskList[0]?.data, metadata: { count: taskList[0].metadata[0].count, page, pageSize, }, }; }