• 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

24.31
/server/src/controllers/listsController.ts
1
import { Request, Response } from "express";
2
import prisma from "../database/prisma";
3
import { SharePermission } from "@prisma/client";
4
import { createNotification } from "./notificationsController";
5

6
export const createList = async (req: Request, res: Response) => {
3✔
7
  try {
×
8
    const { name, description } = req.body;
×
9
    const ownerId = req.user?.id;
×
10

11
    if (!ownerId) {
×
12
      return res.status(401).json({ error: "User not authenticated" });
×
13
    }
14
    const list = await prisma.list.create({
×
15
      data: {
16
        name,
17
        description,
18
        ownerId,
19
      },
20
      include: {
21
        shares: true,
22
        tasks: true,
23
      },
24
    });
25
    return res.status(200).json(list);
×
26
  } catch (error) {
27
    console.error(error);
×
28
    return res.status(500).json({ error: "Error creating list" });
×
29
  }
30
};
31

32
export const deleteList = async (req: Request, res: Response) => {
3✔
33
  try {
×
34
    const { id } = req.params;
×
NEW
35
    const userId = req.user?.id;
×
NEW
36
    if (!userId) {
×
NEW
37
      return res.status(401).json({ error: "User not authenticated" });
×
38
    }
39

NEW
40
    const list = await getListWithPermissions(id, userId);
×
NEW
41
    if (!list) {
×
NEW
42
      return res.status(404).json({ error: "List not found" });
×
43
    }
44

NEW
45
    if (!hasListPermission(list, userId, SharePermission.ADMIN)) {
×
NEW
46
      return res.status(403).json({ error: "Unauthorized" });
×
47
    }
48

NEW
49
    await prisma.list.delete({
×
50
      where: {
51
        id,
52
      },
53
    });
54
    return res.status(200).json(list);
×
55
  } catch (error) {
56
    console.error(error);
×
57
    return res.status(500).json({ error: "Error deleting list" });
×
58
  }
59
};
60

61
export const getUserLists = async (req: Request, res: Response) => {
3✔
62
  try {
×
63
    const userId = req.user?.id;
×
64
    const lists = await prisma.list.findMany({
×
65
      where: {
66
        ownerId: userId,
67
      },
68
      include: {
69
        shares: true,
70
        tasks: true,
71
      },
72
    });
73
    return res.status(200).json(lists);
×
74
  } catch (error) {
75
    console.error(error);
×
76
    return res.status(500).json({ error: "Error getting lists" });
×
77
  }
78
};
79

80
export const getSharedLists = async (req: Request, res: Response) => {
3✔
81
  try {
1✔
82
    const userId = req.user?.id;
1✔
83
    const lists = await prisma.list.findMany({
1✔
84
      where: {
85
        shares: {
86
          some: {
87
            userId: userId,
88
          },
89
        },
90
      },
91
      include: {
92
        shares: true,
93
        tasks: true,
94
        owner: {
95
          select: {
96
            id: true,
97
            name: true,
98
            email: true,
99
            image: true,
100
          },
101
        },
102
      },
103
    });
104
    return res.status(200).json(lists);
1✔
105
  } catch (error) {
106
    console.error(error);
×
107
    return res.status(500).json({ error: "Error getting shared lists" });
×
108
  }
109
};
110

