• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SyTW2526 / Proyecto-E13 / 20038887352

08 Dec 2025 06:39PM UTC coverage: 39.927% (-38.4%) from 78.28%
20038887352

push

github

web-flow
Merge pull request #42 from SyTW2526/feature/permissions

feat: permissions fixed

74 of 257 branches covered (28.79%)

Branch coverage included in aggregate %.

26 of 140 new or added lines in 2 files covered. (18.57%)

253 of 562 relevant lines covered (45.02%)

1.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

17.81
/server/src/controllers/tasksController.ts
1
import { Request, Response } from "express";
2
import prisma from "../database/prisma";
3
import { TaskPriority, TaskStatus } from "../types/task";
4
import { SharePermission } from "@prisma/client";
5
import { createNotification } from "./notificationsController";
6

7
export const createTask = async (req: Request, res: Response) => {
3✔
8
  try {
×
NEW
9
    const userId = req.user?.id;
×
NEW
10
    if (!userId) {
×
NEW
11
      return res.status(401).json({ error: "Unauthorized" });
×
12
    }
13

14
    const { name, description, status, listId, priority, dueDate } = req.body;
×
15

NEW
16
    const list = await prisma.list.findUnique({
×
17
      where: { id: listId },
18
      include: {
19
        shares: {
20
          where: { userId },
21
        },
22
      },
23
    });
24

NEW
25
    if (!list) {
×
NEW
26
      return res.status(404).json({ error: "List not found" });
×
27
    }
28

NEW
29
    if (list.ownerId !== userId) {
×
NEW
30
      const share = list.shares[0];
×
NEW
31
      if (!share || share.permission === SharePermission.VIEW) {
×
NEW
32
        return res.status(403).json({ error: "Unauthorized" });
×
33
      }
34
    }
35

36
    const task = await prisma.task.create({
×
37
      data: {
38
        name,
39
        description,
40
        status,
41
        listId,
42
        priority,
43
        dueDate,
44
      },
45
      include: {
46
        shares: {
47
          include: {
48
            user: {
49
              select: {
50
                id: true,
51
                name: true,
52
                email: true,
53
                image: true,
54
              },
55
            },
56
          },
57
        },
58
        list: true,
59
      },
60
    });
61
    return res.status(200).json(task);
×
62
  } catch (error) {
63
    console.error(error);
×
64
    return res.status(500).json({ error: "Error creating task" });
×
65
  }
66
};
67

68
export const deleteTask = async (req: Request, res: Response) => {
3✔
69
  try {
×
NEW
70
    const userId = req.user?.id;
×
NEW
71
    if (!userId) {
×
NEW
72
      return res.status(401).json({ error: "Unauthorized" });
×
73
    }
74

75
    const { id } = req.params;
×
76

NEW
77
    const task = await getTaskWithPermissions(id, userId);
×
NEW
78
    if (!task) return res.status(404).json({ error: "Task not found" });
×
NEW
79
    if (!task.list) return res.status(404).json({ error: "List not found" });
×
80

NEW
81
    if (!hasPermission(task, userId, SharePermission.ADMIN, true)) {
×
NEW
82
      return res.status(403).json({ error: "Unauthorized" });
×
83
    }
84

NEW
85
    await prisma.task.delete({
×
86
      where: {
87
        id,
88
      },
89
    });
90
    return res.status(200).json(task);
×
91
  } catch (error) {
92
    console.error(error);
×
93
    return res.status(500).json({ error: "Error deleting task" });
×
94
  }
95
};
96

97
export const getUserTasks = async (req: Request, res: Response) => {
3✔
98
  try {
×
99
    const userId = req.user?.id;
×
NEW
100
    if (!userId) {
×
NEW
101
      return res.status(401).json({ error: "Unauthorized" });
×
102
    }
103

104
    const tasks = await prisma.task.findMany({
×
105
      where: {
106
        list: {
107
          ownerId: userId,
108
        },
109
      },
110
      include: {
111
        shares: {
112
          include: {
113
            user: {
114
              select: {
115
                id: true,
116
                name: true,
117
                email: true,
118
                image: true,
119
              },
120
            },
121
          },
122
        },
123
        list: true,
124
      },
125
    });
126
    return res.status(200).json(tasks);
×
127
  } catch (error) {
128
    console.error(error);
×
129
    return res.status(500).json({ error: "Error getting tasks" });
×
130
  }
131
};
132

