@studious-lms/server 1.0.4 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API_SPECIFICATION.md +1117 -0
- package/dist/exportType.js +1 -2
- package/dist/index.js +25 -30
- package/dist/lib/fileUpload.d.ts.map +1 -1
- package/dist/lib/fileUpload.js +31 -29
- package/dist/lib/googleCloudStorage.js +9 -14
- package/dist/lib/prisma.js +4 -7
- package/dist/lib/thumbnailGenerator.js +12 -20
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +17 -22
- package/dist/middleware/logging.js +5 -9
- package/dist/routers/_app.d.ts +3483 -1801
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/_app.js +28 -27
- package/dist/routers/agenda.d.ts +13 -8
- package/dist/routers/agenda.d.ts.map +1 -1
- package/dist/routers/agenda.js +14 -17
- package/dist/routers/announcement.d.ts +4 -3
- package/dist/routers/announcement.d.ts.map +1 -1
- package/dist/routers/announcement.js +28 -31
- package/dist/routers/assignment.d.ts +282 -196
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/assignment.js +256 -202
- package/dist/routers/attendance.d.ts +5 -4
- package/dist/routers/attendance.d.ts.map +1 -1
- package/dist/routers/attendance.js +31 -34
- package/dist/routers/auth.d.ts +1 -0
- package/dist/routers/auth.d.ts.map +1 -1
- package/dist/routers/auth.js +80 -75
- package/dist/routers/class.d.ts +284 -14
- package/dist/routers/class.d.ts.map +1 -1
- package/dist/routers/class.js +435 -164
- package/dist/routers/event.d.ts +47 -38
- package/dist/routers/event.d.ts.map +1 -1
- package/dist/routers/event.js +76 -79
- package/dist/routers/file.d.ts +71 -1
- package/dist/routers/file.d.ts.map +1 -1
- package/dist/routers/file.js +267 -32
- package/dist/routers/folder.d.ts +296 -0
- package/dist/routers/folder.d.ts.map +1 -0
- package/dist/routers/folder.js +693 -0
- package/dist/routers/notifications.d.ts +103 -0
- package/dist/routers/notifications.d.ts.map +1 -0
- package/dist/routers/notifications.js +91 -0
- package/dist/routers/school.d.ts +208 -0
- package/dist/routers/school.d.ts.map +1 -0
- package/dist/routers/school.js +481 -0
- package/dist/routers/section.d.ts +1 -0
- package/dist/routers/section.d.ts.map +1 -1
- package/dist/routers/section.js +30 -33
- package/dist/routers/user.d.ts +2 -1
- package/dist/routers/user.d.ts.map +1 -1
- package/dist/routers/user.js +21 -24
- package/dist/seedDatabase.d.ts +22 -0
- package/dist/seedDatabase.d.ts.map +1 -0
- package/dist/seedDatabase.js +57 -0
- package/dist/socket/handlers.js +26 -30
- package/dist/trpc.d.ts +5 -0
- package/dist/trpc.d.ts.map +1 -1
- package/dist/trpc.js +35 -26
- package/dist/types/trpc.js +1 -2
- package/dist/utils/email.js +2 -8
- package/dist/utils/generateInviteCode.js +1 -5
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +13 -9
- package/dist/utils/prismaErrorHandler.d.ts +9 -0
- package/dist/utils/prismaErrorHandler.d.ts.map +1 -0
- package/dist/utils/prismaErrorHandler.js +234 -0
- package/dist/utils/prismaWrapper.d.ts +14 -0
- package/dist/utils/prismaWrapper.d.ts.map +1 -0
- package/dist/utils/prismaWrapper.js +64 -0
- package/package.json +17 -4
- package/prisma/migrations/20250807062924_init/migration.sql +436 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +67 -0
- package/src/index.ts +2 -2
- package/src/lib/fileUpload.ts +16 -7
- package/src/middleware/auth.ts +0 -2
- package/src/routers/_app.ts +5 -1
- package/src/routers/assignment.ts +82 -22
- package/src/routers/auth.ts +80 -54
- package/src/routers/class.ts +330 -36
- package/src/routers/file.ts +283 -20
- package/src/routers/folder.ts +755 -0
- package/src/routers/notifications.ts +93 -0
- package/src/seedDatabase.ts +66 -0
- package/src/socket/handlers.ts +4 -4
- package/src/trpc.ts +13 -0
- package/src/utils/logger.ts +14 -4
- package/src/utils/prismaErrorHandler.ts +275 -0
- package/src/utils/prismaWrapper.ts +91 -0
- package/tests/auth.test.ts +25 -0
- package/tests/class.test.ts +281 -0
- package/tests/setup.ts +98 -0
- package/tests/startup.test.ts +5 -0
- package/tsconfig.json +2 -1
- package/vitest.config.ts +11 -0
- package/dist/logger.d.ts +0 -26
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js +0 -135
- package/src/logger.ts +0 -163
package/dist/routers/class.js
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
const generateInviteCode_1 = require("../utils/generateInviteCode");
|
|
9
|
-
exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
10
|
-
getAll: trpc_1.protectedProcedure
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createTRPCRouter, protectedProcedure, protectedTeacherProcedure, protectedClassMemberProcedure } from "../trpc";
|
|
3
|
+
import { prisma } from "../lib/prisma";
|
|
4
|
+
import { TRPCError } from "@trpc/server";
|
|
5
|
+
import { generateInviteCode } from "../utils/generateInviteCode";
|
|
6
|
+
export const classRouter = createTRPCRouter({
|
|
7
|
+
getAll: protectedProcedure
|
|
11
8
|
.query(async ({ ctx }) => {
|
|
12
9
|
const [teacherClasses, studentClasses] = await Promise.all([
|
|
13
|
-
|
|
10
|
+
prisma.class.findMany({
|
|
14
11
|
where: {
|
|
15
12
|
teachers: {
|
|
16
13
|
some: {
|
|
@@ -22,17 +19,19 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
22
19
|
assignments: {
|
|
23
20
|
where: {
|
|
24
21
|
dueDate: {
|
|
25
|
-
|
|
22
|
+
lte: new Date(new Date().setHours(23, 59, 59, 999)),
|
|
26
23
|
},
|
|
24
|
+
template: false,
|
|
27
25
|
},
|
|
28
26
|
select: {
|
|
29
27
|
id: true,
|
|
30
28
|
title: true,
|
|
29
|
+
type: true,
|
|
31
30
|
},
|
|
32
31
|
},
|
|
33
32
|
},
|
|
34
33
|
}),
|
|
35
|
-
|
|
34
|
+
prisma.class.findMany({
|
|
36
35
|
where: {
|
|
37
36
|
students: {
|
|
38
37
|
some: {
|
|
@@ -44,39 +43,19 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
44
43
|
assignments: {
|
|
45
44
|
where: {
|
|
46
45
|
dueDate: {
|
|
47
|
-
|
|
46
|
+
lte: new Date(new Date().setHours(23, 59, 59, 999)),
|
|
48
47
|
},
|
|
48
|
+
template: false,
|
|
49
49
|
},
|
|
50
50
|
select: {
|
|
51
51
|
id: true,
|
|
52
52
|
title: true,
|
|
53
|
+
type: true,
|
|
53
54
|
},
|
|
54
55
|
},
|
|
55
56
|
},
|
|
56
57
|
}),
|
|
57
58
|
]);
|
|
58
|
-
const adminClasses = await prisma_1.prisma.class.findMany({
|
|
59
|
-
where: {
|
|
60
|
-
teachers: {
|
|
61
|
-
some: {
|
|
62
|
-
id: ctx.user?.id,
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
include: {
|
|
67
|
-
assignments: {
|
|
68
|
-
where: {
|
|
69
|
-
dueDate: {
|
|
70
|
-
equals: new Date(new Date().setHours(0, 0, 0, 0)),
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
select: {
|
|
74
|
-
id: true,
|
|
75
|
-
title: true,
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
});
|
|
80
59
|
return {
|
|
81
60
|
teacherInClass: teacherClasses.map(cls => ({
|
|
82
61
|
id: cls.id,
|
|
@@ -84,6 +63,7 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
84
63
|
section: cls.section,
|
|
85
64
|
subject: cls.subject,
|
|
86
65
|
dueToday: cls.assignments,
|
|
66
|
+
assignments: cls.assignments,
|
|
87
67
|
color: cls.color,
|
|
88
68
|
})),
|
|
89
69
|
studentInClass: studentClasses.map(cls => ({
|
|
@@ -92,25 +72,18 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
92
72
|
section: cls.section,
|
|
93
73
|
subject: cls.subject,
|
|
94
74
|
dueToday: cls.assignments,
|
|
95
|
-
|
|
96
|
-
})),
|
|
97
|
-
adminInClass: adminClasses.map(cls => ({
|
|
98
|
-
id: cls.id,
|
|
99
|
-
name: cls.name,
|
|
100
|
-
section: cls.section,
|
|
101
|
-
subject: cls.subject,
|
|
102
|
-
dueToday: cls.assignments,
|
|
75
|
+
assignments: cls.assignments,
|
|
103
76
|
color: cls.color,
|
|
104
77
|
})),
|
|
105
78
|
};
|
|
106
79
|
}),
|
|
107
|
-
get:
|
|
108
|
-
.input(
|
|
109
|
-
classId:
|
|
80
|
+
get: protectedProcedure
|
|
81
|
+
.input(z.object({
|
|
82
|
+
classId: z.string(),
|
|
110
83
|
}))
|
|
111
84
|
.query(async ({ ctx, input }) => {
|
|
112
85
|
const { classId } = input;
|
|
113
|
-
const classData = await
|
|
86
|
+
const classData = await prisma.class.findUnique({
|
|
114
87
|
where: {
|
|
115
88
|
id: classId,
|
|
116
89
|
},
|
|
@@ -154,6 +127,8 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
154
127
|
graded: true,
|
|
155
128
|
maxGrade: true,
|
|
156
129
|
instructions: true,
|
|
130
|
+
inProgress: true,
|
|
131
|
+
template: false,
|
|
157
132
|
section: {
|
|
158
133
|
select: {
|
|
159
134
|
id: true,
|
|
@@ -188,7 +163,7 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
188
163
|
},
|
|
189
164
|
},
|
|
190
165
|
});
|
|
191
|
-
const sections = await
|
|
166
|
+
const sections = await prisma.section.findMany({
|
|
192
167
|
where: {
|
|
193
168
|
classId: classId,
|
|
194
169
|
},
|
|
@@ -209,16 +184,16 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
209
184
|
},
|
|
210
185
|
};
|
|
211
186
|
}),
|
|
212
|
-
update:
|
|
213
|
-
.input(
|
|
214
|
-
classId:
|
|
215
|
-
name:
|
|
216
|
-
section:
|
|
217
|
-
subject:
|
|
187
|
+
update: protectedTeacherProcedure
|
|
188
|
+
.input(z.object({
|
|
189
|
+
classId: z.string(),
|
|
190
|
+
name: z.string().optional(),
|
|
191
|
+
section: z.string().optional(),
|
|
192
|
+
subject: z.string().optional(),
|
|
218
193
|
}))
|
|
219
194
|
.mutation(async ({ ctx, input }) => {
|
|
220
195
|
const { classId, ...updateData } = input;
|
|
221
|
-
const updatedClass = await
|
|
196
|
+
const updatedClass = await prisma.class.update({
|
|
222
197
|
where: {
|
|
223
198
|
id: classId,
|
|
224
199
|
},
|
|
@@ -234,22 +209,24 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
234
209
|
updatedClass,
|
|
235
210
|
};
|
|
236
211
|
}),
|
|
237
|
-
create:
|
|
238
|
-
.input(
|
|
239
|
-
students:
|
|
240
|
-
teachers:
|
|
241
|
-
name:
|
|
242
|
-
section:
|
|
243
|
-
subject:
|
|
212
|
+
create: protectedProcedure
|
|
213
|
+
.input(z.object({
|
|
214
|
+
students: z.array(z.string()).optional(),
|
|
215
|
+
teachers: z.array(z.string()).optional(),
|
|
216
|
+
name: z.string(),
|
|
217
|
+
section: z.string(),
|
|
218
|
+
subject: z.string(),
|
|
219
|
+
color: z.string().optional(),
|
|
244
220
|
}))
|
|
245
221
|
.mutation(async ({ ctx, input }) => {
|
|
246
|
-
const { students, teachers, name, section, subject } = input;
|
|
222
|
+
const { students, teachers, name, section, subject, color } = input;
|
|
247
223
|
if (teachers && teachers.length > 0 && students && students.length > 0) {
|
|
248
|
-
const newClass = await
|
|
224
|
+
const newClass = await prisma.class.create({
|
|
249
225
|
data: {
|
|
250
226
|
name,
|
|
251
227
|
section,
|
|
252
228
|
subject,
|
|
229
|
+
color,
|
|
253
230
|
teachers: {
|
|
254
231
|
connect: teachers.map(teacher => ({ id: teacher })),
|
|
255
232
|
},
|
|
@@ -264,11 +241,12 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
264
241
|
});
|
|
265
242
|
return newClass;
|
|
266
243
|
}
|
|
267
|
-
const newClass = await
|
|
244
|
+
const newClass = await prisma.class.create({
|
|
268
245
|
data: {
|
|
269
246
|
name,
|
|
270
247
|
section,
|
|
271
248
|
subject,
|
|
249
|
+
color,
|
|
272
250
|
teachers: {
|
|
273
251
|
connect: {
|
|
274
252
|
id: ctx.user?.id,
|
|
@@ -278,14 +256,14 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
278
256
|
});
|
|
279
257
|
return newClass;
|
|
280
258
|
}),
|
|
281
|
-
delete:
|
|
282
|
-
.input(
|
|
283
|
-
classId:
|
|
284
|
-
id:
|
|
259
|
+
delete: protectedTeacherProcedure
|
|
260
|
+
.input(z.object({
|
|
261
|
+
classId: z.string(),
|
|
262
|
+
id: z.string(),
|
|
285
263
|
}))
|
|
286
264
|
.mutation(async ({ ctx, input }) => {
|
|
287
265
|
// Verify user is the teacher of this class
|
|
288
|
-
const classToDelete = await
|
|
266
|
+
const classToDelete = await prisma.class.findFirst({
|
|
289
267
|
where: {
|
|
290
268
|
id: input.id,
|
|
291
269
|
},
|
|
@@ -293,7 +271,7 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
293
271
|
if (!classToDelete) {
|
|
294
272
|
throw new Error("Class not found or you don't have permission to delete it");
|
|
295
273
|
}
|
|
296
|
-
await
|
|
274
|
+
await prisma.class.delete({
|
|
297
275
|
where: {
|
|
298
276
|
id: input.id,
|
|
299
277
|
},
|
|
@@ -304,14 +282,14 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
304
282
|
}
|
|
305
283
|
};
|
|
306
284
|
}),
|
|
307
|
-
addStudent:
|
|
308
|
-
.input(
|
|
309
|
-
classId:
|
|
310
|
-
studentId:
|
|
285
|
+
addStudent: protectedTeacherProcedure
|
|
286
|
+
.input(z.object({
|
|
287
|
+
classId: z.string(),
|
|
288
|
+
studentId: z.string(),
|
|
311
289
|
}))
|
|
312
290
|
.mutation(async ({ ctx, input }) => {
|
|
313
291
|
const { classId, studentId } = input;
|
|
314
|
-
const student = await
|
|
292
|
+
const student = await prisma.user.findUnique({
|
|
315
293
|
where: {
|
|
316
294
|
id: studentId,
|
|
317
295
|
},
|
|
@@ -319,7 +297,7 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
319
297
|
if (!student) {
|
|
320
298
|
throw new Error("Student not found");
|
|
321
299
|
}
|
|
322
|
-
const updatedClass = await
|
|
300
|
+
const updatedClass = await prisma.class.update({
|
|
323
301
|
where: {
|
|
324
302
|
id: classId,
|
|
325
303
|
},
|
|
@@ -342,15 +320,15 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
342
320
|
newStudent: student,
|
|
343
321
|
};
|
|
344
322
|
}),
|
|
345
|
-
changeRole:
|
|
346
|
-
.input(
|
|
347
|
-
classId:
|
|
348
|
-
userId:
|
|
349
|
-
type:
|
|
323
|
+
changeRole: protectedTeacherProcedure
|
|
324
|
+
.input(z.object({
|
|
325
|
+
classId: z.string(),
|
|
326
|
+
userId: z.string(),
|
|
327
|
+
type: z.enum(['teacher', 'student']),
|
|
350
328
|
}))
|
|
351
329
|
.mutation(async ({ ctx, input }) => {
|
|
352
330
|
const { classId, userId, type } = input;
|
|
353
|
-
const user = await
|
|
331
|
+
const user = await prisma.user.findUnique({
|
|
354
332
|
where: { id: userId },
|
|
355
333
|
select: {
|
|
356
334
|
id: true,
|
|
@@ -360,7 +338,7 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
360
338
|
if (!user) {
|
|
361
339
|
throw new Error("User not found");
|
|
362
340
|
}
|
|
363
|
-
const updatedClass = await
|
|
341
|
+
const updatedClass = await prisma.class.update({
|
|
364
342
|
where: { id: classId },
|
|
365
343
|
data: {
|
|
366
344
|
[type === 'teacher' ? 'teachers' : 'students']: {
|
|
@@ -379,14 +357,14 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
379
357
|
},
|
|
380
358
|
};
|
|
381
359
|
}),
|
|
382
|
-
removeMember:
|
|
383
|
-
.input(
|
|
384
|
-
classId:
|
|
385
|
-
userId:
|
|
360
|
+
removeMember: protectedTeacherProcedure
|
|
361
|
+
.input(z.object({
|
|
362
|
+
classId: z.string(),
|
|
363
|
+
userId: z.string(),
|
|
386
364
|
}))
|
|
387
365
|
.mutation(async ({ ctx, input }) => {
|
|
388
366
|
const { classId, userId } = input;
|
|
389
|
-
const updatedClass = await
|
|
367
|
+
const updatedClass = await prisma.class.update({
|
|
390
368
|
where: { id: classId },
|
|
391
369
|
data: {
|
|
392
370
|
teachers: {
|
|
@@ -402,13 +380,13 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
402
380
|
removedUserId: userId,
|
|
403
381
|
};
|
|
404
382
|
}),
|
|
405
|
-
join:
|
|
406
|
-
.input(
|
|
407
|
-
classCode:
|
|
383
|
+
join: protectedProcedure
|
|
384
|
+
.input(z.object({
|
|
385
|
+
classCode: z.string(),
|
|
408
386
|
}))
|
|
409
387
|
.mutation(async ({ ctx, input }) => {
|
|
410
388
|
const { classCode } = input;
|
|
411
|
-
const session = await
|
|
389
|
+
const session = await prisma.session.findFirst({
|
|
412
390
|
where: {
|
|
413
391
|
id: classCode,
|
|
414
392
|
},
|
|
@@ -419,7 +397,7 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
419
397
|
if (session.expiresAt && session.expiresAt < new Date()) {
|
|
420
398
|
throw new Error("Session expired");
|
|
421
399
|
}
|
|
422
|
-
const updatedClass = await
|
|
400
|
+
const updatedClass = await prisma.class.update({
|
|
423
401
|
where: { id: session.classId },
|
|
424
402
|
data: {
|
|
425
403
|
students: {
|
|
@@ -437,21 +415,21 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
437
415
|
joinedClass: updatedClass,
|
|
438
416
|
};
|
|
439
417
|
}),
|
|
440
|
-
getInviteCode:
|
|
441
|
-
.input(
|
|
442
|
-
classId:
|
|
418
|
+
getInviteCode: protectedTeacherProcedure
|
|
419
|
+
.input(z.object({
|
|
420
|
+
classId: z.string(),
|
|
443
421
|
}))
|
|
444
422
|
.query(async ({ ctx, input }) => {
|
|
445
423
|
const { classId } = input;
|
|
446
|
-
const session = await
|
|
424
|
+
const session = await prisma.session.findFirst({
|
|
447
425
|
where: {
|
|
448
426
|
classId,
|
|
449
427
|
},
|
|
450
428
|
});
|
|
451
429
|
if ((session?.expiresAt && session.expiresAt < new Date()) || !session) {
|
|
452
|
-
const newSession = await
|
|
430
|
+
const newSession = await prisma.session.create({
|
|
453
431
|
data: {
|
|
454
|
-
id:
|
|
432
|
+
id: generateInviteCode(),
|
|
455
433
|
classId,
|
|
456
434
|
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24 hours from now
|
|
457
435
|
}
|
|
@@ -464,16 +442,16 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
464
442
|
code: session?.id,
|
|
465
443
|
};
|
|
466
444
|
}),
|
|
467
|
-
createInviteCode:
|
|
468
|
-
.input(
|
|
469
|
-
classId:
|
|
445
|
+
createInviteCode: protectedTeacherProcedure
|
|
446
|
+
.input(z.object({
|
|
447
|
+
classId: z.string(),
|
|
470
448
|
}))
|
|
471
449
|
.mutation(async ({ ctx, input }) => {
|
|
472
450
|
const { classId } = input;
|
|
473
451
|
// Create a new session for the invite code
|
|
474
|
-
const session = await
|
|
452
|
+
const session = await prisma.session.create({
|
|
475
453
|
data: {
|
|
476
|
-
id:
|
|
454
|
+
id: generateInviteCode(),
|
|
477
455
|
classId,
|
|
478
456
|
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24 hours from now
|
|
479
457
|
}
|
|
@@ -482,14 +460,14 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
482
460
|
code: session.id,
|
|
483
461
|
};
|
|
484
462
|
}),
|
|
485
|
-
getGrades:
|
|
486
|
-
.input(
|
|
487
|
-
classId:
|
|
488
|
-
userId:
|
|
463
|
+
getGrades: protectedClassMemberProcedure
|
|
464
|
+
.input(z.object({
|
|
465
|
+
classId: z.string(),
|
|
466
|
+
userId: z.string(),
|
|
489
467
|
}))
|
|
490
468
|
.query(async ({ ctx, input }) => {
|
|
491
469
|
const { classId, userId } = input;
|
|
492
|
-
const isTeacher = await
|
|
470
|
+
const isTeacher = await prisma.class.findFirst({
|
|
493
471
|
where: {
|
|
494
472
|
id: classId,
|
|
495
473
|
teachers: {
|
|
@@ -499,12 +477,12 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
499
477
|
});
|
|
500
478
|
// If student, only allow viewing their own grades
|
|
501
479
|
if (ctx.user?.id !== userId && !isTeacher) {
|
|
502
|
-
throw new
|
|
480
|
+
throw new TRPCError({
|
|
503
481
|
code: 'UNAUTHORIZED',
|
|
504
482
|
message: 'You can only view your own grades',
|
|
505
483
|
});
|
|
506
484
|
}
|
|
507
|
-
const grades = await
|
|
485
|
+
const grades = await prisma.submission.findMany({
|
|
508
486
|
where: {
|
|
509
487
|
studentId: userId,
|
|
510
488
|
assignment: {
|
|
@@ -527,17 +505,17 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
527
505
|
grades,
|
|
528
506
|
};
|
|
529
507
|
}),
|
|
530
|
-
updateGrade:
|
|
531
|
-
.input(
|
|
532
|
-
classId:
|
|
533
|
-
assignmentId:
|
|
534
|
-
submissionId:
|
|
535
|
-
gradeReceived:
|
|
508
|
+
updateGrade: protectedTeacherProcedure
|
|
509
|
+
.input(z.object({
|
|
510
|
+
classId: z.string(),
|
|
511
|
+
assignmentId: z.string(),
|
|
512
|
+
submissionId: z.string(),
|
|
513
|
+
gradeReceived: z.number().nullable(),
|
|
536
514
|
}))
|
|
537
515
|
.mutation(async ({ ctx, input }) => {
|
|
538
516
|
const { classId, assignmentId, submissionId, gradeReceived } = input;
|
|
539
517
|
// Update the grade
|
|
540
|
-
const updatedSubmission = await
|
|
518
|
+
const updatedSubmission = await prisma.submission.update({
|
|
541
519
|
where: {
|
|
542
520
|
id: submissionId,
|
|
543
521
|
assignmentId: assignmentId,
|
|
@@ -558,13 +536,13 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
558
536
|
});
|
|
559
537
|
return updatedSubmission;
|
|
560
538
|
}),
|
|
561
|
-
getEvents:
|
|
562
|
-
.input(
|
|
563
|
-
classId:
|
|
539
|
+
getEvents: protectedTeacherProcedure
|
|
540
|
+
.input(z.object({
|
|
541
|
+
classId: z.string(),
|
|
564
542
|
}))
|
|
565
543
|
.query(async ({ ctx, input }) => {
|
|
566
544
|
const { classId } = input;
|
|
567
|
-
const events = await
|
|
545
|
+
const events = await prisma.event.findMany({
|
|
568
546
|
where: {
|
|
569
547
|
class: {
|
|
570
548
|
id: classId,
|
|
@@ -578,28 +556,28 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
578
556
|
});
|
|
579
557
|
return events;
|
|
580
558
|
}),
|
|
581
|
-
listMarkSchemes:
|
|
582
|
-
.input(
|
|
583
|
-
classId:
|
|
559
|
+
listMarkSchemes: protectedTeacherProcedure
|
|
560
|
+
.input(z.object({
|
|
561
|
+
classId: z.string(),
|
|
584
562
|
}))
|
|
585
563
|
.query(async ({ ctx, input }) => {
|
|
586
564
|
const { classId } = input;
|
|
587
|
-
const markSchemes = await
|
|
565
|
+
const markSchemes = await prisma.markScheme.findMany({
|
|
588
566
|
where: {
|
|
589
567
|
classId: classId,
|
|
590
568
|
},
|
|
591
569
|
});
|
|
592
570
|
return markSchemes;
|
|
593
571
|
}),
|
|
594
|
-
createMarkScheme:
|
|
595
|
-
.input(
|
|
596
|
-
classId:
|
|
597
|
-
structure:
|
|
572
|
+
createMarkScheme: protectedTeacherProcedure
|
|
573
|
+
.input(z.object({
|
|
574
|
+
classId: z.string(),
|
|
575
|
+
structure: z.string(),
|
|
598
576
|
}))
|
|
599
577
|
.mutation(async ({ ctx, input }) => {
|
|
600
578
|
const { classId, structure } = input;
|
|
601
579
|
const validatedStructure = structure.replace(/\\n/g, '\n');
|
|
602
|
-
const markScheme = await
|
|
580
|
+
const markScheme = await prisma.markScheme.create({
|
|
603
581
|
data: {
|
|
604
582
|
classId: classId,
|
|
605
583
|
structured: validatedStructure,
|
|
@@ -607,55 +585,55 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
607
585
|
});
|
|
608
586
|
return markScheme;
|
|
609
587
|
}),
|
|
610
|
-
updateMarkScheme:
|
|
611
|
-
.input(
|
|
612
|
-
classId:
|
|
613
|
-
markSchemeId:
|
|
614
|
-
structure:
|
|
588
|
+
updateMarkScheme: protectedTeacherProcedure
|
|
589
|
+
.input(z.object({
|
|
590
|
+
classId: z.string(),
|
|
591
|
+
markSchemeId: z.string(),
|
|
592
|
+
structure: z.string(),
|
|
615
593
|
}))
|
|
616
594
|
.mutation(async ({ ctx, input }) => {
|
|
617
595
|
const { classId, markSchemeId, structure } = input;
|
|
618
596
|
const validatedStructure = structure.replace(/\\n/g, '\n');
|
|
619
|
-
const markScheme = await
|
|
597
|
+
const markScheme = await prisma.markScheme.update({
|
|
620
598
|
where: { id: markSchemeId },
|
|
621
599
|
data: { structured: validatedStructure },
|
|
622
600
|
});
|
|
623
601
|
return markScheme;
|
|
624
602
|
}),
|
|
625
|
-
deleteMarkScheme:
|
|
626
|
-
.input(
|
|
627
|
-
classId:
|
|
628
|
-
markSchemeId:
|
|
603
|
+
deleteMarkScheme: protectedTeacherProcedure
|
|
604
|
+
.input(z.object({
|
|
605
|
+
classId: z.string(),
|
|
606
|
+
markSchemeId: z.string(),
|
|
629
607
|
}))
|
|
630
608
|
.mutation(async ({ ctx, input }) => {
|
|
631
609
|
const { classId, markSchemeId } = input;
|
|
632
|
-
const markScheme = await
|
|
610
|
+
const markScheme = await prisma.markScheme.delete({
|
|
633
611
|
where: { id: markSchemeId },
|
|
634
612
|
});
|
|
635
613
|
return markScheme;
|
|
636
614
|
}),
|
|
637
|
-
listGradingBoundaries:
|
|
638
|
-
.input(
|
|
639
|
-
classId:
|
|
615
|
+
listGradingBoundaries: protectedTeacherProcedure
|
|
616
|
+
.input(z.object({
|
|
617
|
+
classId: z.string(),
|
|
640
618
|
}))
|
|
641
619
|
.query(async ({ ctx, input }) => {
|
|
642
620
|
const { classId } = input;
|
|
643
|
-
const gradingBoundaries = await
|
|
621
|
+
const gradingBoundaries = await prisma.gradingBoundary.findMany({
|
|
644
622
|
where: {
|
|
645
623
|
classId: classId,
|
|
646
624
|
},
|
|
647
625
|
});
|
|
648
626
|
return gradingBoundaries;
|
|
649
627
|
}),
|
|
650
|
-
createGradingBoundary:
|
|
651
|
-
.input(
|
|
652
|
-
classId:
|
|
653
|
-
structure:
|
|
628
|
+
createGradingBoundary: protectedTeacherProcedure
|
|
629
|
+
.input(z.object({
|
|
630
|
+
classId: z.string(),
|
|
631
|
+
structure: z.string(),
|
|
654
632
|
}))
|
|
655
633
|
.mutation(async ({ ctx, input }) => {
|
|
656
634
|
const { classId, structure } = input;
|
|
657
635
|
const validatedStructure = structure.replace(/\\n/g, '\n');
|
|
658
|
-
const gradingBoundary = await
|
|
636
|
+
const gradingBoundary = await prisma.gradingBoundary.create({
|
|
659
637
|
data: {
|
|
660
638
|
classId: classId,
|
|
661
639
|
structured: validatedStructure,
|
|
@@ -663,31 +641,324 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
663
641
|
});
|
|
664
642
|
return gradingBoundary;
|
|
665
643
|
}),
|
|
666
|
-
updateGradingBoundary:
|
|
667
|
-
.input(
|
|
668
|
-
classId:
|
|
669
|
-
gradingBoundaryId:
|
|
670
|
-
structure:
|
|
644
|
+
updateGradingBoundary: protectedTeacherProcedure
|
|
645
|
+
.input(z.object({
|
|
646
|
+
classId: z.string(),
|
|
647
|
+
gradingBoundaryId: z.string(),
|
|
648
|
+
structure: z.string(),
|
|
671
649
|
}))
|
|
672
650
|
.mutation(async ({ ctx, input }) => {
|
|
673
651
|
const { classId, gradingBoundaryId, structure } = input;
|
|
674
652
|
const validatedStructure = structure.replace(/\\n/g, '\n');
|
|
675
|
-
const gradingBoundary = await
|
|
653
|
+
const gradingBoundary = await prisma.gradingBoundary.update({
|
|
676
654
|
where: { id: gradingBoundaryId },
|
|
677
655
|
data: { structured: validatedStructure },
|
|
678
656
|
});
|
|
679
657
|
return gradingBoundary;
|
|
680
658
|
}),
|
|
681
|
-
deleteGradingBoundary:
|
|
682
|
-
.input(
|
|
683
|
-
classId:
|
|
684
|
-
gradingBoundaryId:
|
|
659
|
+
deleteGradingBoundary: protectedTeacherProcedure
|
|
660
|
+
.input(z.object({
|
|
661
|
+
classId: z.string(),
|
|
662
|
+
gradingBoundaryId: z.string(),
|
|
685
663
|
}))
|
|
686
664
|
.mutation(async ({ ctx, input }) => {
|
|
687
665
|
const { classId, gradingBoundaryId } = input;
|
|
688
|
-
const gradingBoundary = await
|
|
666
|
+
const gradingBoundary = await prisma.gradingBoundary.delete({
|
|
689
667
|
where: { id: gradingBoundaryId },
|
|
690
668
|
});
|
|
691
669
|
return gradingBoundary;
|
|
692
670
|
}),
|
|
671
|
+
getSyllabus: protectedClassMemberProcedure
|
|
672
|
+
.input(z.object({
|
|
673
|
+
classId: z.string(),
|
|
674
|
+
}))
|
|
675
|
+
.query(async ({ input }) => {
|
|
676
|
+
const { classId } = input;
|
|
677
|
+
const syllabus = (await prisma.class.findUnique({
|
|
678
|
+
where: {
|
|
679
|
+
id: classId,
|
|
680
|
+
},
|
|
681
|
+
}))?.syllabus;
|
|
682
|
+
const markSchemes = await prisma.markScheme.findMany({
|
|
683
|
+
where: {
|
|
684
|
+
classId,
|
|
685
|
+
}
|
|
686
|
+
});
|
|
687
|
+
const gradingBoundaries = await prisma.gradingBoundary.findMany({
|
|
688
|
+
where: {
|
|
689
|
+
classId,
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
return { syllabus, gradingBoundaries, markSchemes };
|
|
693
|
+
}),
|
|
694
|
+
updateSyllabus: protectedTeacherProcedure
|
|
695
|
+
.input(z.object({
|
|
696
|
+
classId: z.string(),
|
|
697
|
+
contents: z.string(),
|
|
698
|
+
}))
|
|
699
|
+
.mutation(async ({ input }) => {
|
|
700
|
+
const { contents, classId } = input;
|
|
701
|
+
if (!contents)
|
|
702
|
+
throw new TRPCError({
|
|
703
|
+
code: 'BAD_REQUEST',
|
|
704
|
+
message: "Missing key contents",
|
|
705
|
+
});
|
|
706
|
+
const updated = await prisma.class.update({
|
|
707
|
+
where: {
|
|
708
|
+
id: classId
|
|
709
|
+
},
|
|
710
|
+
data: {
|
|
711
|
+
syllabus: contents,
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
return updated;
|
|
715
|
+
}),
|
|
716
|
+
// Lab Management Endpoints (Assignment-based)
|
|
717
|
+
listLabDrafts: protectedTeacherProcedure
|
|
718
|
+
.input(z.object({
|
|
719
|
+
classId: z.string(),
|
|
720
|
+
}))
|
|
721
|
+
.query(async ({ ctx, input }) => {
|
|
722
|
+
const { classId } = input;
|
|
723
|
+
const labDrafts = await prisma.assignment.findMany({
|
|
724
|
+
where: {
|
|
725
|
+
classId: classId,
|
|
726
|
+
teacherId: ctx.user?.id,
|
|
727
|
+
inProgress: true,
|
|
728
|
+
},
|
|
729
|
+
orderBy: {
|
|
730
|
+
modifiedAt: 'desc',
|
|
731
|
+
},
|
|
732
|
+
});
|
|
733
|
+
return labDrafts;
|
|
734
|
+
}),
|
|
735
|
+
createLabDraft: protectedTeacherProcedure
|
|
736
|
+
.input(z.object({
|
|
737
|
+
classId: z.string(),
|
|
738
|
+
title: z.string(),
|
|
739
|
+
type: z.enum(['LAB', 'HOMEWORK', 'QUIZ', 'TEST', 'PROJECT', 'ESSAY', 'DISCUSSION', 'PRESENTATION', 'OTHER']),
|
|
740
|
+
instructions: z.string(),
|
|
741
|
+
dueDate: z.date().optional(),
|
|
742
|
+
maxGrade: z.number().optional(),
|
|
743
|
+
weight: z.number().optional(),
|
|
744
|
+
graded: z.boolean().optional(),
|
|
745
|
+
sectionId: z.string().optional(),
|
|
746
|
+
markSchemeId: z.string().optional(),
|
|
747
|
+
gradingBoundaryId: z.string().optional(),
|
|
748
|
+
}))
|
|
749
|
+
.mutation(async ({ ctx, input }) => {
|
|
750
|
+
const { classId, ...draftData } = input;
|
|
751
|
+
const labDraft = await prisma.assignment.create({
|
|
752
|
+
data: {
|
|
753
|
+
classId: classId,
|
|
754
|
+
teacherId: ctx.user?.id,
|
|
755
|
+
inProgress: true,
|
|
756
|
+
graded: draftData.graded ?? false,
|
|
757
|
+
maxGrade: draftData.maxGrade ?? 0,
|
|
758
|
+
weight: draftData.weight ?? 1,
|
|
759
|
+
dueDate: draftData.dueDate || new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // Default 1 week from now
|
|
760
|
+
title: draftData.title,
|
|
761
|
+
instructions: draftData.instructions,
|
|
762
|
+
type: draftData.type,
|
|
763
|
+
...(draftData.sectionId && { sectionId: draftData.sectionId }),
|
|
764
|
+
...(draftData.markSchemeId && { markSchemeId: draftData.markSchemeId }),
|
|
765
|
+
...(draftData.gradingBoundaryId && { gradingBoundaryId: draftData.gradingBoundaryId }),
|
|
766
|
+
},
|
|
767
|
+
});
|
|
768
|
+
return labDraft;
|
|
769
|
+
}),
|
|
770
|
+
updateLabDraft: protectedTeacherProcedure
|
|
771
|
+
.input(z.object({
|
|
772
|
+
classId: z.string(),
|
|
773
|
+
draftId: z.string(),
|
|
774
|
+
title: z.string().optional(),
|
|
775
|
+
instructions: z.string().optional(),
|
|
776
|
+
dueDate: z.date().optional(),
|
|
777
|
+
maxGrade: z.number().optional(),
|
|
778
|
+
weight: z.number().optional(),
|
|
779
|
+
graded: z.boolean().optional(),
|
|
780
|
+
sectionId: z.string().optional(),
|
|
781
|
+
markSchemeId: z.string().optional(),
|
|
782
|
+
gradingBoundaryId: z.string().optional(),
|
|
783
|
+
}))
|
|
784
|
+
.mutation(async ({ ctx, input }) => {
|
|
785
|
+
const { classId, draftId, ...updateData } = input;
|
|
786
|
+
const labDraft = await prisma.assignment.update({
|
|
787
|
+
where: {
|
|
788
|
+
id: draftId,
|
|
789
|
+
classId: classId,
|
|
790
|
+
teacherId: ctx.user?.id,
|
|
791
|
+
inProgress: true,
|
|
792
|
+
},
|
|
793
|
+
data: {
|
|
794
|
+
...(updateData.title && { title: updateData.title }),
|
|
795
|
+
...(updateData.instructions && { instructions: updateData.instructions }),
|
|
796
|
+
...(updateData.dueDate && { dueDate: updateData.dueDate }),
|
|
797
|
+
...(updateData.maxGrade !== undefined && { maxGrade: updateData.maxGrade }),
|
|
798
|
+
...(updateData.weight !== undefined && { weight: updateData.weight }),
|
|
799
|
+
...(updateData.graded !== undefined && { graded: updateData.graded }),
|
|
800
|
+
...(updateData.sectionId !== undefined && { sectionId: updateData.sectionId }),
|
|
801
|
+
...(updateData.markSchemeId !== undefined && { markSchemeId: updateData.markSchemeId }),
|
|
802
|
+
...(updateData.gradingBoundaryId !== undefined && { gradingBoundaryId: updateData.gradingBoundaryId }),
|
|
803
|
+
modifiedAt: new Date(),
|
|
804
|
+
},
|
|
805
|
+
});
|
|
806
|
+
return labDraft;
|
|
807
|
+
}),
|
|
808
|
+
deleteLabDraft: protectedTeacherProcedure
|
|
809
|
+
.input(z.object({
|
|
810
|
+
classId: z.string(),
|
|
811
|
+
draftId: z.string(),
|
|
812
|
+
}))
|
|
813
|
+
.mutation(async ({ ctx, input }) => {
|
|
814
|
+
const { classId, draftId } = input;
|
|
815
|
+
const labDraft = await prisma.assignment.delete({
|
|
816
|
+
where: {
|
|
817
|
+
id: draftId,
|
|
818
|
+
classId: classId,
|
|
819
|
+
teacherId: ctx.user?.id,
|
|
820
|
+
inProgress: true,
|
|
821
|
+
},
|
|
822
|
+
});
|
|
823
|
+
return labDraft;
|
|
824
|
+
}),
|
|
825
|
+
publishLabDraft: protectedTeacherProcedure
|
|
826
|
+
.input(z.object({
|
|
827
|
+
classId: z.string(),
|
|
828
|
+
draftId: z.string(),
|
|
829
|
+
dueDate: z.date().optional(),
|
|
830
|
+
maxGrade: z.number().optional(),
|
|
831
|
+
weight: z.number().optional(),
|
|
832
|
+
graded: z.boolean().optional(),
|
|
833
|
+
}))
|
|
834
|
+
.mutation(async ({ ctx, input }) => {
|
|
835
|
+
const { classId, draftId, ...publishData } = input;
|
|
836
|
+
// Get the lab draft
|
|
837
|
+
const labDraft = await prisma.assignment.findUnique({
|
|
838
|
+
where: {
|
|
839
|
+
id: draftId,
|
|
840
|
+
classId: classId,
|
|
841
|
+
teacherId: ctx.user?.id,
|
|
842
|
+
inProgress: true,
|
|
843
|
+
},
|
|
844
|
+
});
|
|
845
|
+
if (!labDraft) {
|
|
846
|
+
throw new TRPCError({
|
|
847
|
+
code: 'NOT_FOUND',
|
|
848
|
+
message: 'Lab draft not found',
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
// Publish the draft by updating it to not be in progress
|
|
852
|
+
const publishedAssignment = await prisma.assignment.update({
|
|
853
|
+
where: { id: draftId },
|
|
854
|
+
data: {
|
|
855
|
+
inProgress: false,
|
|
856
|
+
dueDate: publishData.dueDate || labDraft.dueDate,
|
|
857
|
+
maxGrade: publishData.maxGrade || labDraft.maxGrade || 100,
|
|
858
|
+
weight: publishData.weight || labDraft.weight || 1,
|
|
859
|
+
graded: publishData.graded !== undefined ? publishData.graded : true,
|
|
860
|
+
modifiedAt: new Date(),
|
|
861
|
+
},
|
|
862
|
+
});
|
|
863
|
+
return publishedAssignment;
|
|
864
|
+
}),
|
|
865
|
+
getFiles: protectedClassMemberProcedure
|
|
866
|
+
.input(z.object({
|
|
867
|
+
classId: z.string(),
|
|
868
|
+
}))
|
|
869
|
+
.query(async ({ ctx, input }) => {
|
|
870
|
+
const { classId } = input;
|
|
871
|
+
// Get all assignments with their files and submissions
|
|
872
|
+
const assignments = await prisma.assignment.findMany({
|
|
873
|
+
where: {
|
|
874
|
+
classId: classId,
|
|
875
|
+
},
|
|
876
|
+
include: {
|
|
877
|
+
attachments: {
|
|
878
|
+
select: {
|
|
879
|
+
id: true,
|
|
880
|
+
name: true,
|
|
881
|
+
type: true,
|
|
882
|
+
size: true,
|
|
883
|
+
path: true,
|
|
884
|
+
thumbnailId: true,
|
|
885
|
+
uploadedAt: true,
|
|
886
|
+
user: {
|
|
887
|
+
select: {
|
|
888
|
+
id: true,
|
|
889
|
+
username: true,
|
|
890
|
+
},
|
|
891
|
+
},
|
|
892
|
+
},
|
|
893
|
+
},
|
|
894
|
+
submissions: {
|
|
895
|
+
include: {
|
|
896
|
+
attachments: {
|
|
897
|
+
select: {
|
|
898
|
+
id: true,
|
|
899
|
+
name: true,
|
|
900
|
+
type: true,
|
|
901
|
+
size: true,
|
|
902
|
+
path: true,
|
|
903
|
+
thumbnailId: true,
|
|
904
|
+
uploadedAt: true,
|
|
905
|
+
user: {
|
|
906
|
+
select: {
|
|
907
|
+
id: true,
|
|
908
|
+
username: true,
|
|
909
|
+
},
|
|
910
|
+
},
|
|
911
|
+
},
|
|
912
|
+
},
|
|
913
|
+
annotations: {
|
|
914
|
+
select: {
|
|
915
|
+
id: true,
|
|
916
|
+
name: true,
|
|
917
|
+
type: true,
|
|
918
|
+
size: true,
|
|
919
|
+
path: true,
|
|
920
|
+
thumbnailId: true,
|
|
921
|
+
uploadedAt: true,
|
|
922
|
+
user: {
|
|
923
|
+
select: {
|
|
924
|
+
id: true,
|
|
925
|
+
username: true,
|
|
926
|
+
},
|
|
927
|
+
},
|
|
928
|
+
},
|
|
929
|
+
},
|
|
930
|
+
student: {
|
|
931
|
+
select: {
|
|
932
|
+
id: true,
|
|
933
|
+
username: true,
|
|
934
|
+
},
|
|
935
|
+
},
|
|
936
|
+
},
|
|
937
|
+
},
|
|
938
|
+
teacher: {
|
|
939
|
+
select: {
|
|
940
|
+
id: true,
|
|
941
|
+
username: true,
|
|
942
|
+
},
|
|
943
|
+
},
|
|
944
|
+
},
|
|
945
|
+
orderBy: {
|
|
946
|
+
createdAt: 'desc',
|
|
947
|
+
},
|
|
948
|
+
});
|
|
949
|
+
// Organize files by assignment structure
|
|
950
|
+
const organizedFiles = assignments.map(assignment => ({
|
|
951
|
+
id: assignment.id,
|
|
952
|
+
title: assignment.title,
|
|
953
|
+
teacher: assignment.teacher,
|
|
954
|
+
teacherAttachments: assignment.attachments,
|
|
955
|
+
students: assignment.submissions.map(submission => ({
|
|
956
|
+
id: submission.student.id,
|
|
957
|
+
username: submission.student.username,
|
|
958
|
+
attachments: submission.attachments,
|
|
959
|
+
annotations: submission.annotations,
|
|
960
|
+
})),
|
|
961
|
+
}));
|
|
962
|
+
return organizedFiles;
|
|
963
|
+
}),
|
|
693
964
|
});
|