111
export const updateList = async (req: Request, res: Response) => {
3✔
112
  try {
×
113
    const { id } = req.params;
×
NEW
114
    const userId = req.user?.id;
×
NEW
115
    if (!userId) {
×
NEW
116
      return res.status(401).json({ error: "User not authenticated" });
×
117
    }
118

NEW
119
    const list = await getListWithPermissions(id, userId);
×
NEW
120
    if (!list) {
×
NEW
121
      return res.status(404).json({ error: "List not found" });
×
122
    }
123

NEW
124
    if (!hasListPermission(list, userId, SharePermission.EDIT)) {
×
NEW
125
      return res.status(403).json({ error: "Unauthorized" });
×
126
    }
127

128
    const { name, description } = req.body;
×
129
    const dataToUpdate: {
130
      name?: string;
131
      description?: string;
132
    } = {};
×
133
    if (name) dataToUpdate.name = name;
×
134
    if (description) dataToUpdate.description = description;
×
135
    if (Object.keys(dataToUpdate).length === 0) {
×
136
      return res.status(400).json({ error: "No fields to update" });
×
137
    }
NEW
138
    const listUpdated = await prisma.list.update({
×
139
      where: {
140
        id,
141
      },
142
      data: dataToUpdate,
143
      include: {
144
        shares: true,
145
        tasks: true,
146
      },
147
    });
NEW
148
    return res.status(200).json(listUpdated);
×
149
  } catch (error) {
150
    console.error(error);
×
151
    return res.status(500).json({ error: "Error updating list" });
×
152
  }
153
};
154

155
export const shareList = async (req: Request, res: Response) => {
3✔
156
  try {
1✔
157
    const { id } = req.params;
1✔
158
    const userId = req.user?.id;
1✔
159
    if (!userId) {
1!
NEW
160
      return res.status(401).json({ error: "User not authenticated" });
×
161
    }
162

163
    const list = await getListWithPermissions(id, userId);
1✔
164
    if (!list) {
1!
NEW
165
      return res.status(404).json({ error: "List not found" });
×
166
    }
167

168
    if (!hasListPermission(list, userId, SharePermission.ADMIN)) {
1!
NEW
169
      return res.status(403).json({ error: "Unauthorized" });
×
170
    }
171

172
    const { email, permission } = req.body;
1✔
173

174
    const user = await prisma.user.findUnique({
1✔
175
      where: {
176
        email,
177
      },
178
    });
179

180
    if (!user) {
1!
181
      return res.status(404).json({ error: "User not found" });
×
182
    }
183

184
    if (user.id === userId) {
1!
NEW
185
      return res.status(400).json({ error: "Cannot share list with yourself" });
×
186
    }
187

188
    const existingShare = await prisma.listShare.findUnique({
1✔
189
      where: {
190
        listId_userId: {
191
          listId: id,
192
          userId: user.id,
193
        },
194
      },
195
    });
196
    if (existingShare) {
1!
197
      return res
×
198
        .status(400)
199
        .json({ error: "List already shared with this user" });
200
    }
201
    const listUpdated = await prisma.list.update({
1✔
202
      where: {
203
        id,
204
      },
205
      data: {
206
        shares: {
207
          create: {
208
            userId: user.id,
209
            permission: permission || "VIEW",
1!
210
          },
211
        },
212
      },
213
      include: {
214
        shares: {
215
          include: {
216
            user: {
217
              select: {
218
                id: true,
219
                name: true,
220
                email: true,
221
                image: true,
222
              },
223
            },
224
          },
225
        },
226
        tasks: true,
227
      },
228
    });
229
    const currentUser = await prisma.user.findUnique({
1✔
230
      where: { id: req.user?.id },
231
      select: { name: true },
232
    });
233
    await createNotification(
1✔
234
      user.id,
235
      "GENERAL",
236
      "Nueva lista compartida",
237
      `${currentUser?.name || "Alguien"} te ha compartido la lista "${listUpdated.name}"`,
1!
238
      currentUser?.name || "Usuario",
1!
239
    );
240
    return res.status(200).json(listUpdated);
1✔
241
  } catch (error) {
242
    console.error(error);
×
243
    return res.status(500).json({ error: "Error sharing list" });
×
244
  }
245
};
246