133
export const getSharedTasks = async (req: Request, res: Response) => {
3✔
134
  try {
1✔
135
    const userId = req.user?.id;
1✔
136
    const tasks = await prisma.task.findMany({
1✔
137
      where: {
138
        OR: [
139
          {
140
            shares: {
141
              some: {
142
                userId: userId,
143
              },
144
            },
145
          },
146
          {
147
            list: {
148
              shares: {
149
                some: {
150
                  userId: userId,
151
                },
152
              },
153
            },
154
          },
155
        ],
156
      },
157
      include: {
158
        shares: {
159
          include: {
160
            user: {
161
              select: {
162
                id: true,
163
                name: true,
164
                email: true,
165
                image: true,
166
              },
167
            },
168
          },
169
        },
170
        list: {
171
          include: {
172
            owner: {
173
              select: {
174
                id: true,
175
                name: true,
176
                email: true,
177
                image: true,
178
              },
179
            },
180
          },
181
        },
182
      },
183
    });
184
    return res.status(200).json(tasks);
1✔
185
  } catch (error) {
186
    console.error(error);
×
187
    return res.status(500).json({ error: "Error getting shared tasks" });
×
188
  }
189
};
190

191
export const updateTask = async (req: Request, res: Response) => {
3✔
192
  try {
×
NEW
193
    const userId = req.user?.id;
×
NEW
194
    if (!userId) {
×
NEW
195
      return res.status(401).json({ error: "Unauthorized" });
×
196
    }
197

198
    const { id } = req.params;
×
NEW
199
    const task = await getTaskWithPermissions(id, userId);
×
NEW
200
    if (!task) return res.status(404).json({ error: "Task not found" });
×
NEW
201
    if (!task.list) return res.status(404).json({ error: "List not found" });
×
202

NEW
203
    if (!hasPermission(task, userId, SharePermission.EDIT)) {
×
NEW
204
      return res.status(403).json({ error: "Unauthorized" });
×
205
    }
206

207
    const { name, description, status, listId, priority, dueDate, favorite } =
208
      req.body;
×
209
    const dataToUpdate: {
210
      name?: string;
211
      description?: string;
212
      status?: TaskStatus;
213
      listId?: string;
214
      priority?: TaskPriority;
215
      dueDate?: string;
216
      favorite?: boolean;
217
    } = {};
×
218

219
    if (name) dataToUpdate.name = name;
×
220
    if (description) dataToUpdate.description = description;
×
221
    if (status) dataToUpdate.status = status;
×
222
    if (listId) dataToUpdate.listId = listId;
×
223
    if (priority) dataToUpdate.priority = priority;
×
224
    if (dueDate) dataToUpdate.dueDate = dueDate;
×
225
    if (favorite) dataToUpdate.favorite = favorite;
×
226

227
    if (Object.keys(dataToUpdate).length === 0) {
×
228
      return res.status(400).json({ error: "No fields to update" });
×
229
    }
230

NEW
231
    const taskUpdated = await prisma.task.update({
×
232
      where: {
233
        id,
234
      },
235
      data: dataToUpdate,
236
      include: {
237
        shares: {
238
          include: {
239
            user: {
240
              select: {
241
                id: true,
242
                name: true,
243
                email: true,
244
                image: true,
245
              },
246
            },
247
          },
248
        },
249
        list: true,
250
      },
251
    });
NEW
252
    return res.status(200).json(taskUpdated);
×
253
  } catch (error) {
254
    console.error(error);
×
255
    return res.status(500).json({ error: "Error updating task" });
×
256
  }
257
};
258

