@studious-lms/server 1.0.6 → 1.0.8
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 +1461 -0
- package/dist/exportType.d.ts +3 -3
- package/dist/exportType.d.ts.map +1 -1
- 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 +3619 -1937
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/_app.js +28 -27
- package/dist/routers/agenda.d.ts +14 -9
- package/dist/routers/agenda.d.ts.map +1 -1
- package/dist/routers/agenda.js +14 -17
- package/dist/routers/announcement.d.ts +5 -4
- package/dist/routers/announcement.d.ts.map +1 -1
- package/dist/routers/announcement.js +28 -31
- package/dist/routers/assignment.d.ts +283 -197
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/assignment.js +256 -202
- package/dist/routers/attendance.d.ts +6 -5
- package/dist/routers/attendance.d.ts.map +1 -1
- package/dist/routers/attendance.js +31 -34
- package/dist/routers/auth.d.ts +2 -1
- package/dist/routers/auth.d.ts.map +1 -1
- package/dist/routers/auth.js +80 -75
- package/dist/routers/class.d.ts +285 -15
- package/dist/routers/class.d.ts.map +1 -1
- package/dist/routers/class.js +440 -164
- package/dist/routers/event.d.ts +48 -39
- package/dist/routers/event.d.ts.map +1 -1
- package/dist/routers/event.js +76 -79
- package/dist/routers/file.d.ts +72 -2
- package/dist/routers/file.d.ts.map +1 -1
- package/dist/routers/file.js +260 -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 +2 -1
- package/dist/routers/section.d.ts.map +1 -1
- package/dist/routers/section.js +30 -33
- package/dist/routers/user.d.ts +3 -2
- 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 +75 -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.d.ts +1 -1
- package/dist/types/trpc.d.ts.map +1 -1
- 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 +12 -4
- package/prisma/migrations/20250807062924_init/migration.sql +436 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +68 -1
- package/src/exportType.ts +3 -3
- package/src/index.ts +6 -6
- package/src/lib/fileUpload.ts +19 -10
- package/src/lib/thumbnailGenerator.ts +2 -2
- package/src/middleware/auth.ts +2 -4
- package/src/middleware/logging.ts +2 -2
- package/src/routers/_app.ts +17 -13
- package/src/routers/agenda.ts +2 -2
- package/src/routers/announcement.ts +2 -2
- package/src/routers/assignment.ts +86 -26
- package/src/routers/attendance.ts +2 -2
- package/src/routers/auth.ts +83 -57
- package/src/routers/class.ts +339 -39
- package/src/routers/event.ts +2 -2
- package/src/routers/file.ts +276 -21
- package/src/routers/folder.ts +755 -0
- package/src/routers/notifications.ts +93 -0
- package/src/routers/section.ts +2 -2
- package/src/routers/user.ts +3 -3
- package/src/seedDatabase.ts +88 -0
- package/src/socket/handlers.ts +5 -5
- package/src/trpc.ts +17 -4
- package/src/types/trpc.ts +1 -1
- 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.js";
|
|
3
|
+
import { prisma } from "../lib/prisma.js";
|
|
4
|
+
import { TRPCError } from "@trpc/server";
|
|
5
|
+
import { generateInviteCode } from "../utils/generateInviteCode.js";
|
|
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,21 @@ 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;
|
|
451
|
+
await prisma.session.deleteMany({
|
|
452
|
+
where: {
|
|
453
|
+
classId,
|
|
454
|
+
},
|
|
455
|
+
});
|
|
473
456
|
// Create a new session for the invite code
|
|
474
|
-
const session = await
|
|
457
|
+
const session = await prisma.session.create({
|
|
475
458
|
data: {
|
|
476
|
-
id:
|
|
459
|
+
id: generateInviteCode(),
|
|
477
460
|
classId,
|
|
478
461
|
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24 hours from now
|
|
479
462
|
}
|
|
@@ -482,14 +465,14 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
482
465
|
code: session.id,
|
|
483
466
|
};
|
|
484
467
|
}),
|
|
485
|
-
getGrades:
|
|
486
|
-
.input(
|
|
487
|
-
classId:
|
|
488
|
-
userId:
|
|
468
|
+
getGrades: protectedClassMemberProcedure
|
|
469
|
+
.input(z.object({
|
|
470
|
+
classId: z.string(),
|
|
471
|
+
userId: z.string(),
|
|
489
472
|
}))
|
|
490
473
|
.query(async ({ ctx, input }) => {
|
|
491
474
|
const { classId, userId } = input;
|
|
492
|
-
const isTeacher = await
|
|
475
|
+
const isTeacher = await prisma.class.findFirst({
|
|
493
476
|
where: {
|
|
494
477
|
id: classId,
|
|
495
478
|
teachers: {
|
|
@@ -499,12 +482,12 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
499
482
|
});
|
|
500
483
|
// If student, only allow viewing their own grades
|
|
501
484
|
if (ctx.user?.id !== userId && !isTeacher) {
|
|
502
|
-
throw new
|
|
485
|
+
throw new TRPCError({
|
|
503
486
|
code: 'UNAUTHORIZED',
|
|
504
487
|
message: 'You can only view your own grades',
|
|
505
488
|
});
|
|
506
489
|
}
|
|
507
|
-
const grades = await
|
|
490
|
+
const grades = await prisma.submission.findMany({
|
|
508
491
|
where: {
|
|
509
492
|
studentId: userId,
|
|
510
493
|
assignment: {
|
|
@@ -527,17 +510,17 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
527
510
|
grades,
|
|
528
511
|
};
|
|
529
512
|
}),
|
|
530
|
-
updateGrade:
|
|
531
|
-
.input(
|
|
532
|
-
classId:
|
|
533
|
-
assignmentId:
|
|
534
|
-
submissionId:
|
|
535
|
-
gradeReceived:
|
|
513
|
+
updateGrade: protectedTeacherProcedure
|
|
514
|
+
.input(z.object({
|
|
515
|
+
classId: z.string(),
|
|
516
|
+
assignmentId: z.string(),
|
|
517
|
+
submissionId: z.string(),
|
|
518
|
+
gradeReceived: z.number().nullable(),
|
|
536
519
|
}))
|
|
537
520
|
.mutation(async ({ ctx, input }) => {
|
|
538
521
|
const { classId, assignmentId, submissionId, gradeReceived } = input;
|
|
539
522
|
// Update the grade
|
|
540
|
-
const updatedSubmission = await
|
|
523
|
+
const updatedSubmission = await prisma.submission.update({
|
|
541
524
|
where: {
|
|
542
525
|
id: submissionId,
|
|
543
526
|
assignmentId: assignmentId,
|
|
@@ -558,13 +541,13 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
558
541
|
});
|
|
559
542
|
return updatedSubmission;
|
|
560
543
|
}),
|
|
561
|
-
getEvents:
|
|
562
|
-
.input(
|
|
563
|
-
classId:
|
|
544
|
+
getEvents: protectedTeacherProcedure
|
|
545
|
+
.input(z.object({
|
|
546
|
+
classId: z.string(),
|
|
564
547
|
}))
|
|
565
548
|
.query(async ({ ctx, input }) => {
|
|
566
549
|
const { classId } = input;
|
|
567
|
-
const events = await
|
|
550
|
+
const events = await prisma.event.findMany({
|
|
568
551
|
where: {
|
|
569
552
|
class: {
|
|
570
553
|
id: classId,
|
|
@@ -578,28 +561,28 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
578
561
|
});
|
|
579
562
|
return events;
|
|
580
563
|
}),
|
|
581
|
-
listMarkSchemes:
|
|
582
|
-
.input(
|
|
583
|
-
classId:
|
|
564
|
+
listMarkSchemes: protectedTeacherProcedure
|
|
565
|
+
.input(z.object({
|
|
566
|
+
classId: z.string(),
|
|
584
567
|
}))
|
|
585
568
|
.query(async ({ ctx, input }) => {
|
|
586
569
|
const { classId } = input;
|
|
587
|
-
const markSchemes = await
|
|
570
|
+
const markSchemes = await prisma.markScheme.findMany({
|
|
588
571
|
where: {
|
|
589
572
|
classId: classId,
|
|
590
573
|
},
|
|
591
574
|
});
|
|
592
575
|
return markSchemes;
|
|
593
576
|
}),
|
|
594
|
-
createMarkScheme:
|
|
595
|
-
.input(
|
|
596
|
-
classId:
|
|
597
|
-
structure:
|
|
577
|
+
createMarkScheme: protectedTeacherProcedure
|
|
578
|
+
.input(z.object({
|
|
579
|
+
classId: z.string(),
|
|
580
|
+
structure: z.string(),
|
|
598
581
|
}))
|
|
599
582
|
.mutation(async ({ ctx, input }) => {
|
|
600
583
|
const { classId, structure } = input;
|
|
601
584
|
const validatedStructure = structure.replace(/\\n/g, '\n');
|
|
602
|
-
const markScheme = await
|
|
585
|
+
const markScheme = await prisma.markScheme.create({
|
|
603
586
|
data: {
|
|
604
587
|
classId: classId,
|
|
605
588
|
structured: validatedStructure,
|
|
@@ -607,55 +590,55 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
607
590
|
});
|
|
608
591
|
return markScheme;
|
|
609
592
|
}),
|
|
610
|
-
updateMarkScheme:
|
|
611
|
-
.input(
|
|
612
|
-
classId:
|
|
613
|
-
markSchemeId:
|
|
614
|
-
structure:
|
|
593
|
+
updateMarkScheme: protectedTeacherProcedure
|
|
594
|
+
.input(z.object({
|
|
595
|
+
classId: z.string(),
|
|
596
|
+
markSchemeId: z.string(),
|
|
597
|
+
structure: z.string(),
|
|
615
598
|
}))
|
|
616
599
|
.mutation(async ({ ctx, input }) => {
|
|
617
600
|
const { classId, markSchemeId, structure } = input;
|
|
618
601
|
const validatedStructure = structure.replace(/\\n/g, '\n');
|
|
619
|
-
const markScheme = await
|
|
602
|
+
const markScheme = await prisma.markScheme.update({
|
|
620
603
|
where: { id: markSchemeId },
|
|
621
604
|
data: { structured: validatedStructure },
|
|
622
605
|
});
|
|
623
606
|
return markScheme;
|
|
624
607
|
}),
|
|
625
|
-
deleteMarkScheme:
|
|
626
|
-
.input(
|
|
627
|
-
classId:
|
|
628
|
-
markSchemeId:
|
|
608
|
+
deleteMarkScheme: protectedTeacherProcedure
|
|
609
|
+
.input(z.object({
|
|
610
|
+
classId: z.string(),
|
|
611
|
+
markSchemeId: z.string(),
|
|
629
612
|
}))
|
|
630
613
|
.mutation(async ({ ctx, input }) => {
|
|
631
614
|
const { classId, markSchemeId } = input;
|
|
632
|
-
const markScheme = await
|
|
615
|
+
const markScheme = await prisma.markScheme.delete({
|
|
633
616
|
where: { id: markSchemeId },
|
|
634
617
|
});
|
|
635
618
|
return markScheme;
|
|
636
619
|
}),
|
|
637
|
-
listGradingBoundaries:
|
|
638
|
-
.input(
|
|
639
|
-
classId:
|
|
620
|
+
listGradingBoundaries: protectedTeacherProcedure
|
|
621
|
+
.input(z.object({
|
|
622
|
+
classId: z.string(),
|
|
640
623
|
}))
|
|
641
624
|
.query(async ({ ctx, input }) => {
|
|
642
625
|
const { classId } = input;
|
|
643
|
-
const gradingBoundaries = await
|
|
626
|
+
const gradingBoundaries = await prisma.gradingBoundary.findMany({
|
|
644
627
|
where: {
|
|
645
628
|
classId: classId,
|
|
646
629
|
},
|
|
647
630
|
});
|
|
648
631
|
return gradingBoundaries;
|
|
649
632
|
}),
|
|
650
|
-
createGradingBoundary:
|
|
651
|
-
.input(
|
|
652
|
-
classId:
|
|
653
|
-
structure:
|
|
633
|
+
createGradingBoundary: protectedTeacherProcedure
|
|
634
|
+
.input(z.object({
|
|
635
|
+
classId: z.string(),
|
|
636
|
+
structure: z.string(),
|
|
654
637
|
}))
|
|
655
638
|
.mutation(async ({ ctx, input }) => {
|
|
656
639
|
const { classId, structure } = input;
|
|
657
640
|
const validatedStructure = structure.replace(/\\n/g, '\n');
|
|
658
|
-
const gradingBoundary = await
|
|
641
|
+
const gradingBoundary = await prisma.gradingBoundary.create({
|
|
659
642
|
data: {
|
|
660
643
|
classId: classId,
|
|
661
644
|
structured: validatedStructure,
|
|
@@ -663,31 +646,324 @@ exports.classRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
663
646
|
});
|
|
664
647
|
return gradingBoundary;
|
|
665
648
|
}),
|
|
666
|
-
updateGradingBoundary:
|
|
667
|
-
.input(
|
|
668
|
-
classId:
|
|
669
|
-
gradingBoundaryId:
|
|
670
|
-
structure:
|
|
649
|
+
updateGradingBoundary: protectedTeacherProcedure
|
|
650
|
+
.input(z.object({
|
|
651
|
+
classId: z.string(),
|
|
652
|
+
gradingBoundaryId: z.string(),
|
|
653
|
+
structure: z.string(),
|
|
671
654
|
}))
|
|
672
655
|
.mutation(async ({ ctx, input }) => {
|
|
673
656
|
const { classId, gradingBoundaryId, structure } = input;
|
|
674
657
|
const validatedStructure = structure.replace(/\\n/g, '\n');
|
|
675
|
-
const gradingBoundary = await
|
|
658
|
+
const gradingBoundary = await prisma.gradingBoundary.update({
|
|
676
659
|
where: { id: gradingBoundaryId },
|
|
677
660
|
data: { structured: validatedStructure },
|
|
678
661
|
});
|
|
679
662
|
return gradingBoundary;
|
|
680
663
|
}),
|
|
681
|
-
deleteGradingBoundary:
|
|
682
|
-
.input(
|
|
683
|
-
classId:
|
|
684
|
-
gradingBoundaryId:
|
|
664
|
+
deleteGradingBoundary: protectedTeacherProcedure
|
|
665
|
+
.input(z.object({
|
|
666
|
+
classId: z.string(),
|
|
667
|
+
gradingBoundaryId: z.string(),
|
|
685
668
|
}))
|
|
686
669
|
.mutation(async ({ ctx, input }) => {
|
|
687
670
|
const { classId, gradingBoundaryId } = input;
|
|
688
|
-
const gradingBoundary = await
|
|
671
|
+
const gradingBoundary = await prisma.gradingBoundary.delete({
|
|
689
672
|
where: { id: gradingBoundaryId },
|
|
690
673
|
});
|
|
691
674
|
return gradingBoundary;
|
|
692
675
|
}),
|
|
676
|
+
getSyllabus: protectedClassMemberProcedure
|
|
677
|
+
.input(z.object({
|
|
678
|
+
classId: z.string(),
|
|
679
|
+
}))
|
|
680
|
+
.query(async ({ input }) => {
|
|
681
|
+
const { classId } = input;
|
|
682
|
+
const syllabus = (await prisma.class.findUnique({
|
|
683
|
+
where: {
|
|
684
|
+
id: classId,
|
|
685
|
+
},
|
|
686
|
+
}))?.syllabus;
|
|
687
|
+
const markSchemes = await prisma.markScheme.findMany({
|
|
688
|
+
where: {
|
|
689
|
+
classId,
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
const gradingBoundaries = await prisma.gradingBoundary.findMany({
|
|
693
|
+
where: {
|
|
694
|
+
classId,
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
return { syllabus, gradingBoundaries, markSchemes };
|
|
698
|
+
}),
|
|
699
|
+
updateSyllabus: protectedTeacherProcedure
|
|
700
|
+
.input(z.object({
|
|
701
|
+
classId: z.string(),
|
|
702
|
+
contents: z.string(),
|
|
703
|
+
}))
|
|
704
|
+
.mutation(async ({ input }) => {
|
|
705
|
+
const { contents, classId } = input;
|
|
706
|
+
if (!contents)
|
|
707
|
+
throw new TRPCError({
|
|
708
|
+
code: 'BAD_REQUEST',
|
|
709
|
+
message: "Missing key contents",
|
|
710
|
+
});
|
|
711
|
+
const updated = await prisma.class.update({
|
|
712
|
+
where: {
|
|
713
|
+
id: classId
|
|
714
|
+
},
|
|
715
|
+
data: {
|
|
716
|
+
syllabus: contents,
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
return updated;
|
|
720
|
+
}),
|
|
721
|
+
// Lab Management Endpoints (Assignment-based)
|
|
722
|
+
listLabDrafts: protectedTeacherProcedure
|
|
723
|
+
.input(z.object({
|
|
724
|
+
classId: z.string(),
|
|
725
|
+
}))
|
|
726
|
+
.query(async ({ ctx, input }) => {
|
|
727
|
+
const { classId } = input;
|
|
728
|
+
const labDrafts = await prisma.assignment.findMany({
|
|
729
|
+
where: {
|
|
730
|
+
classId: classId,
|
|
731
|
+
teacherId: ctx.user?.id,
|
|
732
|
+
inProgress: true,
|
|
733
|
+
},
|
|
734
|
+
orderBy: {
|
|
735
|
+
modifiedAt: 'desc',
|
|
736
|
+
},
|
|
737
|
+
});
|
|
738
|
+
return labDrafts;
|
|
739
|
+
}),
|
|
740
|
+
createLabDraft: protectedTeacherProcedure
|
|
741
|
+
.input(z.object({
|
|
742
|
+
classId: z.string(),
|
|
743
|
+
title: z.string(),
|
|
744
|
+
type: z.enum(['LAB', 'HOMEWORK', 'QUIZ', 'TEST', 'PROJECT', 'ESSAY', 'DISCUSSION', 'PRESENTATION', 'OTHER']),
|
|
745
|
+
instructions: z.string(),
|
|
746
|
+
dueDate: z.date().optional(),
|
|
747
|
+
maxGrade: z.number().optional(),
|
|
748
|
+
weight: z.number().optional(),
|
|
749
|
+
graded: z.boolean().optional(),
|
|
750
|
+
sectionId: z.string().optional(),
|
|
751
|
+
markSchemeId: z.string().optional(),
|
|
752
|
+
gradingBoundaryId: z.string().optional(),
|
|
753
|
+
}))
|
|
754
|
+
.mutation(async ({ ctx, input }) => {
|
|
755
|
+
const { classId, ...draftData } = input;
|
|
756
|
+
const labDraft = await prisma.assignment.create({
|
|
757
|
+
data: {
|
|
758
|
+
classId: classId,
|
|
759
|
+
teacherId: ctx.user?.id,
|
|
760
|
+
inProgress: true,
|
|
761
|
+
graded: draftData.graded ?? false,
|
|
762
|
+
maxGrade: draftData.maxGrade ?? 0,
|
|
763
|
+
weight: draftData.weight ?? 1,
|
|
764
|
+
dueDate: draftData.dueDate || new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // Default 1 week from now
|
|
765
|
+
title: draftData.title,
|
|
766
|
+
instructions: draftData.instructions,
|
|
767
|
+
type: draftData.type,
|
|
768
|
+
...(draftData.sectionId && { sectionId: draftData.sectionId }),
|
|
769
|
+
...(draftData.markSchemeId && { markSchemeId: draftData.markSchemeId }),
|
|
770
|
+
...(draftData.gradingBoundaryId && { gradingBoundaryId: draftData.gradingBoundaryId }),
|
|
771
|
+
},
|
|
772
|
+
});
|
|
773
|
+
return labDraft;
|
|
774
|
+
}),
|
|
775
|
+
updateLabDraft: protectedTeacherProcedure
|
|
776
|
+
.input(z.object({
|
|
777
|
+
classId: z.string(),
|
|
778
|
+
draftId: z.string(),
|
|
779
|
+
title: z.string().optional(),
|
|
780
|
+
instructions: z.string().optional(),
|
|
781
|
+
dueDate: z.date().optional(),
|
|
782
|
+
maxGrade: z.number().optional(),
|
|
783
|
+
weight: z.number().optional(),
|
|
784
|
+
graded: z.boolean().optional(),
|
|
785
|
+
sectionId: z.string().optional(),
|
|
786
|
+
markSchemeId: z.string().optional(),
|
|
787
|
+
gradingBoundaryId: z.string().optional(),
|
|
788
|
+
}))
|
|
789
|
+
.mutation(async ({ ctx, input }) => {
|
|
790
|
+
const { classId, draftId, ...updateData } = input;
|
|
791
|
+
const labDraft = await prisma.assignment.update({
|
|
792
|
+
where: {
|
|
793
|
+
id: draftId,
|
|
794
|
+
classId: classId,
|
|
795
|
+
teacherId: ctx.user?.id,
|
|
796
|
+
inProgress: true,
|
|
797
|
+
},
|
|
798
|
+
data: {
|
|
799
|
+
...(updateData.title && { title: updateData.title }),
|
|
800
|
+
...(updateData.instructions && { instructions: updateData.instructions }),
|
|
801
|
+
...(updateData.dueDate && { dueDate: updateData.dueDate }),
|
|
802
|
+
...(updateData.maxGrade !== undefined && { maxGrade: updateData.maxGrade }),
|
|
803
|
+
...(updateData.weight !== undefined && { weight: updateData.weight }),
|
|
804
|
+
...(updateData.graded !== undefined && { graded: updateData.graded }),
|
|
805
|
+
...(updateData.sectionId !== undefined && { sectionId: updateData.sectionId }),
|
|
806
|
+
...(updateData.markSchemeId !== undefined && { markSchemeId: updateData.markSchemeId }),
|
|
807
|
+
...(updateData.gradingBoundaryId !== undefined && { gradingBoundaryId: updateData.gradingBoundaryId }),
|
|
808
|
+
modifiedAt: new Date(),
|
|
809
|
+
},
|
|
810
|
+
});
|
|
811
|
+
return labDraft;
|
|
812
|
+
}),
|
|
813
|
+
deleteLabDraft: protectedTeacherProcedure
|
|
814
|
+
.input(z.object({
|
|
815
|
+
classId: z.string(),
|
|
816
|
+
draftId: z.string(),
|
|
817
|
+
}))
|
|
818
|
+
.mutation(async ({ ctx, input }) => {
|
|
819
|
+
const { classId, draftId } = input;
|
|
820
|
+
const labDraft = await prisma.assignment.delete({
|
|
821
|
+
where: {
|
|
822
|
+
id: draftId,
|
|
823
|
+
classId: classId,
|
|
824
|
+
teacherId: ctx.user?.id,
|
|
825
|
+
inProgress: true,
|
|
826
|
+
},
|
|
827
|
+
});
|
|
828
|
+
return labDraft;
|
|
829
|
+
}),
|
|
830
|
+
publishLabDraft: protectedTeacherProcedure
|
|
831
|
+
.input(z.object({
|
|
832
|
+
classId: z.string(),
|
|
833
|
+
draftId: z.string(),
|
|
834
|
+
dueDate: z.date().optional(),
|
|
835
|
+
maxGrade: z.number().optional(),
|
|
836
|
+
weight: z.number().optional(),
|
|
837
|
+
graded: z.boolean().optional(),
|
|
838
|
+
}))
|
|
839
|
+
.mutation(async ({ ctx, input }) => {
|
|
840
|
+
const { classId, draftId, ...publishData } = input;
|
|
841
|
+
// Get the lab draft
|
|
842
|
+
const labDraft = await prisma.assignment.findUnique({
|
|
843
|
+
where: {
|
|
844
|
+
id: draftId,
|
|
845
|
+
classId: classId,
|
|
846
|
+
teacherId: ctx.user?.id,
|
|
847
|
+
inProgress: true,
|
|
848
|
+
},
|
|
849
|
+
});
|
|
850
|
+
if (!labDraft) {
|
|
851
|
+
throw new TRPCError({
|
|
852
|
+
code: 'NOT_FOUND',
|
|
853
|
+
message: 'Lab draft not found',
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
// Publish the draft by updating it to not be in progress
|
|
857
|
+
const publishedAssignment = await prisma.assignment.update({
|
|
858
|
+
where: { id: draftId },
|
|
859
|
+
data: {
|
|
860
|
+
inProgress: false,
|
|
861
|
+
dueDate: publishData.dueDate || labDraft.dueDate,
|
|
862
|
+
maxGrade: publishData.maxGrade || labDraft.maxGrade || 100,
|
|
863
|
+
weight: publishData.weight || labDraft.weight || 1,
|
|
864
|
+
graded: publishData.graded !== undefined ? publishData.graded : true,
|
|
865
|
+
modifiedAt: new Date(),
|
|
866
|
+
},
|
|
867
|
+
});
|
|
868
|
+
return publishedAssignment;
|
|
869
|
+
}),
|
|
870
|
+
getFiles: protectedClassMemberProcedure
|
|
871
|
+
.input(z.object({
|
|
872
|
+
classId: z.string(),
|
|
873
|
+
}))
|
|
874
|
+
.query(async ({ ctx, input }) => {
|
|
875
|
+
const { classId } = input;
|
|
876
|
+
// Get all assignments with their files and submissions
|
|
877
|
+
const assignments = await prisma.assignment.findMany({
|
|
878
|
+
where: {
|
|
879
|
+
classId: classId,
|
|
880
|
+
},
|
|
881
|
+
include: {
|
|
882
|
+
attachments: {
|
|
883
|
+
select: {
|
|
884
|
+
id: true,
|
|
885
|
+
name: true,
|
|
886
|
+
type: true,
|
|
887
|
+
size: true,
|
|
888
|
+
path: true,
|
|
889
|
+
thumbnailId: true,
|
|
890
|
+
uploadedAt: true,
|
|
891
|
+
user: {
|
|
892
|
+
select: {
|
|
893
|
+
id: true,
|
|
894
|
+
username: true,
|
|
895
|
+
},
|
|
896
|
+
},
|
|
897
|
+
},
|
|
898
|
+
},
|
|
899
|
+
submissions: {
|
|
900
|
+
include: {
|
|
901
|
+
attachments: {
|
|
902
|
+
select: {
|
|
903
|
+
id: true,
|
|
904
|
+
name: true,
|
|
905
|
+
type: true,
|
|
906
|
+
size: true,
|
|
907
|
+
path: true,
|
|
908
|
+
thumbnailId: true,
|
|
909
|
+
uploadedAt: true,
|
|
910
|
+
user: {
|
|
911
|
+
select: {
|
|
912
|
+
id: true,
|
|
913
|
+
username: true,
|
|
914
|
+
},
|
|
915
|
+
},
|
|
916
|
+
},
|
|
917
|
+
},
|
|
918
|
+
annotations: {
|
|
919
|
+
select: {
|
|
920
|
+
id: true,
|
|
921
|
+
name: true,
|
|
922
|
+
type: true,
|
|
923
|
+
size: true,
|
|
924
|
+
path: true,
|
|
925
|
+
thumbnailId: true,
|
|
926
|
+
uploadedAt: true,
|
|
927
|
+
user: {
|
|
928
|
+
select: {
|
|
929
|
+
id: true,
|
|
930
|
+
username: true,
|
|
931
|
+
},
|
|
932
|
+
},
|
|
933
|
+
},
|
|
934
|
+
},
|
|
935
|
+
student: {
|
|
936
|
+
select: {
|
|
937
|
+
id: true,
|
|
938
|
+
username: true,
|
|
939
|
+
},
|
|
940
|
+
},
|
|
941
|
+
},
|
|
942
|
+
},
|
|
943
|
+
teacher: {
|
|
944
|
+
select: {
|
|
945
|
+
id: true,
|
|
946
|
+
username: true,
|
|
947
|
+
},
|
|
948
|
+
},
|
|
949
|
+
},
|
|
950
|
+
orderBy: {
|
|
951
|
+
createdAt: 'desc',
|
|
952
|
+
},
|
|
953
|
+
});
|
|
954
|
+
// Organize files by assignment structure
|
|
955
|
+
const organizedFiles = assignments.map(assignment => ({
|
|
956
|
+
id: assignment.id,
|
|
957
|
+
title: assignment.title,
|
|
958
|
+
teacher: assignment.teacher,
|
|
959
|
+
teacherAttachments: assignment.attachments,
|
|
960
|
+
students: assignment.submissions.map(submission => ({
|
|
961
|
+
id: submission.student.id,
|
|
962
|
+
username: submission.student.username,
|
|
963
|
+
attachments: submission.attachments,
|
|
964
|
+
annotations: submission.annotations,
|
|
965
|
+
})),
|
|
966
|
+
}));
|
|
967
|
+
return organizedFiles;
|
|
968
|
+
}),
|
|
693
969
|
});
|