import { Router } from "express"; import { AuthRequest, requireAuth } from "../middleware/auth.js"; import { canAccessTask, User } from "../lib/auth-utils.js"; import { db } from "@workspace/db"; import { commentsTable, usersTable, notificationsTable, tasksTable } from "@workspace/db"; import { eq } from "drizzle-orm"; import { ListTaskCommentsParams } from "@workspace/api-zod"; import { logAudit } from "../lib/audit-logger.js"; import { z } from "zod"; const router = Router(); router.get("/tasks/:id/comments", requireAuth, async (req: AuthRequest, res) => { try { const user = req.user as User; if (!user) { res.status(401).json({ error: "Unauthorized" }); return; } const { id } = ListTaskCommentsParams.parse({ id: Number(req.params.id) }); const [task] = await db.select().from(tasksTable).where(eq(tasksTable.id, id)); if (!task) { res.status(404).json({ error: "Task not found" }); return; } if (!canAccessTask(user, task)) { res.status(403).json({ error: "Forbidden: You do not have access to this task's comments" }); return; } const comments = await db.select().from(commentsTable) .where(eq(commentsTable.taskId, id)) .orderBy(commentsTable.createdAt); const userIds = [...new Set(comments.map(c => c.userId).filter(Boolean))] as number[]; const users = userIds.length > 0 ? await db.select().from(usersTable) .then(all => all.filter(u => userIds.includes(u.id))) : []; const userMap = Object.fromEntries(users.map(u => [u.id, { ...u, createdAt: u.createdAt.toISOString() }])); res.json(comments.map(c => ({ ...c, createdAt: c.createdAt.toISOString(), user: c.userId ? userMap[c.userId] ?? null : null, }))); } catch (err) { req.log.error({ err }, "Failed to list comments"); res.status(500).json({ error: "Internal server error" }); } }); const createCommentSchema = z.object({ taskId: z.coerce.number().int().positive("معرف المهمة غير صالح"), content: z.string().trim().min(1, "محتوى التعليق مطلوب"), mentionedUserIds: z.array(z.coerce.number().int().positive("معرف المستخدم غير صالح")).optional(), }).strict(); async function handleCreateComment(req: AuthRequest, res: any) { try { const requestingUser = req.user as User; if (!requestingUser) { res.status(401).json({ error: "Unauthorized" }); return; } const bodyData = req.params.id !== undefined ? { taskId: req.params.id, ...req.body } : req.body; const parseResult = createCommentSchema.safeParse(bodyData); if (!parseResult.success) { res.status(400).json({ error: parseResult.error.errors[0].message }); return; } const { taskId, content, mentionedUserIds } = parseResult.data; const [task] = await db.select().from(tasksTable).where(eq(tasksTable.id, taskId)); if (!task) { res.status(404).json({ error: "Task not found" }); return; } if (!canAccessTask(requestingUser, task)) { res.status(403).json({ error: "Forbidden: You do not have permission to comment on this task" }); return; } const [comment] = await db.insert(commentsTable).values({ taskId, userId: requestingUser.id, content, }).returning(); logAudit({ userId: requestingUser.id, action: "comment_created", entityType: "comment", entityId: comment.id, details: { commentId: comment.id, taskId: comment.taskId, content: comment.content, mentionedUserIds: mentionedUserIds ?? [], }, req, }); if (task) { const notifyUserIds = new Set(); if (task.createdByUserId && task.createdByUserId !== requestingUser.id) notifyUserIds.add(task.createdByUserId); if (task.assignedUserId && task.assignedUserId !== requestingUser.id) notifyUserIds.add(task.assignedUserId); if (mentionedUserIds && mentionedUserIds.length > 0) { mentionedUserIds.forEach(uid => { if (uid !== requestingUser.id) notifyUserIds.add(uid); }); } if (notifyUserIds.size > 0) { await db.insert(notificationsTable).values( [...notifyUserIds].map(uid => ({ userId: uid, taskId, type: "comment_added", message: `تعليق جديد على المهمة: "${task.title}"`, })) ); } } let commentAuthor = null; if (comment.userId) { const [u] = await db.select().from(usersTable).where(eq(usersTable.id, comment.userId)); if (u) commentAuthor = { ...u, createdAt: u.createdAt.toISOString() }; } res.status(201).json({ ...comment, createdAt: comment.createdAt.toISOString(), user: commentAuthor, }); } catch (err) { req.log.error({ err: err instanceof Error ? err.message : String(err) }, "Failed to create comment"); res.status(500).json({ error: "Internal server error" }); } } router.post("/tasks/:id/comments", requireAuth, handleCreateComment); router.post("/comments", requireAuth, handleCreateComment); export default router;