@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,870 +1,248 @@
1
1
  import { z } from "zod";
2
- import { createTRPCRouter, protectedProcedure, protectedTeacherProcedure, protectedClassMemberProcedure } from "../trpc.js";
3
- import { prisma } from "../lib/prisma.js";
4
- import { TRPCError } from "@trpc/server";
5
- import { generateInviteCode } from "../utils/generateInviteCode.js";
2
+ import {
3
+ createTRPCRouter,
4
+ protectedProcedure,
5
+ protectedTeacherProcedure,
6
+ protectedClassMemberProcedure,
7
+ } from "../trpc.js";
8
+ import {
9
+ getAllClasses,
10
+ getClass,
11
+ updateClass,
12
+ createClass,
13
+ deleteClass,
14
+ addStudent,
15
+ changeRole,
16
+ removeMember,
17
+ leaveClass,
18
+ joinClass,
19
+ getInviteCode,
20
+ createInviteCode,
21
+ getGrades,
22
+ updateGrade,
23
+ getEvents,
24
+ listMarkSchemes,
25
+ createMarkScheme,
26
+ updateMarkScheme,
27
+ deleteMarkScheme,
28
+ listGradingBoundaries,
29
+ createGradingBoundary,
30
+ updateGradingBoundary,
31
+ deleteGradingBoundary,
32
+ getSyllabus,
33
+ updateSyllabus,
34
+ listLabDrafts,
35
+ createLabDraft,
36
+ updateLabDraft,
37
+ deleteLabDraft,
38
+ publishLabDraft,
39
+ getFiles,
40
+ exportClass,
41
+ importClass,
42
+ } from "../services/class.js";
6
43
 
