@studious-lms/server 1.1.24 → 1.2.6

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 (485) hide show
  1. package/.coderabbit.yaml +9 -0
  2. package/.env.example +53 -0
  3. package/.env.test.example +37 -0
  4. package/README.md +34 -7
  5. package/dist/exportType.d.ts.map +1 -1
  6. package/dist/exportType.js +4 -0
  7. package/dist/exportType.js.map +1 -0
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +212 -51
  11. package/dist/index.js.map +1 -0
  12. package/dist/instrument.d.ts +2 -0
  13. package/dist/instrument.d.ts.map +1 -0
  14. package/dist/instrument.js +18 -0
  15. package/dist/instrument.js.map +1 -0
  16. package/dist/lib/config/env.d.ts +190 -0
  17. package/dist/lib/config/env.d.ts.map +1 -0
  18. package/dist/lib/config/env.js +121 -0
  19. package/dist/lib/config/env.js.map +1 -0
  20. package/dist/lib/fileUpload.d.ts +2 -2
  21. package/dist/lib/fileUpload.d.ts.map +1 -1
  22. package/dist/lib/fileUpload.js +82 -15
  23. package/dist/lib/fileUpload.js.map +1 -0
  24. package/dist/lib/googleCloudStorage.d.ts +13 -0
  25. package/dist/lib/googleCloudStorage.d.ts.map +1 -1
  26. package/dist/lib/googleCloudStorage.js +45 -6
  27. package/dist/lib/googleCloudStorage.js.map +1 -0
  28. package/dist/lib/jsonConversion.d.ts.map +1 -1
  29. package/dist/lib/jsonConversion.js +16 -14
  30. package/dist/lib/jsonConversion.js.map +1 -0
  31. package/dist/lib/jsonStyles.d.ts.map +1 -1
  32. package/dist/lib/jsonStyles.js +4 -0
  33. package/dist/lib/jsonStyles.js.map +1 -0
  34. package/dist/lib/notificationHandler.d.ts +25 -0
  35. package/dist/lib/notificationHandler.d.ts.map +1 -0
  36. package/dist/lib/notificationHandler.js +32 -0
  37. package/dist/lib/notificationHandler.js.map +1 -0
  38. package/dist/lib/prisma.d.ts +2 -2
  39. package/dist/lib/prisma.d.ts.map +1 -1
  40. package/dist/lib/prisma.js +24 -1
  41. package/dist/lib/prisma.js.map +1 -0
  42. package/dist/lib/pusher.d.ts +4 -1
  43. package/dist/lib/pusher.d.ts.map +1 -1
  44. package/dist/lib/pusher.js +14 -6
  45. package/dist/lib/pusher.js.map +1 -0
  46. package/dist/lib/redis.d.ts +5 -0
  47. package/dist/lib/redis.d.ts.map +1 -0
  48. package/dist/lib/redis.js +53 -0
  49. package/dist/lib/redis.js.map +1 -0
  50. package/dist/lib/thumbnailGenerator.d.ts +0 -21
  51. package/dist/lib/thumbnailGenerator.d.ts.map +1 -1
  52. package/dist/lib/thumbnailGenerator.js +159 -158
  53. package/dist/lib/thumbnailGenerator.js.map +1 -0
  54. package/dist/middleware/auth.d.ts.map +1 -1
  55. package/dist/middleware/auth.js +41 -93
  56. package/dist/middleware/auth.js.map +1 -0
  57. package/dist/middleware/logging.d.ts.map +1 -1
  58. package/dist/middleware/logging.js +4 -0
  59. package/dist/middleware/logging.js.map +1 -0
  60. package/dist/middleware/security.d.ts +5 -0
  61. package/dist/middleware/security.d.ts.map +1 -0
  62. package/dist/middleware/security.js +77 -0
  63. package/dist/middleware/security.js.map +1 -0
  64. package/dist/models/agenda.d.ts +97 -0
  65. package/dist/models/agenda.d.ts.map +1 -0
  66. package/dist/models/agenda.js +40 -0
  67. package/dist/models/agenda.js.map +1 -0
  68. package/dist/models/announcement.d.ts +223 -0
  69. package/dist/models/announcement.d.ts.map +1 -0
  70. package/dist/models/announcement.js +120 -0
  71. package/dist/models/announcement.js.map +1 -0
  72. package/dist/models/assignment.d.ts +1292 -0
  73. package/dist/models/assignment.d.ts.map +1 -0
  74. package/dist/models/assignment.js +309 -0
  75. package/dist/models/assignment.js.map +1 -0
  76. package/dist/models/attendance.d.ts +180 -0
  77. package/dist/models/attendance.d.ts.map +1 -0
  78. package/dist/models/attendance.js +188 -0
  79. package/dist/models/attendance.js.map +1 -0
  80. package/dist/models/auth.d.ts +153 -0
  81. package/dist/models/auth.d.ts.map +1 -0
  82. package/dist/models/auth.js +217 -0
  83. package/dist/models/auth.js.map +1 -0
  84. package/dist/models/class.d.ts +439 -0
  85. package/dist/models/class.d.ts.map +1 -0
  86. package/dist/models/class.js +546 -0
  87. package/dist/models/class.js.map +1 -0
  88. package/dist/models/comment.d.ts +171 -0
  89. package/dist/models/comment.d.ts.map +1 -0
  90. package/dist/models/comment.js +138 -0
  91. package/dist/models/comment.js.map +1 -0
  92. package/dist/models/conversation.d.ts +164 -0
  93. package/dist/models/conversation.d.ts.map +1 -0
  94. package/dist/models/conversation.js +175 -0
  95. package/dist/models/conversation.js.map +1 -0
  96. package/dist/models/event.d.ts +295 -0
  97. package/dist/models/event.d.ts.map +1 -0
  98. package/dist/models/event.js +145 -0
  99. package/dist/models/event.js.map +1 -0
  100. package/dist/models/file.d.ts +536 -0
  101. package/dist/models/file.d.ts.map +1 -0
  102. package/dist/models/file.js +126 -0
  103. package/dist/models/file.js.map +1 -0
  104. package/dist/models/folder.d.ts +295 -0
  105. package/dist/models/folder.d.ts.map +1 -0
  106. package/dist/models/folder.js +202 -0
  107. package/dist/models/folder.js.map +1 -0
  108. package/dist/models/labChat.d.ts +243 -0
  109. package/dist/models/labChat.d.ts.map +1 -0
  110. package/dist/models/labChat.js +204 -0
  111. package/dist/models/labChat.js.map +1 -0
  112. package/dist/models/marketing.d.ts +72 -0
  113. package/dist/models/marketing.d.ts.map +1 -0
  114. package/dist/models/marketing.js +26 -0
  115. package/dist/models/marketing.js.map +1 -0
  116. package/dist/models/message.d.ts +100 -0
  117. package/dist/models/message.d.ts.map +1 -0
  118. package/dist/models/message.js +131 -0
  119. package/dist/models/message.js.map +1 -0
  120. package/dist/models/newtonChat.d.ts +72 -0
  121. package/dist/models/newtonChat.d.ts.map +1 -0
  122. package/dist/models/newtonChat.js +61 -0
  123. package/dist/models/newtonChat.js.map +1 -0
  124. package/dist/models/notification.d.ts +65 -0
  125. package/dist/models/notification.d.ts.map +1 -0
  126. package/dist/models/notification.js +46 -0
  127. package/dist/models/notification.js.map +1 -0
  128. package/dist/models/section.d.ts +102 -0
  129. package/dist/models/section.d.ts.map +1 -0
  130. package/dist/models/section.js +83 -0
  131. package/dist/models/section.js.map +1 -0
  132. package/dist/models/user.d.ts +39 -0
  133. package/dist/models/user.d.ts.map +1 -0
  134. package/dist/models/user.js +38 -0
  135. package/dist/models/user.js.map +1 -0
  136. package/dist/models/worksheet.d.ts +460 -0
  137. package/dist/models/worksheet.d.ts.map +1 -0
  138. package/dist/models/worksheet.js +200 -0
  139. package/dist/models/worksheet.js.map +1 -0
  140. package/dist/pipelines/aiLabChat.d.ts +21 -0
  141. package/dist/pipelines/aiLabChat.d.ts.map +1 -0
  142. package/dist/pipelines/aiLabChat.js +460 -0
  143. package/dist/pipelines/aiLabChat.js.map +1 -0
  144. package/dist/pipelines/aiNewtonChat.d.ts +30 -0
  145. package/dist/pipelines/aiNewtonChat.d.ts.map +1 -0
  146. package/dist/pipelines/aiNewtonChat.js +289 -0
  147. package/dist/pipelines/aiNewtonChat.js.map +1 -0
  148. package/dist/pipelines/gradeWorksheet.d.ts +30 -0
  149. package/dist/pipelines/gradeWorksheet.d.ts.map +1 -0
  150. package/dist/pipelines/gradeWorksheet.js +252 -0
  151. package/dist/pipelines/gradeWorksheet.js.map +1 -0
  152. package/dist/routers/_app.d.ts +6403 -3741
  153. package/dist/routers/_app.d.ts.map +1 -1
  154. package/dist/routers/_app.js +10 -0
  155. package/dist/routers/_app.js.map +1 -0
  156. package/dist/routers/agenda.d.ts +58 -6
  157. package/dist/routers/agenda.d.ts.map +1 -1
  158. package/dist/routers/agenda.js +6 -58
  159. package/dist/routers/agenda.js.map +1 -0
  160. package/dist/routers/announcement.d.ts +325 -6
  161. package/dist/routers/announcement.d.ts.map +1 -1
  162. package/dist/routers/announcement.js +547 -57
  163. package/dist/routers/announcement.js.map +1 -0
  164. package/dist/routers/assignment.d.ts +431 -318
  165. package/dist/routers/assignment.d.ts.map +1 -1
  166. package/dist/routers/assignment.js +104 -1559
  167. package/dist/routers/assignment.js.map +1 -0
  168. package/dist/routers/attendance.d.ts +20 -9
  169. package/dist/routers/attendance.d.ts.map +1 -1
  170. package/dist/routers/attendance.js +10 -263
  171. package/dist/routers/attendance.js.map +1 -0
  172. package/dist/routers/auth.d.ts +21 -1
  173. package/dist/routers/auth.d.ts.map +1 -1
  174. package/dist/routers/auth.js +37 -241
  175. package/dist/routers/auth.js.map +1 -0
  176. package/dist/routers/class.d.ts +198 -68
  177. package/dist/routers/class.d.ts.map +1 -1
  178. package/dist/routers/class.js +88 -909
  179. package/dist/routers/class.js.map +1 -0
  180. package/dist/routers/comment.d.ts +153 -0
  181. package/dist/routers/comment.d.ts.map +1 -0
  182. package/dist/routers/comment.js +58 -0
  183. package/dist/routers/comment.js.map +1 -0
  184. package/dist/routers/conversation.d.ts +73 -3
  185. package/dist/routers/conversation.d.ts.map +1 -1
  186. package/dist/routers/conversation.js +23 -265
  187. package/dist/routers/conversation.js.map +1 -0
  188. package/dist/routers/event.d.ts +46 -37
  189. package/dist/routers/event.d.ts.map +1 -1
  190. package/dist/routers/event.js +15 -431
  191. package/dist/routers/event.js.map +1 -0
  192. package/dist/routers/file.d.ts +4 -2
  193. package/dist/routers/file.d.ts.map +1 -1
  194. package/dist/routers/file.js +11 -295
  195. package/dist/routers/file.js.map +1 -0
  196. package/dist/routers/folder.d.ts +21 -14
  197. package/dist/routers/folder.d.ts.map +1 -1
  198. package/dist/routers/folder.js +36 -743
  199. package/dist/routers/folder.js.map +1 -0
  200. package/dist/routers/labChat.d.ts +12 -9
  201. package/dist/routers/labChat.d.ts.map +1 -1
  202. package/dist/routers/labChat.js +21 -877
  203. package/dist/routers/labChat.js.map +1 -0
  204. package/dist/routers/marketing.d.ts +2 -2
  205. package/dist/routers/marketing.d.ts.map +1 -1
  206. package/dist/routers/marketing.js +9 -54
  207. package/dist/routers/marketing.js.map +1 -0
  208. package/dist/routers/message.d.ts +2 -1
  209. package/dist/routers/message.d.ts.map +1 -1
  210. package/dist/routers/message.js +29 -519
  211. package/dist/routers/message.js.map +1 -0
  212. package/dist/routers/newtonChat.d.ts +55 -0
  213. package/dist/routers/newtonChat.d.ts.map +1 -0
  214. package/dist/routers/newtonChat.js +22 -0
  215. package/dist/routers/newtonChat.js.map +1 -0
  216. package/dist/routers/notifications.d.ts +8 -8
  217. package/dist/routers/notifications.d.ts.map +1 -1
  218. package/dist/routers/notifications.js +20 -81
  219. package/dist/routers/notifications.js.map +1 -0
  220. package/dist/routers/section.d.ts +37 -6
  221. package/dist/routers/section.d.ts.map +1 -1
  222. package/dist/routers/section.js +26 -167
  223. package/dist/routers/section.js.map +1 -0
  224. package/dist/routers/user.d.ts +1 -1
  225. package/dist/routers/user.d.ts.map +1 -1
  226. package/dist/routers/user.js +34 -204
  227. package/dist/routers/user.js.map +1 -0
  228. package/dist/routers/worksheet.d.ts +362 -0
  229. package/dist/routers/worksheet.d.ts.map +1 -0
  230. package/dist/routers/worksheet.js +153 -0
  231. package/dist/routers/worksheet.js.map +1 -0
  232. package/dist/seedDatabase.d.ts +2 -3
  233. package/dist/seedDatabase.d.ts.map +1 -1
  234. package/dist/seedDatabase.js +311 -289
  235. package/dist/seedDatabase.js.map +1 -0
  236. package/dist/server/pipelines/aiLabChat.d.ts +21 -0
  237. package/dist/server/pipelines/aiLabChat.d.ts.map +1 -0
  238. package/dist/server/pipelines/aiLabChat.js +456 -0
  239. package/dist/server/pipelines/aiLabChat.js.map +1 -0
  240. package/dist/server/pipelines/aiNewtonChat.d.ts +30 -0
  241. package/dist/server/pipelines/aiNewtonChat.d.ts.map +1 -0
  242. package/dist/server/pipelines/aiNewtonChat.js +285 -0
  243. package/dist/server/pipelines/aiNewtonChat.js.map +1 -0
  244. package/dist/server/pipelines/gradeWorksheet.d.ts +30 -0
  245. package/dist/server/pipelines/gradeWorksheet.d.ts.map +1 -0
  246. package/dist/server/pipelines/gradeWorksheet.js +248 -0
  247. package/dist/server/pipelines/gradeWorksheet.js.map +1 -0
  248. package/dist/services/agenda.d.ts +100 -0
  249. package/dist/services/agenda.d.ts.map +1 -0
  250. package/dist/services/agenda.js +21 -0
  251. package/dist/services/agenda.js.map +1 -0
  252. package/dist/services/announcement.d.ts +135 -0
  253. package/dist/services/announcement.d.ts.map +1 -0
  254. package/dist/services/announcement.js +223 -0
  255. package/dist/services/announcement.js.map +1 -0
  256. package/dist/services/assignment.d.ts +1462 -0
  257. package/dist/services/assignment.d.ts.map +1 -0
  258. package/dist/services/assignment.js +898 -0
  259. package/dist/services/assignment.js.map +1 -0
  260. package/dist/services/attendance.d.ts +93 -0
  261. package/dist/services/attendance.d.ts.map +1 -0
  262. package/dist/services/attendance.js +61 -0
  263. package/dist/services/attendance.js.map +1 -0
  264. package/dist/services/auth.d.ts +68 -0
  265. package/dist/services/auth.d.ts.map +1 -0
  266. package/dist/services/auth.js +218 -0
  267. package/dist/services/auth.js.map +1 -0
  268. package/dist/services/class.d.ts +621 -0
  269. package/dist/services/class.d.ts.map +1 -0
  270. package/dist/services/class.js +474 -0
  271. package/dist/services/class.js.map +1 -0
  272. package/dist/services/comment.d.ts +100 -0
  273. package/dist/services/comment.d.ts.map +1 -0
  274. package/dist/services/comment.js +83 -0
  275. package/dist/services/comment.js.map +1 -0
  276. package/dist/services/conversation.d.ts +159 -0
  277. package/dist/services/conversation.d.ts.map +1 -0
  278. package/dist/services/conversation.js +138 -0
  279. package/dist/services/conversation.js.map +1 -0
  280. package/dist/services/event.d.ts +216 -0
  281. package/dist/services/event.d.ts.map +1 -0
  282. package/dist/services/event.js +168 -0
  283. package/dist/services/event.js.map +1 -0
  284. package/dist/services/file.d.ts +74 -0
  285. package/dist/services/file.d.ts.map +1 -0
  286. package/dist/services/file.js +133 -0
  287. package/dist/services/file.js.map +1 -0
  288. package/dist/services/folder.d.ts +239 -0
  289. package/dist/services/folder.d.ts.map +1 -0
  290. package/dist/services/folder.js +248 -0
  291. package/dist/services/folder.js.map +1 -0
  292. package/dist/services/labChat.d.ts +165 -0
  293. package/dist/services/labChat.d.ts.map +1 -0
  294. package/dist/services/labChat.js +289 -0
  295. package/dist/services/labChat.js.map +1 -0
  296. package/dist/services/marketing.d.ts +50 -0
  297. package/dist/services/marketing.d.ts.map +1 -0
  298. package/dist/services/marketing.js +32 -0
  299. package/dist/services/marketing.js.map +1 -0
  300. package/dist/services/message.d.ts +95 -0
  301. package/dist/services/message.d.ts.map +1 -0
  302. package/dist/services/message.js +350 -0
  303. package/dist/services/message.js.map +1 -0
  304. package/dist/services/newtonChat.d.ts +22 -0
  305. package/dist/services/newtonChat.d.ts.map +1 -0
  306. package/dist/services/newtonChat.js +174 -0
  307. package/dist/services/newtonChat.js.map +1 -0
  308. package/dist/services/notification.d.ts +65 -0
  309. package/dist/services/notification.d.ts.map +1 -0
  310. package/dist/services/notification.js +33 -0
  311. package/dist/services/notification.js.map +1 -0
  312. package/dist/services/section.d.ts +53 -0
  313. package/dist/services/section.d.ts.map +1 -0
  314. package/dist/services/section.js +199 -0
  315. package/dist/services/section.js.map +1 -0
  316. package/dist/services/user.d.ts +48 -0
  317. package/dist/services/user.d.ts.map +1 -0
  318. package/dist/services/user.js +141 -0
  319. package/dist/services/user.js.map +1 -0
  320. package/dist/services/worksheet.d.ts +239 -0
  321. package/dist/services/worksheet.d.ts.map +1 -0
  322. package/dist/services/worksheet.js +235 -0
  323. package/dist/services/worksheet.js.map +1 -0
  324. package/dist/socket/handlers.d.ts.map +1 -1
  325. package/dist/socket/handlers.js +4 -0
  326. package/dist/socket/handlers.js.map +1 -0
  327. package/dist/trpc.d.ts.map +1 -1
  328. package/dist/trpc.js +4 -0
  329. package/dist/trpc.js.map +1 -0
  330. package/dist/types/trpc.d.ts.map +1 -1
  331. package/dist/types/trpc.js +4 -0
  332. package/dist/types/trpc.js.map +1 -0
  333. package/dist/utils/aiUser.d.ts +1 -3
  334. package/dist/utils/aiUser.d.ts.map +1 -1
  335. package/dist/utils/aiUser.js +8 -3
  336. package/dist/utils/aiUser.js.map +1 -0
  337. package/dist/utils/email.d.ts +12 -1
  338. package/dist/utils/email.d.ts.map +1 -1
  339. package/dist/utils/email.js +26 -4
  340. package/dist/utils/email.js.map +1 -0
  341. package/dist/utils/generateInviteCode.d.ts +1 -2
  342. package/dist/utils/generateInviteCode.d.ts.map +1 -1
  343. package/dist/utils/generateInviteCode.js +5 -2
  344. package/dist/utils/generateInviteCode.js.map +1 -0
  345. package/dist/utils/inference.d.ts +8 -0
  346. package/dist/utils/inference.d.ts.map +1 -1
  347. package/dist/utils/inference.js +78 -10
  348. package/dist/utils/inference.js.map +1 -0
  349. package/dist/utils/logger.d.ts +4 -0
  350. package/dist/utils/logger.d.ts.map +1 -1
  351. package/dist/utils/logger.js +35 -3
  352. package/dist/utils/logger.js.map +1 -0
  353. package/dist/utils/prismaErrorHandler.d.ts.map +1 -1
  354. package/dist/utils/prismaErrorHandler.js +7 -0
  355. package/dist/utils/prismaErrorHandler.js.map +1 -0
  356. package/dist/utils/prismaWrapper.d.ts +1 -0
  357. package/dist/utils/prismaWrapper.d.ts.map +1 -1
  358. package/dist/utils/prismaWrapper.js +8 -0
  359. package/dist/utils/prismaWrapper.js.map +1 -0
  360. package/docker-compose.yml +19 -0
  361. package/package.json +21 -4
  362. package/prisma/migrations/20251109122857_annuoncements_comments/migration.sql +30 -0
  363. package/prisma/migrations/20251109135555_reactions_announcements_comments/migration.sql +35 -0
  364. package/prisma/schema.prisma +180 -12
  365. package/scripts/test-pre-push.ts +14 -0
  366. package/src/index.ts +247 -52
  367. package/src/instrument.ts +15 -0
  368. package/src/lib/config/env.ts +132 -0
  369. package/src/lib/fileUpload.ts +81 -16
  370. package/src/lib/googleCloudStorage.ts +42 -6
  371. package/src/lib/jsonConversion.ts +12 -14
  372. package/src/lib/prisma.ts +23 -2
  373. package/src/lib/pusher.ts +11 -6
  374. package/src/lib/redis.ts +56 -0
  375. package/src/lib/thumbnailGenerator.ts +170 -168
  376. package/src/middleware/auth.ts +86 -137
  377. package/src/middleware/security.ts +80 -0
  378. package/src/models/agenda.ts +46 -0
  379. package/src/models/announcement.ts +134 -0
  380. package/src/models/assignment.ts +322 -0
  381. package/src/models/attendance.ts +208 -0
  382. package/src/models/auth.ts +247 -0
  383. package/src/models/class.ts +598 -0
  384. package/src/models/comment.ts +152 -0
  385. package/src/models/conversation.ts +200 -0
  386. package/src/models/event.ts +177 -0
  387. package/src/models/file.ts +129 -0
  388. package/src/models/folder.ts +225 -0
  389. package/src/models/labChat.ts +213 -0
  390. package/src/models/marketing.ts +45 -0
  391. package/src/models/message.ts +153 -0
  392. package/src/models/newtonChat.ts +70 -0
  393. package/src/models/notification.ts +54 -0
  394. package/src/models/section.ts +98 -0
  395. package/src/models/user.ts +47 -0
  396. package/src/models/worksheet.ts +294 -0
  397. package/src/pipelines/aiLabChat.ts +511 -0
  398. package/src/pipelines/aiNewtonChat.ts +347 -0
  399. package/src/pipelines/gradeWorksheet.ts +286 -0
  400. package/src/routers/_app.ts +6 -0
  401. package/src/routers/agenda.ts +3 -61
  402. package/src/routers/announcement.ts +622 -57
  403. package/src/routers/assignment.ts +157 -1688
  404. package/src/routers/attendance.ts +16 -277
  405. package/src/routers/auth.ts +79 -313
  406. package/src/routers/class.ts +265 -1038
  407. package/src/routers/comment.ts +76 -0
  408. package/src/routers/conversation.ts +53 -284
  409. package/src/routers/event.ts +50 -481
  410. package/src/routers/file.ts +45 -341
  411. package/src/routers/folder.ts +107 -836
  412. package/src/routers/labChat.ts +29 -960
  413. package/src/routers/marketing.ts +35 -77
  414. package/src/routers/message.ts +45 -571
  415. package/src/routers/newtonChat.ts +36 -0
  416. package/src/routers/notifications.ts +32 -82
  417. package/src/routers/section.ts +58 -200
  418. package/src/routers/user.ts +49 -226
  419. package/src/routers/worksheet.ts +252 -0
  420. package/src/seedDatabase.ts +330 -290
  421. package/src/services/agenda.ts +21 -0
  422. package/src/services/announcement.ts +290 -0
  423. package/src/services/assignment.ts +1198 -0
  424. package/src/services/attendance.ts +85 -0
  425. package/src/services/auth.ts +277 -0
  426. package/src/services/class.ts +622 -0
  427. package/src/services/comment.ts +106 -0
  428. package/src/services/conversation.ts +213 -0
  429. package/src/services/event.ts +231 -0
  430. package/src/services/file.ts +167 -0
  431. package/src/services/folder.ts +316 -0
  432. package/src/services/labChat.ts +352 -0
  433. package/src/services/marketing.ts +57 -0
  434. package/src/services/message.ts +461 -0
  435. package/src/services/newtonChat.ts +222 -0
  436. package/src/services/notification.ts +50 -0
  437. package/src/services/section.ts +283 -0
  438. package/src/services/user.ts +172 -0
  439. package/src/services/worksheet.ts +358 -0
  440. package/src/trpc.ts +4 -0
  441. package/src/utils/aiUser.ts +4 -3
  442. package/src/utils/email.ts +33 -4
  443. package/src/utils/generateInviteCode.ts +1 -3
  444. package/src/utils/inference.ts +89 -10
  445. package/src/utils/logger.ts +33 -3
  446. package/src/utils/prismaErrorHandler.ts +3 -0
  447. package/src/utils/prismaWrapper.ts +4 -0
  448. package/tests/globalSetup.ts +62 -0
  449. package/tests/helpers.ts +22 -0
  450. package/tests/middleware/security.test.ts +42 -0
  451. package/tests/routers/agenda.test.ts +138 -0
  452. package/tests/routers/announcement.test.ts +490 -0
  453. package/tests/routers/assignment.test.ts +837 -0
  454. package/tests/routers/attendance.test.ts +160 -0
  455. package/tests/routers/auth.test.ts +171 -0
  456. package/tests/{class.test.ts → routers/class.test.ts} +163 -92
  457. package/tests/routers/comment.test.ts +126 -0
  458. package/tests/routers/conversation.test.ts +145 -0
  459. package/tests/routers/event.test.ts +289 -0
  460. package/tests/routers/folder.test.ts +178 -0
  461. package/tests/routers/labChat.test.ts +115 -0
  462. package/tests/routers/marketing.test.ts +59 -0
  463. package/tests/routers/message.test.ts +123 -0
  464. package/tests/routers/notification.test.ts +69 -0
  465. package/tests/routers/section.test.ts +208 -0
  466. package/tests/server/rateLimit.test.ts +73 -0
  467. package/tests/setup.ts +39 -65
  468. package/tests/user.test.ts +136 -0
  469. package/tests/utils/aiUser.test.ts +22 -0
  470. package/tests/utils/generateInviteCode.test.ts +24 -0
  471. package/tests/utils/logger.test.ts +74 -0
  472. package/tests/utils/prismaErrorHandler.test.ts +101 -0
  473. package/tests/utils/prismaWrapper.test.ts +82 -0
  474. package/tests/worksheet.test.ts +181 -0
  475. package/tsconfig.json +9 -2
  476. package/vitest.config.ts +30 -1
  477. package/vitest.unit.config.ts +21 -0
  478. package/API_SPECIFICATION.md +0 -1597
  479. package/BASE64_REMOVAL_SUMMARY.md +0 -164
  480. package/CHAT_API_SPEC.md +0 -579
  481. package/LAB_CHAT_API_SPEC.md +0 -518
  482. package/dist/routers/school.d.ts +0 -208
  483. package/dist/routers/school.d.ts.map +0 -1
  484. package/dist/routers/school.js +0 -481
  485. package/tests/auth.test.ts +0 -25
