@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,8 +1,8 @@
1
1
  import { TRPCError } from "@trpc/server";
2
2
  import { v4 as uuidv4 } from "uuid";
3
- import { uploadFile as uploadToGCS, getSignedUrl } from "./googleCloudStorage";
4
- import { generateThumbnail, storeThumbnail, generateMediaThumbnail } from "./thumbnailGenerator";
5
- import { prisma } from "./prisma";
3
+ import { uploadFile as uploadToGCS, getSignedUrl } from "./googleCloudStorage.js";
4
+ import { generateThumbnail, storeThumbnail, generateMediaThumbnail } from "./thumbnailGenerator.js";
5
+ import { prisma } from "./prisma.js";
6
6
 
7
7
  export interface FileData {
8
8
  name: string;
@@ -39,22 +39,22 @@ export async function uploadFile(
39
39
  const fileExtension = file.name.split('.').pop();
40
40
  const uniqueFilename = `${uuidv4()}.${fileExtension}`;
41
41
 
42
- // Construct the full path
42
+ // // Construct the full path
43
43
  const filePath = directory
44
44
  ? `${directory}/${uniqueFilename}`
45
45
  : uniqueFilename;
46
46
 
47
- // Upload to Google Cloud Storage
47
+ // // Upload to Google Cloud Storage
48
48
  const uploadedPath = await uploadToGCS(file.data, filePath, file.type);
49
49
 
50
- // Generate and store thumbnail if supported
50
+ // // Generate and store thumbnail if supported
51
51
  let thumbnailId: string | undefined;
52
52
  try {
53
- // Convert base64 to buffer for thumbnail generation
53
+ // // Convert base64 to buffer for thumbnail generation
54
54
  const base64Data = file.data.split(',')[1];
55
55
  const fileBuffer = Buffer.from(base64Data, 'base64');
56
56
 
57
- // Generate thumbnail directly from buffer
57
+ // // Generate thumbnail directly from buffer
58
58
  const thumbnailBuffer = await generateMediaThumbnail(fileBuffer, file.type);
59
59
  if (thumbnailBuffer) {
60
60
  // Store thumbnail in a thumbnails directory
@@ -65,9 +65,10 @@ export async function uploadFile(
65
65
  // Create thumbnail file record
66
66
  const thumbnailFile = await prisma.file.create({
67
67
  data: {
68
- name: `${file.name}_thumb.jpg`,
68
+ name: `${file.name}_thumb.jpg${Math.random()}`,
69
69
  type: 'image/jpeg',
70
70
  path: thumbnailPath,
71
+ // path: '/dummyPath' + Math.random().toString(36).substring(2, 15),
71
72
  user: {
72
73
  connect: { id: userId }
73
74
  }
@@ -81,6 +82,8 @@ export async function uploadFile(
81
82
  }
82
83
 
83
84
  // Create file record in database
85
+
86
+ // const uploadedPath = '/dummyPath' + Math.random().toString(36).substring(2, 15);
84
87
  const fileRecord = await prisma.file.create({
85
88
  data: {
86
89
  name: file.name,
@@ -90,6 +93,11 @@ export async function uploadFile(
90
93
  user: {
91
94
  connect: { id: userId }
92
95
  },
96
+ ...(directory && {
97
+ folder: {
98
+ connect: {id: directory},
99
+ },
100
+ }),
93
101
  ...(thumbnailId && {
94
102
  thumbnail: {
95
103
  connect: { id: thumbnailId }
@@ -112,7 +120,8 @@ export async function uploadFile(
112
120
  path: uploadedPath,
113
121
  thumbnailId: thumbnailId
114
122
  };
115
- } catch (error) {
123
+ }
124
+ catch (error) {
116
125
  console.error('Error uploading file:', error);
117
126
  throw new TRPCError({
118
127
  code: 'INTERNAL_SERVER_ERROR',
@@ -1,6 +1,6 @@
1
1
  import sharp from 'sharp';
2
- import { prisma } from './prisma';
3
- import { uploadFile, deleteFile, getSignedUrl } from './googleCloudStorage';
2
+ import { prisma } from './prisma.js';
3
+ import { uploadFile, deleteFile, getSignedUrl } from './googleCloudStorage.js';
4
4
 
5
5
  // Thumbnail size configuration
6
6
  const THUMBNAIL_WIDTH = 200;
@@ -1,6 +1,6 @@
1
1
  import { TRPCError } from '@trpc/server';
2
- import { prisma } from '../lib/prisma';
3
- import type { MiddlewareContext } from '../types/trpc';
2
+ import { prisma } from '../lib/prisma.js';
3
+ import type { MiddlewareContext } from '../types/trpc.js';
4
4
 
5
5
  export const createAuthMiddleware = (t: any) => {
6
6
 
@@ -43,8 +43,6 @@ export const createAuthMiddleware = (t: any) => {
43
43
  });
44
44
  }
45
45
 
46
- console.log(user)
47
-
48
46
  return next({
49
47
  ctx: {
50
48
  ...ctx,
@@ -1,5 +1,5 @@
1
- import type { MiddlewareContext } from '../types/trpc';
2
- import { logger } from '../utils/logger';
1
+ import type { MiddlewareContext } from '../types/trpc.js';
2
+ import { logger } from '../utils/logger.js';
3
3
 
4
4
  export const createLoggingMiddleware = (t: any) => {
5
5
  return t.middleware(async ({ path, type, next, ctx }: MiddlewareContext) => {
@@ -1,16 +1,18 @@
1
- import { createTRPCRouter } from "../trpc";
2
- import { classRouter } from "./class";
3
- import { announcementRouter } from "./announcement";
4
- import { assignmentRouter } from "./assignment";
5
- import { userRouter } from "./user";
6
- import { createCallerFactory } from "../trpc";
1
+ import { createTRPCRouter } from "../trpc.js";
2
+ import { classRouter } from "./class.js";
3
+ import { announcementRouter } from "./announcement.js";
4
+ import { assignmentRouter } from "./assignment.js";
5
+ import { userRouter } from "./user.js";
6
+ import { createCallerFactory } from "../trpc.js";
7
7
  import type { inferRouterInputs, inferRouterOutputs } from "@trpc/server";
8
- import { sectionRouter } from "./section";
9
- import { attendanceRouter } from "./attendance";
10
- import { eventRouter } from "./event";
11
- import { authRouter } from "./auth";
12
- import { agendaRouter } from "./agenda";
13
- import { fileRouter } from "./file";
8
+ import { sectionRouter } from "./section.js";
9
+ import { attendanceRouter } from "./attendance.js";
10
+ import { eventRouter } from "./event.js";
11
+ import { authRouter } from "./auth.js";
12
+ import { agendaRouter } from "./agenda.js";
13
+ import { fileRouter } from "./file.js";
14
+ import { folderRouter } from "./folder.js";
15
+ import { notificationRouter } from "./notifications.js";
14
16
 
15
17
  export const appRouter = createTRPCRouter({
16
18
  class: classRouter,
@@ -23,7 +25,9 @@ export const appRouter = createTRPCRouter({
23
25
  auth: authRouter,
24
26
  agenda: agendaRouter,
25
27
  file: fileRouter,
26
- });
28
+ folder: folderRouter,
29
+ notification: notificationRouter,
30
+ });
27
31
 
28
32
  // Export type router type definition
29
33
  export type AppRouter = typeof appRouter;
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
- import { createTRPCRouter, protectedProcedure } from "../trpc";
3
- import { prisma } from "../lib/prisma";
2
+ import { createTRPCRouter, protectedProcedure } from "../trpc.js";
3
+ import { prisma } from "../lib/prisma.js";
4
4
  import { TRPCError } from "@trpc/server";
5
5
  import { addDays, startOfDay, endOfDay } from "date-fns";
6
6
 
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
- import { createTRPCRouter, protectedClassMemberProcedure, protectedTeacherProcedure, protectedProcedure } from "../trpc";
3
- import { prisma } from "../lib/prisma";
2
+ import { createTRPCRouter, protectedClassMemberProcedure, protectedTeacherProcedure, protectedProcedure } from "../trpc.js";
3
+ import { prisma } from "../lib/prisma.js";
4
4
  import { TRPCError } from "@trpc/server";
5
5
 
6
6
  const AnnouncementSelect = {
@@ -1,9 +1,9 @@
1
1
  import { z } from "zod";
2
- import { createTRPCRouter, protectedProcedure, protectedClassMemberProcedure, protectedTeacherProcedure } from "../trpc";
2
+ import { createTRPCRouter, protectedProcedure, protectedClassMemberProcedure, protectedTeacherProcedure } from "../trpc.js";
3
3
  import { TRPCError } from "@trpc/server";
4
- import { prisma } from "../lib/prisma";
5
- import { uploadFiles, type UploadedFile } from "../lib/fileUpload";
6
- import { deleteFile } from "../lib/googleCloudStorage";
4
+ import { prisma } from "../lib/prisma.js";
5
+ import { uploadFiles, type UploadedFile } from "../lib/fileUpload.js";
6
+ import { deleteFile } from "../lib/googleCloudStorage.js";
7
7
 
8
8
  const fileSchema = z.object({
9
9
  name: z.string(),
@@ -18,6 +18,7 @@ const createAssignmentSchema = z.object({
18
18
  instructions: z.string(),
19
19
  dueDate: z.string(),
20
20
  files: z.array(fileSchema).optional(),
21
+ existingFileIds: z.array(z.string()).optional(),
21
22
  maxGrade: z.number().optional(),
22
23
  graded: z.boolean().optional(),
23
24
  weight: z.number().optional(),
@@ -25,6 +26,7 @@ const createAssignmentSchema = z.object({
25
26
  type: z.enum(['HOMEWORK', 'QUIZ', 'TEST', 'PROJECT', 'ESSAY', 'DISCUSSION', 'PRESENTATION', 'LAB', 'OTHER']).optional(),
26
27
  markSchemeId: z.string().optional(),
27
28
  gradingBoundaryId: z.string().optional(),
29
+ inProgress: z.boolean().optional(),
28
30
  });
29
31
 
30
32
  const updateAssignmentSchema = z.object({
@@ -34,12 +36,14 @@ const updateAssignmentSchema = z.object({
34
36
  instructions: z.string().optional(),
35
37
  dueDate: z.string().optional(),
36
38
  files: z.array(fileSchema).optional(),
39
+ existingFileIds: z.array(z.string()).optional(),
37
40
  removedAttachments: z.array(z.string()).optional(),
38
41
  maxGrade: z.number().optional(),
39
42
  graded: z.boolean().optional(),
40
43
  weight: z.number().optional(),
41
44
  sectionId: z.string().nullable().optional(),
42
45
  type: z.enum(['HOMEWORK', 'QUIZ', 'TEST', 'PROJECT', 'ESSAY', 'DISCUSSION', 'PRESENTATION', 'LAB', 'OTHER']).optional(),
46
+ inProgress: z.boolean().optional(),
43
47
  });
44
48
 
45
49
  const deleteAssignmentSchema = z.object({
@@ -58,6 +62,7 @@ const submissionSchema = z.object({
58
62
  submissionId: z.string(),
59
63
  submit: z.boolean().optional(),
60
64
  newAttachments: z.array(fileSchema).optional(),
65
+ existingFileIds: z.array(z.string()).optional(),
61
66
  removedAttachments: z.array(z.string()).optional(),
62
67
  });
63
68
 
@@ -68,6 +73,7 @@ const updateSubmissionSchema = z.object({
68
73
  return: z.boolean().optional(),
69
74
  gradeReceived: z.number().nullable().optional(),
70
75
  newAttachments: z.array(fileSchema).optional(),
76
+ existingFileIds: z.array(z.string()).optional(),
71
77
  removedAttachments: z.array(z.string()).optional(),
72
78
  rubricGrades: z.array(z.object({
73
79
  criteriaId: z.string(),
@@ -81,7 +87,7 @@ export const assignmentRouter = createTRPCRouter({
81
87
  create: protectedProcedure
82
88
  .input(createAssignmentSchema)
83
89
  .mutation(async ({ ctx, input }) => {
84
- const { classId, title, instructions, dueDate, files, maxGrade, graded, weight, sectionId, type, markSchemeId, gradingBoundaryId } = input;
90
+ const { classId, title, instructions, dueDate, files, existingFileIds, maxGrade, graded, weight, sectionId, type, markSchemeId, gradingBoundaryId, inProgress } = input;
85
91
 
86
92
  if (!ctx.user) {
87
93
  throw new TRPCError({
@@ -107,20 +113,24 @@ export const assignmentRouter = createTRPCRouter({
107
113
  });
108
114
  }
109
115
 
110
- const rubric = await prisma.markScheme.findUnique({
111
- where: { id: markSchemeId },
112
- select: {
113
- structured: true,
114
- }
115
- });
116
+ let computedMaxGrade = maxGrade;
117
+ if (markSchemeId) {
118
+ const rubric = await prisma.markScheme.findUnique({
119
+ where: { id: markSchemeId },
120
+ select: {
121
+ structured: true,
122
+ }
123
+ });
116
124
 
117
- const parsedRubric = JSON.parse(rubric?.structured || "{}");
125
+ const parsedRubric = JSON.parse(rubric?.structured || "{}");
118
126
 
119
- // Calculate max grade from rubric criteria levels
120
- const computedMaxGrade = parsedRubric.criteria.reduce((acc: number, criterion: any) => {
121
- const maxPoints = Math.max(...criterion.levels.map((level: any) => level.points));
122
- return acc + maxPoints;
123
- }, 0);
127
+ // Calculate max grade from rubric criteria levels
128
+ computedMaxGrade = parsedRubric.criteria.reduce((acc: number, criterion: any) => {
129
+ const maxPoints = Math.max(...criterion.levels.map((level: any) => level.points));
130
+ return acc + maxPoints;
131
+ }, 0);
132
+ }
133
+ console.log(markSchemeId, gradingBoundaryId);
124
134
 
125
135
  // Create assignment with submissions for all students
126
136
  const assignment = await prisma.assignment.create({
@@ -132,6 +142,7 @@ export const assignmentRouter = createTRPCRouter({
132
142
  graded,
133
143
  weight,
134
144
  type,
145
+ inProgress: inProgress || false,
135
146
  class: {
136
147
  connect: { id: classId }
137
148
  },
@@ -197,11 +208,12 @@ export const assignmentRouter = createTRPCRouter({
197
208
  }
198
209
  }
199
210
  });
211
+
200
212
  // Upload files if provided
201
213
  let uploadedFiles: UploadedFile[] = [];
202
214
  if (files && files.length > 0) {
203
215
  // Store files in a class and assignment specific directory
204
- uploadedFiles = await uploadFiles(files, ctx.user.id, `class/${classId}/assignment/${assignment.id}`);
216
+ uploadedFiles = await uploadFiles(files, ctx.user.id);
205
217
  }
206
218
 
207
219
  // Update assignment with new file attachments
@@ -226,12 +238,24 @@ export const assignmentRouter = createTRPCRouter({
226
238
  });
227
239
  }
228
240
 
241
+ // Connect existing files if provided
242
+ if (existingFileIds && existingFileIds.length > 0) {
243
+ await prisma.assignment.update({
244
+ where: { id: assignment.id },
245
+ data: {
246
+ attachments: {
247
+ connect: existingFileIds.map(fileId => ({ id: fileId }))
248
+ }
249
+ }
250
+ });
251
+ }
252
+
229
253
  return assignment;
230
254
  }),
231
255
  update: protectedProcedure
232
256
  .input(updateAssignmentSchema)
233
257
  .mutation(async ({ ctx, input }) => {
234
- const { id, title, instructions, dueDate, files, maxGrade, graded, weight, sectionId, type } = input;
258
+ const { id, title, instructions, dueDate, files, existingFileIds, maxGrade, graded, weight, sectionId, type, inProgress } = input;
235
259
 
236
260
  if (!ctx.user) {
237
261
  throw new TRPCError({
@@ -252,6 +276,8 @@ export const assignmentRouter = createTRPCRouter({
252
276
  id: true,
253
277
  name: true,
254
278
  type: true,
279
+ path: true,
280
+ size: true,
255
281
  thumbnail: {
256
282
  select: {
257
283
  path: true
@@ -279,7 +305,7 @@ export const assignmentRouter = createTRPCRouter({
279
305
  let uploadedFiles: UploadedFile[] = [];
280
306
  if (files && files.length > 0) {
281
307
  // Store files in a class and assignment specific directory
282
- uploadedFiles = await uploadFiles(files, ctx.user.id, `class/${assignment.classId}/assignment/${id}`);
308
+ uploadedFiles = await uploadFiles(files, ctx.user.id);
283
309
  }
284
310
 
285
311
  // Update assignment
@@ -293,6 +319,7 @@ export const assignmentRouter = createTRPCRouter({
293
319
  ...(graded !== undefined && { graded }),
294
320
  ...(weight && { weight }),
295
321
  ...(type && { type }),
322
+ ...(inProgress !== undefined && { inProgress }),
296
323
  ...(sectionId !== undefined && {
297
324
  section: sectionId ? {
298
325
  connect: { id: sectionId }
@@ -315,6 +342,11 @@ export const assignmentRouter = createTRPCRouter({
315
342
  }))
316
343
  }
317
344
  }),
345
+ ...(existingFileIds && existingFileIds.length > 0 && {
346
+ attachments: {
347
+ connect: existingFileIds.map(fileId => ({ id: fileId }))
348
+ }
349
+ }),
318
350
  ...(input.removedAttachments && input.removedAttachments.length > 0 && {
319
351
  attachments: {
320
352
  deleteMany: {
@@ -348,7 +380,11 @@ export const assignmentRouter = createTRPCRouter({
348
380
  id: true,
349
381
  name: true,
350
382
  type: true,
351
- thumbnail: true
383
+ thumbnail: true,
384
+ size: true,
385
+ path: true,
386
+ uploadedAt: true,
387
+ thumbnailId: true,
352
388
  }
353
389
  },
354
390
  section: true,
@@ -497,6 +533,7 @@ export const assignmentRouter = createTRPCRouter({
497
533
  type: true,
498
534
  size: true,
499
535
  path: true,
536
+ uploadedAt: true,
500
537
  thumbnailId: true,
501
538
  }
502
539
  },
@@ -740,7 +777,7 @@ export const assignmentRouter = createTRPCRouter({
740
777
  });
741
778
  }
742
779
 
743
- const { submissionId, submit, newAttachments, removedAttachments } = input;
780
+ const { submissionId, submit, newAttachments, existingFileIds, removedAttachments } = input;
744
781
 
745
782
  const submission = await prisma.submission.findFirst({
746
783
  where: {
@@ -837,7 +874,7 @@ export const assignmentRouter = createTRPCRouter({
837
874
  let uploadedFiles: UploadedFile[] = [];
838
875
  if (newAttachments && newAttachments.length > 0) {
839
876
  // Store files in a class and assignment specific directory
840
- uploadedFiles = await uploadFiles(newAttachments, ctx.user.id, `class/${submission.assignment.classId}/assignment/${submission.assignmentId}/submission/${submission.id}`);
877
+ uploadedFiles = await uploadFiles(newAttachments, ctx.user.id);
841
878
  }
842
879
 
843
880
  // Update submission with new file attachments
@@ -862,6 +899,18 @@ export const assignmentRouter = createTRPCRouter({
862
899
  });
863
900
  }
864
901
 
902
+ // Connect existing files if provided
903
+ if (existingFileIds && existingFileIds.length > 0) {
904
+ await prisma.submission.update({
905
+ where: { id: submission.id },
906
+ data: {
907
+ attachments: {
908
+ connect: existingFileIds.map(fileId => ({ id: fileId }))
909
+ }
910
+ }
911
+ });
912
+ }
913
+
865
914
  // Delete removed attachments if any
866
915
  if (removedAttachments && removedAttachments.length > 0) {
867
916
  const filesToDelete = submission.attachments.filter((file) =>
@@ -1003,7 +1052,7 @@ export const assignmentRouter = createTRPCRouter({
1003
1052
  });
1004
1053
  }
1005
1054
 
1006
- const { submissionId, return: returnSubmission, gradeReceived, newAttachments, removedAttachments, rubricGrades } = input;
1055
+ const { submissionId, return: returnSubmission, gradeReceived, newAttachments, existingFileIds, removedAttachments, rubricGrades } = input;
1007
1056
 
1008
1057
  const submission = await prisma.submission.findFirst({
1009
1058
  where: {
@@ -1093,7 +1142,7 @@ export const assignmentRouter = createTRPCRouter({
1093
1142
  let uploadedFiles: UploadedFile[] = [];
1094
1143
  if (newAttachments && newAttachments.length > 0) {
1095
1144
  // Store files in a class and assignment specific directory
1096
- uploadedFiles = await uploadFiles(newAttachments, ctx.user.id, `class/${submission.assignment.classId}/assignment/${submission.assignmentId}/submission/${submission.id}/annotations`);
1145
+ uploadedFiles = await uploadFiles(newAttachments, ctx.user.id);
1097
1146
  }
1098
1147
 
1099
1148
  // Update submission with new file attachments
@@ -1118,6 +1167,18 @@ export const assignmentRouter = createTRPCRouter({
1118
1167
  });
1119
1168
  }
1120
1169
 
1170
+ // Connect existing files if provided
1171
+ if (existingFileIds && existingFileIds.length > 0) {
1172
+ await prisma.submission.update({
1173
+ where: { id: submission.id },
1174
+ data: {
1175
+ annotations: {
1176
+ connect: existingFileIds.map(fileId => ({ id: fileId }))
1177
+ }
1178
+ }
1179
+ });
1180
+ }
1181
+
1121
1182
  // Delete removed attachments if any
1122
1183
  if (removedAttachments && removedAttachments.length > 0) {
1123
1184
  const filesToDelete = submission.annotations.filter((file) =>
@@ -1552,7 +1613,6 @@ export const assignmentRouter = createTRPCRouter({
1552
1613
 
1553
1614
  return updatedAssignment;
1554
1615
  }),
1555
-
1556
1616
  attachGradingBoundary: protectedTeacherProcedure
1557
1617
  .input(z.object({
1558
1618
  assignmentId: z.string(),
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
- import { createTRPCRouter, protectedProcedure } from "../trpc";
2
+ import { createTRPCRouter, protectedProcedure } from "../trpc.js";
3
3
  import { TRPCError } from "@trpc/server";
4
- import { prisma } from "../lib/prisma";
4
+ import { prisma } from "../lib/prisma.js";
5
5
 
6
6
  const attendanceSchema = z.object({
7
7
  eventId: z.string().optional(),
@@ -1,10 +1,11 @@
1
1
  import { z } from "zod";
2
- import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";
2
+ import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc.js";
3
3
  import { TRPCError } from "@trpc/server";
4
- import { prisma } from "../lib/prisma";
4
+ import { prisma } from "../lib/prisma.js";
5
5
  import { v4 as uuidv4 } from 'uuid';
6
6
  import { compare, hash } from "bcryptjs";
7
- import { transport } from "../utils/email";
7
+ import { transport } from "../utils/email.js";
8
+ import { prismaWrapper } from "../utils/prismaWrapper.js";
8
9
 
9
10
  const loginSchema = z.object({
10
11
  username: z.string(),
@@ -28,20 +29,23 @@ export const authRouter = createTRPCRouter({
28
29
  const { username, email, password } = input;
29
30
 
30
31
  // Check if username already exists
31
- const existingUser = await prisma.user.findFirst({
32
- where: {
33
- OR: [
34
- { username },
35
- { email }
36
- ]
37
- },
38
- select: {
39
- id: true,
40
- username: true,
41
- email: true,
42
- verified: true,
43
- }
44
- });
32
+ const existingUser = await prismaWrapper.findFirst(
33
+ () => prisma.user.findFirst({
34
+ where: {
35
+ OR: [
36
+ { username },
37
+ { email }
38
+ ]
39
+ },
40
+ select: {
41
+ id: true,
42
+ username: true,
43
+ email: true,
44
+ verified: true,
45
+ }
46
+ }),
47
+ 'checking for existing user during registration'
48
+ );
45
49
 
46
50
  if (existingUser && existingUser.verified) {
47
51
  if (existingUser.username === username) {
@@ -57,44 +61,59 @@ export const authRouter = createTRPCRouter({
57
61
  });
58
62
  }
59
63
  } else if (existingUser && !existingUser.verified) {
60
- await prisma.session.deleteMany({
61
- where: { userId: existingUser.id },
62
- });
63
-
64
- await prisma.user.delete({
65
- where: { id: existingUser.id },
66
- });
64
+ await prismaWrapper.deleteMany(
65
+ () => prisma.session.deleteMany({
66
+ where: { userId: existingUser.id },
67
+ }),
68
+ 'deleting existing sessions for unverified user'
69
+ );
70
+
71
+ await prismaWrapper.delete(
72
+ () => prisma.user.delete({
73
+ where: { id: existingUser.id },
74
+ }),
75
+ 'deleting unverified user'
76
+ );
67
77
  }
68
78
 
69
79
  // Create new user
70
- const user = await prisma.user.create({
71
- data: {
72
- username,
73
- email,
74
- password: await hash(password, 10),
75
- profile: {},
76
- },
77
- select: {
78
- id: true,
79
- username: true,
80
- email: true,
81
- }
82
- });
80
+ const user = await prismaWrapper.create(
81
+ async () => await prisma.user.create({
82
+ data: {
83
+ username,
84
+ email,
85
+ password: await hash(password, 10),
86
+ profile: {},
87
+ verified: true, // temporary
88
+ },
89
+ select: {
90
+ id: true,
91
+ username: true,
92
+ email: true,
93
+ }
94
+ }),
95
+ 'creating new user during registration'
96
+ );
83
97
 
84
- const verificationToken = await prisma.session.create({
85
- data: {
86
- id: uuidv4(),
87
- userId: user.id,
88
- expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30),
89
- },
90
- });
98
+ const verificationToken = await prismaWrapper.create(
99
+ () => prisma.session.create({
100
+ data: {
101
+ id: uuidv4(),
102
+ userId: user.id,
103
+ expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30),
104
+ },
105
+ }),
106
+ 'creating verification token'
107
+ );
91
108
 
92
- await transport.sendMail({
93
- from: 'noreply@studious.sh',
94
- to: user.email,
95
- subject: 'Verify your email',
96
- text: `Click the link to verify your email: ${process.env.NEXT_PUBLIC_APP_URL}/verify/${verificationToken.id}`,
97
- });
109
+ // await transport.sendMail({
110
+ // from: 'noreply@studious.sh',
111
+ // to: user.email,
112
+ // subject: 'Verify your email',
113
+ // text: `Click the link to verify your email: ${process.env.NEXT_PUBLIC_APP_URL}/verify/${verificationToken.id}`,
114
+ // });
115
+
116
+ console.log(`${process.env.NEXT_PUBLIC_APP_URL}/verify/${verificationToken.id}`)
98
117
 
99
118
  return {
100
119
  user: {
@@ -127,7 +146,7 @@ export const authRouter = createTRPCRouter({
127
146
  });
128
147
  }
129
148
 
130
- if (await compare(password, user.password)) {
149
+ if (!(await compare(password, user.password))) {
131
150
  throw new TRPCError({
132
151
  code: "UNAUTHORIZED",
133
152
  message: "Invalid username or password",
@@ -218,6 +237,8 @@ export const authRouter = createTRPCRouter({
218
237
  select: {
219
238
  id: true,
220
239
  email: true,
240
+ role: true,
241
+ schoolId: true,
221
242
  },
222
243
  });
223
244
 
@@ -240,12 +261,12 @@ export const authRouter = createTRPCRouter({
240
261
  },
241
262
  });
242
263
 
243
- await transport.sendMail({
244
- from: 'noreply@studious.sh',
245
- to: user.email,
246
- subject: 'Verify your email',
247
- text: `Click the link to verify your email: ${process.env.NEXT_PUBLIC_APP_URL}/verify/${verificationToken.id}`,
248
- });
264
+ // await transport.sendMail({
265
+ // from: 'noreply@studious.sh',
266
+ // to: user.email,
267
+ // subject: 'Verify your email',
268
+ // text: `Click the link to verify your email: ${process.env.NEXT_PUBLIC_APP_URL}/verify/${verificationToken.id}`,
269
+ // });
249
270
 
250
271
  return { success: true };
251
272
  }),
@@ -281,6 +302,11 @@ export const authRouter = createTRPCRouter({
281
302
  },
282
303
  });
283
304
 
305
+ // Clean up the verification token
306
+ await prisma.session.delete({
307
+ where: { id: token },
308
+ });
309
+
284
310
  return { success: true };
285
311
  }),
286
312
  });