259
export const shareTask = async (req: Request, res: Response) => {
3✔
260
  try {
1✔
261
    const userId = req.user?.id;
1✔
262
    if (!userId) {
1!
NEW
263
      return res.status(401).json({ error: "Unauthorized" });
×
264
    }
265
    const { id } = req.params;
1✔
266

267
    const task = await getTaskWithPermissions(id, userId);
1✔
268
    if (!task) return res.status(404).json({ error: "Task not found" });
1!
269
    if (!task.list) return res.status(404).json({ error: "List not found" });
1!
270

271
    if (!hasPermission(task, userId, SharePermission.ADMIN)) {
1!
NEW
272
      return res.status(403).json({ error: "Unauthorized" });
×
273
    }
274

275
    const { email, permission } = req.body;
1✔
276

277
    const userToShare = await prisma.user.findUnique({
1✔
278
      where: {
279
        email,
280
      },
281
    });
282

283
    if (!userToShare) {
1!
284
      return res.status(404).json({ error: "User not found" });
×
285
    }
286

287
    if (userToShare.id === userId) {
1!
288
      return res.status(400).json({ error: "Cannot share task with yourself" });
×
289
    }
290

291
    const taskUpdated = await prisma.task.update({
1✔
292
      where: {
293
        id,
294
      },
295
      data: {
296
        shares: {
297
          create: {
298
            userId: userToShare.id,
299
            permission: permission || SharePermission.VIEW,
1!
300
          },
301
        },
302
      },
303
      include: {
304
        shares: {
305
          include: {
306
            user: {
307
              select: {
308
                id: true,
309
                name: true,
310
                email: true,
311
                image: true,
312
              },
313
            },
314
          },
315
        },
316
        list: true,
317
      },
318
    });
319

320
    const currentUser = await prisma.user.findUnique({
1✔
321
      where: { id: userId },
322
      select: { name: true },
323
    });
324
    await createNotification(
1✔
325
      userToShare.id,
326
      "GENERAL",
327
      "Nueva tarea compartida",
328
      `${currentUser?.name || "Alguien"} te ha compartido la tarea "${taskUpdated.name}"`,
1!
329
      currentUser?.name || "Usuario",
1!
330
    );
331
    return res.status(200).json(taskUpdated);
1✔
332
  } catch (error) {
333
    console.error(error);
×
334
    return res.status(500).json({ error: "Error sharing task" });
×
335
  }
336
};
337

338
export const updateSharePermission = async (req: Request, res: Response) => {
3✔
339
  try {
×
340
    const { id, userId } = req.params;
×
NEW
341
    const originalUserId = req.user?.id;
×
NEW
342
    if (!originalUserId) {
×
NEW
343
      return res.status(401).json({ error: "Unauthorized" });
×
344
    }
345

NEW
346
    const task = await getTaskWithPermissions(id, originalUserId);
×
NEW
347
    if (!task) return res.status(404).json({ error: "Task not found" });
×
NEW
348
    if (!task.list) return res.status(404).json({ error: "List not found" });
×
349

NEW
350
    if (!hasPermission(task, originalUserId, SharePermission.ADMIN)) {
×
NEW
351
      return res.status(403).json({ error: "Unauthorized" });
×
352
    }
353

354
    const { permission } = req.body;
×
355
    const share = await prisma.taskShare.findUnique({
×
356
      where: {
357
        taskId_userId: {
358
          taskId: id,
359
          userId,
360
        },
361
      },
362
    });
363
    if (!share) {
×
364
      return res.status(404).json({ error: "Share not found" });
×
365
    }
366

NEW
367
    const taskUpdated = await prisma.task.update({
×
368
      where: {
369
        id,
370
      },
371
      data: {
372
        shares: {
373
          update: {
374
            where: {
375
              taskId_userId: {
376
                taskId: id,
377
                userId,
378
              },
379
            },
380
            data: {
381
              permission,
382
            },
383
          },
384
        },
385
      },
386
      include: {
387
        shares: {
388
          include: {
389
            user: {
390
              select: {
391
                id: true,
392
                name: true,
393
                email: true,
394
                image: true,
395
              },
396
            },
397
          },
398
        },
399
        list: true,
400
      },
401
    });
NEW
402
    return res.status(200).json(taskUpdated);
×
403
  } catch (error) {
404
    return res.status(500).json({ error: "Error updating share permission" });
×
405
  }
406
};
407

