@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.
Files changed (115) hide show
  1. package/API_SPECIFICATION.md +1461 -0
  2. package/dist/exportType.d.ts +3 -3
  3. package/dist/exportType.d.ts.map +1 -1
  4. package/dist/exportType.js +1 -2
  5. package/dist/index.js +25 -30
  6. package/dist/lib/fileUpload.d.ts.map +1 -1
  7. package/dist/lib/fileUpload.js +31 -29
  8. package/dist/lib/googleCloudStorage.js +9 -14
  9. package/dist/lib/prisma.js +4 -7
  10. package/dist/lib/thumbnailGenerator.js +12 -20
  11. package/dist/middleware/auth.d.ts.map +1 -1
  12. package/dist/middleware/auth.js +17 -22
  13. package/dist/middleware/logging.js +5 -9
  14. package/dist/routers/_app.d.ts +3619 -1937
  15. package/dist/routers/_app.d.ts.map +1 -1
  16. package/dist/routers/_app.js +28 -27
  17. package/dist/routers/agenda.d.ts +14 -9
  18. package/dist/routers/agenda.d.ts.map +1 -1
  19. package/dist/routers/agenda.js +14 -17
  20. package/dist/routers/announcement.d.ts +5 -4
  21. package/dist/routers/announcement.d.ts.map +1 -1
  22. package/dist/routers/announcement.js +28 -31
  23. package/dist/routers/assignment.d.ts +283 -197
  24. package/dist/routers/assignment.d.ts.map +1 -1
  25. package/dist/routers/assignment.js +256 -202
  26. package/dist/routers/attendance.d.ts +6 -5
  27. package/dist/routers/attendance.d.ts.map +1 -1
  28. package/dist/routers/attendance.js +31 -34
  29. package/dist/routers/auth.d.ts +2 -1
  30. package/dist/routers/auth.d.ts.map +1 -1
  31. package/dist/routers/auth.js +80 -75
  32. package/dist/routers/class.d.ts +285 -15
  33. package/dist/routers/class.d.ts.map +1 -1
  34. package/dist/routers/class.js +440 -164
  35. package/dist/routers/event.d.ts +48 -39
  36. package/dist/routers/event.d.ts.map +1 -1
  37. package/dist/routers/event.js +76 -79
  38. package/dist/routers/file.d.ts +72 -2
  39. package/dist/routers/file.d.ts.map +1 -1
  40. package/dist/routers/file.js +260 -32
  41. package/dist/routers/folder.d.ts +296 -0
  42. package/dist/routers/folder.d.ts.map +1 -0
  43. package/dist/routers/folder.js +693 -0
  44. package/dist/routers/notifications.d.ts +103 -0
  45. package/dist/routers/notifications.d.ts.map +1 -0
  46. package/dist/routers/notifications.js +91 -0
  47. package/dist/routers/school.d.ts +208 -0
  48. package/dist/routers/school.d.ts.map +1 -0
  49. package/dist/routers/school.js +481 -0
  50. package/dist/routers/section.d.ts +2 -1
  51. package/dist/routers/section.d.ts.map +1 -1
  52. package/dist/routers/section.js +30 -33
  53. package/dist/routers/user.d.ts +3 -2
  54. package/dist/routers/user.d.ts.map +1 -1
  55. package/dist/routers/user.js +21 -24
  56. package/dist/seedDatabase.d.ts +22 -0
  57. package/dist/seedDatabase.d.ts.map +1 -0
  58. package/dist/seedDatabase.js +75 -0
  59. package/dist/socket/handlers.js +26 -30
  60. package/dist/trpc.d.ts +5 -0
  61. package/dist/trpc.d.ts.map +1 -1
  62. package/dist/trpc.js +35 -26
  63. package/dist/types/trpc.d.ts +1 -1
  64. package/dist/types/trpc.d.ts.map +1 -1
  65. package/dist/types/trpc.js +1 -2
  66. package/dist/utils/email.js +2 -8
  67. package/dist/utils/generateInviteCode.js +1 -5
  68. package/dist/utils/logger.d.ts.map +1 -1
  69. package/dist/utils/logger.js +13 -9
  70. package/dist/utils/prismaErrorHandler.d.ts +9 -0
  71. package/dist/utils/prismaErrorHandler.d.ts.map +1 -0
  72. package/dist/utils/prismaErrorHandler.js +234 -0
  73. package/dist/utils/prismaWrapper.d.ts +14 -0
  74. package/dist/utils/prismaWrapper.d.ts.map +1 -0
  75. package/dist/utils/prismaWrapper.js +64 -0
  76. package/package.json +12 -4
  77. package/prisma/migrations/20250807062924_init/migration.sql +436 -0
  78. package/prisma/migrations/migration_lock.toml +3 -0
  79. package/prisma/schema.prisma +68 -1
  80. package/src/exportType.ts +3 -3
  81. package/src/index.ts +6 -6
  82. package/src/lib/fileUpload.ts +19 -10
  83. package/src/lib/thumbnailGenerator.ts +2 -2
  84. package/src/middleware/auth.ts +2 -4
  85. package/src/middleware/logging.ts +2 -2
  86. package/src/routers/_app.ts +17 -13
  87. package/src/routers/agenda.ts +2 -2
  88. package/src/routers/announcement.ts +2 -2
  89. package/src/routers/assignment.ts +86 -26
  90. package/src/routers/attendance.ts +2 -2
  91. package/src/routers/auth.ts +83 -57
  92. package/src/routers/class.ts +339 -39
  93. package/src/routers/event.ts +2 -2
  94. package/src/routers/file.ts +276 -21
  95. package/src/routers/folder.ts +755 -0
  96. package/src/routers/notifications.ts +93 -0
  97. package/src/routers/section.ts +2 -2
  98. package/src/routers/user.ts +3 -3
  99. package/src/seedDatabase.ts +88 -0
  100. package/src/socket/handlers.ts +5 -5
  101. package/src/trpc.ts +17 -4
  102. package/src/types/trpc.ts +1 -1
  103. package/src/utils/logger.ts +14 -4
  104. package/src/utils/prismaErrorHandler.ts +275 -0
  105. package/src/utils/prismaWrapper.ts +91 -0
  106. package/tests/auth.test.ts +25 -0
  107. package/tests/class.test.ts +281 -0
  108. package/tests/setup.ts +98 -0
  109. package/tests/startup.test.ts +5 -0
  110. package/tsconfig.json +2 -1
  111. package/vitest.config.ts +11 -0
  112. package/dist/logger.d.ts +0 -26
  113. package/dist/logger.d.ts.map +0 -1
  114. package/dist/logger.js +0 -135
  115. package/src/logger.ts +0 -163
