@studious-lms/server 1.2.53 → 1.4.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 (479) 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 +461 -0
  53. package/dist/models/class.d.ts.map +1 -0
  54. package/dist/models/class.js +645 -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 +76 -0
  109. package/dist/pipelines/aiLabChat.d.ts.map +1 -0
  110. package/dist/pipelines/aiLabChat.js +599 -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 +1523 -1315
  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 +160 -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 +21 -11
  167. package/dist/routers/labChat.d.ts.map +1 -1
  168. package/dist/routers/labChat.js +22 -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 +13 -2
  175. package/dist/routers/message.d.ts.map +1 -1
  176. package/dist/routers/message.js +32 -520
  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 +68 -55
  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 +643 -0
  224. package/dist/services/class.d.ts.map +1 -0
  225. package/dist/services/class.js +486 -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 +169 -0
  248. package/dist/services/labChat.d.ts.map +1 -0
  249. package/dist/services/labChat.js +381 -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 +103 -0
  256. package/dist/services/message.d.ts.map +1 -0
  257. package/dist/services/message.js +422 -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/prisma/schema.prisma +1 -1
  309. package/src/index.ts +119 -12
  310. package/src/lib/config/env.ts +6 -0
  311. package/src/lib/fileUpload.ts +0 -1
  312. package/src/lib/googleCloudStorage.ts +17 -0
  313. package/src/lib/pusher.ts +5 -1
  314. package/src/lib/redis.ts +56 -0
  315. package/src/lib/thumbnailGenerator.ts +170 -168
  316. package/src/middleware/auth.ts +80 -137
  317. package/src/models/agenda.ts +46 -0
  318. package/src/models/announcement.ts +134 -0
  319. package/src/models/assignment.ts +322 -0
  320. package/src/models/attendance.ts +208 -0
  321. package/src/models/auth.ts +247 -0
  322. package/src/models/class.ts +703 -0
  323. package/src/models/comment.ts +152 -0
  324. package/src/models/conversation.ts +200 -0
  325. package/src/models/event.ts +177 -0
  326. package/src/models/file.ts +129 -0
  327. package/src/models/folder.ts +225 -0
  328. package/src/models/labChat.ts +213 -0
  329. package/src/models/marketing.ts +45 -0
  330. package/src/models/message.ts +153 -0
  331. package/src/models/newtonChat.ts +70 -0
  332. package/src/models/notification.ts +54 -0
  333. package/src/models/section.ts +98 -0
  334. package/src/models/user.ts +47 -0
  335. package/src/models/worksheet.ts +294 -0
  336. package/src/pipelines/aiLabChat.ts +684 -0
  337. package/src/{server/pipelines → pipelines}/aiNewtonChat.ts +9 -5
  338. package/src/{server/pipelines → pipelines}/gradeWorksheet.ts +25 -14
  339. package/src/routers/agenda.ts +3 -66
  340. package/src/routers/announcement.ts +54 -495
  341. package/src/routers/assignment.ts +126 -2018
  342. package/src/routers/attendance.ts +15 -276
  343. package/src/routers/auth.ts +79 -442
  344. package/src/routers/class.ts +263 -1187
  345. package/src/routers/comment.ts +61 -288
  346. package/src/routers/conversation.ts +51 -360
  347. package/src/routers/event.ts +50 -481
  348. package/src/routers/file.ts +45 -368
  349. package/src/routers/folder.ts +107 -836
  350. package/src/routers/labChat.ts +35 -604
  351. package/src/routers/marketing.ts +35 -77
  352. package/src/routers/message.ts +54 -567
  353. package/src/routers/newtonChat.ts +17 -278
  354. package/src/routers/notifications.ts +32 -82
  355. package/src/routers/section.ts +46 -330
  356. package/src/routers/user.ts +49 -227
  357. package/src/routers/worksheet.ts +215 -503
  358. package/src/services/agenda.ts +21 -0
  359. package/src/services/announcement.ts +290 -0
  360. package/src/services/assignment.ts +1198 -0
  361. package/src/services/attendance.ts +85 -0
  362. package/src/services/auth.ts +277 -0
  363. package/src/services/class.ts +629 -0
  364. package/src/services/comment.ts +106 -0
  365. package/src/services/conversation.ts +213 -0
  366. package/src/services/event.ts +231 -0
  367. package/src/services/file.ts +167 -0
  368. package/src/services/folder.ts +316 -0
  369. package/src/services/labChat.ts +458 -0
  370. package/src/services/marketing.ts +57 -0
  371. package/src/services/message.ts +554 -0
  372. package/src/services/newtonChat.ts +222 -0
  373. package/src/services/notification.ts +50 -0
  374. package/src/services/section.ts +283 -0
  375. package/src/services/user.ts +172 -0
  376. package/src/services/worksheet.ts +358 -0
  377. package/src/utils/aiUser.ts +4 -3
  378. package/src/utils/email.ts +5 -3
  379. package/src/utils/generateInviteCode.ts +1 -3
  380. package/src/utils/inference.ts +5 -2
  381. package/src/utils/logger.ts +3 -1
  382. package/src/utils/prismaErrorHandler.ts +3 -0
  383. package/src/utils/prismaWrapper.ts +4 -0
  384. package/tests/globalSetup.ts +62 -0
  385. package/tests/helpers.ts +22 -0
  386. package/tests/middleware/security.test.ts +42 -0
  387. package/tests/routers/agenda.test.ts +138 -0
  388. package/tests/routers/announcement.test.ts +490 -0
  389. package/tests/routers/assignment.test.ts +837 -0
  390. package/tests/{attendance.test.ts → routers/attendance.test.ts} +6 -14
  391. package/tests/routers/auth.test.ts +171 -0
  392. package/tests/{class.test.ts → routers/class.test.ts} +131 -85
  393. package/tests/routers/comment.test.ts +126 -0
  394. package/tests/routers/conversation.test.ts +145 -0
  395. package/tests/{event.test.ts → routers/event.test.ts} +93 -32
  396. package/tests/routers/folder.test.ts +178 -0
  397. package/tests/routers/labChat.test.ts +115 -0
  398. package/tests/routers/marketing.test.ts +59 -0
  399. package/tests/routers/message.test.ts +123 -0
  400. package/tests/routers/notification.test.ts +69 -0
  401. package/tests/{section.test.ts → routers/section.test.ts} +5 -13
  402. package/tests/server/rateLimit.test.ts +73 -0
  403. package/tests/setup.ts +18 -92
  404. package/tests/user.test.ts +9 -31
  405. package/tests/utils/aiUser.test.ts +22 -0
  406. package/tests/utils/generateInviteCode.test.ts +24 -0
  407. package/tests/utils/logger.test.ts +74 -0
  408. package/tests/utils/prismaErrorHandler.test.ts +101 -0
  409. package/tests/utils/prismaWrapper.test.ts +82 -0
  410. package/tests/worksheet.test.ts +181 -0
  411. package/vitest.config.ts +6 -3
  412. package/vitest.unit.config.ts +21 -0
  413. package/TODO.md +0 -2
  414. package/coverage/base.css +0 -224
  415. package/coverage/block-navigation.js +0 -87
  416. package/coverage/clover.xml +0 -12110
  417. package/coverage/coverage-final.json +0 -44
  418. package/coverage/favicon.png +0 -0
  419. package/coverage/index.html +0 -221
  420. package/coverage/prettify.css +0 -1
  421. package/coverage/prettify.js +0 -2
  422. package/coverage/server/index.html +0 -116
  423. package/coverage/server/src/exportType.ts.html +0 -109
  424. package/coverage/server/src/index.html +0 -161
  425. package/coverage/server/src/index.ts.html +0 -1702
  426. package/coverage/server/src/instrument.ts.html +0 -130
  427. package/coverage/server/src/lib/config/env.ts.html +0 -448
  428. package/coverage/server/src/lib/config/index.html +0 -116
  429. package/coverage/server/src/lib/fileUpload.ts.html +0 -1138
  430. package/coverage/server/src/lib/googleCloudStorage.ts.html +0 -334
  431. package/coverage/server/src/lib/index.html +0 -206
  432. package/coverage/server/src/lib/jsonConversion.ts.html +0 -2323
  433. package/coverage/server/src/lib/jsonStyles.ts.html +0 -193
  434. package/coverage/server/src/lib/notificationHandler.ts.html +0 -193
  435. package/coverage/server/src/lib/pusher.ts.html +0 -121
  436. package/coverage/server/src/lib/thumbnailGenerator.ts.html +0 -592
  437. package/coverage/server/src/middleware/auth.ts.html +0 -646
  438. package/coverage/server/src/middleware/index.html +0 -146
  439. package/coverage/server/src/middleware/logging.ts.html +0 -244
  440. package/coverage/server/src/middleware/security.ts.html +0 -271
  441. package/coverage/server/src/routers/_app.ts.html +0 -232
  442. package/coverage/server/src/routers/agenda.ts.html +0 -319
  443. package/coverage/server/src/routers/announcement.ts.html +0 -3481
  444. package/coverage/server/src/routers/assignment.ts.html +0 -7633
  445. package/coverage/server/src/routers/attendance.ts.html +0 -1030
  446. package/coverage/server/src/routers/auth.ts.html +0 -1081
  447. package/coverage/server/src/routers/class.ts.html +0 -3535
  448. package/coverage/server/src/routers/comment.ts.html +0 -991
  449. package/coverage/server/src/routers/conversation.ts.html +0 -982
  450. package/coverage/server/src/routers/event.ts.html +0 -1609
  451. package/coverage/server/src/routers/file.ts.html +0 -1144
  452. package/coverage/server/src/routers/folder.ts.html +0 -2797
  453. package/coverage/server/src/routers/index.html +0 -386
  454. package/coverage/server/src/routers/labChat.ts.html +0 -3073
  455. package/coverage/server/src/routers/marketing.ts.html +0 -340
  456. package/coverage/server/src/routers/message.ts.html +0 -1912
  457. package/coverage/server/src/routers/notifications.ts.html +0 -364
  458. package/coverage/server/src/routers/section.ts.html +0 -1120
  459. package/coverage/server/src/routers/user.ts.html +0 -862
  460. package/coverage/server/src/routers/worksheet.ts.html +0 -1729
  461. package/coverage/server/src/trpc.ts.html +0 -397
  462. package/coverage/server/src/types/index.html +0 -116
  463. package/coverage/server/src/types/trpc.ts.html +0 -127
  464. package/coverage/server/src/utils/aiUser.ts.html +0 -280
  465. package/coverage/server/src/utils/email.ts.html +0 -121
  466. package/coverage/server/src/utils/generateInviteCode.ts.html +0 -106
  467. package/coverage/server/src/utils/index.html +0 -206
  468. package/coverage/server/src/utils/inference.ts.html +0 -709
  469. package/coverage/server/src/utils/logger.ts.html +0 -664
  470. package/coverage/server/src/utils/prismaErrorHandler.ts.html +0 -907
  471. package/coverage/server/src/utils/prismaWrapper.ts.html +0 -355
  472. package/coverage/server/vitest.config.ts.html +0 -196
  473. package/coverage/sort-arrow-sprite.png +0 -0
  474. package/coverage/sorter.js +0 -210
  475. package/src/lib/notificationHandler.ts +0 -36
  476. package/src/server/pipelines/aiLabChat.ts +0 -507
  477. package/tests/announcement.test.ts +0 -164
  478. package/tests/assignment.test.ts +0 -296
  479. package/tests/auth.test.ts +0 -48
