Spaces:
Sleeping
Sleeping
File size: 5,127 Bytes
8314cf4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | 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<number>();
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;
|