@studious-lms/server 1.4.9 → 1.6.0

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 (237) hide show
  1. package/dist/index.js +15 -3
  2. package/dist/index.js.map +1 -1
  3. package/dist/lib/config/cors.d.ts.map +1 -1
  4. package/dist/lib/config/cors.js +2 -3
  5. package/dist/lib/config/cors.js.map +1 -1
  6. package/dist/lib/fileUpload.d.ts.map +1 -1
  7. package/dist/lib/fileUpload.js +4 -5
  8. package/dist/lib/fileUpload.js.map +1 -1
  9. package/dist/lib/pusher.d.ts +7 -1
  10. package/dist/lib/pusher.d.ts.map +1 -1
  11. package/dist/lib/pusher.js +9 -3
  12. package/dist/lib/pusher.js.map +1 -1
  13. package/dist/models/attendance.d.ts +16 -7
  14. package/dist/models/attendance.d.ts.map +1 -1
  15. package/dist/models/attendance.js +17 -2
  16. package/dist/models/attendance.js.map +1 -1
  17. package/dist/models/auth.d.ts +1 -0
  18. package/dist/models/auth.d.ts.map +1 -1
  19. package/dist/models/auth.js +3 -2
  20. package/dist/models/auth.js.map +1 -1
  21. package/dist/models/comment.d.ts +0 -5
  22. package/dist/models/comment.d.ts.map +1 -1
  23. package/dist/models/conversation.d.ts +17 -16
  24. package/dist/models/conversation.d.ts.map +1 -1
  25. package/dist/models/labChat.d.ts +2 -1
  26. package/dist/models/labChat.d.ts.map +1 -1
  27. package/dist/models/message.d.ts +6 -3
  28. package/dist/models/message.d.ts.map +1 -1
  29. package/dist/models/school.d.ts +0 -22
  30. package/dist/models/school.d.ts.map +1 -1
  31. package/dist/models/school.js +2 -14
  32. package/dist/models/school.js.map +1 -1
  33. package/dist/models/worksheet.d.ts +76 -19
  34. package/dist/models/worksheet.d.ts.map +1 -1
  35. package/dist/models/worksheet.js +2 -2
  36. package/dist/models/worksheet.js.map +1 -1
  37. package/dist/pipelines/aiLabChat.d.ts +20 -9
  38. package/dist/pipelines/aiLabChat.d.ts.map +1 -1
  39. package/dist/pipelines/aiLabChat.js +63 -75
  40. package/dist/pipelines/aiLabChat.js.map +1 -1
  41. package/dist/pipelines/aiNewtonChat.d.ts +13 -4
  42. package/dist/pipelines/aiNewtonChat.d.ts.map +1 -1
  43. package/dist/pipelines/aiNewtonChat.js +39 -27
  44. package/dist/pipelines/aiNewtonChat.js.map +1 -1
  45. package/dist/pipelines/gradeWorksheet.d.ts +5 -3
  46. package/dist/pipelines/gradeWorksheet.d.ts.map +1 -1
  47. package/dist/pipelines/gradeWorksheet.js +60 -24
  48. package/dist/pipelines/gradeWorksheet.js.map +1 -1
  49. package/dist/pipelines/pdfToWorksheet.d.ts +34 -2
  50. package/dist/pipelines/pdfToWorksheet.d.ts.map +1 -1
  51. package/dist/pipelines/pdfToWorksheet.js +205 -32
  52. package/dist/pipelines/pdfToWorksheet.js.map +1 -1
  53. package/dist/routers/_app.d.ts +106 -48
  54. package/dist/routers/_app.d.ts.map +1 -1
  55. package/dist/routers/announcement.d.ts +0 -4
  56. package/dist/routers/announcement.d.ts.map +1 -1
  57. package/dist/routers/assignment.d.ts +0 -8
  58. package/dist/routers/assignment.d.ts.map +1 -1
  59. package/dist/routers/assignment.js +64 -4
  60. package/dist/routers/assignment.js.map +1 -1
  61. package/dist/routers/auth.d.ts +1 -0
  62. package/dist/routers/auth.d.ts.map +1 -1
  63. package/dist/routers/comment.d.ts +0 -3
  64. package/dist/routers/comment.d.ts.map +1 -1
  65. package/dist/routers/conversation.d.ts +2 -1
  66. package/dist/routers/conversation.d.ts.map +1 -1
  67. package/dist/routers/labChat.d.ts +3 -1
  68. package/dist/routers/labChat.d.ts.map +1 -1
  69. package/dist/routers/message.d.ts +3 -0
  70. package/dist/routers/message.d.ts.map +1 -1
  71. package/dist/routers/newtonChat.d.ts +1 -0
  72. package/dist/routers/newtonChat.d.ts.map +1 -1
  73. package/dist/routers/school.d.ts +6 -0
  74. package/dist/routers/school.d.ts.map +1 -1
  75. package/dist/routers/school.js +7 -2
  76. package/dist/routers/school.js.map +1 -1
  77. package/dist/routers/worksheet.d.ts +37 -7
  78. package/dist/routers/worksheet.d.ts.map +1 -1
  79. package/dist/routers/worksheet.js +16 -6
  80. package/dist/routers/worksheet.js.map +1 -1
  81. package/dist/services/announcement.js +4 -4
  82. package/dist/services/announcement.js.map +1 -1
  83. package/dist/services/assignment.d.ts.map +1 -1
  84. package/dist/services/assignment.js +16 -6
  85. package/dist/services/assignment.js.map +1 -1
  86. package/dist/services/attendance.d.ts.map +1 -1
  87. package/dist/services/attendance.js +68 -4
  88. package/dist/services/attendance.js.map +1 -1
  89. package/dist/services/auth.d.ts +1 -0
  90. package/dist/services/auth.d.ts.map +1 -1
  91. package/dist/services/comment.d.ts +0 -3
  92. package/dist/services/comment.d.ts.map +1 -1
  93. package/dist/services/conversation.d.ts +2 -1
  94. package/dist/services/conversation.d.ts.map +1 -1
  95. package/dist/services/labChat.d.ts +10 -2
  96. package/dist/services/labChat.d.ts.map +1 -1
  97. package/dist/services/labChat.js +103 -44
  98. package/dist/services/labChat.js.map +1 -1
  99. package/dist/services/message.d.ts +3 -0
  100. package/dist/services/message.d.ts.map +1 -1
  101. package/dist/services/message.js +20 -2
  102. package/dist/services/message.js.map +1 -1
  103. package/dist/services/newtonChat.d.ts +1 -0
  104. package/dist/services/newtonChat.d.ts.map +1 -1
  105. package/dist/services/newtonChat.js +40 -17
  106. package/dist/services/newtonChat.js.map +1 -1
  107. package/dist/services/school.d.ts +6 -0
  108. package/dist/services/school.d.ts.map +1 -1
  109. package/dist/services/school.js +71 -6
  110. package/dist/services/school.js.map +1 -1
  111. package/dist/services/worksheet.d.ts +48 -11
  112. package/dist/services/worksheet.d.ts.map +1 -1
  113. package/dist/services/worksheet.js +73 -29
  114. package/dist/services/worksheet.js.map +1 -1
  115. package/dist/utils/generateUsername.d.ts +8 -0
  116. package/dist/utils/generateUsername.d.ts.map +1 -0
  117. package/dist/utils/generateUsername.js +103 -0
  118. package/dist/utils/generateUsername.js.map +1 -0
  119. package/dist/utils/inference.d.ts +50 -1
  120. package/dist/utils/inference.d.ts.map +1 -1
  121. package/dist/utils/inference.js +167 -2
  122. package/dist/utils/inference.js.map +1 -1
  123. package/dist/workers/connection.d.ts +3 -0
  124. package/dist/workers/connection.d.ts.map +1 -0
  125. package/dist/workers/connection.js +33 -0
  126. package/dist/workers/connection.js.map +1 -0
  127. package/dist/workers/index.d.ts +10 -0
  128. package/dist/workers/index.d.ts.map +1 -0
  129. package/dist/workers/index.js +63 -0
  130. package/dist/workers/index.js.map +1 -0
  131. package/dist/workers/processors/aiLabChat.d.ts +7 -0
  132. package/dist/workers/processors/aiLabChat.d.ts.map +1 -0
  133. package/dist/workers/processors/aiLabChat.js +52 -0
  134. package/dist/workers/processors/aiLabChat.js.map +1 -0
  135. package/dist/workers/processors/aiNewtonChat.d.ts +7 -0
  136. package/dist/workers/processors/aiNewtonChat.d.ts.map +1 -0
  137. package/dist/workers/processors/aiNewtonChat.js +57 -0
  138. package/dist/workers/processors/aiNewtonChat.js.map +1 -0
  139. package/dist/workers/processors/createRunJobWorker.d.ts +46 -0
  140. package/dist/workers/processors/createRunJobWorker.d.ts.map +1 -0
  141. package/dist/workers/processors/createRunJobWorker.js +118 -0
  142. package/dist/workers/processors/createRunJobWorker.js.map +1 -0
  143. package/dist/workers/processors/gradeWorksheet.d.ts +6 -0
  144. package/dist/workers/processors/gradeWorksheet.d.ts.map +1 -0
  145. package/dist/workers/processors/gradeWorksheet.js +19 -0
  146. package/dist/workers/processors/gradeWorksheet.js.map +1 -0
  147. package/dist/workers/processors/pdfToWorksheet.d.ts +8 -0
  148. package/dist/workers/processors/pdfToWorksheet.d.ts.map +1 -0
  149. package/dist/workers/processors/pdfToWorksheet.js +20 -0
  150. package/dist/workers/processors/pdfToWorksheet.js.map +1 -0
  151. package/dist/workers/processors/studentProgressChat.d.ts +8 -0
  152. package/dist/workers/processors/studentProgressChat.d.ts.map +1 -0
  153. package/dist/workers/processors/studentProgressChat.js +27 -0
  154. package/dist/workers/processors/studentProgressChat.js.map +1 -0
  155. package/dist/workers/queues.d.ts +85 -0
  156. package/dist/workers/queues.d.ts.map +1 -0
  157. package/dist/workers/queues.js +119 -0
  158. package/dist/workers/queues.js.map +1 -0
  159. package/package.json +6 -3
  160. package/prisma/migrations/20260506162500_consolidate_run_job_and_unify_job_status/migration.sql +166 -0
  161. package/prisma/schema.prisma +465 -387
  162. package/src/index.ts +12 -1
  163. package/src/lib/config/cors.ts +0 -1
  164. package/src/lib/fileUpload.ts +2 -4
  165. package/src/lib/pusher.ts +7 -1
  166. package/src/models/attendance.ts +15 -0
  167. package/src/models/auth.ts +1 -0
  168. package/src/models/school.ts +0 -14
  169. package/src/models/worksheet.ts +2 -2
  170. package/src/pipelines/aiLabChat.ts +71 -79
  171. package/src/pipelines/aiNewtonChat.ts +47 -31
  172. package/src/pipelines/gradeWorksheet.ts +63 -25
  173. package/src/pipelines/pdfToWorksheet.ts +272 -34
  174. package/src/routers/assignment.ts +74 -3
  175. package/src/routers/school.ts +5 -0
  176. package/src/routers/worksheet.ts +25 -4
  177. package/src/services/announcement.ts +2 -2
  178. package/src/services/assignment.ts +18 -4
  179. package/src/services/attendance.ts +87 -2
  180. package/src/services/labChat.ts +108 -48
  181. package/src/services/message.ts +19 -0
  182. package/src/services/newtonChat.ts +46 -25
  183. package/src/services/school.ts +89 -6
  184. package/src/services/worksheet.ts +87 -28
  185. package/src/utils/generateUsername.ts +108 -0
  186. package/src/utils/inference.ts +209 -1
  187. package/src/workers/connection.ts +37 -0
  188. package/src/workers/index.ts +71 -0
  189. package/src/workers/processors/aiLabChat.ts +65 -0
  190. package/src/workers/processors/aiNewtonChat.ts +75 -0
  191. package/src/workers/processors/createRunJobWorker.ts +164 -0
  192. package/src/workers/processors/gradeWorksheet.ts +16 -0
  193. package/src/workers/processors/pdfToWorksheet.ts +17 -0
  194. package/src/workers/processors/studentProgressChat.ts +28 -0
  195. package/src/workers/queues.ts +174 -0
  196. package/tests/{globalSetup.ts → integration/globalSetup.ts} +4 -4
  197. package/tests/{helpers.ts → integration/helpers.ts} +2 -2
  198. package/tests/{pipelines → integration/pipelines}/pdfToWorksheet.test.ts +55 -43
  199. package/tests/{routers → integration/routers}/attendance.test.ts +61 -2
  200. package/tests/{routers → integration/routers}/auth.test.ts +3 -3
  201. package/tests/{routers → integration/routers}/class.test.ts +3 -17
  202. package/tests/{routers → integration/routers}/conversation.test.ts +1 -1
  203. package/tests/{routers → integration/routers}/event.test.ts +1 -1
  204. package/tests/{routers → integration/routers}/message.test.ts +1 -1
  205. package/tests/{routers → integration/routers}/newtonChat.test.ts +1 -1
  206. package/tests/{routers → integration/routers}/studentProgress.test.ts +1 -1
  207. package/tests/{user.test.ts → integration/routers/user.test.ts} +2 -2
  208. package/tests/integration/routers/worksheet.test.ts +448 -0
  209. package/tests/{setup.ts → integration/setup.ts} +4 -4
  210. package/tests/{lib → unit/lib}/cors.test.ts +1 -1
  211. package/tests/{middleware → unit/middleware}/security.test.ts +1 -1
  212. package/tests/{pipelines → unit/pipelines}/aiLabChat.test.ts +1 -1
  213. package/tests/{lib → unit/pipelines}/aiLabChatContract.test.ts +1 -1
  214. package/tests/{utils/aiLabChatPrompt.test.ts → unit/pipelines/labChatPrompt.test.ts} +2 -2
  215. package/tests/{utils → unit/services}/studentProgress.test.ts +5 -5
  216. package/tests/{utils → unit/utils}/aiUser.test.ts +1 -1
  217. package/tests/{utils → unit/utils}/generateInviteCode.test.ts +1 -1
  218. package/tests/{utils → unit/utils}/inference.test.ts +6 -6
  219. package/tests/{utils → unit/utils}/logger.test.ts +1 -1
  220. package/tests/{utils → unit/utils}/prismaErrorHandler.test.ts +1 -1
  221. package/tests/{utils → unit/utils}/prismaWrapper.test.ts +1 -1
  222. package/vitest.config.ts +11 -3
  223. package/vitest.unit.config.ts +10 -10
  224. package/tests/routers/worksheet.test.ts +0 -194
  225. package/tests/startup.test.ts +0 -5
  226. package/tests/worksheet.test.ts +0 -181
  227. /package/tests/{routers → integration/routers}/agenda.test.ts +0 -0
  228. /package/tests/{routers → integration/routers}/announcement.test.ts +0 -0
  229. /package/tests/{routers → integration/routers}/assignment.test.ts +0 -0
  230. /package/tests/{routers → integration/routers}/comment.test.ts +0 -0
  231. /package/tests/{routers → integration/routers}/folder.test.ts +0 -0
  232. /package/tests/{routers → integration/routers}/labChat.test.ts +0 -0
  233. /package/tests/{routers → integration/routers}/marketing.test.ts +0 -0
  234. /package/tests/{routers → integration/routers}/notification.test.ts +0 -0
  235. /package/tests/{routers → integration/routers}/search.test.ts +0 -0
  236. /package/tests/{routers → integration/routers}/section.test.ts +0 -0
  237. /package/tests/{server → unit/middleware}/rateLimit.test.ts +0 -0