247
export const unshareList = async (req: Request, res: Response) => {
3✔
248
  try {
×
249
    const { id, userId } = req.params;
×
NEW
250
    const currentUserId = req.user?.id;
×
NEW
251
    if (!currentUserId) {
×
NEW
252
      return res.status(401).json({ error: "User not authenticated" });
×
253
    }
254

NEW
255
    const list = await getListWithPermissions(id, currentUserId);
×
NEW
256
    if (!list) {
×
NEW
257
      return res.status(404).json({ error: "List not found" });
×
258
    }
259

NEW
260
    if (!hasListPermission(list, currentUserId, SharePermission.ADMIN)) {
×
NEW
261
      return res.status(403).json({ error: "Unauthorized" });
×
262
    }
263

NEW
264
    const listUpdated = await prisma.list.update({
×
265
      where: {
266
        id,
267
      },
268
      data: {
269
        shares: {
270
          delete: {
271
            listId_userId: {
272
              listId: id,
273
              userId,
274
            },
275
          },
276
        },
277
      },
278
      include: {
279
        shares: {
280
          include: {
281
            user: {
282
              select: {
283
                id: true,
284
                name: true,
285
                email: true,
286
                image: true,
287
              },
288
            },
289
          },
290
        },
291
        tasks: true,
292
      },
293
    });
NEW
294
    return res.status(200).json(listUpdated);
×
295
  } catch (error) {
296
    console.error(error);
×
297
    return res.status(500).json({ error: "Error unsharing list" });
×
298
  }
299
};
300

301
export const updateSharePermission = async (req: Request, res: Response) => {
3✔
302
  try {
×
303
    const { id, userId } = req.params;
×
NEW
304
    const currentUserId = req.user?.id;
×
NEW
305
    if (!currentUserId) {
×
NEW
306
      return res.status(401).json({ error: "User not authenticated" });
×
307
    }
308

NEW
309
    const list = await getListWithPermissions(id, currentUserId);
×
NEW
310
    if (!list) {
×
NEW
311
      return res.status(404).json({ error: "List not found" });
×
312
    }
313

NEW
314
    if (!hasListPermission(list, currentUserId, SharePermission.ADMIN)) {
×
NEW
315
      return res.status(403).json({ error: "Unauthorized" });
×
316
    }
317

318
    const { permission } = req.body;
×
319

NEW
320
    const listUpdated = await prisma.list.update({
×
321
      where: {
322
        id,
323
      },
324
      data: {
325
        shares: {
326
          update: {
327
            where: {
328
              listId_userId: {
329
                listId: id,
330
                userId,
331
              },
332
            },
333
            data: {
334
              permission,
335
            },
336
          },
337
        },
338
      },
339
      include: {
340
        shares: {
341
          include: {
342
            user: {
343
              select: {
344
                id: true,
345
                name: true,
346
                email: true,
347
                image: true,
348
              },
349
            },
350
          },
351
        },
352
        tasks: true,
353
      },
354
    });
355

NEW
356
    return res.status(200).json(listUpdated);
×
357
  } catch (error) {
358
    console.error(error);
×
359
    return res.status(500).json({ error: "Error updating share permission" });
×
360
  }
361
};
362

363
const getListWithPermissions = async (listId: string, userId: string) => {
3✔
364
  return prisma.list.findUnique({
1✔
365
    where: { id: listId },
366
    include: {
367
      shares: {
368
        where: { userId },
369
      },
370
    },
371
  });
372
};
373

374
const hasListPermission = (
3✔
375
  list: any,
376
  userId: string,
377
  requiredPermission: SharePermission,
378
) => {
379
  if (list.ownerId === userId) return true;
1!
380

NEW
381
  const share = list.shares[0];
×
NEW
382
  if (!share) return false;
×
383

NEW
384
  return checkLevel(share.permission) >= checkLevel(requiredPermission);
×
385
};
386

387
const checkLevel = (permission: SharePermission) => {
3✔
NEW
388
  const levels = {
×
389
    [SharePermission.VIEW]: 1,
390
    [SharePermission.EDIT]: 2,
391
    [SharePermission.ADMIN]: 3,
392
  };
NEW
393
  return levels[permission];
×
394
};
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