@@ -1,16 +1,13 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.classRouter = void 0;
4
- const zod_1 = require("zod");
5
- const trpc_1 = require("../trpc");
6
- const prisma_1 = require("../lib/prisma");
7
- const server_1 = require("@trpc/server");
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
- prisma_1.prisma.class.findMany({
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
- equals: new Date(new Date().setHours(0, 0, 0, 0)),
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
- prisma_1.prisma.class.findMany({
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
- equals: new Date(new Date().setHours(0, 0, 0, 0)),
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
- color: cls.color,
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: trpc_1.protectedProcedure
108
- .input(zod_1.z.object({
109
- classId: zod_1.z.string(),
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 prisma_1.prisma.class.findUnique({
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 prisma_1.prisma.section.findMany({
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: trpc_1.protectedTeacherProcedure
213
- .input(zod_1.z.object({
214
- classId: zod_1.z.string(),
215
- name: zod_1.z.string().optional(),
216
- section: zod_1.z.string().optional(),
217
- subject: zod_1.z.string().optional(),
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 prisma_1.prisma.class.update({
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: trpc_1.protectedProcedure
238
- .input(zod_1.z.object({
239
- students: zod_1.z.array(zod_1.z.string()).optional(),
240
- teachers: zod_1.z.array(zod_1.z.string()).optional(),
241
- name: zod_1.z.string(),
242
- section: zod_1.z.string(),
243
- subject: zod_1.z.string(),
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 prisma_1.prisma.class.create({
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 prisma_1.prisma.class.create({
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: trpc_1.protectedTeacherProcedure
282
- .input(zod_1.z.object({
283
- classId: zod_1.z.string(),
284
- id: zod_1.z.string(),
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 prisma_1.prisma.class.findFirst({
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 prisma_1.prisma.class.delete({
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: trpc_1.protectedTeacherProcedure
308
- .input(zod_1.z.object({
309
- classId: zod_1.z.string(),
310
- studentId: zod_1.z.string(),
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 prisma_1.prisma.user.findUnique({
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 prisma_1.prisma.class.update({
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: trpc_1.protectedTeacherProcedure
346
- .input(zod_1.z.object({
347
- classId: zod_1.z.string(),
348
- userId: zod_1.z.string(),
349
- type: zod_1.z.enum(['teacher', 'student']),
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 prisma_1.prisma.user.findUnique({
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 prisma_1.prisma.class.update({
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: trpc_1.protectedTeacherProcedure
383
- .input(zod_1.z.object({
384
- classId: zod_1.z.string(),
385
- userId: zod_1.z.string(),
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 prisma_1.prisma.class.update({
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: trpc_1.protectedProcedure
406
- .input(zod_1.z.object({
407
- classCode: zod_1.z.string(),
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 prisma_1.prisma.session.findFirst({
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 prisma_1.prisma.class.update({
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: trpc_1.protectedTeacherProcedure
441
- .input(zod_1.z.object({
442
- classId: zod_1.z.string(),
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 prisma_1.prisma.session.findFirst({
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 prisma_1.prisma.session.create({
430
+ const newSession = await prisma.session.create({
453
431
  data: {
454
- id: (0, generateInviteCode_1.generateInviteCode)(),
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: trpc_1.protectedTeacherProcedure
468
- .input(zod_1.z.object({
469
- classId: zod_1.z.string(),
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 prisma_1.prisma.session.create({
457
+ const session = await prisma.session.create({
475
458
  data: {
476
- id: (0, generateInviteCode_1.generateInviteCode)(),
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: trpc_1.protectedClassMemberProcedure
486
- .input(zod_1.z.object({
487
- classId: zod_1.z.string(),
488
- userId: zod_1.z.string(),
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 prisma_1.prisma.class.findFirst({
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 server_1.TRPCError({
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 prisma_1.prisma.submission.findMany({
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: trpc_1.protectedTeacherProcedure
531
- .input(zod_1.z.object({
532
- classId: zod_1.z.string(),
533
- assignmentId: zod_1.z.string(),
534
- submissionId: zod_1.z.string(),
535
- gradeReceived: zod_1.z.number().nullable(),
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 prisma_1.prisma.submission.update({
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: trpc_1.protectedTeacherProcedure
562
- .input(zod_1.z.object({
563
- classId: zod_1.z.string(),
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 prisma_1.prisma.event.findMany({
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: trpc_1.protectedTeacherProcedure
582
- .input(zod_1.z.object({
583
- classId: zod_1.z.string(),
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 prisma_1.prisma.markScheme.findMany({
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: trpc_1.protectedTeacherProcedure
595
- .input(zod_1.z.object({
596
- classId: zod_1.z.string(),
597
- structure: zod_1.z.string(),
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 prisma_1.prisma.markScheme.create({
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: trpc_1.protectedTeacherProcedure
611
- .input(zod_1.z.object({
612
- classId: zod_1.z.string(),
613
- markSchemeId: zod_1.z.string(),
614
- structure: zod_1.z.string(),
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 prisma_1.prisma.markScheme.update({
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: trpc_1.protectedTeacherProcedure
626
- .input(zod_1.z.object({
627
- classId: zod_1.z.string(),
628
- markSchemeId: zod_1.z.string(),
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 prisma_1.prisma.markScheme.delete({
615
+ const markScheme = await prisma.markScheme.delete({
633
616
  where: { id: markSchemeId },
634
617
  });
635
618
  return markScheme;
636
619
  }),
637
- listGradingBoundaries: trpc_1.protectedTeacherProcedure
638
- .input(zod_1.z.object({
639
- classId: zod_1.z.string(),
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 prisma_1.prisma.gradingBoundary.findMany({
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: trpc_1.protectedTeacherProcedure
651
- .input(zod_1.z.object({
652
- classId: zod_1.z.string(),
653
- structure: zod_1.z.string(),
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 prisma_1.prisma.gradingBoundary.create({
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: trpc_1.protectedTeacherProcedure
667
- .input(zod_1.z.object({
668
- classId: zod_1.z.string(),
669
- gradingBoundaryId: zod_1.z.string(),
670
- structure: zod_1.z.string(),
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 prisma_1.prisma.gradingBoundary.update({
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: trpc_1.protectedTeacherProcedure
682
- .input(zod_1.z.object({
683
- classId: zod_1.z.string(),
684
- gradingBoundaryId: zod_1.z.string(),
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 prisma_1.prisma.gradingBoundary.delete({
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
  });