@@ -15,8 +15,8 @@ generator client {
15
15
  // }
16
16
 
17
17
  datasource db {
18
- provider = "postgresql"
19
- url = env("DATABASE_URL")
18
+ provider = "postgresql"
19
+ url = env("DATABASE_URL")
20
20
  directUrl = env("DIRECT_URL")
21
21
  }
22
22
 
@@ -47,10 +47,20 @@ enum UploadStatus {
47
47
  CANCELLED
48
48
  }
49
49
 
50
- enum GenerationStatus {
50
+ /// UI-shaped status shared by every domain row that has an async/AI lifecycle
51
+ /// the user can observe (chat messages, AI grading, etc.).
52
+ ///
53
+ /// Not every row uses every value — e.g. a Message never goes to NOT_STARTED
54
+ /// (the row doesn't exist until the message is sent) or CANCELLED (the app
55
+ /// doesn't cancel chat replies mid-flight). The full set lives here so a
56
+ /// single enum can serve every UI surface.
57
+ ///
58
+ /// For the queue-internal lifecycle (PENDING / RUNNING / SUCCEEDED) see
59
+ /// RunJobStatus — those names matter operationally and shouldn't be mixed
60
+ /// in here.
61
+ enum JobStatus {
51
62
  NOT_STARTED
52
63
  PENDING
53
- GENERATING
54
64
  COMPLETED
55
65
  FAILED
56
66
  CANCELLED
@@ -101,15 +111,14 @@ enum SchoolInviteStatus {
101
111
  }
102
112
 
103
113
  model School {
104
- id String @id @default(uuid())
105
- name String
106
- logo File? @relation(references: [id], fields: [logoId])
107
- logoId String?
108
- subdomain String? @unique
109
- createdAt DateTime @default(now())
110
- updatedAt DateTime @updatedAt
111
-
112
-
114
+ id String @id @default(uuid())
115
+ name String
116
+ logo File? @relation(references: [id], fields: [logoId])
117
+ logoId String?
118
+ subdomain String? @unique
119
+ createdAt DateTime @default(now())
120
+ updatedAt DateTime @updatedAt
121
+
113
122
  users User[]
114
123
  memberships SchoolMembership[]
115
124
  invites SchoolInvite[]
@@ -117,46 +126,45 @@ model School {
117
126
  }
118
127
 
119
128
  model User {
120
- id String @id @default(uuid())
121
- username String
122
- email String
123
- password String
124
- profile UserProfile?
125
- verified Boolean @default(false)
126
- role UserRole @default(NONE)
127
-
128
- teacherIn Class[] @relation("UserTeacherToClass")
129
- studentIn Class[] @relation("UserStudentToClass")
130
-
131
- submissions Submission[]
132
- sessions Session[]
133
- files File[]
134
- assignments Assignment[]
135
- specificAssignments Assignment[] @relation("SpecificAssignmentAssignedTo")
136
- events Event[]
137
- announcements Announcement[]
138
- notificationsSent Notification[] @relation("SentNotifications")
139
- notificationsReceived Notification[] @relation("ReceivedNotifications")
140
- studentQuestionProgress StudentQuestionProgress[]
129
+ id String @id @default(uuid())
130
+ username String
131
+ email String
132
+ password String
133
+ profile UserProfile?
134
+ verified Boolean @default(false)
135
+ role UserRole @default(NONE)
136
+
137
+ teacherIn Class[] @relation("UserTeacherToClass")
138
+ studentIn Class[] @relation("UserStudentToClass")
139
+
140
+ submissions Submission[]
141
+ sessions Session[]
142
+ files File[]
143
+ assignments Assignment[]
144
+ specificAssignments Assignment[] @relation("SpecificAssignmentAssignedTo")
145
+ events Event[]
146
+ announcements Announcement[]
147
+ notificationsSent Notification[] @relation("SentNotifications")
148
+ notificationsReceived Notification[] @relation("ReceivedNotifications")
149
+ studentQuestionProgress StudentQuestionProgress[]
141
150
  studentWorksheetResponses StudentWorksheetResponse[]
142
151
 
143
- presentAttendance Attendance[] @relation("PresentAttendance")
152
+ presentAttendance Attendance[] @relation("PresentAttendance")
144
153
  lateAttendance Attendance[] @relation("LateAttendance")
145
154
  absentAttendance Attendance[] @relation("AbsentAttendance")
146
155
 
147
- school School? @relation(fields: [schoolId], references: [id])
148
- schoolId String?
156
+ school School? @relation(fields: [schoolId], references: [id])
157
+ schoolId String?
149
158
  schoolMemberships SchoolMembership[]
150
- sentSchoolInvites SchoolInvite[] @relation("SchoolInviteInvitedBy")
159
+ sentSchoolInvites SchoolInvite[] @relation("SchoolInviteInvitedBy")
151
160
 
152
161
  // Chat relations
153
162
  conversationMemberships ConversationMember[]
154
- sentMessages Message[] @relation("SentMessages")
155
- mentions Mention[] @relation("UserMentions")
156
- createdLabChats LabChat[] @relation("CreatedLabChats")
157
- comments Comment[]
158
- reactions Reaction[]
159
-
163
+ sentMessages Message[] @relation("SentMessages")
164
+ mentions Mention[] @relation("UserMentions")
165
+ createdLabChats LabChat[] @relation("CreatedLabChats")
166
+ comments Comment[]
167
+ reactions Reaction[]
160
168
  }
161
169
 
162
170
  model SchoolMembership {
@@ -178,230 +186,227 @@ model SchoolMembership {
178
186
  }
179
187
 
180
188
  model SchoolInvite {
181
- id String @id @default(uuid())
182
- token String @unique
183
- email String
184
- role SchoolMembershipRole
185
- status SchoolInviteStatus @default(PENDING)
186
- expiresAt DateTime
187
- acceptedAt DateTime?
188
- revokedAt DateTime?
189
- createdAt DateTime @default(now())
190
- updatedAt DateTime @updatedAt
189
+ id String @id @default(uuid())
190
+ token String @unique
191
+ email String
192
+ role SchoolMembershipRole
193
+ status SchoolInviteStatus @default(PENDING)
194
+ expiresAt DateTime
195
+ acceptedAt DateTime?
196
+ revokedAt DateTime?
197
+ createdAt DateTime @default(now())
198
+ updatedAt DateTime @updatedAt
191
199
 
192
200
  schoolId String
193
- school School @relation(fields: [schoolId], references: [id], onDelete: Cascade)
201
+ school School @relation(fields: [schoolId], references: [id], onDelete: Cascade)
194
202
  invitedById String
195
- invitedBy User @relation("SchoolInviteInvitedBy", fields: [invitedById], references: [id], onDelete: Cascade)
203
+ invitedBy User @relation("SchoolInviteInvitedBy", fields: [invitedById], references: [id], onDelete: Cascade)
196
204
 
197
205
  @@index([schoolId, status])
198
206
  @@index([email])
199
207
  }
200
208
 
201
209
  model UserProfile {
202
- id String @id @default(uuid())
210
+ id String @id @default(uuid())
203
211
  userId String @unique
204
- user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
205
-
206
- displayName String?
207
- bio String?
208
- location String?
209
- website String?
210
- profilePicture String?
212
+ user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
213
+
214
+ displayName String?
215
+ bio String?
216
+ location String?
217
+ website String?
218
+ profilePicture String?
211
219
  profilePictureThumbnail String?
212
-
220
+
213
221
  createdAt DateTime @default(now())
214
222
  updatedAt DateTime @updatedAt
215
- }
223
+ }
216
224
 
217
225
  model Class {
218
- id String @id @default(uuid())
219
- name String
220
- subject String
221
- color String? @default("#3B82F6")
222
- section String
223
- syllabus String?
224
- announcements Announcement[]
225
- assignments Assignment[]
226
- attendance Attendance[]
227
- events Event[]
228
- sections Section[]
229
- sessions Session[]
230
- worksheets Worksheet[]
231
- students User[] @relation("UserStudentToClass")
232
- teachers User[] @relation("UserTeacherToClass")
226
+ id String @id @default(uuid())
227
+ name String
228
+ subject String
229
+ color String? @default("#3B82F6")
230
+ section String
231
+ syllabus String?
232
+ announcements Announcement[]
233
+ assignments Assignment[]
234
+ attendance Attendance[]
235
+ events Event[]
236
+ sections Section[]
237
+ sessions Session[]
238
+ worksheets Worksheet[]
239
+ students User[] @relation("UserStudentToClass")
240
+ teachers User[] @relation("UserTeacherToClass")
233
241
  // markSchemes MarkScheme[] @relation("ClassToMarkScheme")
234
242
  // gradingBoundaries GradingBoundary[] @relation("ClassToGradingBoundary")
235
- draftFiles File[] @relation("ClassDraftFiles")
236
- classFiles Folder? @relation("ClassFiles")
237
- labChats LabChat[] @relation("ClassLabChats")
238
- markSchemes MarkScheme[]
243
+ draftFiles File[] @relation("ClassDraftFiles")
244
+ classFiles Folder? @relation("ClassFiles")
245
+ labChats LabChat[] @relation("ClassLabChats")
246
+ markSchemes MarkScheme[]
239
247
  gradingBoundaries GradingBoundary[]
240
- school School? @relation(fields: [schoolId], references: [id])
241
- schoolId String?
248
+ school School? @relation(fields: [schoolId], references: [id])
249
+ schoolId String?
242
250
  }
243
251
 
244
252
  model MarkScheme {
245
- id String @id @default(uuid())
246
- classId String
253
+ id String @id @default(uuid())
254
+ classId String
247
255
  // class Class[] @relation("ClassToMarkScheme")
248
- class Class? @relation(fields: [classId], references: [id], onDelete: Cascade)
249
- structured String
256
+ class Class? @relation(fields: [classId], references: [id], onDelete: Cascade)
257
+ structured String
250
258
  assignments Assignment[]
251
259
  }
252
260
 
253
261
  model GradingBoundary {
254
- id String @id @default(uuid())
255
- classId String
262
+ id String @id @default(uuid())
263
+ classId String
256
264
  // class Class[] @relation("ClassToGradingBoundary")
257
- class Class? @relation(fields: [classId], references: [id], onDelete: Cascade)
258
- structured String
265
+ class Class? @relation(fields: [classId], references: [id], onDelete: Cascade)
266
+ structured String
259
267
  assignments Assignment[]
260
268
  }
261
269
 
262
270
  model Folder {
263
- id String @id @default(uuid())
264
- name String
265
- files File[] @relation("FolderFiles")
266
- childFolders Folder[] @relation("ParentChildFolders")
267
- parentFolder Folder? @relation("ParentChildFolders", fields: [parentFolderId], references: [id])
271
+ id String @id @default(uuid())
272
+ name String
273
+ files File[] @relation("FolderFiles")
274
+ childFolders Folder[] @relation("ParentChildFolders")
275
+ parentFolder Folder? @relation("ParentChildFolders", fields: [parentFolderId], references: [id])
268
276
  parentFolderId String?
269
- color String? @default("#3B82F6")
270
- class Class? @relation("ClassFiles", fields: [classId], references: [id])
271
- classId String? @unique
277
+ color String? @default("#3B82F6")
278
+ class Class? @relation("ClassFiles", fields: [classId], references: [id])
279
+ classId String? @unique
272
280
  }
273
281
 
274
282
  model File {
275
- id String @id @default(uuid())
283
+ id String @id @default(uuid())
276
284
  name String
277
285
  path String
278
286
  size Int?
279
287
  type String
280
- user User? @relation(fields: [userId], references: [id])
288
+ user User? @relation(fields: [userId], references: [id])
281
289
  userId String?
282
290
  uploadedAt DateTime? @default(now())
283
291
 
284
292
  // Upload tracking fields
285
- uploadStatus UploadStatus @default(PENDING)
286
- uploadUrl String?
287
- uploadExpiresAt DateTime?
288
- uploadSessionId String?
289
- uploadProgress Int?
290
- uploadError String?
291
- uploadRetryCount Int @default(0)
292
- isOrphaned Boolean @default(false)
293
- cleanupAt DateTime?
293
+ uploadStatus UploadStatus @default(PENDING)
294
+ uploadUrl String?
295
+ uploadExpiresAt DateTime?
296
+ uploadSessionId String?
297
+ uploadProgress Int?
298
+ uploadError String?
299
+ uploadRetryCount Int @default(0)
300
+ isOrphaned Boolean @default(false)
301
+ cleanupAt DateTime?
294
302
 
295
303
  // Thumbnail relationship
296
- thumbnail File? @relation("Thumbnail", fields: [thumbnailId], references: [id])
297
- thumbnailId String? @unique
298
- originalFile File? @relation("Thumbnail")
304
+ thumbnail File? @relation("Thumbnail", fields: [thumbnailId], references: [id])
305
+ thumbnailId String? @unique
306
+ originalFile File? @relation("Thumbnail")
299
307
 
300
- assignment Assignment? @relation(fields: [assignmentId], references: [id], onDelete: Cascade)
308
+ assignment Assignment? @relation(fields: [assignmentId], references: [id], onDelete: Cascade)
301
309
  assignmentId String?
302
310
 
303
- submission Submission? @relation("SubmissionFile", fields: [submissionId], references: [id], onDelete: Cascade)
311
+ submission Submission? @relation("SubmissionFile", fields: [submissionId], references: [id], onDelete: Cascade)
304
312
  submissionId String?
305
313
 
306
- annotations Submission? @relation("SubmissionAnnotations", fields: [annotationId], references: [id], onDelete: Cascade)
314
+ annotations Submission? @relation("SubmissionAnnotations", fields: [annotationId], references: [id], onDelete: Cascade)
307
315
  annotationId String?
308
316
 
309
- classDraft Class? @relation("ClassDraftFiles", fields: [classDraftId], references: [id], onDelete: Cascade)
317
+ classDraft Class? @relation("ClassDraftFiles", fields: [classDraftId], references: [id], onDelete: Cascade)
310
318
  classDraftId String?
311
319
 
312
- folder Folder? @relation("FolderFiles", fields: [folderId], references: [id], onDelete: Cascade)
320
+ folder Folder? @relation("FolderFiles", fields: [folderId], references: [id], onDelete: Cascade)
313
321
  folderId String?
314
322
 
315
323
  conversationId String?
316
324
 
317
325
  messageId String?
318
- message Message? @relation("MessageAttachments", fields: [messageId], references: [id], onDelete: Cascade)
326
+ message Message? @relation("MessageAttachments", fields: [messageId], references: [id], onDelete: Cascade)
319
327
 
320
- announcement Announcement? @relation("AnnouncementAttachments", fields: [announcementId], references: [id], onDelete: Cascade)
328
+ announcement Announcement? @relation("AnnouncementAttachments", fields: [announcementId], references: [id], onDelete: Cascade)
321
329
  announcementId String?
322
330
 
323
- schools School[]
331
+ schools School[]
324
332
 
325
- schoolDevelopementProgram SchoolDevelopementProgram? @relation("SchoolDevelopementProgramSupportingDocumentation", fields: [schoolDevelopementProgramId], references: [id], onDelete: Cascade)
333
+ schoolDevelopementProgram SchoolDevelopementProgram? @relation("SchoolDevelopementProgramSupportingDocumentation", fields: [schoolDevelopementProgramId], references: [id], onDelete: Cascade)
326
334
  schoolDevelopementProgramId String?
327
335
  }
328
336
 
329
337
  model Assignment {
330
- id String @id @default(uuid())
331
- title String
332
- instructions String
333
- dueDate DateTime
334
- createdAt DateTime? @default(now())
335
- modifiedAt DateTime? @updatedAt
336
- teacher User @relation(fields: [teacherId], references: [id], onDelete: NoAction)
337
- teacherId String
338
- class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
339
- classId String
340
- attachments File[]
341
- acceptFiles Boolean @default(false)
342
- acceptExtendedResponse Boolean @default(false)
343
- acceptWorksheet Boolean @default(false)
344
- gradeWithAI Boolean @default(false)
338
+ id String @id @default(uuid())
339
+ title String
340
+ instructions String
341
+ dueDate DateTime
342
+ createdAt DateTime? @default(now())
343
+ modifiedAt DateTime? @updatedAt
344
+ teacher User @relation(fields: [teacherId], references: [id], onDelete: NoAction)
345
+ teacherId String
346
+ class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
347
+ classId String
348
+ attachments File[]
349
+ acceptFiles Boolean @default(false)
350
+ acceptExtendedResponse Boolean @default(false)
351
+ acceptWorksheet Boolean @default(false)
352
+ gradeWithAI Boolean @default(false)
345
353
 
346
354
  aiPolicyLevel Int @default(0)
347
355
 
348
- submissions Submission[]
349
- comments Comment[] @relation("AssignmentComments")
350
- assignedTo User[] @relation("SpecificAssignmentAssignedTo")
351
- section Section? @relation(fields: [sectionId], references: [id], onDelete: Cascade)
352
- sectionId String?
353
- graded Boolean @default(false)
354
- maxGrade Int? @default(0)
355
- weight Float @default(1)
356
- type AssignmentType @default(HOMEWORK)
357
- inProgress Boolean @default(false)
358
- template Boolean @default(false)
359
- eventAttached Event? @relation(fields: [eventId], references: [id], onDelete: NoAction)
360
- eventId String?
361
- markScheme MarkScheme? @relation(fields: [markSchemeId], references: [id], onDelete: Cascade)
362
- markSchemeId String?
363
- order Int?
364
- gradingBoundary GradingBoundary? @relation(fields: [gradingBoundaryId], references: [id], onDelete: Cascade)
365
- gradingBoundaryId String?
366
- worksheets Worksheet[] @relation("AssignmentWorksheets")
367
- targetedBySubmissions Submission[] @relation("SubmissionTargetedAssignment")
356
+ submissions Submission[]
357
+ comments Comment[] @relation("AssignmentComments")
358
+ assignedTo User[] @relation("SpecificAssignmentAssignedTo")
359
+ section Section? @relation(fields: [sectionId], references: [id], onDelete: Cascade)
360
+ sectionId String?
361
+ graded Boolean @default(false)
362
+ maxGrade Int? @default(0)
363
+ weight Float @default(1)
364
+ type AssignmentType @default(HOMEWORK)
365
+ inProgress Boolean @default(false)
366
+ template Boolean @default(false)
367
+ eventAttached Event? @relation(fields: [eventId], references: [id], onDelete: NoAction)
368
+ eventId String?
369
+ markScheme MarkScheme? @relation(fields: [markSchemeId], references: [id], onDelete: Cascade)
370
+ markSchemeId String?
371
+ order Int?
372
+ gradingBoundary GradingBoundary? @relation(fields: [gradingBoundaryId], references: [id], onDelete: Cascade)
373
+ gradingBoundaryId String?
374
+ worksheets Worksheet[] @relation("AssignmentWorksheets")
375
+ targetedBySubmissions Submission[] @relation("SubmissionTargetedAssignment")
368
376
  }
369
377
 
370
-
371
-
372
378
  model Announcement {
373
- id String @id @default(uuid())
374
- remarks String
375
- teacher User @relation(fields: [teacherId], references: [id])
376
- teacherId String
377
- createdAt DateTime @default(now())
378
- modifiedAt DateTime? @updatedAt
379
- class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
380
- classId String
381
- attachments File[] @relation("AnnouncementAttachments")
382
- comments Comment[]
383
- reactions Reaction[]
379
+ id String @id @default(uuid())
380
+ remarks String
381
+ teacher User @relation(fields: [teacherId], references: [id])
382
+ teacherId String
383
+ createdAt DateTime @default(now())
384
+ modifiedAt DateTime? @updatedAt
385
+ class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
386
+ classId String
387
+ attachments File[] @relation("AnnouncementAttachments")
388
+ comments Comment[]
389
+ reactions Reaction[]
384
390
  }
385
391
 
386
392
  model Comment {
387
- id String @id @default(uuid())
388
- content String
389
- author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
390
- authorId String
391
- announcement Announcement? @relation(fields: [announcementId], references: [id], onDelete: Cascade)
392
- announcementId String?
393
- assignment Assignment? @relation("AssignmentComments", fields: [assignmentId], references: [id], onDelete: Cascade)
394
- assignmentId String?
395
- submission Submission? @relation("SubmissionComments", fields: [submissionId], references: [id], onDelete: Cascade)
396
- submissionId String?
397
- parentComment Comment? @relation("CommentReplies", fields: [parentCommentId], references: [id], onDelete: Cascade)
398
- parentCommentId String?
399
- status GenerationStatus?
400
- replies Comment[] @relation("CommentReplies")
401
- reactions Reaction[]
402
- createdAt DateTime @default(now())
403
- modifiedAt DateTime? @updatedAt
404
- studentQuestionProgress StudentQuestionProgress? @relation("StudentQuestionProgressComments", fields: [studentQuestionProgressId], references: [id], onDelete: Cascade)
393
+ id String @id @default(uuid())
394
+ content String
395
+ author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
396
+ authorId String
397
+ announcement Announcement? @relation(fields: [announcementId], references: [id], onDelete: Cascade)
398
+ announcementId String?
399
+ assignment Assignment? @relation("AssignmentComments", fields: [assignmentId], references: [id], onDelete: Cascade)
400
+ assignmentId String?
401
+ submission Submission? @relation("SubmissionComments", fields: [submissionId], references: [id], onDelete: Cascade)
402
+ submissionId String?
403
+ parentComment Comment? @relation("CommentReplies", fields: [parentCommentId], references: [id], onDelete: Cascade)
404
+ parentCommentId String?
405
+ replies Comment[] @relation("CommentReplies")
406
+ reactions Reaction[]
407
+ createdAt DateTime @default(now())
408
+ modifiedAt DateTime? @updatedAt
409
+ studentQuestionProgress StudentQuestionProgress? @relation("StudentQuestionProgressComments", fields: [studentQuestionProgressId], references: [id], onDelete: Cascade)
405
410
  studentQuestionProgressId String?
406
411
 
407
412
  @@index([assignmentId])
@@ -409,16 +414,16 @@ model Comment {
409
414
  }
410
415
 
411
416
  model Reaction {
412
- id String @id @default(uuid())
413
- type ReactionType
414
- user User @relation(fields: [userId], references: [id], onDelete: Cascade)
415
- userId String
416
- announcement Announcement? @relation(fields: [announcementId], references: [id], onDelete: Cascade)
417
+ id String @id @default(uuid())
418
+ type ReactionType
419
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
420
+ userId String
421
+ announcement Announcement? @relation(fields: [announcementId], references: [id], onDelete: Cascade)
417
422
  announcementId String?
418
- comment Comment? @relation(fields: [commentId], references: [id], onDelete: Cascade)
419
- commentId String?
420
- createdAt DateTime @default(now())
421
-
423
+ comment Comment? @relation(fields: [commentId], references: [id], onDelete: Cascade)
424
+ commentId String?
425
+ createdAt DateTime @default(now())
426
+
422
427
  @@unique([userId, announcementId])
423
428
  @@unique([userId, commentId])
424
429
  @@index([announcementId])
@@ -426,57 +431,57 @@ model Reaction {
426
431
  }
427
432
 
428
433
  model Submission {
429
- id String @id @default(uuid())
430
- createdAt DateTime @default(now())
434
+ id String @id @default(uuid())
435
+ createdAt DateTime @default(now())
431
436
  modifiedAt DateTime @default(now())
432
437
 
433
- assignment Assignment @relation(fields: [assignmentId], references: [id], onDelete: Cascade)
438
+ assignment Assignment @relation(fields: [assignmentId], references: [id], onDelete: Cascade)
434
439
  assignmentId String
435
440
 
436
- student User @relation(fields: [studentId], references: [id])
441
+ student User @relation(fields: [studentId], references: [id])
437
442
  studentId String
438
443
 
439
- attachments File[] @relation("SubmissionFile")
440
- annotations File[] @relation("SubmissionAnnotations")
441
- comments Comment[] @relation("SubmissionComments")
442
- worksheetResponses StudentWorksheetResponse[]
443
- extendedResponse String?
444
- gradeReceived Int?
445
- recommendationState SubmissionRecommendationState @default(NONE)
444
+ attachments File[] @relation("SubmissionFile")
445
+ annotations File[] @relation("SubmissionAnnotations")
446
+ comments Comment[] @relation("SubmissionComments")
447
+ worksheetResponses StudentWorksheetResponse[]
448
+ extendedResponse String?
449
+ gradeReceived Int?
450
+ recommendationState SubmissionRecommendationState @default(NONE)
446
451
  recommendationUpdatedAt DateTime?
447
- targetedAssignmentId String?
448
- targetedAssignment Assignment? @relation("SubmissionTargetedAssignment", fields: [targetedAssignmentId], references: [id], onDelete: SetNull)
452
+ targetedAssignmentId String?
453
+ targetedAssignment Assignment? @relation("SubmissionTargetedAssignment", fields: [targetedAssignmentId], references: [id], onDelete: SetNull)
449
454
 
450
- rubricState String?
455
+ rubricState String?
451
456
  teacherComments String?
452
457
 
453
- submittedAt DateTime?
454
- submitted Boolean? @default(false)
455
- returned Boolean? @default(false)
458
+ submittedAt DateTime?
459
+ submitted Boolean? @default(false)
460
+ returned Boolean? @default(false)
456
461
  newtonTutors NewtonChat[]
457
462
 
458
463
  @@index([studentId, recommendationState])
459
464
  }
460
465
 
461
466
  model NewtonChat {
462
- id String @id @default(uuid())
463
- submissionId String
464
- submission Submission @relation(fields: [submissionId], references: [id], onDelete: Cascade)
465
- conversationId String @unique
466
- conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
467
- createdAt DateTime @default(now())
468
- updatedAt DateTime @updatedAt
469
- title String @default("Session with Newton Tutor")
467
+ id String @id @default(uuid())
468
+ submissionId String
469
+ submission Submission @relation(fields: [submissionId], references: [id], onDelete: Cascade)
470
+ conversationId String @unique
471
+ conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
472
+ createdAt DateTime @default(now())
473
+ updatedAt DateTime @updatedAt
474
+ title String @default("Session with Newton Tutor")
470
475
  }
471
476
 
472
477
  model Section {
473
- id String @id @default(uuid())
474
- name String
475
- classId String
476
- class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
478
+ id String @id @default(uuid())
479
+ name String
480
+ classId String
481
+ class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
477
482
  assignments Assignment[]
478
- color String? @default("#3B82F6")
479
- order Int?
483
+ color String? @default("#3B82F6")
484
+ order Int?
480
485
  }
481
486
 
482
487
  enum SessionType {
@@ -487,57 +492,57 @@ enum SessionType {
487
492
  }
488
493
 
489
494
  model Session {
490
- id String @id @default(uuid())
491
- createdAt DateTime? @default(now())
495
+ id String @id @default(uuid())
496
+ createdAt DateTime? @default(now())
492
497
  expiresAt DateTime?
493
498
  type SessionType @default(AUTH)
494
499
  userId String?
495
- user User? @relation(fields: [userId], references: [id], onDelete: NoAction)
500
+ user User? @relation(fields: [userId], references: [id], onDelete: NoAction)
496
501
  classId String?
497
- class Class? @relation(fields: [classId], references: [id], onDelete: Cascade)
502
+ class Class? @relation(fields: [classId], references: [id], onDelete: Cascade)
498
503
  }
499
504
 
500
505
  model Event {
501
- id String @id @default(uuid())
502
- name String?
503
- startTime DateTime
504
- endTime DateTime
505
- location String?
506
- remarks String?
507
- userId String?
508
- user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
509
- classId String?
510
- class Class? @relation(fields: [classId], references: [id], onDelete: Cascade)
511
- color String? @default("#3B82F6")
506
+ id String @id @default(uuid())
507
+ name String?
508
+ startTime DateTime
509
+ endTime DateTime
510
+ location String?
511
+ remarks String?
512
+ userId String?
513
+ user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
514
+ classId String?
515
+ class Class? @relation(fields: [classId], references: [id], onDelete: Cascade)
516
+ color String? @default("#3B82F6")
512
517
  assignmentsAttached Assignment[]
513
- attendance Attendance[]
518
+ attendance Attendance[]
514
519
  }
515
520
 
516
521
  model Attendance {
517
- id String @id @default(uuid())
518
- date DateTime @default(now())
519
-
520
- class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
521
- classId String
522
+ id String @id @default(uuid())
523
+ date DateTime @default(now())
524
+
525
+ class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
526
+ classId String
522
527
 
523
- event Event? @relation(fields: [eventId], references: [id], onDelete: Cascade)
524
- eventId String?
528
+ event Event? @relation(fields: [eventId], references: [id], onDelete: Cascade)
529
+ eventId String?
525
530
 
526
- present User[] @relation("PresentAttendance")
527
- late User[] @relation("LateAttendance")
528
- absent User[] @relation("AbsentAttendance")
531
+ present User[] @relation("PresentAttendance")
532
+ late User[] @relation("LateAttendance")
533
+ absent User[] @relation("AbsentAttendance")
529
534
  }
530
535
 
531
536
  model Notification {
532
- id String @id @default(uuid())
533
- title String
534
- content String
535
- createdAt DateTime @default(now())
536
- senderId String? // no ID means sys
537
+ id String @id @default(uuid())
538
+ title String
539
+ content String
540
+ createdAt DateTime @default(now())
541
+ senderId String? // no ID means sys
537
542
  receiverId String
538
- read Boolean @default(false)
539
- sender User? @relation("SentNotifications", fields: [senderId], references: [id])
540
- receiver User @relation("ReceivedNotifications", fields: [receiverId], references: [id], onDelete: Cascade)
543
+ read Boolean @default(false)
544
+ sender User? @relation("SentNotifications", fields: [senderId], references: [id])
545
+ receiver User @relation("ReceivedNotifications", fields: [receiverId], references: [id], onDelete: Cascade)
541
546
  }
542
547
 
543
548
  enum ConversationType {
@@ -551,179 +556,201 @@ enum ConversationRole {
551
556
  }
552
557
 
553
558
  model Conversation {
554
- id String @id @default(uuid())
555
- type ConversationType
556
- name String?
557
- createdAt DateTime @default(now())
558
- updatedAt DateTime @updatedAt
559
+ id String @id @default(uuid())
560
+ type ConversationType
561
+ name String?
562
+ createdAt DateTime @default(now())
563
+ updatedAt DateTime @updatedAt
559
564
 
560
565
  displayInChat Boolean @default(true)
561
-
562
- members ConversationMember[]
563
- messages Message[]
564
- labChat LabChat?
566
+
567
+ members ConversationMember[]
568
+ messages Message[]
569
+ labChat LabChat?
565
570
  newtonChat NewtonChat?
566
571
  }
567
572
 
568
573
  model LabChat {
569
- id String @id @default(uuid())
570
- title String
571
- context String // JSON string for LLM context
572
- classId String
573
- conversationId String @unique
574
- createdById String // Teacher who created the lab
575
- createdAt DateTime @default(now())
576
- updatedAt DateTime @updatedAt
577
- class Class @relation("ClassLabChats", fields: [classId], references: [id], onDelete: Cascade)
578
- conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
579
- createdBy User @relation("CreatedLabChats", fields: [createdById], references: [id], onDelete: NoAction)
574
+ id String @id @default(uuid())
575
+ title String
576
+ context String // JSON string for LLM context
577
+ classId String
578
+ conversationId String @unique
579
+ createdById String // Teacher who created the lab
580
+ createdAt DateTime @default(now())
581
+ updatedAt DateTime @updatedAt
582
+ class Class @relation("ClassLabChats", fields: [classId], references: [id], onDelete: Cascade)
583
+ conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
584
+ createdBy User @relation("CreatedLabChats", fields: [createdById], references: [id], onDelete: NoAction)
580
585
  }
581
586
 
582
587
  model ConversationMember {
583
- id String @id @default(uuid())
584
- userId String
585
- conversationId String
586
- role ConversationRole @default(MEMBER)
587
- joinedAt DateTime @default(now())
588
- hiddenAt DateTime? // When user last hid this conversation from their list
589
- lastViewedAt DateTime? // When user last viewed this conversation
588
+ id String @id @default(uuid())
589
+ userId String
590
+ conversationId String
591
+ role ConversationRole @default(MEMBER)
592
+ joinedAt DateTime @default(now())
593
+ hiddenAt DateTime? // When user last hid this conversation from their list
594
+ lastViewedAt DateTime? // When user last viewed this conversation
590
595
  lastViewedMentionAt DateTime? // When user last viewed mentions in this conversation
591
-
592
- user User @relation(fields: [userId], references: [id], onDelete: Cascade)
596
+
597
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
593
598
  conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
594
-
599
+
595
600
  @@unique([userId, conversationId])
596
601
  }
597
602
 
598
603
  model Message {
599
- id String @id @default(uuid())
600
- content String
601
- senderId String
604
+ id String @id @default(uuid())
605
+ content String
606
+ senderId String
602
607
  conversationId String
603
- createdAt DateTime @default(now())
604
- status GenerationStatus? @default(NOT_STARTED)
605
- sender User @relation("SentMessages", fields: [senderId], references: [id], onDelete: Cascade)
606
- conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
607
- mentions Mention[]
608
- attachments File[] @relation("MessageAttachments")
608
+ createdAt DateTime @default(now())
609
+ /// UI-shaped status. NULL for messages with no AI lifecycle (normal user DMs).
610
+ /// Operational/queue state (retries, errors, attempts) lives on `runJob`.
611
+ status JobStatus?
612
+ /// Link to the background job that's generating / generated the AI response
613
+ /// for this message. NULL for non-AI messages.
614
+ runJobId String?
615
+ runJob RunJob? @relation("MessageRunJob", fields: [runJobId], references: [id], onDelete: SetNull)
616
+ sender User @relation("SentMessages", fields: [senderId], references: [id], onDelete: Cascade)
617
+ conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
618
+ mentions Mention[]
619
+ attachments File[] @relation("MessageAttachments")
609
620
 
610
621
  meta Json? @default("{}")
622
+
623
+ @@index([runJobId])
611
624
  }
612
625
 
613
626
  model AiUsageLog {
614
- id String @id @default(uuid())
615
- reason String
616
- provider String @default("openai")
617
- model String
618
- operation String
619
- success Boolean @default(true)
620
- promptTokens Int?
621
- completionTokens Int?
622
- totalTokens Int?
627
+ id String @id @default(uuid())
628
+ reason String
629
+ provider String @default("openai")
630
+ model String
631
+ operation String
632
+ success Boolean @default(true)
633
+ promptTokens Int?
634
+ completionTokens Int?
635
+ totalTokens Int?
623
636
  estimatedInputTokens Int?
624
- metadata Json? @default("{}")
625
- error String?
626
- createdAt DateTime @default(now())
637
+ metadata Json? @default("{}")
638
+ error String?
639
+ createdAt DateTime @default(now())
627
640
 
628
641
  @@index([reason, createdAt])
629
642
  }
630
643
 
631
644
  model Mention {
632
- id String @id @default(uuid())
645
+ id String @id @default(uuid())
633
646
  messageId String
634
- userId String
647
+ userId String
635
648
  createdAt DateTime @default(now())
636
-
649
+
637
650
  message Message @relation(fields: [messageId], references: [id], onDelete: Cascade)
638
- user User @relation("UserMentions", fields: [userId], references: [id], onDelete: Cascade)
639
-
651
+ user User @relation("UserMentions", fields: [userId], references: [id], onDelete: Cascade)
652
+
640
653
  @@unique([messageId, userId])
641
654
  }
642
655
 
643
656
  model Worksheet {
644
- id String @id @default(uuid())
645
- name String
646
- createdAt DateTime @default(now())
647
- updatedAt DateTime @updatedAt
648
- classId String
649
- class Class? @relation(fields: [classId], references: [id], onDelete: Cascade)
650
- assignments Assignment[] @relation("AssignmentWorksheets")
651
- questions WorksheetQuestion[]
657
+ id String @id @default(uuid())
658
+ name String
659
+ sourceUrl String?
660
+ createdAt DateTime @default(now())
661
+ updatedAt DateTime @updatedAt
662
+ classId String
663
+ class Class? @relation(fields: [classId], references: [id], onDelete: Cascade)
664
+ assignments Assignment[] @relation("AssignmentWorksheets")
665
+ questions WorksheetQuestion[]
652
666
  studentWorksheetResponses StudentWorksheetResponse[]
667
+
668
+ runJobId String?
669
+ runJob RunJob? @relation("WorksheetRunJob", fields: [runJobId], references: [id], onDelete: SetNull)
670
+
671
+ status JobStatus? @default(NOT_STARTED)
672
+
673
+ @@unique([classId, sourceUrl])
653
674
  }
654
675
 
655
676
  model WorksheetQuestion {
656
- id String @id @default(uuid())
657
- worksheetId String
658
- worksheet Worksheet @relation(fields: [worksheetId], references: [id], onDelete: Cascade)
659
- type WorksheetQuestionType
660
- question String
661
- answer String
662
- options Json? @default("{}")
663
- markScheme Json? @default("{}")
664
- points Int @default(0)
665
- order Int? @default(0)
666
- createdAt DateTime @default(now())
667
- updatedAt DateTime @updatedAt
677
+ id String @id @default(uuid())
678
+ worksheetId String
679
+ worksheet Worksheet @relation(fields: [worksheetId], references: [id], onDelete: Cascade)
680
+ type WorksheetQuestionType
681
+ question String
682
+ answer String
683
+ options Json? @default("{}")
684
+ markScheme Json? @default("{}")
685
+ points Int @default(0)
686
+ order Int? @default(0)
687
+ createdAt DateTime @default(now())
688
+ updatedAt DateTime @updatedAt
668
689
  studentResponses StudentQuestionProgress[]
669
690
  }
670
691
 
671
692
  model StudentWorksheetResponse {
672
- id String @id @default(uuid())
673
- studentId String
674
- student User @relation(fields: [studentId], references: [id], onDelete: Cascade)
675
- worksheetId String
676
- worksheet Worksheet @relation(fields: [worksheetId], references: [id], onDelete: Cascade)
677
- responses StudentQuestionProgress[]
678
- submission Submission? @relation(fields: [submissionId], references: [id], onDelete: Cascade)
693
+ id String @id @default(uuid())
694
+ studentId String
695
+ student User @relation(fields: [studentId], references: [id], onDelete: Cascade)
696
+ worksheetId String
697
+ worksheet Worksheet @relation(fields: [worksheetId], references: [id], onDelete: Cascade)
698
+ responses StudentQuestionProgress[]
699
+ submission Submission? @relation(fields: [submissionId], references: [id], onDelete: Cascade)
679
700
  submissionId String?
680
- createdAt DateTime @default(now())
681
- updatedAt DateTime @updatedAt
682
- submittedAt DateTime?
683
- submitted Boolean @default(false)
701
+ createdAt DateTime @default(now())
702
+ updatedAt DateTime @updatedAt
703
+ submittedAt DateTime?
704
+ submitted Boolean @default(false)
684
705
  }
685
706
 
686
707
  model StudentQuestionProgress {
687
- id String @id @default(uuid())
688
- studentId String
689
- student User @relation(fields: [studentId], references: [id], onDelete: Cascade)
690
- questionId String
691
- question WorksheetQuestion @relation(fields: [questionId], references: [id], onDelete: Cascade)
692
- response String
693
- isCorrect Boolean @default(false)
694
- markschemeState Json? @default("{}")
695
- points Int @default(0)
696
- comments Comment[] @relation("StudentQuestionProgressComments")
697
- feedback String?
698
- createdAt DateTime @default(now())
699
- updatedAt DateTime? @updatedAt
708
+ id String @id @default(uuid())
709
+ studentId String
710
+ student User @relation(fields: [studentId], references: [id], onDelete: Cascade)
711
+ questionId String
712
+ question WorksheetQuestion @relation(fields: [questionId], references: [id], onDelete: Cascade)
713
+ response String
714
+ isCorrect Boolean @default(false)
715
+ markschemeState Json? @default("{}")
716
+ points Int @default(0)
717
+ comments Comment[] @relation("StudentQuestionProgressComments")
718
+ feedback String?
719
+ createdAt DateTime @default(now())
720
+ updatedAt DateTime? @updatedAt
700
721
  studentWorksheetResponseId String?
701
- studentWorksheetResponse StudentWorksheetResponse? @relation(fields: [studentWorksheetResponseId], references: [id], onDelete: Cascade)
702
- status GenerationStatus? @default(NOT_STARTED)
703
-
722
+ studentWorksheetResponse StudentWorksheetResponse? @relation(fields: [studentWorksheetResponseId], references: [id], onDelete: Cascade)
723
+ /// UI-shaped grading lifecycle. Operational/queue state lives on `runJob`.
724
+ status JobStatus? @default(NOT_STARTED)
725
+ /// Link to the auto-grading background job. NULL when no auto-grading
726
+ /// has ever been attempted for this question.
727
+ runJobId String?
728
+ runJob RunJob? @relation("StudentQuestionProgressRunJob", fields: [runJobId], references: [id], onDelete: SetNull)
729
+
704
730
  @@index([studentId, questionId])
731
+ @@index([runJobId])
705
732
  }
706
733
 
707
734
  model SchoolDevelopementProgram {
708
- id String @id
709
- name String
710
- type String
711
- address String
712
- city String
713
- country String
735
+ id String @id
736
+ name String
737
+ type String
738
+ address String
739
+ city String
740
+ country String
714
741
  numberOfStudents Int
715
742
  numberOfTeachers Int
716
- website String?
743
+ website String?
717
744
 
718
- contactName String?
719
- contactRole String?
720
- contactEmail String?
721
- contactPhone String? @default("")
745
+ contactName String?
746
+ contactRole String?
747
+ contactEmail String?
748
+ contactPhone String? @default("")
722
749
  eligibilityInformation String?
723
- whyHelp String?
724
- additionalInformation String?
725
- submittedAt DateTime? @default(now())
726
- reviewedAt DateTime?
750
+ whyHelp String?
751
+ additionalInformation String?
752
+ submittedAt DateTime? @default(now())
753
+ reviewedAt DateTime?
727
754
 
728
755
  status String @default("NOT_STARTED") // NOT_STARTED, PENDING, APPROVED, REJECTED, REFERRED
729
756
 
@@ -731,8 +758,59 @@ model SchoolDevelopementProgram {
731
758
  }
732
759
 
733
760
  model EarlyAccessRequest {
734
- id String @id @default(uuid())
735
- email String
736
- createdAt DateTime @default(now())
761
+ id String @id @default(uuid())
762
+ email String
763
+ createdAt DateTime @default(now())
737
764
  institutionSize String
738
765
  }
766
+
767
+ enum RunJobType {
768
+ PDF_TO_WORKSHEET
769
+ GRADE_WORKSHEET
770
+ AI_LAB_CHAT
771
+ AI_NEWTON_CHAT
772
+ STUDENT_PROGRESS_CHAT
773
+ }
774
+
775
+ enum RunJobStatus {
776
+ PENDING
777
+ RUNNING
778
+ SUCCEEDED
779
+ FAILED
780
+ CANCELLED
781
+ }
782
+
783
+ /// App-level audit/status record for background jobs.
784
+ /// BullMQ owns queue state in Redis (retries, scheduling, in-flight execution);
785
+ /// this table is the durable, queryable record of who started what and how it ended.
786
+ model RunJob {
787
+ id String @id @default(uuid())
788
+ type RunJobType
789
+ status RunJobStatus @default(PENDING)
790
+
791
+ classId String?
792
+ userId String?
793
+
794
+ /// BullMQ job id, set after enqueue. Null if enqueue failed before the queue accepted it.
795
+ bullJobId String?
796
+
797
+ input Json
798
+ result Json?
799
+ error String?
800
+ attempts Int @default(0)
801
+
802
+ startedAt DateTime?
803
+ finishedAt DateTime?
804
+ createdAt DateTime @default(now())
805
+ updatedAt DateTime @updatedAt
806
+
807
+ /// Domain rows produced or driven by this job. Each relation is optional —
808
+ /// most jobs touch zero or one of these.
809
+ messages Message[] @relation("MessageRunJob")
810
+ studentQuestionProgresses StudentQuestionProgress[] @relation("StudentQuestionProgressRunJob")
811
+ worksheet Worksheet[] @relation("WorksheetRunJob")
812
+
813
+ @@index([userId, type, status])
814
+ @@index([classId, status])
815
+ @@index([status, createdAt])
816
+ }