@@ -0,0 +1,172 @@
1
+ /**
2
+ * User service – profile get/update, profile picture upload URLs.
3
+ */
4
+ import { TRPCError } from "@trpc/server";
5
+ import { logger } from "../utils/logger.js";
6
+ import { env } from "../lib/config/env.js";
7
+ import { findUserById, findUserProfileByUserId, upsertUserProfile } from "../models/user.js";
8
+
9
+ function getFileUrl(filePath: string | null): string | null {
10
+ if (!filePath) return null;
11
+ if (filePath.startsWith("http")) {
12
+ return filePath;
13
+ }
14
+ const backendUrl = env.BACKEND_URL || "http://localhost:3001";
15
+ return `${backendUrl}/api/files/${encodeURIComponent(filePath)}`;
16
+ }
17
+
18
+ /** Get user profile with display info and profile picture URLs. */
19
+ export async function getProfile(userId: string) {
20
+ const user = await findUserById(userId);
21
+ if (!user) {
22
+ throw new TRPCError({
23
+ code: "NOT_FOUND",
24
+ message: "User not found",
25
+ });
26
+ }
27
+
28
+ const userProfile = await findUserProfileByUserId(userId);
29
+
30
+ return {
31
+ id: user.id,
32
+ username: user.username,
33
+ profile: userProfile
34
+ ? {
35
+ displayName: (userProfile as any).displayName || null,
36
+ bio: (userProfile as any).bio || null,
37
+ location: (userProfile as any).location || null,
38
+ website: (userProfile as any).website || null,
39
+ profilePicture: getFileUrl((userProfile as any).profilePicture),
40
+ profilePictureThumbnail: getFileUrl(
41
+ (userProfile as any).profilePictureThumbnail
42
+ ),
43
+ }
44
+ : {
45
+ displayName: null,
46
+ bio: null,
47
+ location: null,
48
+ website: null,
49
+ profilePicture: null,
50
+ profilePictureThumbnail: null,
51
+ },
52
+ };
53
+ }
54
+
55
+ /** Update user profile (display name, bio, etc.) and/or profile picture. */
56
+ export async function updateProfile(
57
+ userId: string,
58
+ input: {
59
+ profile?: {
60
+ displayName?: string | null;
61
+ bio?: string | null;
62
+ location?: string | null;
63
+ website?: string | null;
64
+ };
65
+ profilePicture?: {
66
+ filePath: string;
67
+ fileName: string;
68
+ fileType: string;
69
+ fileSize: number;
70
+ };
71
+ dicebearAvatar?: { url: string };
72
+ }
73
+ ) {
74
+ let profilePictureUrl: string | null = null;
75
+ let profilePictureThumbnail: string | null = null;
76
+
77
+ if (input.profilePicture) {
78
+ try {
79
+ profilePictureUrl = input.profilePicture.filePath;
80
+ profilePictureThumbnail = null;
81
+ } catch (error) {
82
+ logger.error("Profile picture processing failed", {
83
+ userId,
84
+ error: error instanceof Error ? error.message : "Unknown error",
85
+ });
86
+ throw new TRPCError({
87
+ code: "INTERNAL_SERVER_ERROR",
88
+ message: "Failed to process profile picture. Please try again.",
89
+ });
90
+ }
91
+ }
92
+
93
+ if (input.dicebearAvatar) {
94
+ profilePictureUrl = input.dicebearAvatar.url;
95
+ profilePictureThumbnail = null;
96
+ }
97
+
98
+ const updateData: any = {};
99
+ if (input.profile) {
100
+ if (input.profile.displayName !== undefined && input.profile.displayName !== null) {
101
+ updateData.displayName = input.profile.displayName;
102
+ }
103
+ if (input.profile.bio !== undefined && input.profile.bio !== null) {
104
+ updateData.bio = input.profile.bio;
105
+ }
106
+ if (input.profile.location !== undefined && input.profile.location !== null) {
107
+ updateData.location = input.profile.location;
108
+ }
109
+ if (input.profile.website !== undefined && input.profile.website !== null) {
110
+ updateData.website = input.profile.website;
111
+ }
112
+ }
113
+ if (profilePictureUrl !== null) updateData.profilePicture = profilePictureUrl;
114
+ if (profilePictureThumbnail !== null)
115
+ updateData.profilePictureThumbnail = profilePictureThumbnail;
116
+
117
+ const updatedProfile = await upsertUserProfile(userId, updateData);
118
+ const user = await findUserById(userId);
119
+
120
+ return {
121
+ id: userId,
122
+ username: user?.username || "",
123
+ profile: {
124
+ displayName: (updatedProfile as any).displayName || null,
125
+ bio: (updatedProfile as any).bio || null,
126
+ location: (updatedProfile as any).location || null,
127
+ website: (updatedProfile as any).website || null,
128
+ profilePicture: getFileUrl((updatedProfile as any).profilePicture),
129
+ profilePictureThumbnail: getFileUrl(
130
+ (updatedProfile as any).profilePictureThumbnail
131
+ ),
132
+ },
133
+ };
134
+ }
135
+
136
+ export async function getUploadUrl(
137
+ userId: string,
138
+ fileName: string,
139
+ fileType: string
140
+ ) {
141
+ try {
142
+ const fileExtension = fileName.split(".").pop();
143
+ const uniqueFilename = `${userId}-${Date.now()}.${fileExtension}`;
144
+ const filePath = `users/${userId}/profile/${uniqueFilename}`;
145
+
146
+ const backendUrl = env.BACKEND_URL || "http://localhost:3001";
147
+ const uploadUrl = `${backendUrl}/api/upload/${encodeURIComponent(filePath)}`;
148
+
149
+ logger.info("Generated upload URL", {
150
+ userId,
151
+ filePath,
152
+ fileName: uniqueFilename,
153
+ fileType,
154
+ uploadUrl,
155
+ });
156
+
157
+ return {
158
+ uploadUrl,
159
+ filePath,
160
+ fileName: uniqueFilename,
161
+ };
162
+ } catch (error) {
163
+ logger.error("Failed to generate upload URL", {
164
+ userId,
165
+ error: error instanceof Error ? error.message : "Unknown error",
166
+ });
167
+ throw new TRPCError({
168
+ code: "INTERNAL_SERVER_ERROR",
169
+ message: "Failed to generate upload URL",
170
+ });
171
+ }
172
+ }
@@ -0,0 +1,358 @@
1
+ /**
2
+ * Worksheet service – CRUD for worksheets, questions, submissions, and AI grading.
3
+ * Integrates with gradeWorksheet pipeline for auto-grading.
4
+ */
5
+ import { TRPCError } from "@trpc/server";
6
+ import { GenerationStatus, type WorksheetQuestionType } from "@prisma/client";
7
+ import { prisma } from "../lib/prisma.js";
8
+ import { cancelGradePipeline, regradeWorksheetPipeline } from "../pipelines/gradeWorksheet.js";
9
+ import {
10
+ findWorksheetById,
11
+ findWorksheetByIdMinimal,
12
+ findWorksheetsByClassId,
13
+ createWorksheet,
14
+ updateWorksheet,
15
+ deleteWorksheet,
16
+ findQuestionsByWorksheetId,
17
+ createWorksheetQuestion,
18
+ updateWorksheetQuestion,
19
+ deleteWorksheetQuestion,
20
+ findSubmissionById,
21
+ findOrCreateWorksheetResponse,
22
+ findWorksheetResponseWithResponses,
23
+ findWorksheetQuestionById,
24
+ updateStudentQuestionProgress,
25
+ createStudentQuestionProgress,
26
+ findStudentQuestionProgress,
27
+ updateStudentQuestionProgressForGrading,
28
+ createStudentQuestionProgressForGrading,
29
+ createComment,
30
+ } from "../models/worksheet.js";
31
+
32
+ type MCQOptions = { id: string; text: string; isCorrect: boolean }[];
33
+
34
+ /** Get worksheet with questions and mark schemes. */
35
+ export async function getWorksheet(worksheetId: string) {
36
+ const worksheet = await findWorksheetById(worksheetId);
37
+ if (!worksheet) {
38
+ throw new TRPCError({
39
+ code: "NOT_FOUND",
40
+ message: "Worksheet not found",
41
+ });
42
+ }
43
+ return worksheet;
44
+ }
45
+
46
+ /** Check if worksheet exists. */
47
+ export async function worksheetExists(id: string) {
48
+ const worksheet = await findWorksheetByIdMinimal(id);
49
+ return !!worksheet;
50
+ }
51
+
52
+ export async function listWorksheets(classId: string) {
53
+ const worksheets = await findWorksheetsByClassId(classId);
54
+ return worksheets.map((w) => ({
55
+ ...w,
56
+ questionCount: w.questions.length,
57
+ }));
58
+ }
59
+
60
+ export async function updateWorksheetRecord(
61
+ worksheetId: string,
62
+ data: { name?: string }
63
+ ) {
64
+ return updateWorksheet(worksheetId, data);
65
+ }
66
+
67
+ export async function deleteWorksheetRecord(worksheetId: string) {
68
+ return deleteWorksheet(worksheetId);
69
+ }
70
+
71
+ export async function createWorksheetRecord(classId: string, name: string) {
72
+ return createWorksheet({ classId, name });
73
+ }
74
+
75
+ export async function addQuestionToWorksheet(
76
+ worksheetId: string,
77
+ data: {
78
+ question: string;
79
+ answer: string;
80
+ points?: number;
81
+ options?: object;
82
+ markScheme?: object;
83
+ type: WorksheetQuestionType;
84
+ }
85
+ ) {
86
+ const worksheet = await findWorksheetByIdMinimal(worksheetId);
87
+ if (!worksheet) {
88
+ throw new TRPCError({
89
+ code: "NOT_FOUND",
90
+ message: "Worksheet not found",
91
+ });
92
+ }
93
+ return createWorksheetQuestion({
94
+ worksheetId,
95
+ ...data,
96
+ });
97
+ }
98
+
99
+ export async function reorderWorksheetQuestions(
100
+ worksheetId: string,
101
+ movedId: string,
102
+ position: "before" | "after",
103
+ targetId: string
104
+ ) {
105
+ const worksheet = await findWorksheetByIdMinimal(worksheetId);
106
+ if (!worksheet) {
107
+ throw new TRPCError({
108
+ code: "NOT_FOUND",
109
+ message: "Worksheet not found",
110
+ });
111
+ }
112
+
113
+ const questions = await findQuestionsByWorksheetId(worksheetId);
114
+ const movedIdx = questions.findIndex((q) => q.id === movedId);
115
+ if (movedIdx === -1) {
116
+ throw new TRPCError({
117
+ code: "NOT_FOUND",
118
+ message: "Moved question not found",
119
+ });
120
+ }
121
+ const targetIdx = questions.findIndex((q) => q.id === targetId);
122
+ if (targetIdx === -1) {
123
+ throw new TRPCError({
124
+ code: "NOT_FOUND",
125
+ message: "Target question not found",
126
+ });
127
+ }
128
+
129
+ const withoutMoved = questions.filter((q) => q.id !== movedId);
130
+ const next =
131
+ position === "before"
132
+ ? [
133
+ ...withoutMoved.slice(0, targetIdx).map((item) => ({ id: item.id })),
134
+ { id: movedId },
135
+ ...withoutMoved.slice(targetIdx).map((item) => ({ id: item.id })),
136
+ ]
137
+ : [
138
+ ...withoutMoved
139
+ .slice(0, targetIdx + 1)
140
+ .map((item) => ({ id: item.id })),
141
+ { id: movedId },
142
+ ...withoutMoved
143
+ .slice(targetIdx + 1)
144
+ .map((item) => ({ id: item.id })),
145
+ ];
146
+
147
+ await prisma.$transaction(
148
+ next.map((item, index) =>
149
+ prisma.worksheetQuestion.update({
150
+ where: { id: item.id },
151
+ data: { order: index },
152
+ })
153
+ )
154
+ );
155
+ return next;
156
+ }
157
+
158
+ export async function updateWorksheetQuestionRecord(
159
+ worksheetId: string,
160
+ questionId: string,
161
+ data: {
162
+ question?: string;
163
+ answer?: string;
164
+ points?: number;
165
+ options?: object;
166
+ markScheme?: object;
167
+ type?: WorksheetQuestionType;
168
+ }
169
+ ) {
170
+ const worksheet = await findWorksheetByIdMinimal(worksheetId);
171
+ if (!worksheet) {
172
+ throw new TRPCError({
173
+ code: "NOT_FOUND",
174
+ message: "Worksheet not found",
175
+ });
176
+ }
177
+ return updateWorksheetQuestion(questionId, data);
178
+ }
179
+
180
+ export async function deleteWorksheetQuestionRecord(
181
+ worksheetId: string,
182
+ questionId: string
183
+ ) {
184
+ const worksheet = await findWorksheetByIdMinimal(worksheetId);
185
+ if (!worksheet) {
186
+ throw new TRPCError({
187
+ code: "NOT_FOUND",
188
+ message: "Worksheet not found",
189
+ });
190
+ }
191
+ return deleteWorksheetQuestion(questionId);
192
+ }
193
+
194
+ export async function getWorksheetSubmission(
195
+ worksheetId: string,
196
+ submissionId: string
197
+ ) {
198
+ const submission = await findSubmissionById(submissionId);
199
+ if (!submission) {
200
+ throw new TRPCError({
201
+ code: "NOT_FOUND",
202
+ message: "Submission not found",
203
+ });
204
+ }
205
+ return findOrCreateWorksheetResponse(
206
+ submissionId,
207
+ worksheetId,
208
+ submission.studentId
209
+ );
210
+ }
211
+
212
+ export async function answerWorksheetQuestion(
213
+ worksheetResponseId: string,
214
+ questionId: string,
215
+ response: string,
216
+ userId: string
217
+ ) {
218
+ const worksheetResponse = await findWorksheetResponseWithResponses(
219
+ worksheetResponseId,
220
+ questionId
221
+ );
222
+ if (!worksheetResponse) {
223
+ throw new TRPCError({
224
+ code: "NOT_FOUND",
225
+ message: "Worksheet response not found",
226
+ });
227
+ }
228
+
229
+ const question = await findWorksheetQuestionById(questionId);
230
+ const isMarkableByAlgo =
231
+ question?.type === "MULTIPLE_CHOICE" || question?.type === "TRUE_FALSE";
232
+ const marksAwardedIfCorrect = question?.points || 0;
233
+ const correctAnswer = isMarkableByAlgo
234
+ ? question?.type === "MULTIPLE_CHOICE"
235
+ ? (question?.options as MCQOptions)?.find((o) => o.isCorrect)?.id
236
+ : question?.answer?.toString()
237
+ : null;
238
+
239
+ const existingResponse = worksheetResponse.responses[0];
240
+
241
+ if (existingResponse) {
242
+ await updateStudentQuestionProgress(existingResponse.id, {
243
+ response,
244
+ ...(isMarkableByAlgo && {
245
+ isCorrect: response === correctAnswer,
246
+ }),
247
+ ...(isMarkableByAlgo && {
248
+ points: response === correctAnswer ? marksAwardedIfCorrect : 0,
249
+ }),
250
+ status: GenerationStatus.NOT_STARTED,
251
+ });
252
+ } else {
253
+ await createStudentQuestionProgress({
254
+ studentId: worksheetResponse.studentId,
255
+ questionId,
256
+ response,
257
+ studentWorksheetResponseId: worksheetResponseId,
258
+ ...(isMarkableByAlgo && {
259
+ isCorrect: response === correctAnswer,
260
+ }),
261
+ ...(isMarkableByAlgo && {
262
+ points: response === correctAnswer ? marksAwardedIfCorrect : 0,
263
+ }),
264
+ });
265
+ }
266
+
267
+ const updated = await prisma.studentWorksheetResponse.findUnique({
268
+ where: { id: worksheetResponseId },
269
+ include: { responses: true },
270
+ });
271
+ return updated!;
272
+ }
273
+
274
+ export async function cancelGrading(
275
+ worksheetResponseId: string,
276
+ progressId: string
277
+ ) {
278
+ return cancelGradePipeline(worksheetResponseId, progressId);
279
+ }
280
+
281
+ export async function regradeQuestion(
282
+ worksheetResponseId: string,
283
+ progressId: string
284
+ ) {
285
+ return regradeWorksheetPipeline(worksheetResponseId, progressId);
286
+ }
287
+
288
+ export async function gradeAnswer(
289
+ questionId: string,
290
+ studentWorksheetResponseId: string,
291
+ data: {
292
+ responseId?: string;
293
+ response?: string;
294
+ isCorrect: boolean;
295
+ feedback?: string;
296
+ markschemeState?: object;
297
+ points?: number;
298
+ }
299
+ ) {
300
+ if (data.responseId) {
301
+ return updateStudentQuestionProgressForGrading(data.responseId, {
302
+ isCorrect: data.isCorrect,
303
+ feedback: data.feedback,
304
+ markschemeState: data.markschemeState,
305
+ points: data.points,
306
+ });
307
+ }
308
+
309
+ const worksheetResponse = await prisma.studentWorksheetResponse.findUnique({
310
+ where: { id: studentWorksheetResponseId },
311
+ select: { studentId: true },
312
+ });
313
+ if (!worksheetResponse) {
314
+ throw new TRPCError({
315
+ code: "NOT_FOUND",
316
+ message: "Student worksheet response not found",
317
+ });
318
+ }
319
+
320
+ const existing = await findStudentQuestionProgress(
321
+ worksheetResponse.studentId,
322
+ questionId,
323
+ studentWorksheetResponseId
324
+ );
325
+
326
+ if (existing) {
327
+ return updateStudentQuestionProgressForGrading(existing.id, {
328
+ isCorrect: data.isCorrect,
329
+ response: data.response,
330
+ feedback: data.feedback,
331
+ markschemeState: data.markschemeState,
332
+ points: data.points,
333
+ });
334
+ }
335
+
336
+ return createStudentQuestionProgressForGrading({
337
+ studentId: worksheetResponse.studentId,
338
+ questionId,
339
+ studentWorksheetResponseId,
340
+ response: data.response || "",
341
+ isCorrect: data.isCorrect,
342
+ feedback: data.feedback,
343
+ markschemeState: data.markschemeState,
344
+ points: data.points || 0,
345
+ });
346
+ }
347
+
348
+ export async function addCommentToResponse(
349
+ responseId: string,
350
+ comment: string,
351
+ authorId: string
352
+ ) {
353
+ return createComment({
354
+ studentQuestionProgressId: responseId,
355
+ content: comment,
356
+ authorId,
357
+ });
358
+ }
@@ -1,11 +1,12 @@
1
+ /**
2
+ * AI user util – ensures AI assistant user exists, provides ID and isAIUser check.
3
+ */
1
4
  import { prisma } from '../lib/prisma.js';