@@ -1,9 +1,11 @@
1
+
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="6ca6683e-2a89-5dab-a4c5-bf0292654572")}catch(e){}}();
1
3
  import { z } from "zod";
2
4
  import { createTRPCRouter, protectedProcedure, protectedClassMemberProcedure, protectedTeacherProcedure } from "../trpc.js";
3
5
  import { TRPCError } from "@trpc/server";
4
6
  import { prisma } from "../lib/prisma.js";
5
7
  import { createDirectUploadFiles, confirmDirectUpload, updateUploadProgress } from "../lib/fileUpload.js";
6
- import { deleteFile } from "../lib/googleCloudStorage.js";
8
+ import { assignmentExists, getDueToday, getAssignment, getSubmission, getSubmissionById, getSubmissions, createAssignmentRecord, updateAssignmentRecord, deleteAssignmentRecord, updateSubmissionRecord, updateSubmissionAsTeacherRecord, attachAssignmentToEventRecord, detachAssignmentFromEventRecord, getAvailableEventsForAssignment, attachMarkSchemeRecord, detachMarkSchemeRecord, attachGradingBoundaryRecord, detachGradingBoundaryRecord, reorderAssignmentRecord, moveAssignmentRecord, } from "../services/assignment.js";
7
9
  // DEPRECATED: This schema is no longer used - files are uploaded directly to GCS
