File size: 4,150 Bytes
f6c14df
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
const { query } = require('../utils/db');
const { auditLog } = require('../utils/auditLogger');
const { v4: uuidv4 } = require('uuid');

/**
 * GET /api/tasks/:id/comments
 */
const listComments = async (req, res) => {
  try {
    const { organization_id: orgId, id: userId, role } = req.user;
    const { id: taskId } = req.params;

    // Verify task belongs to org
    const taskCheck = await query(
      `SELECT id, creator_id, assignee_id FROM tasks WHERE id = $1 AND organization_id = $2`,
      [taskId, orgId]
    );
    if (!taskCheck.rows.length) return res.status(404).json({ error: 'Task not found.' });

    const task = taskCheck.rows[0];
    if (role === 'member' && task.creator_id !== userId && task.assignee_id !== userId) {
      return res.status(403).json({ error: 'Access denied.' });
    }

    const { rows } = await query(
      `SELECT c.id, c.body, c.created_at, c.updated_at,
              c.author_id, u.name AS author_name, u.email AS author_email
       FROM task_comments c
       JOIN users u ON u.id = c.author_id
       WHERE c.task_id = $1 AND c.organization_id = $2
       ORDER BY c.created_at ASC`,
      [taskId, orgId]
    );
    return res.json(rows);
  } catch (err) {
    console.error('List comments error:', err);
    return res.status(500).json({ error: 'Failed to retrieve comments.' });
  }
};

/**
 * POST /api/tasks/:id/comments
 */
const addComment = async (req, res) => {
  try {
    const { organization_id: orgId, id: userId, name: userName, email: userEmail, role } = req.user;
    const { id: taskId } = req.params;
    const { body } = req.body;

    if (!body || !body.trim()) {
      return res.status(422).json({ error: 'Comment cannot be empty.' });
    }

    const taskCheck = await query(
      `SELECT id, creator_id, assignee_id FROM tasks WHERE id = $1 AND organization_id = $2`,
      [taskId, orgId]
    );
    if (!taskCheck.rows.length) return res.status(404).json({ error: 'Task not found.' });

    const task = taskCheck.rows[0];
    if (role === 'member' && task.creator_id !== userId && task.assignee_id !== userId) {
      return res.status(403).json({ error: 'Access denied.' });
    }

    const { rows } = await query(
      `INSERT INTO task_comments (id, task_id, organization_id, author_id, body)
       VALUES ($1, $2, $3, $4, $5)
       RETURNING id, body, created_at, author_id`,
      [uuidv4(), taskId, orgId, userId, body.trim()]
    );

    // Touch task updated_at so it surfaces in recent activity
    await query(`UPDATE tasks SET updated_at = NOW() WHERE id = $1`, [taskId]);

    await auditLog({
      organizationId: orgId,
      taskId,
      actorId: userId,
      actorName: userName,
      actorEmail: userEmail,
      action: 'COMMENT_ADDED',
      entityType: 'comment',
      newValues: { body: body.trim().slice(0, 120) },
    });

    return res.status(201).json({
      ...rows[0],
      author_name: userName,
      author_email: userEmail,
    });
  } catch (err) {
    console.error('Add comment error:', err);
    return res.status(500).json({ error: 'Failed to add comment.' });
  }
};

/**
 * DELETE /api/tasks/:id/comments/:commentId
 */
const deleteComment = async (req, res) => {
  try {
    const { organization_id: orgId, id: userId, role } = req.user;
    const { id: taskId, commentId } = req.params;

    const { rows } = await query(
      `SELECT c.id, c.author_id FROM task_comments c
       JOIN tasks t ON t.id = c.task_id
       WHERE c.id = $1 AND c.task_id = $2 AND c.organization_id = $3`,
      [commentId, taskId, orgId]
    );

    if (!rows.length) return res.status(404).json({ error: 'Comment not found.' });

    const comment = rows[0];
    if (role !== 'admin' && comment.author_id !== userId) {
      return res.status(403).json({ error: 'You can only delete your own comments.' });
    }

    await query(`DELETE FROM task_comments WHERE id = $1`, [commentId]);
    return res.status(204).send();
  } catch (err) {
    console.error('Delete comment error:', err);
    return res.status(500).json({ error: 'Failed to delete comment.' });
  }
};

module.exports = { listComments, addComment, deleteComment };