@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
|
@@ -1,90 +1,93 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
type: zod_1.z.string(),
|
|
13
|
-
size: zod_1.z.number(),
|
|
14
|
-
data: zod_1.z.string(), // base64 encoded file data
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createTRPCRouter, protectedProcedure, protectedClassMemberProcedure, protectedTeacherProcedure } from "../trpc";
|
|
3
|
+
import { TRPCError } from "@trpc/server";
|
|
4
|
+
import { prisma } from "../lib/prisma";
|
|
5
|
+
import { uploadFiles } from "../lib/fileUpload";
|
|
6
|
+
import { deleteFile } from "../lib/googleCloudStorage";
|
|
7
|
+
const fileSchema = z.object({
|
|
8
|
+
name: z.string(),
|
|
9
|
+
type: z.string(),
|
|
10
|
+
size: z.number(),
|
|
11
|
+
data: z.string(), // base64 encoded file data
|
|
15
12
|
});
|
|
16
|
-
const createAssignmentSchema =
|
|
17
|
-
classId:
|
|
18
|
-
title:
|
|
19
|
-
instructions:
|
|
20
|
-
dueDate:
|
|
21
|
-
files:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
13
|
+
const createAssignmentSchema = z.object({
|
|
14
|
+
classId: z.string(),
|
|
15
|
+
title: z.string(),
|
|
16
|
+
instructions: z.string(),
|
|
17
|
+
dueDate: z.string(),
|
|
18
|
+
files: z.array(fileSchema).optional(),
|
|
19
|
+
existingFileIds: z.array(z.string()).optional(),
|
|
20
|
+
maxGrade: z.number().optional(),
|
|
21
|
+
graded: z.boolean().optional(),
|
|
22
|
+
weight: z.number().optional(),
|
|
23
|
+
sectionId: z.string().optional(),
|
|
24
|
+
type: z.enum(['HOMEWORK', 'QUIZ', 'TEST', 'PROJECT', 'ESSAY', 'DISCUSSION', 'PRESENTATION', 'LAB', 'OTHER']).optional(),
|
|
25
|
+
markSchemeId: z.string().optional(),
|
|
26
|
+
gradingBoundaryId: z.string().optional(),
|
|
27
|
+
inProgress: z.boolean().optional(),
|
|
29
28
|
});
|
|
30
|
-
const updateAssignmentSchema =
|
|
31
|
-
classId:
|
|
32
|
-
id:
|
|
33
|
-
title:
|
|
34
|
-
instructions:
|
|
35
|
-
dueDate:
|
|
36
|
-
files:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
29
|
+
const updateAssignmentSchema = z.object({
|
|
30
|
+
classId: z.string(),
|
|
31
|
+
id: z.string(),
|
|
32
|
+
title: z.string().optional(),
|
|
33
|
+
instructions: z.string().optional(),
|
|
34
|
+
dueDate: z.string().optional(),
|
|
35
|
+
files: z.array(fileSchema).optional(),
|
|
36
|
+
existingFileIds: z.array(z.string()).optional(),
|
|
37
|
+
removedAttachments: z.array(z.string()).optional(),
|
|
38
|
+
maxGrade: z.number().optional(),
|
|
39
|
+
graded: z.boolean().optional(),
|
|
40
|
+
weight: z.number().optional(),
|
|
41
|
+
sectionId: z.string().nullable().optional(),
|
|
42
|
+
type: z.enum(['HOMEWORK', 'QUIZ', 'TEST', 'PROJECT', 'ESSAY', 'DISCUSSION', 'PRESENTATION', 'LAB', 'OTHER']).optional(),
|
|
43
|
+
inProgress: z.boolean().optional(),
|
|
43
44
|
});
|
|
44
|
-
const deleteAssignmentSchema =
|
|
45
|
-
id:
|
|
46
|
-
classId:
|
|
45
|
+
const deleteAssignmentSchema = z.object({
|
|
46
|
+
id: z.string(),
|
|
47
|
+
classId: z.string(),
|
|
47
48
|
});
|
|
48
|
-
const getAssignmentSchema =
|
|
49
|
-
id:
|
|
50
|
-
classId:
|
|
49
|
+
const getAssignmentSchema = z.object({
|
|
50
|
+
id: z.string(),
|
|
51
|
+
classId: z.string(),
|
|
51
52
|
});
|
|
52
|
-
const submissionSchema =
|
|
53
|
-
assignmentId:
|
|
54
|
-
classId:
|
|
55
|
-
submissionId:
|
|
56
|
-
submit:
|
|
57
|
-
newAttachments:
|
|
58
|
-
|
|
53
|
+
const submissionSchema = z.object({
|
|
54
|
+
assignmentId: z.string(),
|
|
55
|
+
classId: z.string(),
|
|
56
|
+
submissionId: z.string(),
|
|
57
|
+
submit: z.boolean().optional(),
|
|
58
|
+
newAttachments: z.array(fileSchema).optional(),
|
|
59
|
+
existingFileIds: z.array(z.string()).optional(),
|
|
60
|
+
removedAttachments: z.array(z.string()).optional(),
|
|
59
61
|
});
|
|
60
|
-
const updateSubmissionSchema =
|
|
61
|
-
assignmentId:
|
|
62
|
-
classId:
|
|
63
|
-
submissionId:
|
|
64
|
-
return:
|
|
65
|
-
gradeReceived:
|
|
66
|
-
newAttachments:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
62
|
+
const updateSubmissionSchema = z.object({
|
|
63
|
+
assignmentId: z.string(),
|
|
64
|
+
classId: z.string(),
|
|
65
|
+
submissionId: z.string(),
|
|
66
|
+
return: z.boolean().optional(),
|
|
67
|
+
gradeReceived: z.number().nullable().optional(),
|
|
68
|
+
newAttachments: z.array(fileSchema).optional(),
|
|
69
|
+
existingFileIds: z.array(z.string()).optional(),
|
|
70
|
+
removedAttachments: z.array(z.string()).optional(),
|
|
71
|
+
rubricGrades: z.array(z.object({
|
|
72
|
+
criteriaId: z.string(),
|
|
73
|
+
selectedLevelId: z.string(),
|
|
74
|
+
points: z.number(),
|
|
75
|
+
comments: z.string(),
|
|
73
76
|
})).optional(),
|
|
74
77
|
});
|
|
75
|
-
|
|
76
|
-
create:
|
|
78
|
+
export const assignmentRouter = createTRPCRouter({
|
|
79
|
+
create: protectedProcedure
|
|
77
80
|
.input(createAssignmentSchema)
|
|
78
81
|
.mutation(async ({ ctx, input }) => {
|
|
79
|
-
const { classId, title, instructions, dueDate, files, maxGrade, graded, weight, sectionId, type, markSchemeId, gradingBoundaryId } = input;
|
|
82
|
+
const { classId, title, instructions, dueDate, files, existingFileIds, maxGrade, graded, weight, sectionId, type, markSchemeId, gradingBoundaryId, inProgress } = input;
|
|
80
83
|
if (!ctx.user) {
|
|
81
|
-
throw new
|
|
84
|
+
throw new TRPCError({
|
|
82
85
|
code: "UNAUTHORIZED",
|
|
83
86
|
message: "User must be authenticated",
|
|
84
87
|
});
|
|
85
88
|
}
|
|
86
89
|
// Get all students in the class
|
|
87
|
-
const classData = await
|
|
90
|
+
const classData = await prisma.class.findUnique({
|
|
88
91
|
where: { id: classId },
|
|
89
92
|
include: {
|
|
90
93
|
students: {
|
|
@@ -93,25 +96,29 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
93
96
|
}
|
|
94
97
|
});
|
|
95
98
|
if (!classData) {
|
|
96
|
-
throw new
|
|
99
|
+
throw new TRPCError({
|
|
97
100
|
code: "NOT_FOUND",
|
|
98
101
|
message: "Class not found",
|
|
99
102
|
});
|
|
100
103
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
104
|
+
let computedMaxGrade = maxGrade;
|
|
105
|
+
if (markSchemeId) {
|
|
106
|
+
const rubric = await prisma.markScheme.findUnique({
|
|
107
|
+
where: { id: markSchemeId },
|
|
108
|
+
select: {
|
|
109
|
+
structured: true,
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
const parsedRubric = JSON.parse(rubric?.structured || "{}");
|
|
113
|
+
// Calculate max grade from rubric criteria levels
|
|
114
|
+
computedMaxGrade = parsedRubric.criteria.reduce((acc, criterion) => {
|
|
115
|
+
const maxPoints = Math.max(...criterion.levels.map((level) => level.points));
|
|
116
|
+
return acc + maxPoints;
|
|
117
|
+
}, 0);
|
|
118
|
+
}
|
|
119
|
+
console.log(markSchemeId, gradingBoundaryId);
|
|
113
120
|
// Create assignment with submissions for all students
|
|
114
|
-
const assignment = await
|
|
121
|
+
const assignment = await prisma.assignment.create({
|
|
115
122
|
data: {
|
|
116
123
|
title,
|
|
117
124
|
instructions,
|
|
@@ -120,6 +127,7 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
120
127
|
graded,
|
|
121
128
|
weight,
|
|
122
129
|
type,
|
|
130
|
+
inProgress: inProgress || false,
|
|
123
131
|
class: {
|
|
124
132
|
connect: { id: classId }
|
|
125
133
|
},
|
|
@@ -189,11 +197,11 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
189
197
|
let uploadedFiles = [];
|
|
190
198
|
if (files && files.length > 0) {
|
|
191
199
|
// Store files in a class and assignment specific directory
|
|
192
|
-
uploadedFiles = await
|
|
200
|
+
uploadedFiles = await uploadFiles(files, ctx.user.id);
|
|
193
201
|
}
|
|
194
202
|
// Update assignment with new file attachments
|
|
195
203
|
if (uploadedFiles.length > 0) {
|
|
196
|
-
await
|
|
204
|
+
await prisma.assignment.update({
|
|
197
205
|
where: { id: assignment.id },
|
|
198
206
|
data: {
|
|
199
207
|
attachments: {
|
|
@@ -212,20 +220,31 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
212
220
|
}
|
|
213
221
|
});
|
|
214
222
|
}
|
|
223
|
+
// Connect existing files if provided
|
|
224
|
+
if (existingFileIds && existingFileIds.length > 0) {
|
|
225
|
+
await prisma.assignment.update({
|
|
226
|
+
where: { id: assignment.id },
|
|
227
|
+
data: {
|
|
228
|
+
attachments: {
|
|
229
|
+
connect: existingFileIds.map(fileId => ({ id: fileId }))
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
}
|
|
215
234
|
return assignment;
|
|
216
235
|
}),
|
|
217
|
-
update:
|
|
236
|
+
update: protectedProcedure
|
|
218
237
|
.input(updateAssignmentSchema)
|
|
219
238
|
.mutation(async ({ ctx, input }) => {
|
|
220
|
-
const { id, title, instructions, dueDate, files, maxGrade, graded, weight, sectionId, type } = input;
|
|
239
|
+
const { id, title, instructions, dueDate, files, existingFileIds, maxGrade, graded, weight, sectionId, type, inProgress } = input;
|
|
221
240
|
if (!ctx.user) {
|
|
222
|
-
throw new
|
|
241
|
+
throw new TRPCError({
|
|
223
242
|
code: "UNAUTHORIZED",
|
|
224
243
|
message: "User must be authenticated",
|
|
225
244
|
});
|
|
226
245
|
}
|
|
227
246
|
// Get the assignment with current attachments
|
|
228
|
-
const assignment = await
|
|
247
|
+
const assignment = await prisma.assignment.findFirst({
|
|
229
248
|
where: {
|
|
230
249
|
id,
|
|
231
250
|
teacherId: ctx.user.id,
|
|
@@ -236,6 +255,8 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
236
255
|
id: true,
|
|
237
256
|
name: true,
|
|
238
257
|
type: true,
|
|
258
|
+
path: true,
|
|
259
|
+
size: true,
|
|
239
260
|
thumbnail: {
|
|
240
261
|
select: {
|
|
241
262
|
path: true
|
|
@@ -252,7 +273,7 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
252
273
|
},
|
|
253
274
|
});
|
|
254
275
|
if (!assignment) {
|
|
255
|
-
throw new
|
|
276
|
+
throw new TRPCError({
|
|
256
277
|
code: "NOT_FOUND",
|
|
257
278
|
message: "Assignment not found",
|
|
258
279
|
});
|
|
@@ -261,10 +282,10 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
261
282
|
let uploadedFiles = [];
|
|
262
283
|
if (files && files.length > 0) {
|
|
263
284
|
// Store files in a class and assignment specific directory
|
|
264
|
-
uploadedFiles = await
|
|
285
|
+
uploadedFiles = await uploadFiles(files, ctx.user.id);
|
|
265
286
|
}
|
|
266
287
|
// Update assignment
|
|
267
|
-
const updatedAssignment = await
|
|
288
|
+
const updatedAssignment = await prisma.assignment.update({
|
|
268
289
|
where: { id },
|
|
269
290
|
data: {
|
|
270
291
|
...(title && { title }),
|
|
@@ -274,6 +295,7 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
274
295
|
...(graded !== undefined && { graded }),
|
|
275
296
|
...(weight && { weight }),
|
|
276
297
|
...(type && { type }),
|
|
298
|
+
...(inProgress !== undefined && { inProgress }),
|
|
277
299
|
...(sectionId !== undefined && {
|
|
278
300
|
section: sectionId ? {
|
|
279
301
|
connect: { id: sectionId }
|
|
@@ -296,6 +318,11 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
296
318
|
}))
|
|
297
319
|
}
|
|
298
320
|
}),
|
|
321
|
+
...(existingFileIds && existingFileIds.length > 0 && {
|
|
322
|
+
attachments: {
|
|
323
|
+
connect: existingFileIds.map(fileId => ({ id: fileId }))
|
|
324
|
+
}
|
|
325
|
+
}),
|
|
299
326
|
...(input.removedAttachments && input.removedAttachments.length > 0 && {
|
|
300
327
|
attachments: {
|
|
301
328
|
deleteMany: {
|
|
@@ -329,7 +356,11 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
329
356
|
id: true,
|
|
330
357
|
name: true,
|
|
331
358
|
type: true,
|
|
332
|
-
thumbnail: true
|
|
359
|
+
thumbnail: true,
|
|
360
|
+
size: true,
|
|
361
|
+
path: true,
|
|
362
|
+
uploadedAt: true,
|
|
363
|
+
thumbnailId: true,
|
|
333
364
|
}
|
|
334
365
|
},
|
|
335
366
|
section: true,
|
|
@@ -338,7 +369,7 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
338
369
|
}
|
|
339
370
|
});
|
|
340
371
|
if (assignment.markSchemeId) {
|
|
341
|
-
const rubric = await
|
|
372
|
+
const rubric = await prisma.markScheme.findUnique({
|
|
342
373
|
where: { id: assignment.markSchemeId },
|
|
343
374
|
select: {
|
|
344
375
|
structured: true,
|
|
@@ -349,7 +380,7 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
349
380
|
const maxPoints = Math.max(...criterion.levels.map((level) => level.points));
|
|
350
381
|
return acc + maxPoints;
|
|
351
382
|
}, 0);
|
|
352
|
-
await
|
|
383
|
+
await prisma.assignment.update({
|
|
353
384
|
where: { id },
|
|
354
385
|
data: {
|
|
355
386
|
maxGrade: computedMaxGrade,
|
|
@@ -358,18 +389,18 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
358
389
|
}
|
|
359
390
|
return updatedAssignment;
|
|
360
391
|
}),
|
|
361
|
-
delete:
|
|
392
|
+
delete: protectedProcedure
|
|
362
393
|
.input(deleteAssignmentSchema)
|
|
363
394
|
.mutation(async ({ ctx, input }) => {
|
|
364
395
|
const { id, classId } = input;
|
|
365
396
|
if (!ctx.user) {
|
|
366
|
-
throw new
|
|
397
|
+
throw new TRPCError({
|
|
367
398
|
code: "UNAUTHORIZED",
|
|
368
399
|
message: "User must be authenticated",
|
|
369
400
|
});
|
|
370
401
|
}
|
|
371
402
|
// Get the assignment with all related files
|
|
372
|
-
const assignment = await
|
|
403
|
+
const assignment = await prisma.assignment.findFirst({
|
|
373
404
|
where: {
|
|
374
405
|
id,
|
|
375
406
|
teacherId: ctx.user.id,
|
|
@@ -397,7 +428,7 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
397
428
|
}
|
|
398
429
|
});
|
|
399
430
|
if (!assignment) {
|
|
400
|
-
throw new
|
|
431
|
+
throw new TRPCError({
|
|
401
432
|
code: "NOT_FOUND",
|
|
402
433
|
message: "Assignment not found",
|
|
403
434
|
});
|
|
@@ -411,10 +442,10 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
411
442
|
await Promise.all(filesToDelete.map(async (file) => {
|
|
412
443
|
try {
|
|
413
444
|
// Delete the main file
|
|
414
|
-
await
|
|
445
|
+
await deleteFile(file.path);
|
|
415
446
|
// Delete thumbnail if it exists
|
|
416
447
|
if (file.thumbnail) {
|
|
417
|
-
await
|
|
448
|
+
await deleteFile(file.thumbnail.path);
|
|
418
449
|
}
|
|
419
450
|
}
|
|
420
451
|
catch (error) {
|
|
@@ -422,24 +453,24 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
422
453
|
}
|
|
423
454
|
}));
|
|
424
455
|
// Delete the assignment (this will cascade delete all related records)
|
|
425
|
-
await
|
|
456
|
+
await prisma.assignment.delete({
|
|
426
457
|
where: { id },
|
|
427
458
|
});
|
|
428
459
|
return {
|
|
429
460
|
id,
|
|
430
461
|
};
|
|
431
462
|
}),
|
|
432
|
-
get:
|
|
463
|
+
get: protectedProcedure
|
|
433
464
|
.input(getAssignmentSchema)
|
|
434
465
|
.query(async ({ ctx, input }) => {
|
|
435
466
|
const { id, classId } = input;
|
|
436
467
|
if (!ctx.user) {
|
|
437
|
-
throw new
|
|
468
|
+
throw new TRPCError({
|
|
438
469
|
code: "UNAUTHORIZED",
|
|
439
470
|
message: "User must be authenticated",
|
|
440
471
|
});
|
|
441
472
|
}
|
|
442
|
-
const assignment = await
|
|
473
|
+
const assignment = await prisma.assignment.findUnique({
|
|
443
474
|
where: {
|
|
444
475
|
id,
|
|
445
476
|
// classId,
|
|
@@ -462,6 +493,7 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
462
493
|
type: true,
|
|
463
494
|
size: true,
|
|
464
495
|
path: true,
|
|
496
|
+
uploadedAt: true,
|
|
465
497
|
thumbnailId: true,
|
|
466
498
|
}
|
|
467
499
|
},
|
|
@@ -508,12 +540,12 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
508
540
|
}
|
|
509
541
|
});
|
|
510
542
|
if (!assignment) {
|
|
511
|
-
throw new
|
|
543
|
+
throw new TRPCError({
|
|
512
544
|
code: "NOT_FOUND",
|
|
513
545
|
message: "Assignment not found",
|
|
514
546
|
});
|
|
515
547
|
}
|
|
516
|
-
const sections = await
|
|
548
|
+
const sections = await prisma.section.findMany({
|
|
517
549
|
where: {
|
|
518
550
|
classId: assignment.classId,
|
|
519
551
|
},
|
|
@@ -524,20 +556,20 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
524
556
|
});
|
|
525
557
|
return { ...assignment, sections };
|
|
526
558
|
}),
|
|
527
|
-
getSubmission:
|
|
528
|
-
.input(
|
|
529
|
-
assignmentId:
|
|
530
|
-
classId:
|
|
559
|
+
getSubmission: protectedClassMemberProcedure
|
|
560
|
+
.input(z.object({
|
|
561
|
+
assignmentId: z.string(),
|
|
562
|
+
classId: z.string(),
|
|
531
563
|
}))
|
|
532
564
|
.query(async ({ ctx, input }) => {
|
|
533
565
|
if (!ctx.user) {
|
|
534
|
-
throw new
|
|
566
|
+
throw new TRPCError({
|
|
535
567
|
code: "UNAUTHORIZED",
|
|
536
568
|
message: "User must be authenticated",
|
|
537
569
|
});
|
|
538
570
|
}
|
|
539
571
|
const { assignmentId } = input;
|
|
540
|
-
const submission = await
|
|
572
|
+
const submission = await prisma.submission.findFirst({
|
|
541
573
|
where: {
|
|
542
574
|
assignmentId,
|
|
543
575
|
studentId: ctx.user.id,
|
|
@@ -572,7 +604,7 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
572
604
|
});
|
|
573
605
|
if (!submission) {
|
|
574
606
|
// Create a new submission if it doesn't exist
|
|
575
|
-
return await
|
|
607
|
+
return await prisma.submission.create({
|
|
576
608
|
data: {
|
|
577
609
|
assignment: {
|
|
578
610
|
connect: { id: assignmentId },
|
|
@@ -615,20 +647,20 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
615
647
|
late: submission.assignment.dueDate < new Date(),
|
|
616
648
|
};
|
|
617
649
|
}),
|
|
618
|
-
getSubmissionById:
|
|
619
|
-
.input(
|
|
620
|
-
submissionId:
|
|
621
|
-
classId:
|
|
650
|
+
getSubmissionById: protectedTeacherProcedure
|
|
651
|
+
.input(z.object({
|
|
652
|
+
submissionId: z.string(),
|
|
653
|
+
classId: z.string(),
|
|
622
654
|
}))
|
|
623
655
|
.query(async ({ ctx, input }) => {
|
|
624
656
|
if (!ctx.user) {
|
|
625
|
-
throw new
|
|
657
|
+
throw new TRPCError({
|
|
626
658
|
code: "UNAUTHORIZED",
|
|
627
659
|
message: "User must be authenticated",
|
|
628
660
|
});
|
|
629
661
|
}
|
|
630
662
|
const { submissionId, classId } = input;
|
|
631
|
-
const submission = await
|
|
663
|
+
const submission = await prisma.submission.findFirst({
|
|
632
664
|
where: {
|
|
633
665
|
id: submissionId,
|
|
634
666
|
assignment: {
|
|
@@ -671,7 +703,7 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
671
703
|
},
|
|
672
704
|
});
|
|
673
705
|
if (!submission) {
|
|
674
|
-
throw new
|
|
706
|
+
throw new TRPCError({
|
|
675
707
|
code: "NOT_FOUND",
|
|
676
708
|
message: "Submission not found",
|
|
677
709
|
});
|
|
@@ -681,17 +713,17 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
681
713
|
late: submission.assignment.dueDate < new Date(),
|
|
682
714
|
};
|
|
683
715
|
}),
|
|
684
|
-
updateSubmission:
|
|
716
|
+
updateSubmission: protectedClassMemberProcedure
|
|
685
717
|
.input(submissionSchema)
|
|
686
718
|
.mutation(async ({ ctx, input }) => {
|
|
687
719
|
if (!ctx.user) {
|
|
688
|
-
throw new
|
|
720
|
+
throw new TRPCError({
|
|
689
721
|
code: "UNAUTHORIZED",
|
|
690
722
|
message: "User must be authenticated",
|
|
691
723
|
});
|
|
692
724
|
}
|
|
693
|
-
const { submissionId, submit, newAttachments, removedAttachments } = input;
|
|
694
|
-
const submission = await
|
|
725
|
+
const { submissionId, submit, newAttachments, existingFileIds, removedAttachments } = input;
|
|
726
|
+
const submission = await prisma.submission.findFirst({
|
|
695
727
|
where: {
|
|
696
728
|
id: submissionId,
|
|
697
729
|
OR: [
|
|
@@ -739,14 +771,14 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
739
771
|
},
|
|
740
772
|
});
|
|
741
773
|
if (!submission) {
|
|
742
|
-
throw new
|
|
774
|
+
throw new TRPCError({
|
|
743
775
|
code: "NOT_FOUND",
|
|
744
776
|
message: "Submission not found",
|
|
745
777
|
});
|
|
746
778
|
}
|
|
747
779
|
if (submit !== undefined) {
|
|
748
780
|
// Toggle submission status
|
|
749
|
-
return await
|
|
781
|
+
return await prisma.submission.update({
|
|
750
782
|
where: { id: submission.id },
|
|
751
783
|
data: {
|
|
752
784
|
submitted: !submission.submitted,
|
|
@@ -783,11 +815,11 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
783
815
|
let uploadedFiles = [];
|
|
784
816
|
if (newAttachments && newAttachments.length > 0) {
|
|
785
817
|
// Store files in a class and assignment specific directory
|
|
786
|
-
uploadedFiles = await
|
|
818
|
+
uploadedFiles = await uploadFiles(newAttachments, ctx.user.id);
|
|
787
819
|
}
|
|
788
820
|
// Update submission with new file attachments
|
|
789
821
|
if (uploadedFiles.length > 0) {
|
|
790
|
-
await
|
|
822
|
+
await prisma.submission.update({
|
|
791
823
|
where: { id: submission.id },
|
|
792
824
|
data: {
|
|
793
825
|
attachments: {
|
|
@@ -806,6 +838,17 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
806
838
|
}
|
|
807
839
|
});
|
|
808
840
|
}
|
|
841
|
+
// Connect existing files if provided
|
|
842
|
+
if (existingFileIds && existingFileIds.length > 0) {
|
|
843
|
+
await prisma.submission.update({
|
|
844
|
+
where: { id: submission.id },
|
|
845
|
+
data: {
|
|
846
|
+
attachments: {
|
|
847
|
+
connect: existingFileIds.map(fileId => ({ id: fileId }))
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
});
|
|
851
|
+
}
|
|
809
852
|
// Delete removed attachments if any
|
|
810
853
|
if (removedAttachments && removedAttachments.length > 0) {
|
|
811
854
|
const filesToDelete = submission.attachments.filter((file) => removedAttachments.includes(file.id));
|
|
@@ -813,10 +856,10 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
813
856
|
await Promise.all(filesToDelete.map(async (file) => {
|
|
814
857
|
try {
|
|
815
858
|
// Delete the main file
|
|
816
|
-
await
|
|
859
|
+
await deleteFile(file.path);
|
|
817
860
|
// Delete thumbnail if it exists
|
|
818
861
|
if (file.thumbnail?.path) {
|
|
819
|
-
await
|
|
862
|
+
await deleteFile(file.thumbnail.path);
|
|
820
863
|
}
|
|
821
864
|
}
|
|
822
865
|
catch (error) {
|
|
@@ -825,7 +868,7 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
825
868
|
}));
|
|
826
869
|
}
|
|
827
870
|
// Update submission with attachments
|
|
828
|
-
return await
|
|
871
|
+
return await prisma.submission.update({
|
|
829
872
|
where: { id: submission.id },
|
|
830
873
|
data: {
|
|
831
874
|
...(removedAttachments && removedAttachments.length > 0 && {
|
|
@@ -868,20 +911,20 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
868
911
|
},
|
|
869
912
|
});
|
|
870
913
|
}),
|
|
871
|
-
getSubmissions:
|
|
872
|
-
.input(
|
|
873
|
-
assignmentId:
|
|
874
|
-
classId:
|
|
914
|
+
getSubmissions: protectedTeacherProcedure
|
|
915
|
+
.input(z.object({
|
|
916
|
+
assignmentId: z.string(),
|
|
917
|
+
classId: z.string(),
|
|
875
918
|
}))
|
|
876
919
|
.query(async ({ ctx, input }) => {
|
|
877
920
|
if (!ctx.user) {
|
|
878
|
-
throw new
|
|
921
|
+
throw new TRPCError({
|
|
879
922
|
code: "UNAUTHORIZED",
|
|
880
923
|
message: "User must be authenticated",
|
|
881
924
|
});
|
|
882
925
|
}
|
|
883
926
|
const { assignmentId } = input;
|
|
884
|
-
const submissions = await
|
|
927
|
+
const submissions = await prisma.submission.findMany({
|
|
885
928
|
where: {
|
|
886
929
|
assignment: {
|
|
887
930
|
id: assignmentId,
|
|
@@ -928,17 +971,17 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
928
971
|
late: submission.assignment.dueDate < new Date(),
|
|
929
972
|
}));
|
|
930
973
|
}),
|
|
931
|
-
updateSubmissionAsTeacher:
|
|
974
|
+
updateSubmissionAsTeacher: protectedTeacherProcedure
|
|
932
975
|
.input(updateSubmissionSchema)
|
|
933
976
|
.mutation(async ({ ctx, input }) => {
|
|
934
977
|
if (!ctx.user) {
|
|
935
|
-
throw new
|
|
978
|
+
throw new TRPCError({
|
|
936
979
|
code: "UNAUTHORIZED",
|
|
937
980
|
message: "User must be authenticated",
|
|
938
981
|
});
|
|
939
982
|
}
|
|
940
|
-
const { submissionId, return: returnSubmission, gradeReceived, newAttachments, removedAttachments, rubricGrades } = input;
|
|
941
|
-
const submission = await
|
|
983
|
+
const { submissionId, return: returnSubmission, gradeReceived, newAttachments, existingFileIds, removedAttachments, rubricGrades } = input;
|
|
984
|
+
const submission = await prisma.submission.findFirst({
|
|
942
985
|
where: {
|
|
943
986
|
id: submissionId,
|
|
944
987
|
assignment: {
|
|
@@ -980,14 +1023,14 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
980
1023
|
},
|
|
981
1024
|
});
|
|
982
1025
|
if (!submission) {
|
|
983
|
-
throw new
|
|
1026
|
+
throw new TRPCError({
|
|
984
1027
|
code: "NOT_FOUND",
|
|
985
1028
|
message: "Submission not found",
|
|
986
1029
|
});
|
|
987
1030
|
}
|
|
988
1031
|
if (returnSubmission !== undefined) {
|
|
989
1032
|
// Toggle return status
|
|
990
|
-
return await
|
|
1033
|
+
return await prisma.submission.update({
|
|
991
1034
|
where: { id: submissionId },
|
|
992
1035
|
data: {
|
|
993
1036
|
returned: !submission.returned,
|
|
@@ -1023,11 +1066,11 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
1023
1066
|
let uploadedFiles = [];
|
|
1024
1067
|
if (newAttachments && newAttachments.length > 0) {
|
|
1025
1068
|
// Store files in a class and assignment specific directory
|
|
1026
|
-
uploadedFiles = await
|
|
1069
|
+
uploadedFiles = await uploadFiles(newAttachments, ctx.user.id);
|
|
1027
1070
|
}
|
|
1028
1071
|
// Update submission with new file attachments
|
|
1029
1072
|
if (uploadedFiles.length > 0) {
|
|
1030
|
-
await
|
|
1073
|
+
await prisma.submission.update({
|
|
1031
1074
|
where: { id: submission.id },
|
|
1032
1075
|
data: {
|
|
1033
1076
|
annotations: {
|
|
@@ -1046,6 +1089,17 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
1046
1089
|
}
|
|
1047
1090
|
});
|
|
1048
1091
|
}
|
|
1092
|
+
// Connect existing files if provided
|
|
1093
|
+
if (existingFileIds && existingFileIds.length > 0) {
|
|
1094
|
+
await prisma.submission.update({
|
|
1095
|
+
where: { id: submission.id },
|
|
1096
|
+
data: {
|
|
1097
|
+
annotations: {
|
|
1098
|
+
connect: existingFileIds.map(fileId => ({ id: fileId }))
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1049
1103
|
// Delete removed attachments if any
|
|
1050
1104
|
if (removedAttachments && removedAttachments.length > 0) {
|
|
1051
1105
|
const filesToDelete = submission.annotations.filter((file) => removedAttachments.includes(file.id));
|
|
@@ -1053,10 +1107,10 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
1053
1107
|
await Promise.all(filesToDelete.map(async (file) => {
|
|
1054
1108
|
try {
|
|
1055
1109
|
// Delete the main file
|
|
1056
|
-
await
|
|
1110
|
+
await deleteFile(file.path);
|
|
1057
1111
|
// Delete thumbnail if it exists
|
|
1058
1112
|
if (file.thumbnail?.path) {
|
|
1059
|
-
await
|
|
1113
|
+
await deleteFile(file.thumbnail.path);
|
|
1060
1114
|
}
|
|
1061
1115
|
}
|
|
1062
1116
|
catch (error) {
|
|
@@ -1065,7 +1119,7 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
1065
1119
|
}));
|
|
1066
1120
|
}
|
|
1067
1121
|
// Update submission with grade and attachments
|
|
1068
|
-
return await
|
|
1122
|
+
return await prisma.submission.update({
|
|
1069
1123
|
where: { id: submissionId },
|
|
1070
1124
|
data: {
|
|
1071
1125
|
...(gradeReceived !== undefined && { gradeReceived }),
|
|
@@ -1115,21 +1169,21 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
1115
1169
|
},
|
|
1116
1170
|
});
|
|
1117
1171
|
}),
|
|
1118
|
-
attachToEvent:
|
|
1119
|
-
.input(
|
|
1120
|
-
assignmentId:
|
|
1121
|
-
eventId:
|
|
1172
|
+
attachToEvent: protectedTeacherProcedure
|
|
1173
|
+
.input(z.object({
|
|
1174
|
+
assignmentId: z.string(),
|
|
1175
|
+
eventId: z.string(),
|
|
1122
1176
|
}))
|
|
1123
1177
|
.mutation(async ({ ctx, input }) => {
|
|
1124
1178
|
if (!ctx.user) {
|
|
1125
|
-
throw new
|
|
1179
|
+
throw new TRPCError({
|
|
1126
1180
|
code: "UNAUTHORIZED",
|
|
1127
1181
|
message: "User must be authenticated",
|
|
1128
1182
|
});
|
|
1129
1183
|
}
|
|
1130
1184
|
const { assignmentId, eventId } = input;
|
|
1131
1185
|
// Check if assignment exists and user is a teacher of the class
|
|
1132
|
-
const assignment = await
|
|
1186
|
+
const assignment = await prisma.assignment.findFirst({
|
|
1133
1187
|
where: {
|
|
1134
1188
|
id: assignmentId,
|
|
1135
1189
|
class: {
|
|
@@ -1143,26 +1197,26 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
1143
1197
|
},
|
|
1144
1198
|
});
|
|
1145
1199
|
if (!assignment) {
|
|
1146
|
-
throw new
|
|
1200
|
+
throw new TRPCError({
|
|
1147
1201
|
code: "NOT_FOUND",
|
|
1148
1202
|
message: "Assignment not found or you are not authorized",
|
|
1149
1203
|
});
|
|
1150
1204
|
}
|
|
1151
1205
|
// Check if event exists and belongs to the same class
|
|
1152
|
-
const event = await
|
|
1206
|
+
const event = await prisma.event.findFirst({
|
|
1153
1207
|
where: {
|
|
1154
1208
|
id: eventId,
|
|
1155
1209
|
classId: assignment.classId,
|
|
1156
1210
|
},
|
|
1157
1211
|
});
|
|
1158
1212
|
if (!event) {
|
|
1159
|
-
throw new
|
|
1213
|
+
throw new TRPCError({
|
|
1160
1214
|
code: "NOT_FOUND",
|
|
1161
1215
|
message: "Event not found or does not belong to the same class",
|
|
1162
1216
|
});
|
|
1163
1217
|
}
|
|
1164
1218
|
// Attach assignment to event
|
|
1165
|
-
const updatedAssignment = await
|
|
1219
|
+
const updatedAssignment = await prisma.assignment.update({
|
|
1166
1220
|
where: { id: assignmentId },
|
|
1167
1221
|
data: {
|
|
1168
1222
|
eventAttached: {
|
|
@@ -1201,20 +1255,20 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
1201
1255
|
});
|
|
1202
1256
|
return { assignment: updatedAssignment };
|
|
1203
1257
|
}),
|
|
1204
|
-
detachEvent:
|
|
1205
|
-
.input(
|
|
1206
|
-
assignmentId:
|
|
1258
|
+
detachEvent: protectedTeacherProcedure
|
|
1259
|
+
.input(z.object({
|
|
1260
|
+
assignmentId: z.string(),
|
|
1207
1261
|
}))
|
|
1208
1262
|
.mutation(async ({ ctx, input }) => {
|
|
1209
1263
|
if (!ctx.user) {
|
|
1210
|
-
throw new
|
|
1264
|
+
throw new TRPCError({
|
|
1211
1265
|
code: "UNAUTHORIZED",
|
|
1212
1266
|
message: "User must be authenticated",
|
|
1213
1267
|
});
|
|
1214
1268
|
}
|
|
1215
1269
|
const { assignmentId } = input;
|
|
1216
1270
|
// Check if assignment exists and user is a teacher of the class
|
|
1217
|
-
const assignment = await
|
|
1271
|
+
const assignment = await prisma.assignment.findFirst({
|
|
1218
1272
|
where: {
|
|
1219
1273
|
id: assignmentId,
|
|
1220
1274
|
class: {
|
|
@@ -1225,13 +1279,13 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
1225
1279
|
},
|
|
1226
1280
|
});
|
|
1227
1281
|
if (!assignment) {
|
|
1228
|
-
throw new
|
|
1282
|
+
throw new TRPCError({
|
|
1229
1283
|
code: "NOT_FOUND",
|
|
1230
1284
|
message: "Assignment not found or you are not authorized",
|
|
1231
1285
|
});
|
|
1232
1286
|
}
|
|
1233
1287
|
// Detach assignment from event
|
|
1234
|
-
const updatedAssignment = await
|
|
1288
|
+
const updatedAssignment = await prisma.assignment.update({
|
|
1235
1289
|
where: { id: assignmentId },
|
|
1236
1290
|
data: {
|
|
1237
1291
|
eventAttached: {
|
|
@@ -1270,20 +1324,20 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
1270
1324
|
});
|
|
1271
1325
|
return { assignment: updatedAssignment };
|
|
1272
1326
|
}),
|
|
1273
|
-
getAvailableEvents:
|
|
1274
|
-
.input(
|
|
1275
|
-
assignmentId:
|
|
1327
|
+
getAvailableEvents: protectedTeacherProcedure
|
|
1328
|
+
.input(z.object({
|
|
1329
|
+
assignmentId: z.string(),
|
|
1276
1330
|
}))
|
|
1277
1331
|
.query(async ({ ctx, input }) => {
|
|
1278
1332
|
if (!ctx.user) {
|
|
1279
|
-
throw new
|
|
1333
|
+
throw new TRPCError({
|
|
1280
1334
|
code: "UNAUTHORIZED",
|
|
1281
1335
|
message: "User must be authenticated",
|
|
1282
1336
|
});
|
|
1283
1337
|
}
|
|
1284
1338
|
const { assignmentId } = input;
|
|
1285
1339
|
// Get the assignment to find the class
|
|
1286
|
-
const assignment = await
|
|
1340
|
+
const assignment = await prisma.assignment.findFirst({
|
|
1287
1341
|
where: {
|
|
1288
1342
|
id: assignmentId,
|
|
1289
1343
|
class: {
|
|
@@ -1295,13 +1349,13 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
1295
1349
|
select: { classId: true }
|
|
1296
1350
|
});
|
|
1297
1351
|
if (!assignment) {
|
|
1298
|
-
throw new
|
|
1352
|
+
throw new TRPCError({
|
|
1299
1353
|
code: "NOT_FOUND",
|
|
1300
1354
|
message: "Assignment not found or you are not authorized",
|
|
1301
1355
|
});
|
|
1302
1356
|
}
|
|
1303
1357
|
// Get all events for the class that don't already have this assignment attached
|
|
1304
|
-
const events = await
|
|
1358
|
+
const events = await prisma.event.findMany({
|
|
1305
1359
|
where: {
|
|
1306
1360
|
classId: assignment.classId,
|
|
1307
1361
|
assignmentsAttached: {
|
|
@@ -1324,15 +1378,15 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
1324
1378
|
});
|
|
1325
1379
|
return { events };
|
|
1326
1380
|
}),
|
|
1327
|
-
dueToday:
|
|
1381
|
+
dueToday: protectedProcedure
|
|
1328
1382
|
.query(async ({ ctx }) => {
|
|
1329
1383
|
if (!ctx.user) {
|
|
1330
|
-
throw new
|
|
1384
|
+
throw new TRPCError({
|
|
1331
1385
|
code: "UNAUTHORIZED",
|
|
1332
1386
|
message: "User must be authenticated",
|
|
1333
1387
|
});
|
|
1334
1388
|
}
|
|
1335
|
-
const assignments = await
|
|
1389
|
+
const assignments = await prisma.assignment.findMany({
|
|
1336
1390
|
where: {
|
|
1337
1391
|
dueDate: {
|
|
1338
1392
|
equals: new Date(),
|
|
@@ -1358,39 +1412,39 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
1358
1412
|
dueDate: assignment.dueDate.toISOString(),
|
|
1359
1413
|
}));
|
|
1360
1414
|
}),
|
|
1361
|
-
attachMarkScheme:
|
|
1362
|
-
.input(
|
|
1363
|
-
assignmentId:
|
|
1364
|
-
markSchemeId:
|
|
1415
|
+
attachMarkScheme: protectedTeacherProcedure
|
|
1416
|
+
.input(z.object({
|
|
1417
|
+
assignmentId: z.string(),
|
|
1418
|
+
markSchemeId: z.string().nullable(),
|
|
1365
1419
|
}))
|
|
1366
1420
|
.mutation(async ({ ctx, input }) => {
|
|
1367
1421
|
const { assignmentId, markSchemeId } = input;
|
|
1368
|
-
const assignment = await
|
|
1422
|
+
const assignment = await prisma.assignment.findFirst({
|
|
1369
1423
|
where: {
|
|
1370
1424
|
id: assignmentId,
|
|
1371
1425
|
},
|
|
1372
1426
|
});
|
|
1373
1427
|
if (!assignment) {
|
|
1374
|
-
throw new
|
|
1428
|
+
throw new TRPCError({
|
|
1375
1429
|
code: "NOT_FOUND",
|
|
1376
1430
|
message: "Assignment not found",
|
|
1377
1431
|
});
|
|
1378
1432
|
}
|
|
1379
1433
|
// If markSchemeId is provided, verify it exists
|
|
1380
1434
|
if (markSchemeId) {
|
|
1381
|
-
const markScheme = await
|
|
1435
|
+
const markScheme = await prisma.markScheme.findFirst({
|
|
1382
1436
|
where: {
|
|
1383
1437
|
id: markSchemeId,
|
|
1384
1438
|
},
|
|
1385
1439
|
});
|
|
1386
1440
|
if (!markScheme) {
|
|
1387
|
-
throw new
|
|
1441
|
+
throw new TRPCError({
|
|
1388
1442
|
code: "NOT_FOUND",
|
|
1389
1443
|
message: "Mark scheme not found",
|
|
1390
1444
|
});
|
|
1391
1445
|
}
|
|
1392
1446
|
}
|
|
1393
|
-
const updatedAssignment = await
|
|
1447
|
+
const updatedAssignment = await prisma.assignment.update({
|
|
1394
1448
|
where: { id: assignmentId },
|
|
1395
1449
|
data: {
|
|
1396
1450
|
markScheme: markSchemeId ? {
|
|
@@ -1409,24 +1463,24 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
1409
1463
|
});
|
|
1410
1464
|
return updatedAssignment;
|
|
1411
1465
|
}),
|
|
1412
|
-
detachMarkScheme:
|
|
1413
|
-
.input(
|
|
1414
|
-
assignmentId:
|
|
1466
|
+
detachMarkScheme: protectedTeacherProcedure
|
|
1467
|
+
.input(z.object({
|
|
1468
|
+
assignmentId: z.string(),
|
|
1415
1469
|
}))
|
|
1416
1470
|
.mutation(async ({ ctx, input }) => {
|
|
1417
1471
|
const { assignmentId } = input;
|
|
1418
|
-
const assignment = await
|
|
1472
|
+
const assignment = await prisma.assignment.findFirst({
|
|
1419
1473
|
where: {
|
|
1420
1474
|
id: assignmentId,
|
|
1421
1475
|
},
|
|
1422
1476
|
});
|
|
1423
1477
|
if (!assignment) {
|
|
1424
|
-
throw new
|
|
1478
|
+
throw new TRPCError({
|
|
1425
1479
|
code: "NOT_FOUND",
|
|
1426
1480
|
message: "Assignment not found",
|
|
1427
1481
|
});
|
|
1428
1482
|
}
|
|
1429
|
-
const updatedAssignment = await
|
|
1483
|
+
const updatedAssignment = await prisma.assignment.update({
|
|
1430
1484
|
where: { id: assignmentId },
|
|
1431
1485
|
data: {
|
|
1432
1486
|
markScheme: {
|
|
@@ -1443,39 +1497,39 @@ exports.assignmentRouter = (0, trpc_1.createTRPCRouter)({
|
|
|
1443
1497
|
});
|
|
1444
1498
|
return updatedAssignment;
|
|
1445
1499
|
}),
|
|
1446
|
-
attachGradingBoundary:
|
|
1447
|
-
.input(
|
|
1448
|
-
assignmentId:
|
|
1449
|
-
gradingBoundaryId:
|
|
1500
|
+
attachGradingBoundary: protectedTeacherProcedure
|
|
1501
|
+
.input(z.object({
|
|
1502
|
+
assignmentId: z.string(),
|
|
1503
|
+
gradingBoundaryId: z.string().nullable(),
|
|
1450
1504
|
}))
|
|
1451
1505
|
.mutation(async ({ ctx, input }) => {
|
|
1452
1506
|
const { assignmentId, gradingBoundaryId } = input;
|
|
1453
|
-
const assignment = await
|
|
1507
|
+
const assignment = await prisma.assignment.findFirst({
|
|
1454
1508
|
where: {
|
|
1455
1509
|
id: assignmentId,
|
|
1456
1510
|
},
|
|
1457
1511
|
});
|
|
1458
1512
|
if (!assignment) {
|
|
1459
|
-
throw new
|
|
1513
|
+
throw new TRPCError({
|
|
1460
1514
|
code: "NOT_FOUND",
|
|
1461
1515
|
message: "Assignment not found",
|
|
1462
1516
|
});
|
|
1463
1517
|
}
|
|
1464
1518
|
// If gradingBoundaryId is provided, verify it exists
|
|
1465
1519
|
if (gradingBoundaryId) {
|
|
1466
|
-
const gradingBoundary = await
|
|
1520
|
+
const gradingBoundary = await prisma.gradingBoundary.findFirst({
|
|
1467
1521
|
where: {
|
|
1468
1522
|
id: gradingBoundaryId,
|
|
1469
1523
|
},
|
|
1470
1524
|
});
|
|
1471
1525
|
if (!gradingBoundary) {
|
|
1472
|
-
throw new
|
|
1526
|
+
throw new TRPCError({
|
|
1473
1527
|
code: "NOT_FOUND",
|
|
1474
1528
|
message: "Grading boundary not found",
|
|
1475
1529
|
});
|
|
1476
1530
|
}
|
|
1477
1531
|
}
|
|
1478
|
-
const updatedAssignment = await
|
|
1532
|
+
const updatedAssignment = await prisma.assignment.update({
|
|
1479
1533
|
where: { id: assignmentId },
|
|
1480
1534
|
data: {
|
|
1481
1535
|
gradingBoundary: gradingBoundaryId ? {
|