2
5
  import { logger } from './logger.js';
3
6
 
4
7
  const AI_USER_ID = 'AI_ASSISTANT';
5
8
 
6
- /**
7
- * Ensure AI assistant user exists in the database
8
- */
9
+ /** Ensure AI assistant user exists in the database. */
9
10
  export async function ensureAIUserExists(): Promise<void> {
10
11
  try {
11
12
  // Check if AI user already exists
@@ -1,8 +1,10 @@
1
+ /**
2
+ * Email util – nodemailer transport and sendMail wrapper. Supports EMAIL_DRY_RUN.
3
+ */
1
4
  import nodemailer from 'nodemailer';
2
5
  import { env } from '../lib/config/env.js';
3
6
  import { logger } from './logger.js';
4
7
 
5
-
6
8
  type sendMailProps = {
7
9
  from: string;
8
10
  to: string;
@@ -13,7 +15,7 @@ type sendMailProps = {
13
15
 
14
16
  export const transport = nodemailer.createTransport({
15
17
  host: env.EMAIL_HOST,
16
- port: 587,
18
+ port: env.EMAIL_PORT,
17
19
  secure: false,
18
20
  auth: {
19
21
  user: env.EMAIL_USER,
@@ -30,7 +32,7 @@ export const sendMail = async ({ from, to, subject, text }: sendMailProps) => {
30
32
  }
31
33
 
32
34
  await transport.sendMail({
33
- from,
35
+ from: `"Studious" <${from}>`,
34
36
  to,
35
37
  subject,
36
38
  text,
@@ -1,8 +1,6 @@
1
1
  /**
2
- * Generates a random invite code
3
- * @returns {string} The invite code with length 5
2
+ * Generate invite code util – random 5-char alphanumeric code for class invites.
4
3
  */
5
-
6
4
  export const generateInviteCode = () => {
7
5
  return (Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)).slice(0, 5);
8
6
  }
@@ -1,7 +1,10 @@
1
+ /**
2
+ * Inference util – OpenAI client, inference() for structured output, sendAIMessage for chat.
3
+ */
1
4
  import OpenAI from 'openai';
2
5
  import { logger } from './logger.js';
3
6
  import { prisma } from '../lib/prisma.js';
4
- import { pusher } from '../lib/pusher.js';
7
+ import { chatChannel, pusher } from '../lib/pusher.js';
5
8
  import { ensureAIUserExists, getAIUserId } from './aiUser.js';
6
9
  import { env } from '../lib/config/env.js';
7
10
  import { ZodSchema } from 'zod';
@@ -103,7 +106,7 @@ export async function sendAIMessage(
103
106
 
104
107
  // Broadcast via Pusher
105
108
  try {
106
- await pusher.trigger(`conversation-${conversationId}`, 'new-message', {
109
+ await pusher.trigger(chatChannel(conversationId), 'new-message', {
107
110
  id: aiMessage.id,
108
111
  content: aiMessage.content,
109
112
  senderId: getAIUserId(),
@@ -1,4 +1,6 @@
1
-
1
+ /**
2
+ * Logger util – structured logging with levels, modes, and colored output.
3
+ */
2
4
  export enum LogLevel {
3
5
  INFO = 'info',
4
6
  WARN = 'warn',
@@ -1,3 +1,6 @@
1
+ /**
2
+ * Prisma error handler – maps Prisma errors to user-friendly messages and tRPC codes.
3
+ */
1
4
  import {
2
5
  PrismaClientKnownRequestError,
3
6
  PrismaClientUnknownRequestError,
@@ -1,6 +1,10 @@
1
+ /**
2
+ * Prisma wrapper – wraps Prisma operations with error handling and tRPC error mapping.
3
+ */
1
4
  import { TRPCError } from '@trpc/server';
2
5
  import { handlePrismaError } from './prismaErrorHandler.js';
3
6
 
7
+ /** Execute a Prisma operation and convert errors to tRPC errors. */
4
8
  export async function withPrismaErrorHandling<T>(
5
9
  operation: () => Promise<T>,
6
10
  context: string = 'database operation'
@@ -0,0 +1,62 @@
1
+ import { config } from 'dotenv';
2
+ import { resolve } from 'path';
3
+ import type { TestProject } from 'vitest/node';
4
+
5
+ config({ path: resolve(process.cwd(), '.env.test'), override: true });
6
+
7
+ import { prisma } from '../src/lib/prisma';
8
+ import { appRouter } from '../src/routers/_app';
9
+ import { createTRPCContext } from '../src/trpc';
10
+ import { clearDatabase } from '../src/seedDatabase';
11
+ import { execSync } from 'child_process';
12
+
13
+ async function runSetup() {
14
+ const ctx = await createTRPCContext({
15
+ req: { headers: {} } as any,
16
+ res: {} as any,
17
+ });
18
+ const caller = appRouter.createCaller(ctx);
19
+
20
+ execSync('npm run test:pre');
21
+
22
+ const user1 = await caller.auth.register({
23
+ username: 'testuser1',
24
+ email: 'test@studious.sh',
25
+ password: 'password_is_1234',
26
+ confirmPassword: 'password_is_1234',
27
+ });
28
+
29
+ const user2 = await caller.auth.register({
30
+ username: 'testuser2',
31
+ email: 'test2@studious.sh',
32
+ password: 'password_is_1234',
33
+ confirmPassword: 'password_is_1234',
34
+ });
35
+
36
+ const user3 = await caller.auth.register({
37
+ username: 'testuser3',
38
+ email: 'test3@studious.sh',
39
+ password: 'password_is_1234',
40
+ confirmPassword: 'password_is_1234',
41
+ });
42
+
43
+ const session1 = await prisma.session.findFirst({ where: { userId: user1.user.id } });
44
+ const session2 = await prisma.session.findFirst({ where: { userId: user2.user.id } });
45
+ const session3 = await prisma.session.findFirst({ where: { userId: user3.user.id } });
46
+
47
+ if (!session1 || !session2 || !session3) {
48
+ throw new Error('Failed to create sessions for test users');
49
+ }
50
+
51
+ await caller.auth.verify({ token: session1.id });
52
+ await caller.auth.verify({ token: session2.id });
53
+ await caller.auth.verify({ token: session3.id });
54
+
55
+ await prisma.$disconnect();
56
+ }
57
+
58
+ export default async function setup(project: TestProject) {
59
+ project.onTestsRerun(runSetup);
60
+ await runSetup();
61
+ }
62
+