@studious-lms/server 1.2.53 → 1.3.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 (477) hide show
  1. package/.coderabbit.yaml +9 -0
  2. package/.env.example +9 -1
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +102 -8
  5. package/dist/index.js.map +1 -1
  6. package/dist/lib/config/env.d.ts +21 -0
  7. package/dist/lib/config/env.d.ts.map +1 -1
  8. package/dist/lib/config/env.js +8 -2
  9. package/dist/lib/config/env.js.map +1 -1
  10. package/dist/lib/fileUpload.d.ts.map +1 -1
  11. package/dist/lib/fileUpload.js +2 -2
  12. package/dist/lib/fileUpload.js.map +1 -1
  13. package/dist/lib/googleCloudStorage.d.ts +6 -0
  14. package/dist/lib/googleCloudStorage.d.ts.map +1 -1
  15. package/dist/lib/googleCloudStorage.js +19 -2
  16. package/dist/lib/googleCloudStorage.js.map +1 -1
  17. package/dist/lib/pusher.d.ts +4 -1
  18. package/dist/lib/pusher.d.ts.map +1 -1
  19. package/dist/lib/pusher.js +6 -3
  20. package/dist/lib/pusher.js.map +1 -1
  21. package/dist/lib/redis.d.ts +5 -0
  22. package/dist/lib/redis.d.ts.map +1 -0
  23. package/dist/lib/redis.js +53 -0
  24. package/dist/lib/redis.js.map +1 -0
  25. package/dist/lib/thumbnailGenerator.d.ts +0 -21
  26. package/dist/lib/thumbnailGenerator.d.ts.map +1 -1
  27. package/dist/lib/thumbnailGenerator.js +157 -160
  28. package/dist/lib/thumbnailGenerator.js.map +1 -1
  29. package/dist/middleware/auth.d.ts.map +1 -1
  30. package/dist/middleware/auth.js +33 -95
  31. package/dist/middleware/auth.js.map +1 -1
  32. package/dist/models/agenda.d.ts +97 -0
  33. package/dist/models/agenda.d.ts.map +1 -0
  34. package/dist/models/agenda.js +40 -0
  35. package/dist/models/agenda.js.map +1 -0
  36. package/dist/models/announcement.d.ts +223 -0
  37. package/dist/models/announcement.d.ts.map +1 -0
  38. package/dist/models/announcement.js +120 -0
  39. package/dist/models/announcement.js.map +1 -0
  40. package/dist/models/assignment.d.ts +1292 -0
  41. package/dist/models/assignment.d.ts.map +1 -0
  42. package/dist/models/assignment.js +309 -0
  43. package/dist/models/assignment.js.map +1 -0
  44. package/dist/models/attendance.d.ts +180 -0
  45. package/dist/models/attendance.d.ts.map +1 -0
  46. package/dist/models/attendance.js +188 -0
  47. package/dist/models/attendance.js.map +1 -0
  48. package/dist/models/auth.d.ts +153 -0
  49. package/dist/models/auth.d.ts.map +1 -0
  50. package/dist/models/auth.js +217 -0
  51. package/dist/models/auth.js.map +1 -0
  52. package/dist/models/class.d.ts +439 -0
  53. package/dist/models/class.d.ts.map +1 -0
  54. package/dist/models/class.js +546 -0
  55. package/dist/models/class.js.map +1 -0
  56. package/dist/models/comment.d.ts +171 -0
  57. package/dist/models/comment.d.ts.map +1 -0
  58. package/dist/models/comment.js +138 -0
  59. package/dist/models/comment.js.map +1 -0
  60. package/dist/models/conversation.d.ts +164 -0
  61. package/dist/models/conversation.d.ts.map +1 -0
  62. package/dist/models/conversation.js +175 -0
  63. package/dist/models/conversation.js.map +1 -0
  64. package/dist/models/event.d.ts +295 -0
  65. package/dist/models/event.d.ts.map +1 -0
  66. package/dist/models/event.js +145 -0
  67. package/dist/models/event.js.map +1 -0
  68. package/dist/models/file.d.ts +536 -0
  69. package/dist/models/file.d.ts.map +1 -0
  70. package/dist/models/file.js +126 -0
  71. package/dist/models/file.js.map +1 -0
  72. package/dist/models/folder.d.ts +295 -0
  73. package/dist/models/folder.d.ts.map +1 -0
  74. package/dist/models/folder.js +202 -0
  75. package/dist/models/folder.js.map +1 -0
  76. package/dist/models/labChat.d.ts +243 -0
  77. package/dist/models/labChat.d.ts.map +1 -0
  78. package/dist/models/labChat.js +204 -0
  79. package/dist/models/labChat.js.map +1 -0
  80. package/dist/models/marketing.d.ts +72 -0
  81. package/dist/models/marketing.d.ts.map +1 -0
  82. package/dist/models/marketing.js +26 -0
  83. package/dist/models/marketing.js.map +1 -0
  84. package/dist/models/message.d.ts +100 -0
  85. package/dist/models/message.d.ts.map +1 -0
  86. package/dist/models/message.js +131 -0
  87. package/dist/models/message.js.map +1 -0
  88. package/dist/models/newtonChat.d.ts +72 -0
  89. package/dist/models/newtonChat.d.ts.map +1 -0
  90. package/dist/models/newtonChat.js +61 -0
  91. package/dist/models/newtonChat.js.map +1 -0
  92. package/dist/models/notification.d.ts +65 -0
  93. package/dist/models/notification.d.ts.map +1 -0
  94. package/dist/models/notification.js +46 -0
  95. package/dist/models/notification.js.map +1 -0
  96. package/dist/models/section.d.ts +102 -0
  97. package/dist/models/section.d.ts.map +1 -0
  98. package/dist/models/section.js +83 -0
  99. package/dist/models/section.js.map +1 -0
  100. package/dist/models/user.d.ts +39 -0
  101. package/dist/models/user.d.ts.map +1 -0
  102. package/dist/models/user.js +38 -0
  103. package/dist/models/user.js.map +1 -0
  104. package/dist/models/worksheet.d.ts +460 -0
  105. package/dist/models/worksheet.d.ts.map +1 -0
  106. package/dist/models/worksheet.js +200 -0
  107. package/dist/models/worksheet.js.map +1 -0
  108. package/dist/pipelines/aiLabChat.d.ts +21 -0
  109. package/dist/pipelines/aiLabChat.d.ts.map +1 -0
  110. package/dist/pipelines/aiLabChat.js +460 -0
  111. package/dist/pipelines/aiLabChat.js.map +1 -0
  112. package/dist/pipelines/aiNewtonChat.d.ts +30 -0
  113. package/dist/pipelines/aiNewtonChat.d.ts.map +1 -0
  114. package/dist/pipelines/aiNewtonChat.js +289 -0
  115. package/dist/pipelines/aiNewtonChat.js.map +1 -0
  116. package/dist/pipelines/gradeWorksheet.d.ts +30 -0
  117. package/dist/pipelines/gradeWorksheet.d.ts.map +1 -0
  118. package/dist/pipelines/gradeWorksheet.js +252 -0
  119. package/dist/pipelines/gradeWorksheet.js.map +1 -0
  120. package/dist/routers/_app.d.ts +1393 -1267
  121. package/dist/routers/_app.d.ts.map +1 -1
  122. package/dist/routers/agenda.d.ts +22 -22
  123. package/dist/routers/agenda.d.ts.map +1 -1
  124. package/dist/routers/agenda.js +4 -65
  125. package/dist/routers/agenda.js.map +1 -1
  126. package/dist/routers/announcement.d.ts +16 -16
  127. package/dist/routers/announcement.d.ts.map +1 -1
  128. package/dist/routers/announcement.js +37 -446
  129. package/dist/routers/announcement.js.map +1 -1
  130. package/dist/routers/assignment.d.ts +300 -378
  131. package/dist/routers/assignment.d.ts.map +1 -1
  132. package/dist/routers/assignment.js +78 -1868
  133. package/dist/routers/assignment.js.map +1 -1
  134. package/dist/routers/attendance.d.ts +19 -9
  135. package/dist/routers/attendance.d.ts.map +1 -1
  136. package/dist/routers/attendance.js +7 -264
  137. package/dist/routers/attendance.js.map +1 -1
  138. package/dist/routers/auth.d.ts +2 -2
  139. package/dist/routers/auth.d.ts.map +1 -1
  140. package/dist/routers/auth.js +29 -354
  141. package/dist/routers/auth.js.map +1 -1
  142. package/dist/routers/class.d.ts +139 -68
  143. package/dist/routers/class.d.ts.map +1 -1
  144. package/dist/routers/class.js +82 -1052
  145. package/dist/routers/class.js.map +1 -1
  146. package/dist/routers/comment.d.ts +6 -42
  147. package/dist/routers/comment.d.ts.map +1 -1
  148. package/dist/routers/comment.js +24 -244
  149. package/dist/routers/comment.js.map +1 -1
  150. package/dist/routers/conversation.d.ts +45 -7
  151. package/dist/routers/conversation.d.ts.map +1 -1
  152. package/dist/routers/conversation.js +19 -327
  153. package/dist/routers/conversation.js.map +1 -1
  154. package/dist/routers/event.d.ts +36 -36
  155. package/dist/routers/event.d.ts.map +1 -1
  156. package/dist/routers/event.js +13 -433
  157. package/dist/routers/event.js.map +1 -1
  158. package/dist/routers/file.d.ts +2 -2
  159. package/dist/routers/file.d.ts.map +1 -1
  160. package/dist/routers/file.js +9 -323
  161. package/dist/routers/file.js.map +1 -1
  162. package/dist/routers/folder.d.ts +21 -14
  163. package/dist/routers/folder.d.ts.map +1 -1
  164. package/dist/routers/folder.js +34 -745
  165. package/dist/routers/folder.js.map +1 -1
  166. package/dist/routers/labChat.d.ts +11 -10
  167. package/dist/routers/labChat.d.ts.map +1 -1
  168. package/dist/routers/labChat.js +19 -570
  169. package/dist/routers/labChat.js.map +1 -1
  170. package/dist/routers/marketing.d.ts +1 -1
  171. package/dist/routers/marketing.d.ts.map +1 -1
  172. package/dist/routers/marketing.js +7 -56
  173. package/dist/routers/marketing.js.map +1 -1
  174. package/dist/routers/message.d.ts +2 -2
  175. package/dist/routers/message.d.ts.map +1 -1
  176. package/dist/routers/message.js +27 -522
  177. package/dist/routers/message.js.map +1 -1
  178. package/dist/routers/newtonChat.d.ts +1 -1
  179. package/dist/routers/newtonChat.d.ts.map +1 -1
  180. package/dist/routers/newtonChat.js +7 -246
  181. package/dist/routers/newtonChat.js.map +1 -1
  182. package/dist/routers/notifications.d.ts +4 -4
  183. package/dist/routers/notifications.d.ts.map +1 -1
  184. package/dist/routers/notifications.js +18 -83
  185. package/dist/routers/notifications.js.map +1 -1
  186. package/dist/routers/section.d.ts +4 -4
  187. package/dist/routers/section.d.ts.map +1 -1
  188. package/dist/routers/section.js +14 -286
  189. package/dist/routers/section.js.map +1 -1
  190. package/dist/routers/user.d.ts +1 -1
  191. package/dist/routers/user.d.ts.map +1 -1
  192. package/dist/routers/user.js +32 -207
  193. package/dist/routers/user.js.map +1 -1
  194. package/dist/routers/worksheet.d.ts +51 -38
  195. package/dist/routers/worksheet.d.ts.map +1 -1
  196. package/dist/routers/worksheet.js +79 -394
  197. package/dist/routers/worksheet.js.map +1 -1
  198. package/dist/seedDatabase.d.ts +1 -1
  199. package/dist/server/pipelines/gradeWorksheet.d.ts +6 -6
  200. package/dist/server/pipelines/gradeWorksheet.d.ts.map +1 -1
  201. package/dist/server/pipelines/gradeWorksheet.js +12 -5
  202. package/dist/server/pipelines/gradeWorksheet.js.map +1 -1
  203. package/dist/services/agenda.d.ts +100 -0
  204. package/dist/services/agenda.d.ts.map +1 -0
  205. package/dist/services/agenda.js +21 -0
  206. package/dist/services/agenda.js.map +1 -0
  207. package/dist/services/announcement.d.ts +135 -0
  208. package/dist/services/announcement.d.ts.map +1 -0
  209. package/dist/services/announcement.js +223 -0
  210. package/dist/services/announcement.js.map +1 -0
  211. package/dist/services/assignment.d.ts +1462 -0
  212. package/dist/services/assignment.d.ts.map +1 -0
  213. package/dist/services/assignment.js +898 -0
  214. package/dist/services/assignment.js.map +1 -0
  215. package/dist/services/attendance.d.ts +93 -0
  216. package/dist/services/attendance.d.ts.map +1 -0
  217. package/dist/services/attendance.js +61 -0
  218. package/dist/services/attendance.js.map +1 -0
  219. package/dist/services/auth.d.ts +68 -0
  220. package/dist/services/auth.d.ts.map +1 -0
  221. package/dist/services/auth.js +218 -0
  222. package/dist/services/auth.js.map +1 -0
  223. package/dist/services/class.d.ts +621 -0
  224. package/dist/services/class.d.ts.map +1 -0
  225. package/dist/services/class.js +474 -0
  226. package/dist/services/class.js.map +1 -0
  227. package/dist/services/comment.d.ts +100 -0
  228. package/dist/services/comment.d.ts.map +1 -0
  229. package/dist/services/comment.js +83 -0
  230. package/dist/services/comment.js.map +1 -0
  231. package/dist/services/conversation.d.ts +159 -0
  232. package/dist/services/conversation.d.ts.map +1 -0
  233. package/dist/services/conversation.js +138 -0
  234. package/dist/services/conversation.js.map +1 -0
  235. package/dist/services/event.d.ts +216 -0
  236. package/dist/services/event.d.ts.map +1 -0
  237. package/dist/services/event.js +168 -0
  238. package/dist/services/event.js.map +1 -0
  239. package/dist/services/file.d.ts +74 -0
  240. package/dist/services/file.d.ts.map +1 -0
  241. package/dist/services/file.js +133 -0
  242. package/dist/services/file.js.map +1 -0
  243. package/dist/services/folder.d.ts +239 -0
  244. package/dist/services/folder.d.ts.map +1 -0
  245. package/dist/services/folder.js +248 -0
  246. package/dist/services/folder.js.map +1 -0
  247. package/dist/services/labChat.d.ts +165 -0
  248. package/dist/services/labChat.d.ts.map +1 -0
  249. package/dist/services/labChat.js +289 -0
  250. package/dist/services/labChat.js.map +1 -0
  251. package/dist/services/marketing.d.ts +50 -0
  252. package/dist/services/marketing.d.ts.map +1 -0
  253. package/dist/services/marketing.js +32 -0
  254. package/dist/services/marketing.js.map +1 -0
  255. package/dist/services/message.d.ts +95 -0
  256. package/dist/services/message.d.ts.map +1 -0
  257. package/dist/services/message.js +350 -0
  258. package/dist/services/message.js.map +1 -0
  259. package/dist/services/newtonChat.d.ts +22 -0
  260. package/dist/services/newtonChat.d.ts.map +1 -0
  261. package/dist/services/newtonChat.js +174 -0
  262. package/dist/services/newtonChat.js.map +1 -0
  263. package/dist/services/notification.d.ts +65 -0
  264. package/dist/services/notification.d.ts.map +1 -0
  265. package/dist/services/notification.js +33 -0
  266. package/dist/services/notification.js.map +1 -0
  267. package/dist/services/section.d.ts +53 -0
  268. package/dist/services/section.d.ts.map +1 -0
  269. package/dist/services/section.js +199 -0
  270. package/dist/services/section.js.map +1 -0
  271. package/dist/services/user.d.ts +48 -0
  272. package/dist/services/user.d.ts.map +1 -0
  273. package/dist/services/user.js +141 -0
  274. package/dist/services/user.js.map +1 -0
  275. package/dist/services/worksheet.d.ts +239 -0
  276. package/dist/services/worksheet.d.ts.map +1 -0
  277. package/dist/services/worksheet.js +235 -0
  278. package/dist/services/worksheet.js.map +1 -0
  279. package/dist/utils/aiUser.d.ts +1 -3
  280. package/dist/utils/aiUser.d.ts.map +1 -1
  281. package/dist/utils/aiUser.js +6 -5
  282. package/dist/utils/aiUser.js.map +1 -1
  283. package/dist/utils/email.d.ts +3 -0
  284. package/dist/utils/email.d.ts.map +1 -1
  285. package/dist/utils/email.js +7 -4
  286. package/dist/utils/email.js.map +1 -1
  287. package/dist/utils/generateInviteCode.d.ts +1 -2
  288. package/dist/utils/generateInviteCode.d.ts.map +1 -1
  289. package/dist/utils/generateInviteCode.js +3 -4
  290. package/dist/utils/generateInviteCode.js.map +1 -1
  291. package/dist/utils/inference.d.ts +3 -0
  292. package/dist/utils/inference.d.ts.map +1 -1
  293. package/dist/utils/inference.js +7 -4
  294. package/dist/utils/inference.js.map +1 -1
  295. package/dist/utils/logger.d.ts +3 -0
  296. package/dist/utils/logger.d.ts.map +1 -1
  297. package/dist/utils/logger.js +5 -2
  298. package/dist/utils/logger.js.map +1 -1
  299. package/dist/utils/prismaErrorHandler.d.ts.map +1 -1
  300. package/dist/utils/prismaErrorHandler.js +5 -2
  301. package/dist/utils/prismaErrorHandler.js.map +1 -1
  302. package/dist/utils/prismaWrapper.d.ts +1 -0
  303. package/dist/utils/prismaWrapper.d.ts.map +1 -1
  304. package/dist/utils/prismaWrapper.js +6 -2
  305. package/dist/utils/prismaWrapper.js.map +1 -1
  306. package/docker-compose.yml +5 -0
  307. package/package.json +4 -3
  308. package/src/index.ts +119 -12
  309. package/src/lib/config/env.ts +6 -0
  310. package/src/lib/fileUpload.ts +0 -1
  311. package/src/lib/googleCloudStorage.ts +17 -0
  312. package/src/lib/pusher.ts +5 -1
  313. package/src/lib/redis.ts +56 -0
  314. package/src/lib/thumbnailGenerator.ts +170 -168
  315. package/src/middleware/auth.ts +80 -137
  316. package/src/models/agenda.ts +46 -0
  317. package/src/models/announcement.ts +134 -0
  318. package/src/models/assignment.ts +322 -0
  319. package/src/models/attendance.ts +208 -0
  320. package/src/models/auth.ts +247 -0
  321. package/src/models/class.ts +598 -0
  322. package/src/models/comment.ts +152 -0
  323. package/src/models/conversation.ts +200 -0
  324. package/src/models/event.ts +177 -0
  325. package/src/models/file.ts +129 -0
  326. package/src/models/folder.ts +225 -0
  327. package/src/models/labChat.ts +213 -0
  328. package/src/models/marketing.ts +45 -0
  329. package/src/models/message.ts +153 -0
  330. package/src/models/newtonChat.ts +70 -0
  331. package/src/models/notification.ts +54 -0
  332. package/src/models/section.ts +98 -0
  333. package/src/models/user.ts +47 -0
  334. package/src/models/worksheet.ts +294 -0
  335. package/src/{server/pipelines → pipelines}/aiLabChat.ts +11 -7
  336. package/src/{server/pipelines → pipelines}/aiNewtonChat.ts +9 -5
  337. package/src/{server/pipelines → pipelines}/gradeWorksheet.ts +25 -14
  338. package/src/routers/agenda.ts +3 -66
  339. package/src/routers/announcement.ts +54 -495
  340. package/src/routers/assignment.ts +126 -2018
  341. package/src/routers/attendance.ts +15 -276
  342. package/src/routers/auth.ts +79 -442
  343. package/src/routers/class.ts +263 -1187
  344. package/src/routers/comment.ts +61 -288
  345. package/src/routers/conversation.ts +51 -360
  346. package/src/routers/event.ts +50 -481
  347. package/src/routers/file.ts +45 -368
  348. package/src/routers/folder.ts +107 -836
  349. package/src/routers/labChat.ts +29 -605
  350. package/src/routers/marketing.ts +35 -77
  351. package/src/routers/message.ts +45 -571
  352. package/src/routers/newtonChat.ts +17 -278
  353. package/src/routers/notifications.ts +32 -82
  354. package/src/routers/section.ts +46 -330
  355. package/src/routers/user.ts +49 -227
  356. package/src/routers/worksheet.ts +215 -503
  357. package/src/services/agenda.ts +21 -0
  358. package/src/services/announcement.ts +290 -0
  359. package/src/services/assignment.ts +1198 -0
  360. package/src/services/attendance.ts +85 -0
  361. package/src/services/auth.ts +277 -0
  362. package/src/services/class.ts +622 -0
  363. package/src/services/comment.ts +106 -0
  364. package/src/services/conversation.ts +213 -0
  365. package/src/services/event.ts +231 -0
  366. package/src/services/file.ts +167 -0
  367. package/src/services/folder.ts +316 -0
  368. package/src/services/labChat.ts +352 -0
  369. package/src/services/marketing.ts +57 -0
  370. package/src/services/message.ts +461 -0
  371. package/src/services/newtonChat.ts +222 -0
  372. package/src/services/notification.ts +50 -0
  373. package/src/services/section.ts +283 -0
  374. package/src/services/user.ts +172 -0
  375. package/src/services/worksheet.ts +358 -0
  376. package/src/utils/aiUser.ts +4 -3
  377. package/src/utils/email.ts +5 -3
  378. package/src/utils/generateInviteCode.ts +1 -3
  379. package/src/utils/inference.ts +5 -2
  380. package/src/utils/logger.ts +3 -1
  381. package/src/utils/prismaErrorHandler.ts +3 -0
  382. package/src/utils/prismaWrapper.ts +4 -0
  383. package/tests/globalSetup.ts +62 -0
  384. package/tests/helpers.ts +22 -0
  385. package/tests/middleware/security.test.ts +42 -0
  386. package/tests/routers/agenda.test.ts +138 -0
  387. package/tests/routers/announcement.test.ts +490 -0
  388. package/tests/routers/assignment.test.ts +837 -0
  389. package/tests/{attendance.test.ts → routers/attendance.test.ts} +6 -14
  390. package/tests/routers/auth.test.ts +171 -0
  391. package/tests/{class.test.ts → routers/class.test.ts} +131 -85
  392. package/tests/routers/comment.test.ts +126 -0
  393. package/tests/routers/conversation.test.ts +145 -0
  394. package/tests/{event.test.ts → routers/event.test.ts} +93 -32
  395. package/tests/routers/folder.test.ts +178 -0
  396. package/tests/routers/labChat.test.ts +115 -0
  397. package/tests/routers/marketing.test.ts +59 -0
  398. package/tests/routers/message.test.ts +123 -0
  399. package/tests/routers/notification.test.ts +69 -0
  400. package/tests/{section.test.ts → routers/section.test.ts} +5 -13
  401. package/tests/server/rateLimit.test.ts +73 -0
  402. package/tests/setup.ts +18 -92
  403. package/tests/user.test.ts +9 -31
  404. package/tests/utils/aiUser.test.ts +22 -0
  405. package/tests/utils/generateInviteCode.test.ts +24 -0
  406. package/tests/utils/logger.test.ts +74 -0
  407. package/tests/utils/prismaErrorHandler.test.ts +101 -0
  408. package/tests/utils/prismaWrapper.test.ts +82 -0
  409. package/tests/worksheet.test.ts +181 -0
  410. package/vitest.config.ts +6 -3
  411. package/vitest.unit.config.ts +21 -0
  412. package/TODO.md +0 -2
  413. package/coverage/base.css +0 -224
  414. package/coverage/block-navigation.js +0 -87
  415. package/coverage/clover.xml +0 -12110
  416. package/coverage/coverage-final.json +0 -44
  417. package/coverage/favicon.png +0 -0
  418. package/coverage/index.html +0 -221
  419. package/coverage/prettify.css +0 -1
  420. package/coverage/prettify.js +0 -2
  421. package/coverage/server/index.html +0 -116
  422. package/coverage/server/src/exportType.ts.html +0 -109
  423. package/coverage/server/src/index.html +0 -161
  424. package/coverage/server/src/index.ts.html +0 -1702
  425. package/coverage/server/src/instrument.ts.html +0 -130
  426. package/coverage/server/src/lib/config/env.ts.html +0 -448
  427. package/coverage/server/src/lib/config/index.html +0 -116
  428. package/coverage/server/src/lib/fileUpload.ts.html +0 -1138
  429. package/coverage/server/src/lib/googleCloudStorage.ts.html +0 -334
  430. package/coverage/server/src/lib/index.html +0 -206
  431. package/coverage/server/src/lib/jsonConversion.ts.html +0 -2323
  432. package/coverage/server/src/lib/jsonStyles.ts.html +0 -193
  433. package/coverage/server/src/lib/notificationHandler.ts.html +0 -193
  434. package/coverage/server/src/lib/pusher.ts.html +0 -121
  435. package/coverage/server/src/lib/thumbnailGenerator.ts.html +0 -592
  436. package/coverage/server/src/middleware/auth.ts.html +0 -646
  437. package/coverage/server/src/middleware/index.html +0 -146
  438. package/coverage/server/src/middleware/logging.ts.html +0 -244
  439. package/coverage/server/src/middleware/security.ts.html +0 -271
  440. package/coverage/server/src/routers/_app.ts.html +0 -232
  441. package/coverage/server/src/routers/agenda.ts.html +0 -319
  442. package/coverage/server/src/routers/announcement.ts.html +0 -3481
  443. package/coverage/server/src/routers/assignment.ts.html +0 -7633
  444. package/coverage/server/src/routers/attendance.ts.html +0 -1030
  445. package/coverage/server/src/routers/auth.ts.html +0 -1081
  446. package/coverage/server/src/routers/class.ts.html +0 -3535
  447. package/coverage/server/src/routers/comment.ts.html +0 -991
  448. package/coverage/server/src/routers/conversation.ts.html +0 -982
  449. package/coverage/server/src/routers/event.ts.html +0 -1609
  450. package/coverage/server/src/routers/file.ts.html +0 -1144
  451. package/coverage/server/src/routers/folder.ts.html +0 -2797
  452. package/coverage/server/src/routers/index.html +0 -386
  453. package/coverage/server/src/routers/labChat.ts.html +0 -3073
  454. package/coverage/server/src/routers/marketing.ts.html +0 -340
  455. package/coverage/server/src/routers/message.ts.html +0 -1912
  456. package/coverage/server/src/routers/notifications.ts.html +0 -364
  457. package/coverage/server/src/routers/section.ts.html +0 -1120
  458. package/coverage/server/src/routers/user.ts.html +0 -862
  459. package/coverage/server/src/routers/worksheet.ts.html +0 -1729
  460. package/coverage/server/src/trpc.ts.html +0 -397
  461. package/coverage/server/src/types/index.html +0 -116
  462. package/coverage/server/src/types/trpc.ts.html +0 -127
  463. package/coverage/server/src/utils/aiUser.ts.html +0 -280
  464. package/coverage/server/src/utils/email.ts.html +0 -121
  465. package/coverage/server/src/utils/generateInviteCode.ts.html +0 -106
  466. package/coverage/server/src/utils/index.html +0 -206
  467. package/coverage/server/src/utils/inference.ts.html +0 -709
  468. package/coverage/server/src/utils/logger.ts.html +0 -664
  469. package/coverage/server/src/utils/prismaErrorHandler.ts.html +0 -907
  470. package/coverage/server/src/utils/prismaWrapper.ts.html +0 -355
  471. package/coverage/server/vitest.config.ts.html +0 -196
  472. package/coverage/sort-arrow-sprite.png +0 -0
  473. package/coverage/sorter.js +0 -210
  474. package/src/lib/notificationHandler.ts +0 -36
  475. package/tests/announcement.test.ts +0 -164
  476. package/tests/assignment.test.ts +0 -296
  477. package/tests/auth.test.ts +0 -48