8
10
  // Use directFileSchema instead
9
11
  // New schema for direct file uploads (no base64 data)
@@ -15,11 +17,19 @@ const directFileSchema = z.object({
15
17
  });
16
18
  const createAssignmentSchema = z.object({
17
19
  classId: z.string(),
20
+ id: z.string().optional(),
18
21
  title: z.string(),
19
22
  instructions: z.string(),
20
23
  dueDate: z.string(),
21
24
  files: z.array(directFileSchema).optional(), // Use direct file schema
22
25
  existingFileIds: z.array(z.string()).optional(),
26
+ aiPolicyLevel: z.number().default(0),
27
+ acceptFiles: z.boolean().optional(),
28
+ acceptExtendedResponse: z.boolean().optional(),
29
+ acceptWorksheet: z.boolean().optional(),
30
+ worksheetIds: z.array(z.string()).optional(),
31
+ gradeWithAI: z.boolean().optional(),
32
+ studentIds: z.array(z.string()).optional(),
23
33
  maxGrade: z.number().optional(),
24
34
  graded: z.boolean().optional(),
25
35
  weight: z.number().optional(),
@@ -36,6 +46,13 @@ const updateAssignmentSchema = z.object({
36
46
  instructions: z.string().optional(),
37
47
  dueDate: z.string().optional(),
38
48
  files: z.array(directFileSchema).optional(), // Use direct file schema
49
+ aiPolicyLevel: z.number().default(0),
50
+ acceptFiles: z.boolean().optional(),
51
+ acceptExtendedResponse: z.boolean().optional(),
52
+ acceptWorksheet: z.boolean().optional(),
53
+ worksheetIds: z.array(z.string()).optional(),
54
+ gradeWithAI: z.boolean().optional(),
55
+ studentIds: z.array(z.string()).optional(),
39
56
  existingFileIds: z.array(z.string()).optional(),
40
57
  removedAttachments: z.array(z.string()).optional(),
41
58
  maxGrade: z.number().optional(),
@@ -59,6 +76,7 @@ const submissionSchema = z.object({
59
76
  submissionId: z.string(),
60
77
  submit: z.boolean().optional(),
61
78
  newAttachments: z.array(directFileSchema).optional(), // Use direct file schema
79
+ extendedResponse: z.string().optional(),
62
80
  existingFileIds: z.array(z.string()).optional(),
63
81
  removedAttachments: z.array(z.string()).optional(),
64
82
  });
@@ -115,1584 +133,109 @@ const updateUploadProgressSchema = z.object({
115
133
  progress: z.number().min(0).max(100),
116
134
  });
117
135
  export const assignmentRouter = createTRPCRouter({
118
- order: protectedTeacherProcedure
136
+ reorder: protectedTeacherProcedure
119
137
  .input(z.object({
120
- id: z.string(),
121
138
  classId: z.string(),
122
- order: z.number(),
139
+ movedId: z.string(),
140
+ position: z.enum(['start', 'end', 'before', 'after']),
141
+ targetId: z.string().optional(),
123
142
  }))
124
- .mutation(async ({ ctx, input }) => {
125
- const { id, order } = input;
126
- const assignment = await prisma.assignment.update({
127
- where: { id },
128
- data: { order },
129
- });
130
- return assignment;
131
- }),
143
+ .mutation(({ ctx, input }) => reorderAssignmentRecord(ctx.user.id, {
144
+ classId: input.classId,
145
+ movedId: input.movedId,
146
+ position: input.position,
147
+ targetId: input.targetId,
148
+ })),
149
+ exists: protectedClassMemberProcedure
150
+ .input(z.object({ id: z.string() }))
151
+ .query(({ input }) => assignmentExists(input.id)),
132
152
  move: protectedTeacherProcedure
133
153
  .input(z.object({
134
154
  id: z.string(),
135
155
  classId: z.string(),
136
- targetSectionId: z.string(),
156
+ targetSectionId: z.string().nullable().optional(),
137
157
  }))
138
- .mutation(async ({ ctx, input }) => {
139
- const { id, targetSectionId } = input;
140
- const assignments = await prisma.assignment.findMany({
141
- where: { sectionId: targetSectionId },
142
- });
143
- const stack = assignments.sort((a, b) => (a.order || 0) - (b.order || 0)).map((assignment, index) => ({
144
- id: assignment.id,
145
- order: index + 1,
146
- })).map((assignment) => ({
147
- where: { id: assignment.id },
148
- data: { order: assignment.order },
149
- }));
150
- await Promise.all(stack.map(({ where, data }) => prisma.assignment.update({ where, data })));
151
- const assignment = await prisma.assignment.update({
152
- where: { id },
153
- data: { sectionId: targetSectionId, order: 0 },
154
- });
155
- return assignment;
156
- }),
157
- create: protectedProcedure
158
+ .mutation(({ ctx, input }) => moveAssignmentRecord(ctx.user.id, {
159
+ id: input.id,
160
+ classId: input.classId,
161
+ targetSectionId: (input.targetSectionId ?? null) || null,
162
+ })),
163
+ create: protectedTeacherProcedure
158
164
  .input(createAssignmentSchema)
159
- .mutation(async ({ ctx, input }) => {
160
- const { classId, title, instructions, dueDate, files, existingFileIds, maxGrade, graded, weight, sectionId, type, markSchemeId, gradingBoundaryId, inProgress } = input;
161
- if (!ctx.user) {
162
- throw new TRPCError({
163
- code: "UNAUTHORIZED",
164
- message: "User must be authenticated",
165
- });
166
- }
167
- // Get all students in the class
168
- const classData = await prisma.class.findUnique({
169
- where: { id: classId },
170
- include: {
171
- students: {
172
- select: { id: true }
173
- }
174
- }
175
- });
176
- if (!classData) {
177
- throw new TRPCError({
178
- code: "NOT_FOUND",
179
- message: "Class not found",
180
- });
181
- }
182
- let computedMaxGrade = maxGrade;
183
- if (markSchemeId) {
184
- const rubric = await prisma.markScheme.findUnique({
185
- where: { id: markSchemeId },
186
- select: {
187
- structured: true,
188
- }
189
- });
190
- const parsedRubric = JSON.parse(rubric?.structured || "{}");
191
- // Calculate max grade from rubric criteria levels
192
- computedMaxGrade = parsedRubric.criteria.reduce((acc, criterion) => {
193
- const maxPoints = Math.max(...criterion.levels.map((level) => level.points));
194
- return acc + maxPoints;
195
- }, 0);
196
- }
197
- console.log(markSchemeId, gradingBoundaryId);
198
- // find all assignments in the section it is in (or none) and reorder them
199
- const assignments = await prisma.assignment.findMany({
200
- where: {
201
- classId: classId,
202
- ...(sectionId && {
203
- sectionId: sectionId,
204
- }),
205
- },
206
- });
207
- const stack = assignments.sort((a, b) => (a.order || 0) - (b.order || 0)).map((assignment, index) => ({
208
- id: assignment.id,
209
- order: index + 1,
210
- })).map((assignment) => ({
211
- where: { id: assignment.id },
212
- data: { order: assignment.order },
213
- }));
214
- // Create assignment with submissions for all students
215
- const assignment = await prisma.assignment.create({
216
- data: {
217
- title,
218
- instructions,
219
- dueDate: new Date(dueDate),
220
- maxGrade: markSchemeId ? computedMaxGrade : maxGrade,
221
- graded,
222
- weight,
223
- type,
224
- order: 0,
225
- inProgress: inProgress || false,
226
- class: {
227
- connect: { id: classId }
228
- },
229
- ...(sectionId && {
230
- section: {
231
- connect: { id: sectionId }
232
- }
233
- }),
234
- ...(markSchemeId && {
235
- markScheme: {
236
- connect: { id: markSchemeId }
237
- }
238
- }),
239
- ...(gradingBoundaryId && {
240
- gradingBoundary: {
241
- connect: { id: gradingBoundaryId }
242
- }
243
- }),
244
- submissions: {
245
- create: classData.students.map((student) => ({
246
- student: {
247
- connect: { id: student.id }
248
- }
249
- }))
250
- },
251
- teacher: {
252
- connect: { id: ctx.user.id }
253
- }
254
- },
255
- select: {
256
- id: true,
257
- title: true,
258
- instructions: true,
259
- dueDate: true,
260
- maxGrade: true,
261
- graded: true,
262
- weight: true,
263
- type: true,
264
- attachments: {
265
- select: {
266
- id: true,
267
- name: true,
268
- type: true,
269
- }
270
- },
271
- section: {
272
- select: {
273
- id: true,
274
- name: true
275
- }
276
- },
277
- teacher: {
278
- select: {
279
- id: true,
280
- username: true
281
- }
282
- },
283
- class: {
284
- select: {
285
- id: true,
286
- name: true
287
- }
288
- }
289
- }
290
- });
291
- await Promise.all(stack.map(({ where, data }) => prisma.assignment.update({ where, data })));
292
- // NOTE: Files are now handled via direct upload endpoints
293
- // The files field in the schema is for metadata only
294
- // Actual file uploads should use getAssignmentUploadUrls endpoint
295
- let uploadedFiles = [];
296
- if (files && files.length > 0) {
297
- // Create direct upload files instead of processing base64
298
- uploadedFiles = await createDirectUploadFiles(files, ctx.user.id, undefined, assignment.id);
299
- }
300
- // Update assignment with new file attachments
301
- if (uploadedFiles.length > 0) {
302
- await prisma.assignment.update({
303
- where: { id: assignment.id },
304
- data: {
305
- attachments: {
306
- create: uploadedFiles.map(file => ({
307
- name: file.name,
308
- type: file.type,
309
- size: file.size,
310
- path: file.path,
311
- ...(file.thumbnailId && {
312
- thumbnail: {
313
- connect: { id: file.thumbnailId }
314
- }
315
- })
316
- }))
317
- }
318
- }
319
- });
320
- }
321
- // Connect existing files if provided
322
- if (existingFileIds && existingFileIds.length > 0) {
323
- await prisma.assignment.update({
324
- where: { id: assignment.id },
325
- data: {
326
- attachments: {
327
- connect: existingFileIds.map(fileId => ({ id: fileId }))
328
- }
329
- }
330
- });
331
- }
332
- return assignment;
333
- }),
334
- update: protectedProcedure
165
+ .mutation(({ ctx, input }) => createAssignmentRecord(ctx.user.id, input)),
166
+ update: protectedTeacherProcedure
335
167
  .input(updateAssignmentSchema)
336
- .mutation(async ({ ctx, input }) => {
337
- const { id, title, instructions, dueDate, files, existingFileIds, maxGrade, graded, weight, sectionId, type, inProgress } = input;
338
- if (!ctx.user) {
339
- throw new TRPCError({
340
- code: "UNAUTHORIZED",
341
- message: "User must be authenticated",
342
- });
343
- }
344
- // Get the assignment with current attachments
345
- const assignment = await prisma.assignment.findFirst({
346
- where: {
347
- id,
348
- teacherId: ctx.user.id,
349
- },
350
- include: {
351
- attachments: {
352
- select: {
353
- id: true,
354
- name: true,
355
- type: true,
356
- path: true,
357
- size: true,
358
- thumbnail: {
359
- select: {
360
- path: true
361
- }
362
- }
363
- },
364
- },
365
- class: {
366
- select: {
367
- id: true,
368
- name: true
369
- }
370
- },
371
- },
372
- });
373
- if (!assignment) {
374
- throw new TRPCError({
375
- code: "NOT_FOUND",
376
- message: "Assignment not found",
377
- });
378
- }
379
- // NOTE: Files are now handled via direct upload endpoints
380
- let uploadedFiles = [];
381
- if (files && files.length > 0) {
382
- // Create direct upload files instead of processing base64
383
- uploadedFiles = await createDirectUploadFiles(files, ctx.user.id, undefined, input.id);
384
- }
385
- // Update assignment
386
- const updatedAssignment = await prisma.assignment.update({
387
- where: { id },
388
- data: {
389
- ...(title && { title }),
390
- ...(instructions && { instructions }),
391
- ...(dueDate && { dueDate: new Date(dueDate) }),
392
- ...(maxGrade && { maxGrade }),
393
- ...(graded !== undefined && { graded }),
394
- ...(weight && { weight }),
395
- ...(type && { type }),
396
- ...(inProgress !== undefined && { inProgress }),
397
- ...(sectionId !== undefined && {
398
- section: sectionId ? {
399
- connect: { id: sectionId }
400
- } : {
401
- disconnect: true
402
- }
403
- }),
404
- ...(uploadedFiles.length > 0 && {
405
- attachments: {
406
- create: uploadedFiles.map(file => ({
407
- name: file.name,
408
- type: file.type,
409
- size: file.size,
410
- path: file.path,
411
- ...(file.thumbnailId && {
412
- thumbnail: {
413
- connect: { id: file.thumbnailId }
414
- }
415
- })
416
- }))
417
- }
418
- }),
419
- ...(existingFileIds && existingFileIds.length > 0 && {
420
- attachments: {
421
- connect: existingFileIds.map(fileId => ({ id: fileId }))
422
- }
423
- }),
424
- ...(input.removedAttachments && input.removedAttachments.length > 0 && {
425
- attachments: {
426
- deleteMany: {
427
- id: { in: input.removedAttachments }
428
- }
429
- }
430
- }),
431
- },
432
- select: {
433
- id: true,
434
- title: true,
435
- instructions: true,
436
- dueDate: true,
437
- maxGrade: true,
438
- graded: true,
439
- weight: true,
440
- type: true,
441
- createdAt: true,
442
- submissions: {
443
- select: {
444
- student: {
445
- select: {
446
- id: true,
447
- username: true
448
- }
449
- }
450
- }
451
- },
452
- attachments: {
453
- select: {
454
- id: true,
455
- name: true,
456
- type: true,
457
- thumbnail: true,
458
- size: true,
459
- path: true,
460
- uploadedAt: true,
461
- thumbnailId: true,
462
- }
463
- },
464
- section: true,
465
- teacher: true,
466
- class: true
467
- }
468
- });
469
- if (assignment.markSchemeId) {
470
- const rubric = await prisma.markScheme.findUnique({
471
- where: { id: assignment.markSchemeId },
472
- select: {
473
- structured: true,
474
- }
475
- });
476
- const parsedRubric = JSON.parse(rubric?.structured || "{}");
477
- const computedMaxGrade = parsedRubric.criteria.reduce((acc, criterion) => {
478
- const maxPoints = Math.max(...criterion.levels.map((level) => level.points));
479
- return acc + maxPoints;
480
- }, 0);
481
- await prisma.assignment.update({
482
- where: { id },
483
- data: {
484
- maxGrade: computedMaxGrade,
485
- }
486
- });
487
- }
488
- return updatedAssignment;
489
- }),
168
+ .mutation(({ ctx, input }) => updateAssignmentRecord(ctx.user.id, input)),
490
169
  delete: protectedProcedure
491
170
  .input(deleteAssignmentSchema)
492
- .mutation(async ({ ctx, input }) => {
493
- const { id, classId } = input;
494
- if (!ctx.user) {
495
- throw new TRPCError({
496
- code: "UNAUTHORIZED",
497
- message: "User must be authenticated",
498
- });
499
- }
500
- // Get the assignment with all related files
501
- const assignment = await prisma.assignment.findFirst({
502
- where: {
503
- id,
504
- teacherId: ctx.user.id,
505
- },
506
- include: {
507
- attachments: {
508
- include: {
509
- thumbnail: true
510
- }
511
- },
512
- submissions: {
513
- include: {
514
- attachments: {
515
- include: {
516
- thumbnail: true
517
- }
518
- },
519
- annotations: {
520
- include: {
521
- thumbnail: true
522
- }
523
- }
524
- }
525
- }
526
- }
527
- });
528
- if (!assignment) {
529
- throw new TRPCError({
530
- code: "NOT_FOUND",
531
- message: "Assignment not found",
532
- });
533
- }
534
- // Delete all files from storage
535
- const filesToDelete = [
536
- ...assignment.attachments,
537
- ...assignment.submissions.flatMap(sub => [...sub.attachments, ...sub.annotations])
538
- ];
539
- // Delete files from storage
540
- await Promise.all(filesToDelete.map(async (file) => {
541
- try {
542
- // Delete the main file
543
- await deleteFile(file.path);
544
- // Delete thumbnail if it exists
545
- if (file.thumbnail) {
546
- await deleteFile(file.thumbnail.path);
547
- }
548
- }
549
- catch (error) {
550
- console.warn(`Failed to delete file ${file.path}:`, error);
551
- }
552
- }));
553
- // Delete the assignment (this will cascade delete all related records)
554
- await prisma.assignment.delete({
555
- where: { id },
556
- });
557
- return {
558
- id,
559
- };
560
- }),
561
- get: protectedProcedure
171
+ .mutation(({ ctx, input }) => deleteAssignmentRecord(ctx.user.id, input.id, input.classId)),
172
+ get: protectedClassMemberProcedure
562
173
  .input(getAssignmentSchema)
563
- .query(async ({ ctx, input }) => {
564
- const { id, classId } = input;
565
- if (!ctx.user) {
566
- throw new TRPCError({
567
- code: "UNAUTHORIZED",
568
- message: "User must be authenticated",
569
- });
570
- }
571
- const assignment = await prisma.assignment.findUnique({
572
- where: {
573
- id,
574
- // classId,
575
- },
576
- include: {
577
- submissions: {
578
- select: {
579
- student: {
580
- select: {
581
- id: true,
582
- username: true
583
- }
584
- }
585
- }
586
- },
587
- attachments: {
588
- select: {
589
- id: true,
590
- name: true,
591
- type: true,
592
- size: true,
593
- path: true,
594
- uploadedAt: true,
595
- thumbnailId: true,
596
- }
597
- },
598
- section: {
599
- select: {
600
- id: true,
601
- name: true,
602
- }
603
- },
604
- teacher: {
605
- select: {
606
- id: true,
607
- username: true
608
- }
609
- },
610
- class: {
611
- select: {
612
- id: true,
613
- name: true
614
- }
615
- },
616
- eventAttached: {
617
- select: {
618
- id: true,
619
- name: true,
620
- startTime: true,
621
- endTime: true,
622
- location: true,
623
- remarks: true,
624
- }
625
- },
626
- markScheme: {
627
- select: {
628
- id: true,
629
- structured: true,
630
- }
631
- },
632
- gradingBoundary: {
633
- select: {
634
- id: true,
635
- structured: true,
636
- }
637
- }
638
- }
639
- });
640
- if (!assignment) {
641
- throw new TRPCError({
642
- code: "NOT_FOUND",
643
- message: "Assignment not found",
644
- });
645
- }
646
- const sections = await prisma.section.findMany({
647
- where: {
648
- classId: assignment.classId,
649
- },
650
- select: {
651
- id: true,
652
- name: true,
653
- },
654
- });
655
- return { ...assignment, sections };
656
- }),
174
+ .query(({ input }) => getAssignment(input.id, input.classId)),
657
175
  getSubmission: protectedClassMemberProcedure
176
+ .input(z.object({ assignmentId: z.string(), classId: z.string() }))
177
+ .query(({ ctx, input }) => getSubmission(input.assignmentId, ctx.user.id)),
178
+ getSubmissionById: protectedClassMemberProcedure
179
+ .input(z.object({ classId: z.string(), submissionId: z.string() }))
180
+ .query(({ ctx, input }) => getSubmissionById(input.submissionId, input.classId, ctx.user.id)),
181
+ updateSubmission: protectedClassMemberProcedure
182
+ .input(submissionSchema)
183
+ .mutation(({ ctx, input }) => updateSubmissionRecord(ctx.user.id, {
184
+ submissionId: input.submissionId,
185
+ assignmentId: input.assignmentId,
186
+ classId: input.classId,
187
+ submit: input.submit,
188
+ newAttachments: input.newAttachments,
189
+ extendedResponse: input.extendedResponse,
190
+ existingFileIds: input.existingFileIds,
191
+ removedAttachments: input.removedAttachments,
192
+ })),
193
+ getSubmissions: protectedTeacherProcedure
194
+ .input(z.object({ assignmentId: z.string(), classId: z.string() }))
195
+ .query(({ ctx, input }) => getSubmissions(input.assignmentId, ctx.user.id)),
196
+ updateSubmissionAsTeacher: protectedTeacherProcedure
197
+ .input(updateSubmissionSchema)
198
+ .mutation(({ ctx, input }) => updateSubmissionAsTeacherRecord(ctx.user.id, {
199
+ submissionId: input.submissionId,
200
+ assignmentId: input.assignmentId,
201
+ classId: input.classId,
202
+ return: input.return,
203
+ gradeReceived: input.gradeReceived,
204
+ newAttachments: input.newAttachments,
205
+ existingFileIds: input.existingFileIds,
206
+ removedAttachments: input.removedAttachments,
207
+ rubricGrades: input.rubricGrades,
208
+ feedback: input.feedback,
209
+ })),
210
+ attachToEvent: protectedTeacherProcedure
211
+ .input(z.object({ assignmentId: z.string(), eventId: z.string() }))
212
+ .mutation(({ ctx, input }) => attachAssignmentToEventRecord(ctx.user.id, input.assignmentId, input.eventId)),
213
+ detachEvent: protectedTeacherProcedure
214
+ .input(z.object({ assignmentId: z.string() }))
215
+ .mutation(({ ctx, input }) => detachAssignmentFromEventRecord(ctx.user.id, input.assignmentId)),
216
+ getAvailableEvents: protectedTeacherProcedure
217
+ .input(z.object({ assignmentId: z.string() }))
218
+ .query(({ ctx, input }) => getAvailableEventsForAssignment(ctx.user.id, input.assignmentId)),
219
+ dueToday: protectedProcedure
220
+ .query(() => getDueToday()),
221
+ attachMarkScheme: protectedTeacherProcedure
658
222
  .input(z.object({
659
223
  assignmentId: z.string(),
660
- classId: z.string(),
224
+ markSchemeId: z.string().nullable(),
661
225
  }))
662
- .query(async ({ ctx, input }) => {
663
- if (!ctx.user) {
664
- throw new TRPCError({
665
- code: "UNAUTHORIZED",
666
- message: "User must be authenticated",
667
- });
668
- }
669
- const { assignmentId } = input;
670
- const submission = await prisma.submission.findFirst({
671
- where: {
672
- assignmentId,
673
- studentId: ctx.user.id,
674
- },
675
- include: {
676
- attachments: true,
677
- student: {
678
- select: {
679
- id: true,
680
- username: true,
681
- },
682
- },
683
- assignment: {
684
- include: {
685
- class: true,
686
- markScheme: {
687
- select: {
688
- id: true,
689
- structured: true,
690
- }
691
- },
692
- gradingBoundary: {
693
- select: {
694
- id: true,
695
- structured: true,
696
- }
697
- }
698
- },
699
- },
700
- annotations: true,
701
- },
702
- });
703
- if (!submission) {
704
- // Create a new submission if it doesn't exist
705
- return await prisma.submission.create({
706
- data: {
707
- assignment: {
708
- connect: { id: assignmentId },
709
- },
710
- student: {
711
- connect: { id: ctx.user.id },
712
- },
713
- },
714
- include: {
715
- attachments: true,
716
- annotations: true,
717
- student: {
718
- select: {
719
- id: true,
720
- username: true,
721
- },
722
- },
723
- assignment: {
724
- include: {
725
- class: true,
726
- markScheme: {
727
- select: {
728
- id: true,
729
- structured: true,
730
- }
731
- },
732
- gradingBoundary: {
733
- select: {
734
- id: true,
735
- structured: true,
736
- }
737
- }
738
- },
739
- },
740
- },
741
- });
742
- }
743
- return {
744
- ...submission,
745
- late: submission.assignment.dueDate < new Date(),
746
- };
747
- }),
748
- getSubmissionById: protectedTeacherProcedure
226
+ .mutation(({ ctx, input }) => attachMarkSchemeRecord(ctx.user.id, input.assignmentId, input.markSchemeId)),
227
+ detachMarkScheme: protectedTeacherProcedure
228
+ .input(z.object({ assignmentId: z.string() }))
229
+ .mutation(({ ctx, input }) => detachMarkSchemeRecord(ctx.user.id, input.assignmentId)),
230
+ attachGradingBoundary: protectedTeacherProcedure
749
231
  .input(z.object({
750
- submissionId: z.string(),
751
- classId: z.string(),
232
+ assignmentId: z.string(),
233
+ gradingBoundaryId: z.string().nullable(),
752
234
  }))
753
- .query(async ({ ctx, input }) => {
754
- if (!ctx.user) {
755
- throw new TRPCError({
756
- code: "UNAUTHORIZED",
757
- message: "User must be authenticated",
758
- });
759
- }
760
- const { submissionId, classId } = input;
761
- const submission = await prisma.submission.findFirst({
762
- where: {
763
- id: submissionId,
764
- assignment: {
765
- classId,
766
- class: {
767
- teachers: {
768
- some: {
769
- id: ctx.user.id
770
- }
771
- }
772
- }
773
- },
774
- },
775
- include: {
776
- attachments: true,
777
- annotations: true,
778
- student: {
779
- select: {
780
- id: true,
781
- username: true,
782
- profile: {
783
- select: {
784
- displayName: true,
785
- profilePicture: true,
786
- }
787
- }
788
- },
789
- },
790
- assignment: {
791
- include: {
792
- class: true,
793
- markScheme: {
794
- select: {
795
- id: true,
796
- structured: true,
797
- }
798
- },
799
- gradingBoundary: {
800
- select: {
801
- id: true,
802
- structured: true,
803
- }
804
- }
805
- },
806
- },
807
- },
808
- });
809
- if (!submission) {
810
- throw new TRPCError({
811
- code: "NOT_FOUND",
812
- message: "Submission not found",
813
- });
814
- }
815
- return {
816
- ...submission,
817
- late: submission.assignment.dueDate < new Date(),
818
- };
819
- }),
820
- updateSubmission: protectedClassMemberProcedure
821
- .input(submissionSchema)
822
- .mutation(async ({ ctx, input }) => {
823
- if (!ctx.user) {
824
- throw new TRPCError({
825
- code: "UNAUTHORIZED",
826
- message: "User must be authenticated",
827
- });
828
- }
829
- const { submissionId, submit, newAttachments, existingFileIds, removedAttachments } = input;
830
- const submission = await prisma.submission.findFirst({
831
- where: {
832
- id: submissionId,
833
- OR: [
834
- {
835
- student: {
836
- id: ctx.user.id,
837
- },
838
- },
839
- {
840
- assignment: {
841
- class: {
842
- teachers: {
843
- some: {
844
- id: ctx.user.id,
845
- },
846
- },
847
- },
848
- },
849
- },
850
- ],
851
- },
852
- include: {
853
- attachments: {
854
- include: {
855
- thumbnail: true
856
- }
857
- },
858
- assignment: {
859
- include: {
860
- class: true,
861
- markScheme: {
862
- select: {
863
- id: true,
864
- structured: true,
865
- }
866
- },
867
- gradingBoundary: {
868
- select: {
869
- id: true,
870
- structured: true,
871
- }
872
- }
873
- },
874
- },
875
- },
876
- });
877
- if (!submission) {
878
- throw new TRPCError({
879
- code: "NOT_FOUND",
880
- message: "Submission not found",
881
- });
882
- }
883
- if (submit !== undefined) {
884
- // Toggle submission status
885
- return await prisma.submission.update({
886
- where: { id: submission.id },
887
- data: {
888
- submitted: !submission.submitted,
889
- submittedAt: new Date(),
890
- },
891
- include: {
892
- attachments: true,
893
- student: {
894
- select: {
895
- id: true,
896
- username: true,
897
- },
898
- },
899
- assignment: {
900
- include: {
901
- class: true,
902
- markScheme: {
903
- select: {
904
- id: true,
905
- structured: true,
906
- }
907
- },
908
- gradingBoundary: {
909
- select: {
910
- id: true,
911
- structured: true,
912
- }
913
- }
914
- },
915
- },
916
- },
917
- });
918
- }
919
- let uploadedFiles = [];
920
- if (newAttachments && newAttachments.length > 0) {
921
- // Store files in a class and assignment specific directory
922
- uploadedFiles = await createDirectUploadFiles(newAttachments, ctx.user.id, undefined, undefined, submission.id);
923
- }
924
- // Update submission with new file attachments
925
- if (uploadedFiles.length > 0) {
926
- await prisma.submission.update({
927
- where: { id: submission.id },
928
- data: {
929
- attachments: {
930
- create: uploadedFiles.map(file => ({
931
- name: file.name,
932
- type: file.type,
933
- size: file.size,
934
- path: file.path,
935
- ...(file.thumbnailId && {
936
- thumbnail: {
937
- connect: { id: file.thumbnailId }
938
- }
939
- })
940
- }))
941
- }
942
- }
943
- });
944
- }
945
- // Connect existing files if provided
946
- if (existingFileIds && existingFileIds.length > 0) {
947
- await prisma.submission.update({
948
- where: { id: submission.id },
949
- data: {
950
- attachments: {
951
- connect: existingFileIds.map(fileId => ({ id: fileId }))
952
- }
953
- }
954
- });
955
- }
956
- // Delete removed attachments if any
957
- if (removedAttachments && removedAttachments.length > 0) {
958
- const filesToDelete = submission.attachments.filter((file) => removedAttachments.includes(file.id));
959
- // Delete files from storage
960
- await Promise.all(filesToDelete.map(async (file) => {
961
- try {
962
- // Delete the main file
963
- await deleteFile(file.path);
964
- // Delete thumbnail if it exists
965
- if (file.thumbnail?.path) {
966
- await deleteFile(file.thumbnail.path);
967
- }
968
- }
969
- catch (error) {
970
- console.warn(`Failed to delete file ${file.path}:`, error);
971
- }
972
- }));
973
- }
974
- // Update submission with attachments
975
- return await prisma.submission.update({
976
- where: { id: submission.id },
977
- data: {
978
- ...(removedAttachments && removedAttachments.length > 0 && {
979
- attachments: {
980
- deleteMany: {
981
- id: { in: removedAttachments },
982
- },
983
- },
984
- }),
985
- },
986
- include: {
987
- attachments: {
988
- include: {
989
- thumbnail: true
990
- }
991
- },
992
- student: {
993
- select: {
994
- id: true,
995
- username: true,
996
- },
997
- },
998
- assignment: {
999
- include: {
1000
- class: true,
1001
- markScheme: {
1002
- select: {
1003
- id: true,
1004
- structured: true,
1005
- }
1006
- },
1007
- gradingBoundary: {
1008
- select: {
1009
- id: true,
1010
- structured: true,
1011
- }
1012
- }
1013
- },
1014
- },
1015
- },
1016
- });
1017
- }),
1018
- getSubmissions: protectedTeacherProcedure
1019
- .input(z.object({
1020
- assignmentId: z.string(),
1021
- classId: z.string(),
1022
- }))
1023
- .query(async ({ ctx, input }) => {
1024
- if (!ctx.user) {
1025
- throw new TRPCError({
1026
- code: "UNAUTHORIZED",
1027
- message: "User must be authenticated",
1028
- });
1029
- }
1030
- const { assignmentId } = input;
1031
- const submissions = await prisma.submission.findMany({
1032
- where: {
1033
- assignment: {
1034
- id: assignmentId,
1035
- class: {
1036
- teachers: {
1037
- some: { id: ctx.user.id },
1038
- },
1039
- },
1040
- },
1041
- },
1042
- include: {
1043
- attachments: {
1044
- include: {
1045
- thumbnail: true
1046
- }
1047
- },
1048
- student: {
1049
- select: {
1050
- id: true,
1051
- username: true,
1052
- profile: {
1053
- select: {
1054
- displayName: true,
1055
- profilePicture: true,
1056
- profilePictureThumbnail: true,
1057
- },
1058
- },
1059
- },
1060
- },
1061
- assignment: {
1062
- include: {
1063
- class: true,
1064
- markScheme: {
1065
- select: {
1066
- id: true,
1067
- structured: true,
1068
- }
1069
- },
1070
- gradingBoundary: {
1071
- select: {
1072
- id: true,
1073
- structured: true,
1074
- }
1075
- }
1076
- },
1077
- },
1078
- },
1079
- });
1080
- return submissions.map(submission => ({
1081
- ...submission,
1082
- late: submission.assignment.dueDate < new Date(),
1083
- }));
1084
- }),
1085
- updateSubmissionAsTeacher: protectedTeacherProcedure
1086
- .input(updateSubmissionSchema)
1087
- .mutation(async ({ ctx, input }) => {
1088
- if (!ctx.user) {
1089
- throw new TRPCError({
1090
- code: "UNAUTHORIZED",
1091
- message: "User must be authenticated",
1092
- });
1093
- }
1094
- const { submissionId, return: returnSubmission, gradeReceived, newAttachments, existingFileIds, removedAttachments, rubricGrades, feedback } = input;
1095
- const submission = await prisma.submission.findFirst({
1096
- where: {
1097
- id: submissionId,
1098
- assignment: {
1099
- class: {
1100
- teachers: {
1101
- some: { id: ctx.user.id },
1102
- },
1103
- },
1104
- },
1105
- },
1106
- include: {
1107
- attachments: {
1108
- include: {
1109
- thumbnail: true
1110
- }
1111
- },
1112
- annotations: {
1113
- include: {
1114
- thumbnail: true
1115
- }
1116
- },
1117
- assignment: {
1118
- include: {
1119
- class: true,
1120
- markScheme: {
1121
- select: {
1122
- id: true,
1123
- structured: true,
1124
- }
1125
- },
1126
- gradingBoundary: {
1127
- select: {
1128
- id: true,
1129
- structured: true,
1130
- }
1131
- }
1132
- },
1133
- },
1134
- },
1135
- });
1136
- if (!submission) {
1137
- throw new TRPCError({
1138
- code: "NOT_FOUND",
1139
- message: "Submission not found",
1140
- });
1141
- }
1142
- if (returnSubmission !== undefined) {
1143
- // Toggle return status
1144
- return await prisma.submission.update({
1145
- where: { id: submissionId },
1146
- data: {
1147
- returned: !submission.returned,
1148
- },
1149
- include: {
1150
- attachments: true,
1151
- student: {
1152
- select: {
1153
- id: true,
1154
- username: true,
1155
- profile: {
1156
- select: {
1157
- displayName: true,
1158
- profilePicture: true,
1159
- profilePictureThumbnail: true,
1160
- },
1161
- },
1162
- },
1163
- },
1164
- assignment: {
1165
- include: {
1166
- class: true,
1167
- markScheme: {
1168
- select: {
1169
- id: true,
1170
- structured: true,
1171
- }
1172
- },
1173
- gradingBoundary: {
1174
- select: {
1175
- id: true,
1176
- structured: true,
1177
- }
1178
- }
1179
- },
1180
- },
1181
- },
1182
- });
1183
- }
1184
- // NOTE: Teacher annotation files are now handled via direct upload endpoints
1185
- // Use getAnnotationUploadUrls and confirmAnnotationUpload endpoints instead
1186
- // The newAttachments field is deprecated for annotations
1187
- if (newAttachments && newAttachments.length > 0) {
1188
- throw new TRPCError({
1189
- code: "BAD_REQUEST",
1190
- message: "Direct file upload is deprecated. Use getAnnotationUploadUrls endpoint instead.",
1191
- });
1192
- }
1193
- // Connect existing files if provided
1194
- if (existingFileIds && existingFileIds.length > 0) {
1195
- await prisma.submission.update({
1196
- where: { id: submission.id },
1197
- data: {
1198
- annotations: {
1199
- connect: existingFileIds.map(fileId => ({ id: fileId }))
1200
- }
1201
- }
1202
- });
1203
- }
1204
- // Delete removed attachments if any
1205
- if (removedAttachments && removedAttachments.length > 0) {
1206
- const filesToDelete = submission.annotations.filter((file) => removedAttachments.includes(file.id));
1207
- // Delete files from storage
1208
- await Promise.all(filesToDelete.map(async (file) => {
1209
- try {
1210
- // Delete the main file
1211
- await deleteFile(file.path);
1212
- // Delete thumbnail if it exists
1213
- if (file.thumbnail?.path) {
1214
- await deleteFile(file.thumbnail.path);
1215
- }
1216
- }
1217
- catch (error) {
1218
- console.warn(`Failed to delete file ${file.path}:`, error);
1219
- }
1220
- }));
1221
- }
1222
- // Update submission with grade and attachments
1223
- return await prisma.submission.update({
1224
- where: { id: submissionId },
1225
- data: {
1226
- ...(gradeReceived !== undefined && { gradeReceived }),
1227
- ...(rubricGrades && { rubricState: JSON.stringify(rubricGrades) }),
1228
- ...(feedback && { teacherComments: feedback }),
1229
- ...(removedAttachments && removedAttachments.length > 0 && {
1230
- annotations: {
1231
- deleteMany: {
1232
- id: { in: removedAttachments },
1233
- },
1234
- },
1235
- }),
1236
- ...(returnSubmission && { returned: returnSubmission }),
1237
- },
1238
- include: {
1239
- attachments: {
1240
- include: {
1241
- thumbnail: true
1242
- }
1243
- },
1244
- annotations: {
1245
- include: {
1246
- thumbnail: true
1247
- }
1248
- },
1249
- student: {
1250
- select: {
1251
- id: true,
1252
- username: true,
1253
- profile: {
1254
- select: {
1255
- displayName: true,
1256
- profilePicture: true,
1257
- profilePictureThumbnail: true,
1258
- },
1259
- },
1260
- },
1261
- },
1262
- assignment: {
1263
- include: {
1264
- class: true,
1265
- markScheme: {
1266
- select: {
1267
- id: true,
1268
- structured: true,
1269
- }
1270
- },
1271
- gradingBoundary: {
1272
- select: {
1273
- id: true,
1274
- structured: true,
1275
- }
1276
- }
1277
- },
1278
- },
1279
- },
1280
- });
1281
- }),
1282
- attachToEvent: protectedTeacherProcedure
1283
- .input(z.object({
1284
- assignmentId: z.string(),
1285
- eventId: z.string(),
1286
- }))
1287
- .mutation(async ({ ctx, input }) => {
1288
- if (!ctx.user) {
1289
- throw new TRPCError({
1290
- code: "UNAUTHORIZED",
1291
- message: "User must be authenticated",
1292
- });
1293
- }
1294
- const { assignmentId, eventId } = input;
1295
- // Check if assignment exists and user is a teacher of the class
1296
- const assignment = await prisma.assignment.findFirst({
1297
- where: {
1298
- id: assignmentId,
1299
- class: {
1300
- teachers: {
1301
- some: { id: ctx.user.id },
1302
- },
1303
- },
1304
- },
1305
- include: {
1306
- class: true,
1307
- },
1308
- });
1309
- if (!assignment) {
1310
- throw new TRPCError({
1311
- code: "NOT_FOUND",
1312
- message: "Assignment not found or you are not authorized",
1313
- });
1314
- }
1315
- // Check if event exists and belongs to the same class
1316
- const event = await prisma.event.findFirst({
1317
- where: {
1318
- id: eventId,
1319
- classId: assignment.classId,
1320
- },
1321
- });
1322
- if (!event) {
1323
- throw new TRPCError({
1324
- code: "NOT_FOUND",
1325
- message: "Event not found or does not belong to the same class",
1326
- });
1327
- }
1328
- // Attach assignment to event
1329
- const updatedAssignment = await prisma.assignment.update({
1330
- where: { id: assignmentId },
1331
- data: {
1332
- eventAttached: {
1333
- connect: { id: eventId }
1334
- }
1335
- },
1336
- include: {
1337
- attachments: {
1338
- select: {
1339
- id: true,
1340
- name: true,
1341
- type: true,
1342
- }
1343
- },
1344
- section: {
1345
- select: {
1346
- id: true,
1347
- name: true
1348
- }
1349
- },
1350
- teacher: {
1351
- select: {
1352
- id: true,
1353
- username: true
1354
- }
1355
- },
1356
- eventAttached: {
1357
- select: {
1358
- id: true,
1359
- name: true,
1360
- startTime: true,
1361
- endTime: true,
1362
- }
1363
- }
1364
- }
1365
- });
1366
- return { assignment: updatedAssignment };
1367
- }),
1368
- detachEvent: protectedTeacherProcedure
1369
- .input(z.object({
1370
- assignmentId: z.string(),
1371
- }))
1372
- .mutation(async ({ ctx, input }) => {
1373
- if (!ctx.user) {
1374
- throw new TRPCError({
1375
- code: "UNAUTHORIZED",
1376
- message: "User must be authenticated",
1377
- });
1378
- }
1379
- const { assignmentId } = input;
1380
- // Check if assignment exists and user is a teacher of the class
1381
- const assignment = await prisma.assignment.findFirst({
1382
- where: {
1383
- id: assignmentId,
1384
- class: {
1385
- teachers: {
1386
- some: { id: ctx.user.id },
1387
- },
1388
- },
1389
- },
1390
- });
1391
- if (!assignment) {
1392
- throw new TRPCError({
1393
- code: "NOT_FOUND",
1394
- message: "Assignment not found or you are not authorized",
1395
- });
1396
- }
1397
- // Detach assignment from event
1398
- const updatedAssignment = await prisma.assignment.update({
1399
- where: { id: assignmentId },
1400
- data: {
1401
- eventAttached: {
1402
- disconnect: true
1403
- }
1404
- },
1405
- include: {
1406
- attachments: {
1407
- select: {
1408
- id: true,
1409
- name: true,
1410
- type: true,
1411
- }
1412
- },
1413
- section: {
1414
- select: {
1415
- id: true,
1416
- name: true
1417
- }
1418
- },
1419
- teacher: {
1420
- select: {
1421
- id: true,
1422
- username: true
1423
- }
1424
- },
1425
- eventAttached: {
1426
- select: {
1427
- id: true,
1428
- name: true,
1429
- startTime: true,
1430
- endTime: true,
1431
- }
1432
- }
1433
- }
1434
- });
1435
- return { assignment: updatedAssignment };
1436
- }),
1437
- getAvailableEvents: protectedTeacherProcedure
1438
- .input(z.object({
1439
- assignmentId: z.string(),
1440
- }))
1441
- .query(async ({ ctx, input }) => {
1442
- if (!ctx.user) {
1443
- throw new TRPCError({
1444
- code: "UNAUTHORIZED",
1445
- message: "User must be authenticated",
1446
- });
1447
- }
1448
- const { assignmentId } = input;
1449
- // Get the assignment to find the class
1450
- const assignment = await prisma.assignment.findFirst({
1451
- where: {
1452
- id: assignmentId,
1453
- class: {
1454
- teachers: {
1455
- some: { id: ctx.user.id },
1456
- },
1457
- },
1458
- },
1459
- select: { classId: true }
1460
- });
1461
- if (!assignment) {
1462
- throw new TRPCError({
1463
- code: "NOT_FOUND",
1464
- message: "Assignment not found or you are not authorized",
1465
- });
1466
- }
1467
- // Get all events for the class that don't already have this assignment attached
1468
- const events = await prisma.event.findMany({
1469
- where: {
1470
- classId: assignment.classId,
1471
- assignmentsAttached: {
1472
- none: {
1473
- id: assignmentId
1474
- }
1475
- }
1476
- },
1477
- select: {
1478
- id: true,
1479
- name: true,
1480
- startTime: true,
1481
- endTime: true,
1482
- location: true,
1483
- remarks: true,
1484
- },
1485
- orderBy: {
1486
- startTime: 'asc'
1487
- }
1488
- });
1489
- return { events };
1490
- }),
1491
- dueToday: protectedProcedure
1492
- .query(async ({ ctx }) => {
1493
- if (!ctx.user) {
1494
- throw new TRPCError({
1495
- code: "UNAUTHORIZED",
1496
- message: "User must be authenticated",
1497
- });
1498
- }
1499
- const assignments = await prisma.assignment.findMany({
1500
- where: {
1501
- dueDate: {
1502
- equals: new Date(),
1503
- },
1504
- },
1505
- select: {
1506
- id: true,
1507
- title: true,
1508
- dueDate: true,
1509
- type: true,
1510
- graded: true,
1511
- maxGrade: true,
1512
- class: {
1513
- select: {
1514
- id: true,
1515
- name: true,
1516
- }
1517
- }
1518
- }
1519
- });
1520
- return assignments.map(assignment => ({
1521
- ...assignment,
1522
- dueDate: assignment.dueDate.toISOString(),
1523
- }));
1524
- }),
1525
- attachMarkScheme: protectedTeacherProcedure
1526
- .input(z.object({
1527
- assignmentId: z.string(),
1528
- markSchemeId: z.string().nullable(),
1529
- }))
1530
- .mutation(async ({ ctx, input }) => {
1531
- const { assignmentId, markSchemeId } = input;
1532
- const assignment = await prisma.assignment.findFirst({
1533
- where: {
1534
- id: assignmentId,
1535
- },
1536
- });
1537
- if (!assignment) {
1538
- throw new TRPCError({
1539
- code: "NOT_FOUND",
1540
- message: "Assignment not found",
1541
- });
1542
- }
1543
- // If markSchemeId is provided, verify it exists
1544
- if (markSchemeId) {
1545
- const markScheme = await prisma.markScheme.findFirst({
1546
- where: {
1547
- id: markSchemeId,
1548
- },
1549
- });
1550
- if (!markScheme) {
1551
- throw new TRPCError({
1552
- code: "NOT_FOUND",
1553
- message: "Mark scheme not found",
1554
- });
1555
- }
1556
- }
1557
- const updatedAssignment = await prisma.assignment.update({
1558
- where: { id: assignmentId },
1559
- data: {
1560
- markScheme: markSchemeId ? {
1561
- connect: { id: markSchemeId },
1562
- } : {
1563
- disconnect: true,
1564
- },
1565
- },
1566
- include: {
1567
- attachments: true,
1568
- section: true,
1569
- teacher: true,
1570
- eventAttached: true,
1571
- markScheme: true,
1572
- },
1573
- });
1574
- return updatedAssignment;
1575
- }),
1576
- detachMarkScheme: protectedTeacherProcedure
1577
- .input(z.object({
1578
- assignmentId: z.string(),
1579
- }))
1580
- .mutation(async ({ ctx, input }) => {
1581
- const { assignmentId } = input;
1582
- const assignment = await prisma.assignment.findFirst({
1583
- where: {
1584
- id: assignmentId,
1585
- },
1586
- });
1587
- if (!assignment) {
1588
- throw new TRPCError({
1589
- code: "NOT_FOUND",
1590
- message: "Assignment not found",
1591
- });
1592
- }
1593
- const updatedAssignment = await prisma.assignment.update({
1594
- where: { id: assignmentId },
1595
- data: {
1596
- markScheme: {
1597
- disconnect: true,
1598
- },
1599
- },
1600
- include: {
1601
- attachments: true,
1602
- section: true,
1603
- teacher: true,
1604
- eventAttached: true,
1605
- markScheme: true,
1606
- },
1607
- });
1608
- return updatedAssignment;
1609
- }),
1610
- attachGradingBoundary: protectedTeacherProcedure
1611
- .input(z.object({
1612
- assignmentId: z.string(),
1613
- gradingBoundaryId: z.string().nullable(),
1614
- }))
1615
- .mutation(async ({ ctx, input }) => {
1616
- const { assignmentId, gradingBoundaryId } = input;
1617
- const assignment = await prisma.assignment.findFirst({
1618
- where: {
1619
- id: assignmentId,
1620
- },
1621
- });
1622
- if (!assignment) {
1623
- throw new TRPCError({
1624
- code: "NOT_FOUND",
1625
- message: "Assignment not found",
1626
- });
1627
- }
1628
- // If gradingBoundaryId is provided, verify it exists
1629
- if (gradingBoundaryId) {
1630
- const gradingBoundary = await prisma.gradingBoundary.findFirst({
1631
- where: {
1632
- id: gradingBoundaryId,
1633
- },
1634
- });
1635
- if (!gradingBoundary) {
1636
- throw new TRPCError({
1637
- code: "NOT_FOUND",
1638
- message: "Grading boundary not found",
1639
- });
1640
- }
1641
- }
1642
- const updatedAssignment = await prisma.assignment.update({
1643
- where: { id: assignmentId },
1644
- data: {
1645
- gradingBoundary: gradingBoundaryId ? {
1646
- connect: { id: gradingBoundaryId },
1647
- } : {
1648
- disconnect: true,
1649
- },
1650
- },
1651
- include: {
1652
- attachments: true,
1653
- section: true,
1654
- teacher: true,
1655
- eventAttached: true,
1656
- gradingBoundary: true,
1657
- },
1658
- });
1659
- return updatedAssignment;
1660
- }),
235
+ .mutation(({ ctx, input }) => attachGradingBoundaryRecord(ctx.user.id, input.assignmentId, input.gradingBoundaryId)),
1661
236
  detachGradingBoundary: protectedTeacherProcedure
1662
- .input(z.object({
1663
- classId: z.string(),
1664
- assignmentId: z.string(),
1665
- }))
1666
- .mutation(async ({ ctx, input }) => {
1667
- const { assignmentId } = input;
1668
- const assignment = await prisma.assignment.findFirst({
1669
- where: {
1670
- id: assignmentId,
1671
- },
1672
- });
1673
- if (!assignment) {
1674
- throw new TRPCError({
1675
- code: "NOT_FOUND",
1676
- message: "Assignment not found",
1677
- });
1678
- }
1679
- const updatedAssignment = await prisma.assignment.update({
1680
- where: { id: assignmentId },
1681
- data: {
1682
- gradingBoundary: {
1683
- disconnect: true,
1684
- },
1685
- },
1686
- include: {
1687
- attachments: true,
1688
- section: true,
1689
- teacher: true,
1690
- eventAttached: true,
1691
- gradingBoundary: true,
1692
- },
1693
- });
1694
- return updatedAssignment;
1695
- }),
237
+ .input(z.object({ classId: z.string(), assignmentId: z.string() }))
238
+ .mutation(({ ctx, input }) => detachGradingBoundaryRecord(ctx.user.id, input.assignmentId)),
1696
239
  // New direct upload endpoints
1697
240
  getAssignmentUploadUrls: protectedTeacherProcedure
1698
241
  .input(getAssignmentUploadUrlsSchema)
@@ -1974,3 +517,5 @@ export const assignmentRouter = createTRPCRouter({
1974
517
  };
1975
518
  }),
1976
519
  });
520
+ //# sourceMappingURL=assignment.js.map
521
+ //# debugId=6ca6683e-2a89-5dab-a4c5-bf0292654572