7
44
  export const classRouter = createTRPCRouter({
8
- getAll: protectedProcedure
9
-
10
- .query(async ({ ctx }) => {
11
- const [teacherClasses, studentClasses] = await Promise.all([
12
- prisma.class.findMany({
13
- where: {
14
- teachers: {
15
- some: {
16
- id: ctx.user?.id,
17
- },
18
- },
19
- },
20
- include: {
21
- assignments: {
22
- where: {
23
- dueDate: {
24
- lte: new Date(new Date().setHours(23, 59, 59, 999)),
25
- },
26
- template: false,
27
- },
28
- select: {
29
- id: true,
30
- title: true,
31
- type: true,
32
- dueDate: true,
33
- },
34
- },
35
- },
36
- }),
37
- prisma.class.findMany({
38
- where: {
39
- students: {
40
- some: {
41
- id: ctx.user?.id,
42
- },
43
- },
44
- },
45
- include: {
46
- assignments: {
47
- where: {
48
- dueDate: {
49
- lte: new Date(new Date().setHours(23, 59, 59, 999)),
50
- },
51
- template: false,
52
- },
53
- select: {
54
- id: true,
55
- title: true,
56
- type: true,
57
- dueDate: true,
58
- },
59
- },
60
- },
61
- }),
62
- ]);
45
+ getAll: protectedProcedure.query(({ ctx }) =>
46
+ getAllClasses(ctx.user?.id ?? "")
47
+ ),
63
48
 
64
- return {
65
- teacherInClass: teacherClasses.map(cls => ({
66
- id: cls.id,
67
- name: cls.name,
68
- section: cls.section,
69
- subject: cls.subject,
70
- dueToday: cls.assignments,
71
- assignments: cls.assignments,
72
- color: cls.color,
73
- })),
74
- studentInClass: studentClasses.map(cls => ({
75
- id: cls.id,
76
- name: cls.name,
77
- section: cls.section,
78
- subject: cls.subject,
79
- dueToday: cls.assignments,
80
- assignments: cls.assignments,
81
- color: cls.color,
82
- })),
83
- };
84
- }),
85
49
  get: protectedProcedure
86
- .input(z.object({
87
- classId: z.string(),
88
- }))
89
- .query(async ({ ctx, input }) => {
90
- const { classId } = input;
91
-
92
- const isTeacher = await prisma.class.findFirst({
93
- where: {
94
- id: classId,
95
- teachers: {
96
- some: { id: ctx.user?.id },
97
- },
98
- },
99
- });
100
-
101
- const classData = await prisma.class.findUnique({
102
- where: {
103
- id: classId,
104
- },
105
- include: {
106
- teachers: {
107
- select: {
108
- id: true,
109
- username: true,
110
- profile: {
111
- select: {
112
- displayName: true,
113
- profilePicture: true,
114
- profilePictureThumbnail: true,
115
- }
116
- }
117
- },
118
- },
119
- students: {
120
- select: {
121
- id: true,
122
- username: true,
123
- profile: {
124
- select: {
125
- displayName: true,
126
- profilePicture: true,
127
- profilePictureThumbnail: true,
128
- },
129
- },
130
- },
131
- },
132
- announcements: {
133
- orderBy: {
134
- createdAt: 'desc',
135
- },
136
- select: {
137
- id: true,
138
- remarks: true,
139
- createdAt: true,
140
- teacher: {
141
- select: {
142
- id: true,
143
- username: true,
144
- profile: {
145
- select: {
146
- displayName: true,
147
- profilePicture: true,
148
- profilePictureThumbnail: true,
149
- },
150
- },
151
- },
152
- },
153
- },
154
- },
155
- assignments: {
156
- select: {
157
- type: true,
158
- id: true,
159
- title: true,
160
- dueDate: true,
161
- createdAt: true,
162
- weight: true,
163
- order: true,
164
- graded: true,
165
- maxGrade: true,
166
- instructions: true,
167
- inProgress: true,
168
- template: false,
169
- section: {
170
- select: {
171
- id: true,
172
- name: true,
173
- },
174
- },
175
- markScheme: {
176
- select: {
177
- id: true,
178
- structured: true,
179
- },
180
- },
181
- gradingBoundary: {
182
- select: {
183
- id: true,
184
- structured: true,
185
- },
186
- },
187
- submissions: {
188
- ...(!isTeacher && {
189
- where: {
190
- studentId: ctx.user?.id,
191
- },
192
- }),
193
- select: {
194
- studentId: true,
195
- id: true,
196
- submitted: true,
197
- returned: true,
198
- submittedAt: true,
199
- },
200
- },
201
- },
202
- },
203
- },
204
- });
205
-
206
-
207
- if (!classData) {
208
- throw new Error('Class not found');
209
- }
210
-
211
- const formattedClassData = {
212
- ...classData,
213
- assignments: classData.assignments.map(assignment => ({
214
- ...assignment,
215
- late: assignment.dueDate < new Date(),
216
- submitted: assignment.submissions.some(submission => submission.studentId === ctx.user?.id),
217
- returned: assignment.submissions.some(submission => submission.studentId === ctx.user?.id && submission.returned),
218
- })),
219
- }
220
-
221
- const sections = await prisma.section.findMany({
222
- where: {
223
- classId: classId,
224
- },
225
- });
226
-
227
- return {
228
- class: {
229
- ...formattedClassData,
230
- sections,
231
- },
232
- };
233
- }),
50
+ .input(z.object({ classId: z.string() }))
51
+ .query(({ ctx, input }) =>
52
+ getClass(ctx.user?.id ?? "", input.classId)
53
+ ),
234
54
  update: protectedTeacherProcedure
235
- .input(z.object({
236
- classId: z.string(),
237
- name: z.string().optional(),
238
- section: z.string().optional(),
239
- subject: z.string().optional(),
240
- }))
241
- .mutation(async ({ ctx, input }) => {
55
+ .input(
56
+ z.object({
57
+ classId: z.string(),
58
+ name: z.string().optional(),
59
+ section: z.string().optional(),
60
+ subject: z.string().optional(),
61
+ })
62
+ )
63
+ .mutation(({ input }) => {
242
64
  const { classId, ...updateData } = input;
243
-
244
- const updatedClass = await prisma.class.update({
245
- where: {
246
- id: classId,
247
- },
248
- data: updateData,
249
- select: {
250
- id: true,
251
- name: true,
252
- section: true,
253
- subject: true,
254
- }
255
- });
256
-
257
- return {
258
- updatedClass,
259
- }
65
+ return updateClass(classId, updateData);
260
66
  }),
67
+
261
68
  create: protectedProcedure
262
- .input(z.object({
263
- students: z.array(z.string()).optional(),
264
- teachers: z.array(z.string()).optional(),
265
- name: z.string(),
266
- section: z.string(),
267
- subject: z.string(),
268
- color: z.string().optional(),
269
- }))
270
- .mutation(async ({ ctx, input }) => {
271
- const { students, teachers, name, section, subject, color } = input;
272
-
273
- if (teachers && teachers.length > 0 && students && students.length > 0) {
274
- const newClass = await prisma.class.create({
275
- data: {
276
- name,
277
- section,
278
- subject,
279
- color,
280
- teachers: {
281
- connect: teachers.map(teacher => ({ id: teacher })),
282
- },
283
- students: {
284
- connect: students.map(student => ({ id: student })),
285
- },
286
- },
287
- include: {
288
- teachers: true,
289
- students: true,
290
- },
291
- });
292
- return newClass;
293
- }
69
+ .input(
70
+ z.object({
71
+ students: z.array(z.string()).optional(),
72
+ teachers: z.array(z.string()).optional(),
73
+ name: z.string(),
74
+ section: z.string(),
75
+ subject: z.string(),
76
+ color: z.string().optional(),
77
+ })
78
+ )
79
+ .mutation(({ ctx, input }) =>
80
+ createClass(ctx.user?.id ?? "", input)
81
+ ),
294
82
 
295
- const newClass = await prisma.class.create({
296
- data: {
297
- name,
298
- section,
299
- subject,
300
- color,
301
- teachers: {
302
- connect: {
303
- id: ctx.user?.id,
304
- },
305
- },
306
- },
307
- });
308
-
309
- return newClass;
310
- }),
311
83
  delete: protectedTeacherProcedure
312
- .input(z.object({
313
- classId: z.string(),
314
- id: z.string(),
315
- }))
316
- .mutation(async ({ ctx, input }) => {
317
- // Verify user is the teacher of this class
318
- const classToDelete = await prisma.class.findFirst({
319
- where: {
320
- id: input.id,
321
- },
322
- });
323
-
324
- if (!classToDelete) {
325
- throw new Error("Class not found or you don't have permission to delete it");
326
- }
327
-
328
- await prisma.class.delete({
329
- where: {
330
- id: input.id,
331
- },
332
- });
333
-
334
- return {
335
- deletedClass: {
336
- id: input.id,
337
- }
338
- }
339
- }),
84
+ .input(z.object({ classId: z.string(), id: z.string() }))
85
+ .mutation(({ input }) => deleteClass(input.id)),
340
86
  addStudent: protectedTeacherProcedure
341
- .input(z.object({
342
- classId: z.string(),
343
- studentId: z.string(),
344
- }))
345
- .mutation(async ({ ctx, input }) => {
346
- const { classId, studentId } = input;
87
+ .input(z.object({ classId: z.string(), studentId: z.string() }))
88
+ .mutation(({ input }) => addStudent(input.classId, input.studentId)),
347
89
 
348
- const student = await prisma.user.findUnique({
349
- where: {
350
- id: studentId,
351
- },
352
- });
353
-
354
- if (!student) {
355
- throw new Error("Student not found");
356
- }
357
-
358
- const updatedClass = await prisma.class.update({
359
- where: {
360
- id: classId,
361
- },
362
- data: {
363
- students: {
364
- connect: {
365
- id: studentId,
366
- },
367
- },
368
- },
369
- select: {
370
- id: true,
371
- name: true,
372
- section: true,
373
- subject: true,
374
- }
375
- });
376
-
377
- return {
378
- updatedClass,
379
- newStudent: student,
380
- }
381
- }),
382
90
  changeRole: protectedTeacherProcedure
383
- .input(z.object({
384
- classId: z.string(),
385
- userId: z.string(),
386
- type: z.enum(['teacher', 'student']),
387
- }))
388
- .mutation(async ({ ctx, input }) => {
389
- const { classId, userId, type } = input;
390
-
391
- const user = await prisma.user.findUnique({
392
- where: { id: userId },
393
- select: {
394
- id: true,
395
- username: true,
396
- },
397
- });
91
+ .input(
92
+ z.object({
93
+ classId: z.string(),
94
+ userId: z.string(),
95
+ type: z.enum(["teacher", "student"]),
96
+ })
97
+ )
98
+ .mutation(({ input }) =>
99
+ changeRole(input.classId, input.userId, input.type)
100
+ ),
398
101
 
399
- if (!user) {
400
- throw new Error("User not found");
102
+ removeMember: protectedTeacherProcedure
103
+ .input(z.object({ classId: z.string(), userId: z.string() }))
104
+ .mutation(({ input }) => removeMember(input.classId, input.userId)),
105
+
106
+ leaveClass: protectedProcedure
107
+ .input(z.object({ classId: z.string() }))
108
+ .mutation(({ ctx, input }) => {
109
+ if (!ctx.user?.id) {
110
+ throw new Error("User not authenticated");
401
111
  }
402
-
403
- const updatedClass = await prisma.class.update({
404
- where: { id: classId },
405
- data: {
406
- [type === 'teacher' ? 'teachers' : 'students']: {
407
- connect: { id: userId },
408
- },
409
- [type === 'teacher' ? 'students' : 'teachers']: {
410
- disconnect: { id: userId },
411
- },
412
- },
413
- });
414
-
415
- return {
416
- updatedClass,
417
- user: {
418
- ...user,
419
- type,
420
- },
421
- };
112
+ return leaveClass(ctx.user.id, input.classId);
422
113
  }),
423
- removeMember: protectedTeacherProcedure
424
- .input(z.object({
425
- classId: z.string(),
426
- userId: z.string(),
427
- }))
428
- .mutation(async ({ ctx, input }) => {
429
- const { classId, userId } = input;
430
-
431
- const updatedClass = await prisma.class.update({
432
- where: { id: classId },
433
- data: {
434
- teachers: {
435
- disconnect: { id: userId },
436
- },
437
- students: {
438
- disconnect: { id: userId },
439
- },
440
- },
441
- });
442
114
 
443
- return {
444
- updatedClass,
445
- removedUserId: userId,
446
- };
447
- }),
448
115
  join: protectedProcedure
449
- .input(z.object({
450
- classCode: z.string(),
451
- }))
452
- .mutation(async ({ ctx, input }) => {
453
- const { classCode } = input;
454
-
455
- const session = await prisma.session.findFirst({
456
- where: {
457
- id: classCode,
458
- },
459
- });
116
+ .input(z.object({ classCode: z.string() }))
117
+ .mutation(({ ctx, input }) =>
118
+ joinClass(ctx.user?.id ?? "", input.classCode)
119
+ ),
460
120
 
461
- if (!session || !session.classId) {
462
- throw new Error("Class not found");
463
- }
464
-
465
- if (session.expiresAt && session.expiresAt < new Date()) {
466
- throw new Error("Session expired");
467
- }
468
-
469
- const updatedClass = await prisma.class.update({
470
- where: { id: session.classId },
471
- data: {
472
- students: {
473
- connect: { id: ctx.user?.id },
474
- },
475
- },
476
- select: {
477
- id: true,
478
- name: true,
479
- section: true,
480
- subject: true,
481
- },
482
- });
483
-
484
- return {
485
- joinedClass: updatedClass,
486
- }
487
- }),
488
121
  getInviteCode: protectedTeacherProcedure
489
- .input(z.object({
490
- classId: z.string(),
491
- }))
492
- .query(async ({ ctx, input }) => {
493
- const { classId } = input;
122
+ .input(z.object({ classId: z.string() }))
123
+ .query(({ input }) => getInviteCode(input.classId)),
494
124
 
495
- const session = await prisma.session.findFirst({
496
- where: {
497
- classId,
498
- },
499
- });
500
-
501
- if ((session?.expiresAt && session.expiresAt < new Date()) || !session) {
502
- const newSession = await prisma.session.create({
503
- data: {
504
- id: generateInviteCode(),
505
- classId,
506
- expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24 hours from now
507
- }
508
- });
509
- return {
510
- code: newSession.id,
511
- }
512
- }
513
-
514
- return {
515
- code: session?.id,
516
- };
517
- }),
518
125
  createInviteCode: protectedTeacherProcedure
519
- .input(z.object({
520
- classId: z.string(),
521
- }))
522
- .mutation(async ({ ctx, input }) => {
523
- const { classId } = input;
524
-
525
- await prisma.session.deleteMany({
526
- where: {
527
- classId,
528
- },
529
- });
530
-
531
- // Create a new session for the invite code
532
- const session = await prisma.session.create({
533
- data: {
534
- id: generateInviteCode(),
535
- classId,
536
- expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24 hours from now
537
- }
538
- });
539
-
540
- return {
541
- code: session.id,
542
- };
543
- }),
126
+ .input(z.object({ classId: z.string() }))
127
+ .mutation(({ input }) => createInviteCode(input.classId)),
544
128
  getGrades: protectedClassMemberProcedure
545
- .input(z.object({
546
- classId: z.string(),
547
- userId: z.string(),
548
- }))
549
- .query(async ({ ctx, input }) => {
550
- const { classId, userId } = input;
551
-
552
- const isTeacher = await prisma.class.findFirst({
553
- where: {
554
- id: classId,
555
- teachers: {
556
- some: { id: ctx.user?.id }
557
- }
558
- }
559
- });
560
- // If student, only allow viewing their own grades
561
- if (ctx.user?.id !== userId && !isTeacher) {
562
- throw new TRPCError({
563
- code: 'UNAUTHORIZED',
564
- message: 'You can only view your own grades',
565
- });
566
- }
567
-
568
- const grades = await prisma.submission.findMany({
569
- where: {
570
- studentId: userId,
571
- assignment: {
572
- classId: classId,
573
- graded: true
574
- }
575
- },
576
- include: {
577
- assignment: {
578
- select: {
579
- id: true,
580
- title: true,
581
- maxGrade: true,
582
- weight: true,
583
- markSchemeId: true,
584
- markScheme: {
585
- select: {
586
- structured: true,
587
- }
588
- },
589
- gradingBoundaryId: true,
590
- gradingBoundary: {
591
- select: {
592
- structured: true,
593
- }
594
- },
595
- }
596
- },
597
- }
598
- });
129
+ .input(z.object({ classId: z.string(), userId: z.string() }))
130
+ .query(({ ctx, input }) =>
131
+ getGrades(ctx.user?.id ?? "", input.classId, input.userId)
132
+ ),
599
133
 
600
- return {
601
- grades,
602
- };
603
- }),
604
134
  updateGrade: protectedTeacherProcedure
605
- .input(z.object({
606
- classId: z.string(),
607
- assignmentId: z.string(),
608
- submissionId: z.string(),
609
- gradeReceived: z.number().nullable(),
610
- }))
611
- .mutation(async ({ ctx, input }) => {
612
- const { classId, assignmentId, submissionId, gradeReceived } = input;
613
-
614
- // Update the grade
615
- const updatedSubmission = await prisma.submission.update({
616
- where: {
617
- id: submissionId,
618
- assignmentId: assignmentId,
619
- },
620
- data: {
621
- gradeReceived,
622
- },
623
- include: {
624
- assignment: {
625
- select: {
626
- id: true,
627
- title: true,
628
- maxGrade: true,
629
- weight: true,
630
- }
631
- }
632
- }
633
- });
634
-
635
- return updatedSubmission;
636
- }),
637
- getEvents: protectedTeacherProcedure
638
- .input(z.object({
639
- classId: z.string(),
640
- }))
641
- .query(async ({ ctx, input }) => {
642
- const { classId } = input;
643
-
644
- const events = await prisma.event.findMany({
645
- where: {
646
- class: {
647
- id: classId,
648
- }
649
- },
650
- select: {
651
- name: true,
652
- startTime: true,
653
- endTime: true,
654
- }
655
- });
656
-
657
- return events;
658
- }),
659
- listMarkSchemes: protectedTeacherProcedure
660
- .input(z.object({
135
+ .input(
136
+ z.object({
661
137
  classId: z.string(),
662
- }))
663
- .query(async ({ ctx, input }) => {
664
- const { classId } = input;
665
-
666
- const markSchemes = await prisma.markScheme.findMany({
667
- where: {
668
- classId: classId,
669
- },
670
- });
671
-
672
- return markSchemes;
673
- }),
674
- createMarkScheme: protectedTeacherProcedure
675
- .input(z.object({
676
- classId: z.string(),
677
- structure: z.string(),
678
- }))
679
- .mutation(async ({ ctx, input }) => {
680
- const { classId, structure } = input;
681
-
682
- const validatedStructure = structure.replace(/\\n/g, '\n');
683
-
684
- const markScheme = await prisma.markScheme.create({
685
- data: {
686
- classId: classId,
687
- structured: validatedStructure,
688
- },
689
- });
690
-
691
- return markScheme;
692
- }),
693
- updateMarkScheme: protectedTeacherProcedure
694
- .input(z.object({
138
+ assignmentId: z.string(),
139
+ submissionId: z.string(),
140
+ gradeReceived: z.number().nullable(),
141
+ })
142
+ )
143
+ .mutation(({ input }) =>
144
+ updateGrade(
145
+ input.assignmentId,
146
+ input.submissionId,
147
+ input.gradeReceived
148
+ )
149
+ ),
150
+
151
+ getEvents: protectedTeacherProcedure
152
+ .input(z.object({ classId: z.string() }))
153
+ .query(({ input }) => getEvents(input.classId)),
154
+ listMarkSchemes: protectedClassMemberProcedure
155
+ .input(z.object({ classId: z.string() }))
156
+ .query(({ input }) => listMarkSchemes(input.classId)),
157
+
158
+ createMarkScheme: protectedTeacherProcedure
159
+ .input(z.object({ classId: z.string(), structure: z.string() }))
160
+ .mutation(({ input }) =>
161
+ createMarkScheme(input.classId, input.structure)
162
+ ),
163
+
164
+ updateMarkScheme: protectedTeacherProcedure
165
+ .input(
166
+ z.object({
695
167
  classId: z.string(),
696
168
  markSchemeId: z.string(),
697
169
  structure: z.string(),
698
- }))
699
- .mutation(async ({ ctx, input }) => {
700
- const { classId, markSchemeId, structure } = input;
701
-
702
- const validatedStructure = structure.replace(/\\n/g, '\n');
703
-
704
- const markScheme = await prisma.markScheme.update({
705
- where: { id: markSchemeId },
706
- data: { structured: validatedStructure },
707
- });
708
-
709
- return markScheme;
710
- }),
711
- deleteMarkScheme: protectedTeacherProcedure
712
- .input(z.object({
713
- classId: z.string(),
714
- markSchemeId: z.string(),
715
- }))
716
- .mutation(async ({ ctx, input }) => {
717
- const { classId, markSchemeId } = input;
718
-
719
- const markScheme = await prisma.markScheme.delete({
720
- where: { id: markSchemeId },
721
- });
722
-
723
- return markScheme;
724
- }),
725
- listGradingBoundaries: protectedTeacherProcedure
726
- .input(z.object({
727
- classId: z.string(),
728
- }))
729
- .query(async ({ ctx, input }) => {
730
- const { classId } = input;
731
-
732
- const gradingBoundaries = await prisma.gradingBoundary.findMany({
733
- where: {
734
- classId: classId,
735
- },
736
- });
737
-
738
- return gradingBoundaries;
739
- }),
740
- createGradingBoundary: protectedTeacherProcedure
741
- .input(z.object({
742
- classId: z.string(),
743
- structure: z.string(),
744
- }))
745
- .mutation(async ({ ctx, input }) => {
746
- const { classId, structure } = input;
747
-
748
- const validatedStructure = structure.replace(/\\n/g, '\n');
749
-
750
- const gradingBoundary = await prisma.gradingBoundary.create({
751
- data: {
752
- classId: classId,
753
- structured: validatedStructure,
754
- },
755
- });
756
-
757
- return gradingBoundary;
758
- }),
759
- updateGradingBoundary: protectedTeacherProcedure
760
- .input(z.object({
170
+ })
171
+ )
172
+ .mutation(({ input }) =>
173
+ updateMarkScheme(
174
+ input.markSchemeId,
175
+ input.classId,
176
+ input.structure
177
+ )
178
+ ),
179
+
180
+ deleteMarkScheme: protectedTeacherProcedure
181
+ .input(z.object({ classId: z.string(), markSchemeId: z.string() }))
182
+ .mutation(({ input }) => deleteMarkScheme(input.markSchemeId)),
183
+
184
+ listGradingBoundaries: protectedClassMemberProcedure
185
+ .input(z.object({ classId: z.string() }))
186
+ .query(({ input }) => listGradingBoundaries(input.classId)),
187
+
188
+ createGradingBoundary: protectedTeacherProcedure
189
+ .input(z.object({ classId: z.string(), structure: z.string() }))
190
+ .mutation(({ input }) =>
191
+ createGradingBoundary(input.classId, input.structure)
192
+ ),
193
+
194
+ updateGradingBoundary: protectedTeacherProcedure
195
+ .input(
196
+ z.object({
761
197
  classId: z.string(),
762
198
  gradingBoundaryId: z.string(),
763
199
  structure: z.string(),
764
- }))
765
- .mutation(async ({ ctx, input }) => {
766
- const { classId, gradingBoundaryId, structure } = input;
767
-
768
- const validatedStructure = structure.replace(/\\n/g, '\n');
769
-
770
- const gradingBoundary = await prisma.gradingBoundary.update({
771
- where: { id: gradingBoundaryId },
772
- data: { structured: validatedStructure },
773
- });
774
-
775
- return gradingBoundary;
776
- }),
777
- deleteGradingBoundary: protectedTeacherProcedure
778
- .input(z.object({
779
- classId: z.string(),
780
- gradingBoundaryId: z.string(),
781
- }))
782
- .mutation(async ({ ctx, input }) => {
783
- const { classId, gradingBoundaryId } = input;
784
-
785
- const gradingBoundary = await prisma.gradingBoundary.delete({
786
- where: { id: gradingBoundaryId },
787
- });
788
-
789
- return gradingBoundary;
790
- }),
791
- getSyllabus: protectedClassMemberProcedure
792
- .input(z.object({
793
- classId: z.string(),
794
- }))
795
- .query(async ({input}) => {
796
- const {classId} = input;
797
-
798
- const syllabus = (await prisma.class.findUnique({
799
- where: {
800
- id: classId,
801
- },
802
- }))?.syllabus;
803
-
804
- const markSchemes = await prisma.markScheme.findMany({
805
- where: {
806
- classId,
807
- }
808
- });
809
-
810
- const gradingBoundaries = await prisma.gradingBoundary.findMany({
811
- where: {
812
- classId,
813
- }
814
- });
815
-
816
- return {syllabus, gradingBoundaries, markSchemes};
817
- }),
818
- updateSyllabus: protectedTeacherProcedure
819
- .input(z.object({
820
- classId: z.string(),
821
- contents: z.string(),
822
- }))
823
- .mutation(async ({ input }) => {
824
- const { contents, classId } = input;
825
-
826
- if (!contents) throw new TRPCError({
827
- code: 'BAD_REQUEST',
828
- message: "Missing key contents",
829
- });
830
-
831
- const updated = await prisma.class.update({
832
- where: {
833
- id: classId
834
- },
835
- data: {
836
- syllabus: contents,
837
- }
838
- });
839
-
840
- return updated;
841
- }),
842
- // Lab Management Endpoints (Assignment-based)
843
- listLabDrafts: protectedTeacherProcedure
844
- .input(z.object({
845
- classId: z.string(),
846
- }))
847
- .query(async ({ ctx, input }) => {
848
- const { classId } = input;
849
-
850
- const labDrafts = await prisma.assignment.findMany({
851
- where: {
852
- classId: classId,
853
- teacherId: ctx.user?.id,
854
- inProgress: true,
855
- },
856
- orderBy: {
857
- modifiedAt: 'desc',
858
- },
859
- });
860
-
861
- return labDrafts;
862
- }),
863
- createLabDraft: protectedTeacherProcedure
864
- .input(z.object({
200
+ })
201
+ )
202
+ .mutation(({ input }) =>
203
+ updateGradingBoundary(
204
+ input.gradingBoundaryId,
205
+ input.classId,
206
+ input.structure
207
+ )
208
+ ),
209
+
210
+ deleteGradingBoundary: protectedTeacherProcedure
211
+ .input(
212
+ z.object({ classId: z.string(), gradingBoundaryId: z.string() })
213
+ )
214
+ .mutation(({ input }) => deleteGradingBoundary(input.gradingBoundaryId)),
215
+ getSyllabus: protectedClassMemberProcedure
216
+ .input(z.object({ classId: z.string() }))
217
+ .query(({ input }) => getSyllabus(input.classId)),
218
+
219
+ updateSyllabus: protectedTeacherProcedure
220
+ .input(z.object({ classId: z.string(), contents: z.string() }))
221
+ .mutation(({ input }) =>
222
+ updateSyllabus(input.classId, input.contents)
223
+ ),
224
+ listLabDrafts: protectedTeacherProcedure
225
+ .input(z.object({ classId: z.string() }))
226
+ .query(({ ctx, input }) =>
227
+ listLabDrafts(input.classId, ctx.user?.id ?? "")
228
+ ),
229
+
230
+ createLabDraft: protectedTeacherProcedure
231
+ .input(
232
+ z.object({
865
233
  classId: z.string(),
866
234
  title: z.string(),
867
- type: z.enum(['LAB', 'HOMEWORK', 'QUIZ', 'TEST', 'PROJECT', 'ESSAY', 'DISCUSSION', 'PRESENTATION', 'OTHER']),
235
+ type: z.enum([
236
+ "LAB",
237
+ "HOMEWORK",
238
+ "QUIZ",
239
+ "TEST",
240
+ "PROJECT",
241
+ "ESSAY",
242
+ "DISCUSSION",
243
+ "PRESENTATION",
244
+ "OTHER",
245
+ ]),
868
246
  instructions: z.string(),
869
247
  dueDate: z.date().optional(),
870
248
  maxGrade: z.number().optional(),
@@ -873,32 +251,16 @@ export const classRouter = createTRPCRouter({
873
251
  sectionId: z.string().optional(),
874
252
  markSchemeId: z.string().optional(),
875
253
  gradingBoundaryId: z.string().optional(),
876
- }))
877
- .mutation(async ({ ctx, input }) => {
878
- const { classId, ...draftData } = input;
879
-
880
- const labDraft = await prisma.assignment.create({
881
- data: {
882
- classId: classId,
883
- teacherId: ctx.user?.id!,
884
- inProgress: true,
885
- graded: draftData.graded ?? false,
886
- maxGrade: draftData.maxGrade ?? 0,
887
- weight: draftData.weight ?? 1,
888
- dueDate: draftData.dueDate || new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // Default 1 week from now
889
- title: draftData.title,
890
- instructions: draftData.instructions,
891
- type: draftData.type,
892
- ...(draftData.sectionId && { sectionId: draftData.sectionId }),
893
- ...(draftData.markSchemeId && { markSchemeId: draftData.markSchemeId }),
894
- ...(draftData.gradingBoundaryId && { gradingBoundaryId: draftData.gradingBoundaryId }),
895
- },
896
- });
254
+ })
255
+ )
256
+ .mutation(({ ctx, input }) => {
257
+ const { classId, ...draftData } = input;
258
+ return createLabDraft(classId, ctx.user?.id ?? "", draftData);
259
+ }),
897
260
 
898
- return labDraft;
899
- }),
900
- updateLabDraft: protectedTeacherProcedure
901
- .input(z.object({
261
+ updateLabDraft: protectedTeacherProcedure
262
+ .input(
263
+ z.object({
902
264
  classId: z.string(),
903
265
  draftId: z.string(),
904
266
  title: z.string().optional(),
@@ -910,196 +272,61 @@ export const classRouter = createTRPCRouter({
910
272
  sectionId: z.string().optional(),
911
273
  markSchemeId: z.string().optional(),
912
274
  gradingBoundaryId: z.string().optional(),
913
- }))
914
- .mutation(async ({ ctx, input }) => {
915
- const { classId, draftId, ...updateData } = input;
916
-
917
- const labDraft = await prisma.assignment.update({
918
- where: {
919
- id: draftId,
920
- classId: classId,
921
- teacherId: ctx.user?.id!,
922
- inProgress: true,
923
- },
924
- data: {
925
- ...(updateData.title && { title: updateData.title }),
926
- ...(updateData.instructions && { instructions: updateData.instructions }),
927
- ...(updateData.dueDate && { dueDate: updateData.dueDate }),
928
- ...(updateData.maxGrade !== undefined && { maxGrade: updateData.maxGrade }),
929
- ...(updateData.weight !== undefined && { weight: updateData.weight }),
930
- ...(updateData.graded !== undefined && { graded: updateData.graded }),
931
- ...(updateData.sectionId !== undefined && { sectionId: updateData.sectionId }),
932
- ...(updateData.markSchemeId !== undefined && { markSchemeId: updateData.markSchemeId }),
933
- ...(updateData.gradingBoundaryId !== undefined && { gradingBoundaryId: updateData.gradingBoundaryId }),
934
- modifiedAt: new Date(),
935
- },
936
- });
937
-
938
- return labDraft;
939
- }),
940
- deleteLabDraft: protectedTeacherProcedure
941
- .input(z.object({
942
- classId: z.string(),
943
- draftId: z.string(),
944
- }))
945
- .mutation(async ({ ctx, input }) => {
946
- const { classId, draftId } = input;
275
+ })
276
+ )
277
+ .mutation(({ ctx, input }) => {
278
+ const { classId, draftId, ...updateData } = input;
279
+ return updateLabDraft(
280
+ classId,
281
+ ctx.user?.id ?? "",
282
+ draftId,
283
+ updateData
284
+ );
285
+ }),
947
286
 
948
- const labDraft = await prisma.assignment.delete({
949
- where: {
950
- id: draftId,
951
- classId: classId,
952
- teacherId: ctx.user?.id!,
953
- inProgress: true,
954
- },
955
- });
287
+ deleteLabDraft: protectedTeacherProcedure
288
+ .input(z.object({ classId: z.string(), draftId: z.string() }))
289
+ .mutation(({ ctx, input }) =>
290
+ deleteLabDraft(input.classId, ctx.user?.id ?? "", input.draftId)
291
+ ),
956
292
 
957
- return labDraft;
958
- }),
959
- publishLabDraft: protectedTeacherProcedure
960
- .input(z.object({
293
+ publishLabDraft: protectedTeacherProcedure
294
+ .input(
295
+ z.object({
961
296
  classId: z.string(),
962
297
  draftId: z.string(),
963
298
  dueDate: z.date().optional(),
964
299
  maxGrade: z.number().optional(),
965
300
  weight: z.number().optional(),
966
301
  graded: z.boolean().optional(),
967
- }))
968
- .mutation(async ({ ctx, input }) => {
969
- const { classId, draftId, ...publishData } = input;
970
-
971
- // Get the lab draft
972
- const labDraft = await prisma.assignment.findUnique({
973
- where: {
974
- id: draftId,
975
- classId: classId,
976
- teacherId: ctx.user?.id!,
977
- inProgress: true,
978
- },
979
- });
980
-
981
- if (!labDraft) {
982
- throw new TRPCError({
983
- code: 'NOT_FOUND',
984
- message: 'Lab draft not found',
985
- });
986
- }
302
+ })
303
+ )
304
+ .mutation(({ ctx, input }) => {
305
+ const { classId, draftId, ...publishData } = input;
306
+ return publishLabDraft(
307
+ classId,
308
+ ctx.user?.id ?? "",
309
+ draftId,
310
+ publishData
311
+ );
312
+ }),
987
313
 
988
- // Publish the draft by updating it to not be in progress
989
- const publishedAssignment = await prisma.assignment.update({
990
- where: { id: draftId },
991
- data: {
992
- inProgress: false,
993
- dueDate: publishData.dueDate || labDraft.dueDate,
994
- maxGrade: publishData.maxGrade || labDraft.maxGrade || 100,
995
- weight: publishData.weight || labDraft.weight || 1,
996
- graded: publishData.graded !== undefined ? publishData.graded : true,
997
- modifiedAt: new Date(),
998
- },
999
- });
314
+ getFiles: protectedClassMemberProcedure
315
+ .input(z.object({ classId: z.string() }))
316
+ .query(({ input }) => getFiles(input.classId)),
317
+ exportClass: protectedTeacherProcedure
318
+ .input(z.object({ classId: z.string() }))
319
+ .mutation(({ ctx, input }) => exportClass(input.classId, ctx.user?.id ?? "")),
1000
320
 
1001
- return publishedAssignment;
1002
- }),
1003
- getFiles: protectedClassMemberProcedure
1004
- .input(z.object({
321
+ importClass: protectedTeacherProcedure
322
+ .input(
323
+ z.object({
1005
324
  classId: z.string(),
1006
- }))
1007
- .query(async ({ ctx, input }) => {
1008
- const { classId } = input;
1009
-
1010
- // Get all assignments with their files and submissions
1011
- const assignments = await prisma.assignment.findMany({
1012
- where: {
1013
- classId: classId,
1014
- },
1015
- include: {
1016
- attachments: {
1017
- select: {
1018
- id: true,
1019
- name: true,
1020
- type: true,
1021
- size: true,
1022
- path: true,
1023
- thumbnailId: true,
1024
- uploadedAt: true,
1025
- user: {
1026
- select: {
1027
- id: true,
1028
- username: true,
1029
- },
1030
- },
1031
- },
1032
- },
1033
- submissions: {
1034
- include: {
1035
- attachments: {
1036
- select: {
1037
- id: true,
1038
- name: true,
1039
- type: true,
1040
- size: true,
1041
- path: true,
1042
- thumbnailId: true,
1043
- uploadedAt: true,
1044
- user: {
1045
- select: {
1046
- id: true,
1047
- username: true,
1048
- },
1049
- },
1050
- },
1051
- },
1052
- annotations: {
1053
- select: {
1054
- id: true,
1055
- name: true,
1056
- type: true,
1057
- size: true,
1058
- path: true,
1059
- thumbnailId: true,
1060
- uploadedAt: true,
1061
- user: {
1062
- select: {
1063
- id: true,
1064
- username: true,
1065
- },
1066
- },
1067
- },
1068
- },
1069
- student: {
1070
- select: {
1071
- id: true,
1072
- username: true,
1073
- },
1074
- },
1075
- },
1076
- },
1077
- teacher: {
1078
- select: {
1079
- id: true,
1080
- username: true,
1081
- },
1082
- },
1083
- },
1084
- orderBy: {
1085
- createdAt: 'desc',
1086
- },
1087
- });
1088
-
1089
- // Organize files by assignment structure
1090
- const organizedFiles = assignments.map(assignment => ({
1091
- id: assignment.id,
1092
- title: assignment.title,
1093
- teacher: assignment.teacher,
1094
- teacherAttachments: assignment.attachments,
1095
- students: assignment.submissions.map(submission => ({
1096
- id: submission.student.id,
1097
- username: submission.student.username,
1098
- attachments: submission.attachments,
1099
- annotations: submission.annotations,
1100
- })),
1101
- }));
1102
-
1103
- return organizedFiles;
1104
- }),
325
+ year: z.number(),
326
+ classData: z.any(),
327
+ })
328
+ )
329
+ .mutation(({ ctx, input }) =>
330
+ importClass(input.classId, ctx.user?.id ?? "", input.year, input.classData)
331
+ ),
1105
332
  });