@@ -0,0 +1,898 @@
1
+ /**
2
+ * Assignment service – create, read, update, delete assignments and submissions.
3
+ */
4
+
5
+ !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]="9f048e12-9c8f-592d-82cb-74a5ccf39c12")}catch(e){}}();
6
+ import { TRPCError } from "@trpc/server";
7
+ import { prisma } from "../lib/prisma.js";
8
+ import { findAssignmentById, findAssignmentWithDetails, findAssignmentForDelete, findAssignmentsDueToday, findSubmissionByAssignmentAndStudent, findSubmissionByIdForClassMember, findSubmissionForUpdate, findSubmissionForTeacherUpdate, findSubmissionsByAssignmentForTeacher, createSubmission, findSectionsByClassId, findAssignmentWithClassForTeacher, findAvailableEventsForAssignment, attachAssignmentToEvent, detachAssignmentFromEvent, } from "../models/assignment.js";
9
+ import { createDirectUploadFiles } from "../lib/fileUpload.js";
10
+ import { deleteFile } from "../lib/googleCloudStorage.js";
11
+ import { gradeWorksheetPipeline } from "../pipelines/gradeWorksheet.js";
12
+ import { sendToMultiple } from "./notification.js";
13
+ import { logger } from "../utils/logger.js";
14
+ export async function assignmentExists(id) {
15
+ const assignment = await findAssignmentById(id);
16
+ return !!assignment;
17
+ }
18
+ export async function getDueToday() {
19
+ const assignments = await findAssignmentsDueToday();
20
+ return assignments.map((a) => ({
21
+ ...a,
22
+ dueDate: a.dueDate.toISOString(),
23
+ }));
24
+ }
25
+ export async function getAssignment(id, _classId) {
26
+ const assignment = await findAssignmentWithDetails(id);
27
+ if (!assignment) {
28
+ throw new TRPCError({
29
+ code: "NOT_FOUND",
30
+ message: "Assignment not found",
31
+ });
32
+ }
33
+ const sections = await findSectionsByClassId(assignment.classId);
34
+ return { ...assignment, sections };
35
+ }
36
+ export async function getSubmission(assignmentId, studentId) {
37
+ let submission = await findSubmissionByAssignmentAndStudent(assignmentId, studentId);
38
+ if (!submission) {
39
+ const created = await createSubmission(assignmentId, studentId);
40
+ return {
41
+ ...created,
42
+ late: created.assignment.dueDate < new Date(),
43
+ };
44
+ }
45
+ return {
46
+ ...submission,
47
+ late: submission.assignment.dueDate < new Date(),
48
+ };
49
+ }
50
+ export async function getSubmissionById(submissionId, classId, userId) {
51
+ const submission = await findSubmissionByIdForClassMember(submissionId, classId, userId);
52
+ if (!submission) {
53
+ throw new TRPCError({
54
+ code: "NOT_FOUND",
55
+ message: "Submission not found",
56
+ });
57
+ }
58
+ return {
59
+ ...submission,
60
+ late: submission.assignment.dueDate < new Date(),
61
+ };
62
+ }
63
+ export async function getSubmissions(assignmentId, teacherId) {
64
+ const submissions = await findSubmissionsByAssignmentForTeacher(assignmentId, teacherId);
65
+ return submissions.map((s) => ({
66
+ ...s,
67
+ late: s.assignment.dueDate < new Date(),
68
+ }));
69
+ }
70
+ export async function createAssignmentRecord(userId, input) {
71
+ const { classId, id, title, instructions, dueDate, files, existingFileIds, aiPolicyLevel, acceptFiles, acceptExtendedResponse, acceptWorksheet, worksheetIds, gradeWithAI, studentIds, maxGrade, graded, weight, sectionId, type, markSchemeId, gradingBoundaryId, inProgress, } = input;
72
+ const [classData, rubricData] = await Promise.all([
73
+ prisma.class.findUnique({
74
+ where: { id: classId },
75
+ include: { students: { select: { id: true } } },
76
+ }),
77
+ markSchemeId
78
+ ? prisma.markScheme.findUnique({
79
+ where: { id: markSchemeId },
80
+ select: { structured: true },
81
+ })
82
+ : null,
83
+ ]);
84
+ if (!classData) {
85
+ throw new TRPCError({
86
+ code: "NOT_FOUND",
87
+ message: "Class not found",
88
+ });
89
+ }
90
+ let computedMaxGrade = maxGrade;
91
+ if (markSchemeId && rubricData) {
92
+ const parsedRubric = JSON.parse(rubricData.structured || "{}");
93
+ computedMaxGrade = parsedRubric.criteria?.reduce((acc, criterion) => {
94
+ const maxPoints = Math.max(...criterion.levels.map((l) => l.points));
95
+ return acc + maxPoints;
96
+ }, 0);
97
+ }
98
+ const submissionData = studentIds && studentIds.length > 0
99
+ ? studentIds.map((studentId) => ({ student: { connect: { id: studentId } } }))
100
+ : classData.students.map((student) => ({ student: { connect: { id: student.id } } }));
101
+ const assignment = await prisma.$transaction(async (tx) => {
102
+ const created = await tx.assignment.create({
103
+ data: {
104
+ ...(id && { id }),
105
+ title,
106
+ instructions,
107
+ dueDate: new Date(dueDate),
108
+ maxGrade: markSchemeId ? computedMaxGrade : maxGrade,
109
+ graded,
110
+ weight,
111
+ type: type,
112
+ ...(aiPolicyLevel !== undefined && { aiPolicyLevel }),
113
+ acceptFiles,
114
+ acceptExtendedResponse,
115
+ acceptWorksheet,
116
+ ...(worksheetIds?.length && {
117
+ worksheets: { connect: worksheetIds.map((id) => ({ id })) },
118
+ }),
119
+ gradeWithAI,
120
+ ...(studentIds?.length && {
121
+ assignedTo: { connect: studentIds.map((id) => ({ id })) },
122
+ }),
123
+ order: 0,
124
+ inProgress: inProgress || false,
125
+ class: { connect: { id: classId } },
126
+ ...(sectionId && { section: { connect: { id: sectionId } } }),
127
+ ...(markSchemeId && { markScheme: { connect: { id: markSchemeId } } }),
128
+ ...(gradingBoundaryId && {
129
+ gradingBoundary: { connect: { id: gradingBoundaryId } },
130
+ }),
131
+ submissions: { create: submissionData },
132
+ teacher: { connect: { id: userId } },
133
+ },
134
+ select: {
135
+ id: true,
136
+ title: true,
137
+ instructions: true,
138
+ dueDate: true,
139
+ maxGrade: true,
140
+ graded: true,
141
+ weight: true,
142
+ type: true,
143
+ attachments: { select: { id: true, name: true, type: true } },
144
+ section: { select: { id: true, name: true } },
145
+ teacher: { select: { id: true, username: true } },
146
+ class: { select: { id: true, name: true } },
147
+ },
148
+ });
149
+ await tx.assignment.updateMany({
150
+ where: { classId, id: { not: created.id } },
151
+ data: { order: { increment: 1 } },
152
+ });
153
+ await tx.section.updateMany({
154
+ where: { classId },
155
+ data: { order: { increment: 1 } },
156
+ });
157
+ await tx.assignment.update({
158
+ where: { id: created.id },
159
+ data: { order: 1 },
160
+ });
161
+ return created;
162
+ }, { maxWait: 10000, timeout: 20000 });
163
+ const fileOperations = [];
164
+ if (files?.length) {
165
+ fileOperations.push(createDirectUploadFiles(files, userId, undefined, assignment.id).then((uploadedFiles) => {
166
+ if (uploadedFiles.length > 0) {
167
+ return prisma.assignment.update({
168
+ where: { id: assignment.id },
169
+ data: {
170
+ attachments: {
171
+ create: uploadedFiles.map((file) => ({
172
+ name: file.name,
173
+ type: file.type,
174
+ size: file.size,
175
+ path: file.path,
176
+ })),
177
+ },
178
+ },
179
+ });
180
+ }
181
+ }));
182
+ }
183
+ if (existingFileIds?.length) {
184
+ fileOperations.push(prisma.assignment.update({
185
+ where: { id: assignment.id },
186
+ data: {
187
+ attachments: {
188
+ connect: existingFileIds.map((fileId) => ({ id: fileId })),
189
+ },
190
+ },
191
+ }));
192
+ }
193
+ await Promise.all(fileOperations);
194
+ sendToMultiple({
195
+ receiverIds: classData.students.map((s) => s.id),
196
+ title: `🔔 New assignment for ${classData.name}`,
197
+ content: `The assignment "${title}" has been created in ${classData.name}.\nDue date: ${new Date(dueDate).toLocaleDateString()}.\n[Link to assignment](/class/${classId}/assignments/${assignment.id})`,
198
+ }).catch((error) => {
199
+ logger.error("Failed to send assignment notifications:", error);
200
+ });
201
+ return assignment;
202
+ }
203
+ export async function updateAssignmentRecord(userId, input) {
204
+ const [assignment, classData] = await Promise.all([
205
+ prisma.assignment.findFirst({
206
+ where: { id: input.id, teacherId: userId },
207
+ include: {
208
+ attachments: {
209
+ select: {
210
+ id: true,
211
+ name: true,
212
+ type: true,
213
+ path: true,
214
+ size: true,
215
+ uploadStatus: true,
216
+ thumbnail: { select: { path: true } },
217
+ },
218
+ },
219
+ class: { select: { id: true, name: true } },
220
+ markScheme: true,
221
+ },
222
+ }),
223
+ prisma.class.findFirst({
224
+ where: { assignments: { some: { id: input.id } } },
225
+ include: { students: { select: { id: true } } },
226
+ }),
227
+ ]);
228
+ if (!assignment) {
229
+ throw new TRPCError({
230
+ code: "NOT_FOUND",
231
+ message: "Assignment not found",
232
+ });
233
+ }
234
+ const submissionData = input.studentIds?.length
235
+ ? input.studentIds.map((studentId) => ({ student: { connect: { id: studentId } } }))
236
+ : classData?.students.map((student) => ({
237
+ student: { connect: { id: student.id } },
238
+ }));
239
+ if (input.removedAttachments?.length) {
240
+ const filesToDelete = assignment.attachments.filter((f) => input.removedAttachments.includes(f.id));
241
+ await Promise.all(filesToDelete.map(async (file) => {
242
+ if (file.uploadStatus === "COMPLETED") {
243
+ await deleteFile(file.path).catch(() => { });
244
+ if (file.thumbnail?.path) {
245
+ await deleteFile(file.thumbnail.path).catch(() => { });
246
+ }
247
+ }
248
+ }));
249
+ }
250
+ let uploadedFiles = [];
251
+ if (input.files?.length) {
252
+ uploadedFiles = await createDirectUploadFiles(input.files, userId, undefined, input.id);
253
+ }
254
+ const updateData = {
255
+ ...(input.title && { title: input.title }),
256
+ ...(input.instructions && { instructions: input.instructions }),
257
+ ...(input.dueDate && { dueDate: new Date(input.dueDate) }),
258
+ ...(input.maxGrade && { maxGrade: input.maxGrade }),
259
+ ...(input.graded !== undefined && { graded: input.graded }),
260
+ ...(input.weight && { weight: input.weight }),
261
+ ...(input.type && { type: input.type }),
262
+ ...(input.inProgress !== undefined && { inProgress: input.inProgress }),
263
+ ...(input.acceptFiles !== undefined && { acceptFiles: input.acceptFiles }),
264
+ ...(input.acceptExtendedResponse !== undefined && {
265
+ acceptExtendedResponse: input.acceptExtendedResponse,
266
+ }),
267
+ ...(input.acceptWorksheet !== undefined && {
268
+ acceptWorksheet: input.acceptWorksheet,
269
+ }),
270
+ ...(input.gradeWithAI !== undefined && { gradeWithAI: input.gradeWithAI }),
271
+ ...(input.studentIds && {
272
+ assignedTo: {
273
+ set: [],
274
+ connect: input.studentIds.map((id) => ({ id })),
275
+ },
276
+ }),
277
+ ...(submissionData && {
278
+ submissions: {
279
+ deleteMany: {},
280
+ create: submissionData,
281
+ },
282
+ }),
283
+ ...(input.aiPolicyLevel !== undefined && { aiPolicyLevel: input.aiPolicyLevel }),
284
+ ...(input.sectionId !== undefined && {
285
+ section: input.sectionId
286
+ ? { connect: { id: input.sectionId } }
287
+ : { disconnect: true },
288
+ }),
289
+ ...(input.worksheetIds && {
290
+ worksheets: {
291
+ set: [],
292
+ connect: input.worksheetIds.map((id) => ({ id })),
293
+ },
294
+ }),
295
+ ...((uploadedFiles.length > 0 ||
296
+ input.existingFileIds?.length ||
297
+ input.removedAttachments?.length) && {
298
+ attachments: {
299
+ ...(uploadedFiles.length > 0 && {
300
+ create: uploadedFiles.map((file) => ({
301
+ name: file.name,
302
+ type: file.type,
303
+ size: file.size,
304
+ path: file.path,
305
+ ...(file.thumbnailId && {
306
+ thumbnail: { connect: { id: file.thumbnailId } },
307
+ }),
308
+ })),
309
+ }),
310
+ ...(input.existingFileIds?.length && {
311
+ connect: input.existingFileIds.map((fileId) => ({ id: fileId })),
312
+ }),
313
+ ...(input.removedAttachments?.length && {
314
+ deleteMany: { id: { in: input.removedAttachments } },
315
+ }),
316
+ },
317
+ }),
318
+ };
319
+ const updatedAssignment = await prisma.$transaction(async (tx) => {
320
+ return tx.assignment.update({
321
+ where: { id: input.id },
322
+ data: updateData,
323
+ select: {
324
+ id: true,
325
+ title: true,
326
+ instructions: true,
327
+ dueDate: true,
328
+ maxGrade: true,
329
+ graded: true,
330
+ weight: true,
331
+ type: true,
332
+ createdAt: true,
333
+ markSchemeId: true,
334
+ submissions: {
335
+ select: {
336
+ student: { select: { id: true, username: true } },
337
+ },
338
+ },
339
+ attachments: {
340
+ select: {
341
+ id: true,
342
+ name: true,
343
+ type: true,
344
+ thumbnail: true,
345
+ size: true,
346
+ path: true,
347
+ uploadedAt: true,
348
+ thumbnailId: true,
349
+ },
350
+ },
351
+ section: true,
352
+ teacher: true,
353
+ class: true,
354
+ },
355
+ });
356
+ }, { maxWait: 5000, timeout: 10000 });
357
+ if (updatedAssignment.markSchemeId) {
358
+ prisma.markScheme
359
+ .findUnique({
360
+ where: { id: updatedAssignment.markSchemeId },
361
+ select: { structured: true },
362
+ })
363
+ .then((rubric) => {
364
+ if (rubric) {
365
+ const parsed = JSON.parse(rubric.structured || "{}");
366
+ const computedMaxGrade = parsed.criteria?.reduce((acc, c) => acc + Math.max(...c.levels.map((l) => l.points)), 0) || 0;
367
+ return prisma.assignment.update({
368
+ where: { id: input.id },
369
+ data: { maxGrade: computedMaxGrade },
370
+ });
371
+ }
372
+ })
373
+ .catch((error) => logger.error("Failed to update max grade from rubric:", error));
374
+ }
375
+ return updatedAssignment;
376
+ }
377
+ export async function deleteAssignmentRecord(userId, id, _classId) {
378
+ const assignment = await findAssignmentForDelete(id, userId);
379
+ if (!assignment) {
380
+ throw new TRPCError({
381
+ code: "NOT_FOUND",
382
+ message: "Assignment not found",
383
+ });
384
+ }
385
+ const filesToDelete = [
386
+ ...assignment.attachments,
387
+ ...assignment.submissions.flatMap((s) => [
388
+ ...s.attachments,
389
+ ...s.annotations,
390
+ ]),
391
+ ];
392
+ await Promise.all(filesToDelete.map(async (file) => {
393
+ try {
394
+ if (file.uploadStatus === "COMPLETED") {
395
+ await deleteFile(file.path);
396
+ if (file.thumbnail) {
397
+ await deleteFile(file.thumbnail.path);
398
+ }
399
+ }
400
+ }
401
+ catch {
402
+ // ignore
403
+ }
404
+ }));
405
+ await prisma.assignment.delete({
406
+ where: { id },
407
+ });
408
+ return { id };
409
+ }
410
+ export async function updateSubmissionRecord(userId, input) {
411
+ const { submissionId, submit, newAttachments, extendedResponse, existingFileIds, removedAttachments } = input;
412
+ const submission = await findSubmissionForUpdate(submissionId, userId);
413
+ if (!submission) {
414
+ throw new TRPCError({
415
+ code: "NOT_FOUND",
416
+ message: "Submission not found",
417
+ });
418
+ }
419
+ if (submit !== undefined) {
420
+ if (submission.assignment.acceptWorksheet &&
421
+ submission.assignment.gradeWithAI) {
422
+ const worksheetResponses = await prisma.studentWorksheetResponse.findMany({
423
+ where: { submissionId: submission.id },
424
+ });
425
+ for (const wr of worksheetResponses) {
426
+ gradeWorksheetPipeline(wr.id);
427
+ }
428
+ }
429
+ return prisma.submission.update({
430
+ where: { id: submission.id },
431
+ data: {
432
+ submitted: !submission.submitted,
433
+ submittedAt: new Date(),
434
+ },
435
+ include: {
436
+ attachments: true,
437
+ student: { select: { id: true, username: true } },
438
+ assignment: {
439
+ include: {
440
+ class: true,
441
+ markScheme: { select: { id: true, structured: true } },
442
+ gradingBoundary: { select: { id: true, structured: true } },
443
+ },
444
+ },
445
+ },
446
+ });
447
+ }
448
+ let uploadedFiles = [];
449
+ if (newAttachments?.length) {
450
+ uploadedFiles = await createDirectUploadFiles(newAttachments, userId, undefined, undefined, submission.id);
451
+ }
452
+ if (uploadedFiles.length > 0) {
453
+ await prisma.submission.update({
454
+ where: { id: submission.id },
455
+ data: {
456
+ attachments: {
457
+ create: uploadedFiles.map((file) => ({
458
+ name: file.name,
459
+ type: file.type,
460
+ size: file.size,
461
+ path: file.path,
462
+ ...(file.thumbnailId && {
463
+ thumbnail: { connect: { id: file.thumbnailId } },
464
+ }),
465
+ })),
466
+ },
467
+ },
468
+ });
469
+ }
470
+ if (existingFileIds?.length) {
471
+ await prisma.submission.update({
472
+ where: { id: submission.id },
473
+ data: {
474
+ attachments: {
475
+ connect: existingFileIds.map((fileId) => ({ id: fileId })),
476
+ },
477
+ },
478
+ });
479
+ }
480
+ if (removedAttachments?.length) {
481
+ const filesToDelete = submission.attachments.filter((f) => removedAttachments.includes(f.id));
482
+ await Promise.all(filesToDelete.map(async (file) => {
483
+ try {
484
+ if (file.uploadStatus === "COMPLETED") {
485
+ await deleteFile(file.path);
486
+ if (file.thumbnail?.path) {
487
+ await deleteFile(file.thumbnail.path);
488
+ }
489
+ }
490
+ }
491
+ catch {
492
+ // ignore
493
+ }
494
+ }));
495
+ }
496
+ return prisma.submission.update({
497
+ where: { id: submission.id },
498
+ data: {
499
+ ...(removedAttachments?.length && {
500
+ attachments: {
501
+ deleteMany: { id: { in: removedAttachments } },
502
+ },
503
+ }),
504
+ ...(extendedResponse !== undefined && { extendedResponse }),
505
+ },
506
+ include: {
507
+ attachments: { include: { thumbnail: true } },
508
+ student: { select: { id: true, username: true } },
509
+ assignment: {
510
+ include: {
511
+ class: true,
512
+ markScheme: { select: { id: true, structured: true } },
513
+ gradingBoundary: { select: { id: true, structured: true } },
514
+ },
515
+ },
516
+ },
517
+ });
518
+ }
519
+ const submissionTeacherInclude = {
520
+ attachments: { include: { thumbnail: true } },
521
+ annotations: { include: { thumbnail: true } },
522
+ student: {
523
+ select: {
524
+ id: true,
525
+ username: true,
526
+ profile: {
527
+ select: {
528
+ displayName: true,
529
+ profilePicture: true,
530
+ profilePictureThumbnail: true,
531
+ },
532
+ },
533
+ },
534
+ },
535
+ assignment: {
536
+ include: {
537
+ class: true,
538
+ markScheme: { select: { id: true, structured: true } },
539
+ gradingBoundary: { select: { id: true, structured: true } },
540
+ },
541
+ },
542
+ };
543
+ export async function updateSubmissionAsTeacherRecord(teacherId, input) {
544
+ const { submissionId, return: returnSubmission, gradeReceived, existingFileIds, removedAttachments, rubricGrades, feedback, } = input;
545
+ const submission = await findSubmissionForTeacherUpdate(submissionId, teacherId);
546
+ if (!submission) {
547
+ throw new TRPCError({
548
+ code: "NOT_FOUND",
549
+ message: "Submission not found",
550
+ });
551
+ }
552
+ if (input.newAttachments?.length) {
553
+ throw new TRPCError({
554
+ code: "BAD_REQUEST",
555
+ message: "Direct file upload is deprecated. Use getAnnotationUploadUrls endpoint instead.",
556
+ });
557
+ }
558
+ if (returnSubmission !== undefined) {
559
+ return prisma.submission.update({
560
+ where: { id: submissionId },
561
+ data: { returned: !submission.returned },
562
+ include: submissionTeacherInclude,
563
+ });
564
+ }
565
+ if (existingFileIds?.length) {
566
+ await prisma.submission.update({
567
+ where: { id: submission.id },
568
+ data: {
569
+ annotations: {
570
+ connect: existingFileIds.map((fileId) => ({ id: fileId })),
571
+ },
572
+ },
573
+ });
574
+ }
575
+ if (removedAttachments?.length) {
576
+ const filesToDelete = submission.annotations.filter((f) => removedAttachments.includes(f.id));
577
+ await Promise.all(filesToDelete.map(async (file) => {
578
+ try {
579
+ if (file.uploadStatus === "COMPLETED") {
580
+ await deleteFile(file.path);
581
+ if (file.thumbnail?.path) {
582
+ await deleteFile(file.thumbnail.path);
583
+ }
584
+ }
585
+ }
586
+ catch {
587
+ // ignore
588
+ }
589
+ }));
590
+ }
591
+ return prisma.submission.update({
592
+ where: { id: submissionId },
593
+ data: {
594
+ ...(gradeReceived !== undefined && { gradeReceived }),
595
+ ...(rubricGrades && { rubricState: JSON.stringify(rubricGrades) }),
596
+ ...(feedback && { teacherComments: feedback }),
597
+ ...(removedAttachments?.length && {
598
+ annotations: {
599
+ deleteMany: { id: { in: removedAttachments } },
600
+ },
601
+ }),
602
+ },
603
+ include: submissionTeacherInclude,
604
+ });
605
+ }
606
+ export async function attachAssignmentToEventRecord(teacherId, assignmentId, eventId) {
607
+ const assignment = await findAssignmentWithClassForTeacher(assignmentId, teacherId);
608
+ if (!assignment) {
609
+ throw new TRPCError({
610
+ code: "NOT_FOUND",
611
+ message: "Assignment not found or you are not authorized",
612
+ });
613
+ }
614
+ const event = await prisma.event.findFirst({
615
+ where: {
616
+ id: eventId,
617
+ classId: assignment.classId,
618
+ },
619
+ });
620
+ if (!event) {
621
+ throw new TRPCError({
622
+ code: "NOT_FOUND",
623
+ message: "Event not found or does not belong to the same class",
624
+ });
625
+ }
626
+ const updated = await attachAssignmentToEvent(assignmentId, eventId);
627
+ return { assignment: updated };
628
+ }
629
+ export async function detachAssignmentFromEventRecord(teacherId, assignmentId) {
630
+ const assignment = await findAssignmentWithClassForTeacher(assignmentId, teacherId);
631
+ if (!assignment) {
632
+ throw new TRPCError({
633
+ code: "NOT_FOUND",
634
+ message: "Assignment not found or you are not authorized",
635
+ });
636
+ }
637
+ const updated = await detachAssignmentFromEvent(assignmentId);
638
+ return { assignment: updated };
639
+ }
640
+ export async function getAvailableEventsForAssignment(teacherId, assignmentId) {
641
+ const assignment = await findAssignmentWithClassForTeacher(assignmentId, teacherId);
642
+ if (!assignment) {
643
+ throw new TRPCError({
644
+ code: "NOT_FOUND",
645
+ message: "Assignment not found or you are not authorized",
646
+ });
647
+ }
648
+ const events = await findAvailableEventsForAssignment(assignment.classId, assignmentId);
649
+ return { events };
650
+ }
651
+ export async function attachMarkSchemeRecord(teacherId, assignmentId, markSchemeId) {
652
+ const assignment = await findAssignmentById(assignmentId);
653
+ if (!assignment) {
654
+ throw new TRPCError({
655
+ code: "NOT_FOUND",
656
+ message: "Assignment not found",
657
+ });
658
+ }
659
+ if (markSchemeId) {
660
+ const markScheme = await prisma.markScheme.findFirst({
661
+ where: { id: markSchemeId },
662
+ });
663
+ if (!markScheme) {
664
+ throw new TRPCError({
665
+ code: "NOT_FOUND",
666
+ message: "Mark scheme not found",
667
+ });
668
+ }
669
+ }
670
+ return prisma.assignment.update({
671
+ where: { id: assignmentId },
672
+ data: {
673
+ markScheme: markSchemeId
674
+ ? { connect: { id: markSchemeId } }
675
+ : { disconnect: true },
676
+ },
677
+ include: {
678
+ attachments: true,
679
+ section: true,
680
+ teacher: true,
681
+ eventAttached: true,
682
+ markScheme: true,
683
+ },
684
+ });
685
+ }
686
+ export async function detachMarkSchemeRecord(teacherId, assignmentId) {
687
+ const assignment = await findAssignmentById(assignmentId);
688
+ if (!assignment) {
689
+ throw new TRPCError({
690
+ code: "NOT_FOUND",
691
+ message: "Assignment not found",
692
+ });
693
+ }
694
+ return prisma.assignment.update({
695
+ where: { id: assignmentId },
696
+ data: { markScheme: { disconnect: true } },
697
+ include: {
698
+ attachments: true,
699
+ section: true,
700
+ teacher: true,
701
+ eventAttached: true,
702
+ markScheme: true,
703
+ },
704
+ });
705
+ }
706
+ export async function attachGradingBoundaryRecord(teacherId, assignmentId, gradingBoundaryId) {
707
+ const assignment = await findAssignmentById(assignmentId);
708
+ if (!assignment) {
709
+ throw new TRPCError({
710
+ code: "NOT_FOUND",
711
+ message: "Assignment not found",
712
+ });
713
+ }
714
+ if (gradingBoundaryId) {
715
+ const gradingBoundary = await prisma.gradingBoundary.findFirst({
716
+ where: { id: gradingBoundaryId },
717
+ });
718
+ if (!gradingBoundary) {
719
+ throw new TRPCError({
720
+ code: "NOT_FOUND",
721
+ message: "Grading boundary not found",
722
+ });
723
+ }
724
+ }
725
+ return prisma.assignment.update({
726
+ where: { id: assignmentId },
727
+ data: {
728
+ gradingBoundary: gradingBoundaryId
729
+ ? { connect: { id: gradingBoundaryId } }
730
+ : { disconnect: true },
731
+ },
732
+ include: {
733
+ attachments: true,
734
+ section: true,
735
+ teacher: true,
736
+ eventAttached: true,
737
+ gradingBoundary: true,
738
+ },
739
+ });
740
+ }
741
+ export async function detachGradingBoundaryRecord(teacherId, assignmentId) {
742
+ const assignment = await findAssignmentById(assignmentId);
743
+ if (!assignment) {
744
+ throw new TRPCError({
745
+ code: "NOT_FOUND",
746
+ message: "Assignment not found",
747
+ });
748
+ }
749
+ return prisma.assignment.update({
750
+ where: { id: assignmentId },
751
+ data: { gradingBoundary: { disconnect: true } },
752
+ include: {
753
+ attachments: true,
754
+ section: true,
755
+ teacher: true,
756
+ eventAttached: true,
757
+ gradingBoundary: true,
758
+ },
759
+ });
760
+ }
761
+ // --- Reorder & Move (unified list of sections + assignments) ---
762
+ async function getUnifiedList(tx, classId) {
763
+ const [sections, assignments] = await Promise.all([
764
+ tx.section.findMany({
765
+ where: { classId },
766
+ select: { id: true, order: true },
767
+ }),
768
+ tx.assignment.findMany({
769
+ where: { classId },
770
+ select: { id: true, order: true },
771
+ }),
772
+ ]);
773
+ const unified = [
774
+ ...sections.map((s) => ({ id: s.id, order: s.order, type: "section" })),
775
+ ...assignments.map((a) => ({ id: a.id, order: a.order, type: "assignment" })),
776
+ ].sort((a, b) => (a.order ?? Number.MAX_SAFE_INTEGER) - (b.order ?? Number.MAX_SAFE_INTEGER));
777
+ return unified;
778
+ }
779
+ async function normalizeUnifiedList(tx, classId, orderedItems) {
780
+ const BATCH_SIZE = 10;
781
+ const sections = [];
782
+ const assignments = [];
783
+ orderedItems.forEach((item, index) => {
784
+ const orderData = { id: item.id, order: index + 1 };
785
+ if (item.type === "section") {
786
+ sections.push(orderData);
787
+ }
788
+ else {
789
+ assignments.push(orderData);
790
+ }
791
+ });
792
+ const processBatch = async (items, type) => {
793
+ for (let i = 0; i < items.length; i += BATCH_SIZE) {
794
+ const batch = items.slice(i, i + BATCH_SIZE);
795
+ await Promise.all(batch.map((item) => type === "section"
796
+ ? tx.section.update({ where: { id: item.id }, data: { order: item.order } })
797
+ : tx.assignment.update({ where: { id: item.id }, data: { order: item.order } })));
798
+ }
799
+ };
800
+ await processBatch(sections, "section");
801
+ await processBatch(assignments, "assignment");
802
+ }
803
+ export async function reorderAssignmentRecord(teacherId, input) {
804
+ const assignment = await findAssignmentWithClassForTeacher(input.movedId, teacherId);
805
+ if (!assignment) {
806
+ throw new TRPCError({
807
+ code: "NOT_FOUND",
808
+ message: "Assignment not found",
809
+ });
810
+ }
811
+ if ((input.position === "before" || input.position === "after") && !input.targetId) {
812
+ throw new TRPCError({
813
+ code: "BAD_REQUEST",
814
+ message: "targetId required for before/after",
815
+ });
816
+ }
817
+ return prisma.$transaction(async (tx) => {
818
+ const unified = await getUnifiedList(tx, input.classId);
819
+ const movedIdx = unified.findIndex((item) => item.id === input.movedId && item.type === "assignment");
820
+ if (movedIdx === -1) {
821
+ throw new TRPCError({
822
+ code: "NOT_FOUND",
823
+ message: "Assignment not found in unified list",
824
+ });
825
+ }
826
+ const withoutMoved = unified.filter((item) => !(item.id === input.movedId && item.type === "assignment"));
827
+ let next = [];
828
+ if (input.position === "start") {
829
+ next = [
830
+ { id: input.movedId, type: "assignment" },
831
+ ...withoutMoved.map((item) => ({ id: item.id, type: item.type })),
832
+ ];
833
+ }
834
+ else if (input.position === "end") {
835
+ next = [
836
+ ...withoutMoved.map((item) => ({ id: item.id, type: item.type })),
837
+ { id: input.movedId, type: "assignment" },
838
+ ];
839
+ }
840
+ else {
841
+ const targetIdx = withoutMoved.findIndex((item) => item.id === input.targetId);
842
+ if (targetIdx === -1) {
843
+ throw new TRPCError({
844
+ code: "BAD_REQUEST",
845
+ message: "targetId not found in unified list",
846
+ });
847
+ }
848
+ if (input.position === "before") {
849
+ next = [
850
+ ...withoutMoved.slice(0, targetIdx).map((item) => ({ id: item.id, type: item.type })),
851
+ { id: input.movedId, type: "assignment" },
852
+ ...withoutMoved.slice(targetIdx).map((item) => ({ id: item.id, type: item.type })),
853
+ ];
854
+ }
855
+ else {
856
+ next = [
857
+ ...withoutMoved.slice(0, targetIdx + 1).map((item) => ({ id: item.id, type: item.type })),
858
+ { id: input.movedId, type: "assignment" },
859
+ ...withoutMoved.slice(targetIdx + 1).map((item) => ({ id: item.id, type: item.type })),
860
+ ];
861
+ }
862
+ }
863
+ await normalizeUnifiedList(tx, input.classId, next);
864
+ return tx.assignment.findUnique({ where: { id: input.movedId } });
865
+ }, { maxWait: 10000, timeout: 30000 });
866
+ }
867
+ export async function moveAssignmentRecord(teacherId, input) {
868
+ const assignment = await findAssignmentWithClassForTeacher(input.id, teacherId);
869
+ if (!assignment) {
870
+ throw new TRPCError({
871
+ code: "NOT_FOUND",
872
+ message: "Assignment not found",
873
+ });
874
+ }
875
+ if (input.targetSectionId) {
876
+ const section = await prisma.section.findFirst({
877
+ where: {
878
+ id: input.targetSectionId,
879
+ classId: input.classId,
880
+ },
881
+ });
882
+ if (!section) {
883
+ throw new TRPCError({
884
+ code: "NOT_FOUND",
885
+ message: "Section not found",
886
+ });
887
+ }
888
+ }
889
+ return prisma.$transaction(async (tx) => {
890
+ await tx.assignment.update({
891
+ where: { id: input.id },
892
+ data: { sectionId: input.targetSectionId },
893
+ });
894
+ return tx.assignment.findUnique({ where: { id: input.id } });
895
+ }, { maxWait: 5000, timeout: 10000 });
896
+ }
897
+ //# sourceMappingURL=assignment.js.map
898
+ //# debugId=9f048e12-9c8f-592d-82cb-74a5ccf39c12