408
export const unshareTask = async (req: Request, res: Response) => {
3✔
409
  try {
×
410
    const { id, userId } = req.params;
×
NEW
411
    const originalUserId = req.user?.id;
×
NEW
412
    if (!originalUserId) {
×
NEW
413
      return res.status(401).json({ error: "Unauthorized" });
×
414
    }
415

NEW
416
    if (originalUserId !== userId) {
×
NEW
417
      const task = await getTaskWithPermissions(id, originalUserId);
×
NEW
418
      if (!task) return res.status(404).json({ error: "Task not found" });
×
NEW
419
      if (!task.list) return res.status(404).json({ error: "List not found" });
×
420

NEW
421
      if (!hasPermission(task, originalUserId, SharePermission.ADMIN)) {
×
NEW
422
        return res.status(403).json({ error: "Unauthorized" });
×
423
      }
424
    }
425

426
    const share = await prisma.taskShare.findUnique({
×
427
      where: {
428
        taskId_userId: {
429
          taskId: id,
430
          userId,
431
        },
432
      },
433
    });
434

435
    if (!share) {
×
436
      return res.status(404).json({ error: "Share not found" });
×
437
    }
438

NEW
439
    const taskUpdated = await prisma.task.update({
×
440
      where: {
441
        id,
442
      },
443
      data: {
444
        shares: {
445
          delete: {
446
            taskId_userId: {
447
              taskId: id,
448
              userId,
449
            },
450
          },
451
        },
452
      },
453
      include: {
454
        shares: {
455
          include: {
456
            user: {
457
              select: {
458
                id: true,
459
                name: true,
460
                email: true,
461
                image: true,
462
              },
463
            },
464
          },
465
        },
466
        list: true,
467
      },
468
    });
469

NEW
470
    return res.status(200).json(taskUpdated);
×
471
  } catch (error) {
472
    return res.status(500).json({ error: "Error unsharing task" });
×
473
  }
474
};
475

476
const getTaskWithPermissions = async (taskId: string, userId: string) => {
3✔
477
  return prisma.task.findUnique({
1✔
478
    where: { id: taskId },
479
    include: {
480
      list: {
481
        include: {
482
          shares: {
483
            where: { userId },
484
          },
485
        },
486
      },
487
      shares: {
488
        where: { userId },
489
      },
490
    },
491
  });
492
};
493

494
const hasPermission = (
3✔
495
  task: any,
496
  userId: string,
497
  requiredPermission: SharePermission,
498
  requireListPermission: boolean = false,
1✔
499
) => {
500
  if (task.list.ownerId === userId) return true;
1!
501

NEW
502
  const listShare = task.list.shares[0];
×
NEW
503
  const taskShare = task.shares[0];
×
504

NEW
505
  if (listShare) {
×
NEW
506
    if (checkLevel(listShare.permission) >= checkLevel(requiredPermission))
×
NEW
507
      return true;
×
508
  }
509

NEW
510
  if (requireListPermission) return false;
×
511

NEW
512
  if (taskShare) {
×
NEW
513
    if (checkLevel(taskShare.permission) >= checkLevel(requiredPermission))
×
NEW
514
      return true;
×
515
  }
516

NEW
517
  return false;
×
518
};
519

520
const checkLevel = (permission: SharePermission) => {
3✔
NEW
521
  const levels = {
×
522
    [SharePermission.VIEW]: 1,
523
    [SharePermission.EDIT]: 2,
524
    [SharePermission.ADMIN]: 3,
525
  };
NEW
526
  return levels[permission];
×
527
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc