@studious-lms/server 1.2.52 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (480) 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 +36 -94
  31. package/dist/middleware/auth.js.map +1 -1
  32. package/dist/models/agenda.d.ts +97 -0
  33. package/dist/models/agenda.d.ts.map +1 -0
  34. package/dist/models/agenda.js +40 -0
  35. package/dist/models/agenda.js.map +1 -0
  36. package/dist/models/announcement.d.ts +223 -0
  37. package/dist/models/announcement.d.ts.map +1 -0
  38. package/dist/models/announcement.js +120 -0
  39. package/dist/models/announcement.js.map +1 -0
  40. package/dist/models/assignment.d.ts +1292 -0
  41. package/dist/models/assignment.d.ts.map +1 -0
  42. package/dist/models/assignment.js +309 -0
  43. package/dist/models/assignment.js.map +1 -0
  44. package/dist/models/attendance.d.ts +180 -0
  45. package/dist/models/attendance.d.ts.map +1 -0
  46. package/dist/models/attendance.js +188 -0
  47. package/dist/models/attendance.js.map +1 -0
  48. package/dist/models/auth.d.ts +153 -0
  49. package/dist/models/auth.d.ts.map +1 -0
  50. package/dist/models/auth.js +217 -0
  51. package/dist/models/auth.js.map +1 -0
  52. package/dist/models/class.d.ts +439 -0
  53. package/dist/models/class.d.ts.map +1 -0
  54. package/dist/models/class.js +546 -0
  55. package/dist/models/class.js.map +1 -0
  56. package/dist/models/comment.d.ts +171 -0
  57. package/dist/models/comment.d.ts.map +1 -0
  58. package/dist/models/comment.js +138 -0
  59. package/dist/models/comment.js.map +1 -0
  60. package/dist/models/conversation.d.ts +164 -0
  61. package/dist/models/conversation.d.ts.map +1 -0
  62. package/dist/models/conversation.js +175 -0
  63. package/dist/models/conversation.js.map +1 -0
  64. package/dist/models/event.d.ts +295 -0
  65. package/dist/models/event.d.ts.map +1 -0
  66. package/dist/models/event.js +145 -0
  67. package/dist/models/event.js.map +1 -0
  68. package/dist/models/file.d.ts +536 -0
  69. package/dist/models/file.d.ts.map +1 -0
  70. package/dist/models/file.js +126 -0
  71. package/dist/models/file.js.map +1 -0
  72. package/dist/models/folder.d.ts +295 -0
  73. package/dist/models/folder.d.ts.map +1 -0
  74. package/dist/models/folder.js +202 -0
  75. package/dist/models/folder.js.map +1 -0
  76. package/dist/models/labChat.d.ts +243 -0
  77. package/dist/models/labChat.d.ts.map +1 -0
  78. package/dist/models/labChat.js +204 -0
  79. package/dist/models/labChat.js.map +1 -0
  80. package/dist/models/marketing.d.ts +72 -0
  81. package/dist/models/marketing.d.ts.map +1 -0
  82. package/dist/models/marketing.js +26 -0
  83. package/dist/models/marketing.js.map +1 -0
  84. package/dist/models/message.d.ts +100 -0
  85. package/dist/models/message.d.ts.map +1 -0
  86. package/dist/models/message.js +131 -0
  87. package/dist/models/message.js.map +1 -0
  88. package/dist/models/newtonChat.d.ts +72 -0
  89. package/dist/models/newtonChat.d.ts.map +1 -0
  90. package/dist/models/newtonChat.js +61 -0
  91. package/dist/models/newtonChat.js.map +1 -0
  92. package/dist/models/notification.d.ts +65 -0
  93. package/dist/models/notification.d.ts.map +1 -0
  94. package/dist/models/notification.js +46 -0
  95. package/dist/models/notification.js.map +1 -0
  96. package/dist/models/section.d.ts +102 -0
  97. package/dist/models/section.d.ts.map +1 -0
  98. package/dist/models/section.js +83 -0
  99. package/dist/models/section.js.map +1 -0
  100. package/dist/models/user.d.ts +39 -0
  101. package/dist/models/user.d.ts.map +1 -0
  102. package/dist/models/user.js +38 -0
  103. package/dist/models/user.js.map +1 -0
  104. package/dist/models/worksheet.d.ts +460 -0
  105. package/dist/models/worksheet.d.ts.map +1 -0
  106. package/dist/models/worksheet.js +200 -0
  107. package/dist/models/worksheet.js.map +1 -0
  108. package/dist/pipelines/aiLabChat.d.ts +21 -0
  109. package/dist/pipelines/aiLabChat.d.ts.map +1 -0
  110. package/dist/pipelines/aiLabChat.js +460 -0
  111. package/dist/pipelines/aiLabChat.js.map +1 -0
  112. package/dist/pipelines/aiNewtonChat.d.ts +30 -0
  113. package/dist/pipelines/aiNewtonChat.d.ts.map +1 -0
  114. package/dist/pipelines/aiNewtonChat.js +289 -0
  115. package/dist/pipelines/aiNewtonChat.js.map +1 -0
  116. package/dist/pipelines/gradeWorksheet.d.ts +30 -0
  117. package/dist/pipelines/gradeWorksheet.d.ts.map +1 -0
  118. package/dist/pipelines/gradeWorksheet.js +252 -0
  119. package/dist/pipelines/gradeWorksheet.js.map +1 -0
  120. package/dist/routers/_app.d.ts +1399 -1271
  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 +140 -68
  143. package/dist/routers/class.d.ts.map +1 -1
  144. package/dist/routers/class.js +82 -1051
  145. package/dist/routers/class.js.map +1 -1
  146. package/dist/routers/comment.d.ts +6 -42
  147. package/dist/routers/comment.d.ts.map +1 -1
  148. package/dist/routers/comment.js +24 -244
  149. package/dist/routers/comment.js.map +1 -1
  150. package/dist/routers/conversation.d.ts +45 -7
  151. package/dist/routers/conversation.d.ts.map +1 -1
  152. package/dist/routers/conversation.js +19 -327
  153. package/dist/routers/conversation.js.map +1 -1
  154. package/dist/routers/event.d.ts +36 -36
  155. package/dist/routers/event.d.ts.map +1 -1
  156. package/dist/routers/event.js +13 -433
  157. package/dist/routers/event.js.map +1 -1
  158. package/dist/routers/file.d.ts +2 -2
  159. package/dist/routers/file.d.ts.map +1 -1
  160. package/dist/routers/file.js +9 -323
  161. package/dist/routers/file.js.map +1 -1
  162. package/dist/routers/folder.d.ts +21 -14
  163. package/dist/routers/folder.d.ts.map +1 -1
  164. package/dist/routers/folder.js +34 -745
  165. package/dist/routers/folder.js.map +1 -1
  166. package/dist/routers/labChat.d.ts +11 -10
  167. package/dist/routers/labChat.d.ts.map +1 -1
  168. package/dist/routers/labChat.js +19 -570
  169. package/dist/routers/labChat.js.map +1 -1
  170. package/dist/routers/marketing.d.ts +1 -1
  171. package/dist/routers/marketing.d.ts.map +1 -1
  172. package/dist/routers/marketing.js +7 -56
  173. package/dist/routers/marketing.js.map +1 -1
  174. package/dist/routers/message.d.ts +2 -2
  175. package/dist/routers/message.d.ts.map +1 -1
  176. package/dist/routers/message.js +27 -522
  177. package/dist/routers/message.js.map +1 -1
  178. package/dist/routers/newtonChat.d.ts +1 -1
  179. package/dist/routers/newtonChat.d.ts.map +1 -1
  180. package/dist/routers/newtonChat.js +7 -246
  181. package/dist/routers/newtonChat.js.map +1 -1
  182. package/dist/routers/notifications.d.ts +4 -4
  183. package/dist/routers/notifications.d.ts.map +1 -1
  184. package/dist/routers/notifications.js +18 -83
  185. package/dist/routers/notifications.js.map +1 -1
  186. package/dist/routers/section.d.ts +4 -4
  187. package/dist/routers/section.d.ts.map +1 -1
  188. package/dist/routers/section.js +14 -286
  189. package/dist/routers/section.js.map +1 -1
  190. package/dist/routers/user.d.ts +1 -1
  191. package/dist/routers/user.d.ts.map +1 -1
  192. package/dist/routers/user.js +32 -207
  193. package/dist/routers/user.js.map +1 -1
  194. package/dist/routers/worksheet.d.ts +51 -38
  195. package/dist/routers/worksheet.d.ts.map +1 -1
  196. package/dist/routers/worksheet.js +79 -394
  197. package/dist/routers/worksheet.js.map +1 -1
  198. package/dist/seedDatabase.d.ts +1 -1
  199. package/dist/server/pipelines/aiNewtonChat.d.ts.map +1 -1
  200. package/dist/server/pipelines/aiNewtonChat.js +8 -3
  201. package/dist/server/pipelines/aiNewtonChat.js.map +1 -1
  202. package/dist/server/pipelines/gradeWorksheet.d.ts +6 -6
  203. package/dist/server/pipelines/gradeWorksheet.d.ts.map +1 -1
  204. package/dist/server/pipelines/gradeWorksheet.js +12 -5
  205. package/dist/server/pipelines/gradeWorksheet.js.map +1 -1
  206. package/dist/services/agenda.d.ts +100 -0
  207. package/dist/services/agenda.d.ts.map +1 -0
  208. package/dist/services/agenda.js +21 -0
  209. package/dist/services/agenda.js.map +1 -0
  210. package/dist/services/announcement.d.ts +135 -0
  211. package/dist/services/announcement.d.ts.map +1 -0
  212. package/dist/services/announcement.js +223 -0
  213. package/dist/services/announcement.js.map +1 -0
  214. package/dist/services/assignment.d.ts +1462 -0
  215. package/dist/services/assignment.d.ts.map +1 -0
  216. package/dist/services/assignment.js +898 -0
  217. package/dist/services/assignment.js.map +1 -0
  218. package/dist/services/attendance.d.ts +93 -0
  219. package/dist/services/attendance.d.ts.map +1 -0
  220. package/dist/services/attendance.js +61 -0
  221. package/dist/services/attendance.js.map +1 -0
  222. package/dist/services/auth.d.ts +68 -0
  223. package/dist/services/auth.d.ts.map +1 -0
  224. package/dist/services/auth.js +218 -0
  225. package/dist/services/auth.js.map +1 -0
  226. package/dist/services/class.d.ts +621 -0
  227. package/dist/services/class.d.ts.map +1 -0
  228. package/dist/services/class.js +474 -0
  229. package/dist/services/class.js.map +1 -0
  230. package/dist/services/comment.d.ts +100 -0
  231. package/dist/services/comment.d.ts.map +1 -0
  232. package/dist/services/comment.js +83 -0
  233. package/dist/services/comment.js.map +1 -0
  234. package/dist/services/conversation.d.ts +159 -0
  235. package/dist/services/conversation.d.ts.map +1 -0
  236. package/dist/services/conversation.js +138 -0
  237. package/dist/services/conversation.js.map +1 -0
  238. package/dist/services/event.d.ts +216 -0
  239. package/dist/services/event.d.ts.map +1 -0
  240. package/dist/services/event.js +168 -0
  241. package/dist/services/event.js.map +1 -0
  242. package/dist/services/file.d.ts +74 -0
  243. package/dist/services/file.d.ts.map +1 -0
  244. package/dist/services/file.js +133 -0
  245. package/dist/services/file.js.map +1 -0
  246. package/dist/services/folder.d.ts +239 -0
  247. package/dist/services/folder.d.ts.map +1 -0
  248. package/dist/services/folder.js +248 -0
  249. package/dist/services/folder.js.map +1 -0
  250. package/dist/services/labChat.d.ts +165 -0
  251. package/dist/services/labChat.d.ts.map +1 -0
  252. package/dist/services/labChat.js +289 -0
  253. package/dist/services/labChat.js.map +1 -0
  254. package/dist/services/marketing.d.ts +50 -0
  255. package/dist/services/marketing.d.ts.map +1 -0
  256. package/dist/services/marketing.js +32 -0
  257. package/dist/services/marketing.js.map +1 -0
  258. package/dist/services/message.d.ts +95 -0
  259. package/dist/services/message.d.ts.map +1 -0
  260. package/dist/services/message.js +350 -0
  261. package/dist/services/message.js.map +1 -0
  262. package/dist/services/newtonChat.d.ts +22 -0
  263. package/dist/services/newtonChat.d.ts.map +1 -0
  264. package/dist/services/newtonChat.js +174 -0
  265. package/dist/services/newtonChat.js.map +1 -0
  266. package/dist/services/notification.d.ts +65 -0
  267. package/dist/services/notification.d.ts.map +1 -0
  268. package/dist/services/notification.js +33 -0
  269. package/dist/services/notification.js.map +1 -0
  270. package/dist/services/section.d.ts +53 -0
  271. package/dist/services/section.d.ts.map +1 -0
  272. package/dist/services/section.js +199 -0
  273. package/dist/services/section.js.map +1 -0
  274. package/dist/services/user.d.ts +48 -0
  275. package/dist/services/user.d.ts.map +1 -0
  276. package/dist/services/user.js +141 -0
  277. package/dist/services/user.js.map +1 -0
  278. package/dist/services/worksheet.d.ts +239 -0
  279. package/dist/services/worksheet.d.ts.map +1 -0
  280. package/dist/services/worksheet.js +235 -0
  281. package/dist/services/worksheet.js.map +1 -0
  282. package/dist/utils/aiUser.d.ts +1 -3
  283. package/dist/utils/aiUser.d.ts.map +1 -1
  284. package/dist/utils/aiUser.js +6 -5
  285. package/dist/utils/aiUser.js.map +1 -1
  286. package/dist/utils/email.d.ts +3 -0
  287. package/dist/utils/email.d.ts.map +1 -1
  288. package/dist/utils/email.js +7 -4
  289. package/dist/utils/email.js.map +1 -1
  290. package/dist/utils/generateInviteCode.d.ts +1 -2
  291. package/dist/utils/generateInviteCode.d.ts.map +1 -1
  292. package/dist/utils/generateInviteCode.js +3 -4
  293. package/dist/utils/generateInviteCode.js.map +1 -1
  294. package/dist/utils/inference.d.ts +3 -0
  295. package/dist/utils/inference.d.ts.map +1 -1
  296. package/dist/utils/inference.js +7 -4
  297. package/dist/utils/inference.js.map +1 -1
  298. package/dist/utils/logger.d.ts +3 -0
  299. package/dist/utils/logger.d.ts.map +1 -1
  300. package/dist/utils/logger.js +5 -2
  301. package/dist/utils/logger.js.map +1 -1
  302. package/dist/utils/prismaErrorHandler.d.ts.map +1 -1
  303. package/dist/utils/prismaErrorHandler.js +5 -2
  304. package/dist/utils/prismaErrorHandler.js.map +1 -1
  305. package/dist/utils/prismaWrapper.d.ts +1 -0
  306. package/dist/utils/prismaWrapper.d.ts.map +1 -1
  307. package/dist/utils/prismaWrapper.js +6 -2
  308. package/dist/utils/prismaWrapper.js.map +1 -1
  309. package/docker-compose.yml +5 -0
  310. package/package.json +4 -3
  311. package/src/index.ts +119 -12
  312. package/src/lib/config/env.ts +6 -0
  313. package/src/lib/fileUpload.ts +0 -1
  314. package/src/lib/googleCloudStorage.ts +17 -0
  315. package/src/lib/pusher.ts +5 -1
  316. package/src/lib/redis.ts +56 -0
  317. package/src/lib/thumbnailGenerator.ts +170 -168
  318. package/src/middleware/auth.ts +83 -136
  319. package/src/models/agenda.ts +46 -0
  320. package/src/models/announcement.ts +134 -0
  321. package/src/models/assignment.ts +322 -0
  322. package/src/models/attendance.ts +208 -0
  323. package/src/models/auth.ts +247 -0
  324. package/src/models/class.ts +598 -0
  325. package/src/models/comment.ts +152 -0
  326. package/src/models/conversation.ts +200 -0
  327. package/src/models/event.ts +177 -0
  328. package/src/models/file.ts +129 -0
  329. package/src/models/folder.ts +225 -0
  330. package/src/models/labChat.ts +213 -0
  331. package/src/models/marketing.ts +45 -0
  332. package/src/models/message.ts +153 -0
  333. package/src/models/newtonChat.ts +70 -0
  334. package/src/models/notification.ts +54 -0
  335. package/src/models/section.ts +98 -0
  336. package/src/models/user.ts +47 -0
  337. package/src/models/worksheet.ts +294 -0
  338. package/src/{server/pipelines → pipelines}/aiLabChat.ts +11 -7
  339. package/src/{server/pipelines → pipelines}/aiNewtonChat.ts +15 -6
  340. package/src/{server/pipelines → pipelines}/gradeWorksheet.ts +25 -14
  341. package/src/routers/agenda.ts +3 -66
  342. package/src/routers/announcement.ts +54 -495
  343. package/src/routers/assignment.ts +126 -2018
  344. package/src/routers/attendance.ts +15 -276
  345. package/src/routers/auth.ts +79 -442
  346. package/src/routers/class.ts +263 -1186
  347. package/src/routers/comment.ts +61 -288
  348. package/src/routers/conversation.ts +51 -360
  349. package/src/routers/event.ts +50 -481
  350. package/src/routers/file.ts +45 -368
  351. package/src/routers/folder.ts +107 -836
  352. package/src/routers/labChat.ts +29 -605
  353. package/src/routers/marketing.ts +35 -77
  354. package/src/routers/message.ts +45 -571
  355. package/src/routers/newtonChat.ts +17 -278
  356. package/src/routers/notifications.ts +32 -82
  357. package/src/routers/section.ts +46 -330
  358. package/src/routers/user.ts +49 -227
  359. package/src/routers/worksheet.ts +215 -503
  360. package/src/services/agenda.ts +21 -0
  361. package/src/services/announcement.ts +290 -0
  362. package/src/services/assignment.ts +1198 -0
  363. package/src/services/attendance.ts +85 -0
  364. package/src/services/auth.ts +277 -0
  365. package/src/services/class.ts +622 -0
  366. package/src/services/comment.ts +106 -0
  367. package/src/services/conversation.ts +213 -0
  368. package/src/services/event.ts +231 -0
  369. package/src/services/file.ts +167 -0
  370. package/src/services/folder.ts +316 -0
  371. package/src/services/labChat.ts +352 -0
  372. package/src/services/marketing.ts +57 -0
  373. package/src/services/message.ts +461 -0
  374. package/src/services/newtonChat.ts +222 -0
  375. package/src/services/notification.ts +50 -0
  376. package/src/services/section.ts +283 -0
  377. package/src/services/user.ts +172 -0
  378. package/src/services/worksheet.ts +358 -0
  379. package/src/utils/aiUser.ts +4 -3
  380. package/src/utils/email.ts +5 -3
  381. package/src/utils/generateInviteCode.ts +1 -3
  382. package/src/utils/inference.ts +5 -2
  383. package/src/utils/logger.ts +3 -1
  384. package/src/utils/prismaErrorHandler.ts +3 -0
  385. package/src/utils/prismaWrapper.ts +4 -0
  386. package/tests/globalSetup.ts +62 -0
  387. package/tests/helpers.ts +22 -0
  388. package/tests/middleware/security.test.ts +42 -0
  389. package/tests/routers/agenda.test.ts +138 -0
  390. package/tests/routers/announcement.test.ts +490 -0
  391. package/tests/routers/assignment.test.ts +837 -0
  392. package/tests/{attendance.test.ts → routers/attendance.test.ts} +6 -14
  393. package/tests/routers/auth.test.ts +171 -0
  394. package/tests/{class.test.ts → routers/class.test.ts} +131 -85
  395. package/tests/routers/comment.test.ts +126 -0
  396. package/tests/routers/conversation.test.ts +145 -0
  397. package/tests/{event.test.ts → routers/event.test.ts} +93 -32
  398. package/tests/routers/folder.test.ts +178 -0
  399. package/tests/routers/labChat.test.ts +115 -0
  400. package/tests/routers/marketing.test.ts +59 -0
  401. package/tests/routers/message.test.ts +123 -0
  402. package/tests/routers/notification.test.ts +69 -0
  403. package/tests/{section.test.ts → routers/section.test.ts} +5 -13
  404. package/tests/server/rateLimit.test.ts +73 -0
  405. package/tests/setup.ts +18 -92
  406. package/tests/user.test.ts +9 -31
  407. package/tests/utils/aiUser.test.ts +22 -0
  408. package/tests/utils/generateInviteCode.test.ts +24 -0
  409. package/tests/utils/logger.test.ts +74 -0
  410. package/tests/utils/prismaErrorHandler.test.ts +101 -0
  411. package/tests/utils/prismaWrapper.test.ts +82 -0
  412. package/tests/worksheet.test.ts +181 -0
  413. package/vitest.config.ts +6 -3
  414. package/vitest.unit.config.ts +21 -0
  415. package/TODO.md +0 -2
  416. package/coverage/base.css +0 -224
  417. package/coverage/block-navigation.js +0 -87
  418. package/coverage/clover.xml +0 -12110
  419. package/coverage/coverage-final.json +0 -44
  420. package/coverage/favicon.png +0 -0
  421. package/coverage/index.html +0 -221
  422. package/coverage/prettify.css +0 -1
  423. package/coverage/prettify.js +0 -2
  424. package/coverage/server/index.html +0 -116
  425. package/coverage/server/src/exportType.ts.html +0 -109
  426. package/coverage/server/src/index.html +0 -161
  427. package/coverage/server/src/index.ts.html +0 -1702
  428. package/coverage/server/src/instrument.ts.html +0 -130
  429. package/coverage/server/src/lib/config/env.ts.html +0 -448
  430. package/coverage/server/src/lib/config/index.html +0 -116
  431. package/coverage/server/src/lib/fileUpload.ts.html +0 -1138
  432. package/coverage/server/src/lib/googleCloudStorage.ts.html +0 -334
  433. package/coverage/server/src/lib/index.html +0 -206
  434. package/coverage/server/src/lib/jsonConversion.ts.html +0 -2323
  435. package/coverage/server/src/lib/jsonStyles.ts.html +0 -193
  436. package/coverage/server/src/lib/notificationHandler.ts.html +0 -193
  437. package/coverage/server/src/lib/pusher.ts.html +0 -121
  438. package/coverage/server/src/lib/thumbnailGenerator.ts.html +0 -592
  439. package/coverage/server/src/middleware/auth.ts.html +0 -646
  440. package/coverage/server/src/middleware/index.html +0 -146
  441. package/coverage/server/src/middleware/logging.ts.html +0 -244
  442. package/coverage/server/src/middleware/security.ts.html +0 -271
  443. package/coverage/server/src/routers/_app.ts.html +0 -232
  444. package/coverage/server/src/routers/agenda.ts.html +0 -319
  445. package/coverage/server/src/routers/announcement.ts.html +0 -3481
  446. package/coverage/server/src/routers/assignment.ts.html +0 -7633
  447. package/coverage/server/src/routers/attendance.ts.html +0 -1030
  448. package/coverage/server/src/routers/auth.ts.html +0 -1081
  449. package/coverage/server/src/routers/class.ts.html +0 -3535
  450. package/coverage/server/src/routers/comment.ts.html +0 -991
  451. package/coverage/server/src/routers/conversation.ts.html +0 -982
  452. package/coverage/server/src/routers/event.ts.html +0 -1609
  453. package/coverage/server/src/routers/file.ts.html +0 -1144
  454. package/coverage/server/src/routers/folder.ts.html +0 -2797
  455. package/coverage/server/src/routers/index.html +0 -386
  456. package/coverage/server/src/routers/labChat.ts.html +0 -3073
  457. package/coverage/server/src/routers/marketing.ts.html +0 -340
  458. package/coverage/server/src/routers/message.ts.html +0 -1912
  459. package/coverage/server/src/routers/notifications.ts.html +0 -364
  460. package/coverage/server/src/routers/section.ts.html +0 -1120
  461. package/coverage/server/src/routers/user.ts.html +0 -862
  462. package/coverage/server/src/routers/worksheet.ts.html +0 -1729
  463. package/coverage/server/src/trpc.ts.html +0 -397
  464. package/coverage/server/src/types/index.html +0 -116
  465. package/coverage/server/src/types/trpc.ts.html +0 -127
  466. package/coverage/server/src/utils/aiUser.ts.html +0 -280
  467. package/coverage/server/src/utils/email.ts.html +0 -121
  468. package/coverage/server/src/utils/generateInviteCode.ts.html +0 -106
  469. package/coverage/server/src/utils/index.html +0 -206
  470. package/coverage/server/src/utils/inference.ts.html +0 -709
  471. package/coverage/server/src/utils/logger.ts.html +0 -664
  472. package/coverage/server/src/utils/prismaErrorHandler.ts.html +0 -907
  473. package/coverage/server/src/utils/prismaWrapper.ts.html +0 -355
  474. package/coverage/server/vitest.config.ts.html +0 -196
  475. package/coverage/sort-arrow-sprite.png +0 -0
  476. package/coverage/sorter.js +0 -210
  477. package/src/lib/notificationHandler.ts +0 -36
  478. package/tests/announcement.test.ts +0 -164
  479. package/tests/assignment.test.ts +0 -296
  480. package/tests/auth.test.ts +0 -48
@@ -1,8 +1,7 @@
1
1
  import { test, expect, describe, beforeEach } from 'vitest';
2
- import { user1Caller, user2Caller } from './setup';
3
- import { createTRPCContext } from '../src/trpc';
4
- import { appRouter } from '../src/routers/_app';
5
- import { prisma } from '../src/lib/prisma';
2
+ import { user1Caller, user2Caller } from '../setup';
3
+ import { expectUnauth } from '../helpers';
4
+ import { prisma } from '../../src/lib/prisma';
6
5
 
7
6
  describe('Attendance Router', () => {
8
7
  let testClass: any;
@@ -147,22 +146,15 @@ describe('Attendance Router', () => {
147
146
  })).rejects.toThrow();
148
147
  });
149
148
 
150
- test('should fail without authentication', async () => {
151
- const invalidCaller = await createTRPCContext({
152
- req: { headers: {} } as any,
153
- res: {} as any,
154
- });
155
- const router = appRouter.createCaller(invalidCaller);
156
-
157
- await expect(router.attendance.update({
149
+ test('should fail without authentication', () =>
150
+ expectUnauth((c) => c.attendance.update({
158
151
  classId: testClass.id,
159
152
  attendance: {
160
153
  present: [],
161
154
  late: [],
162
155
  absent: [],
163
156
  },
164
- })).rejects.toThrow();
165
- });
157
+ })));
166
158
  });
167
159
  });
168
160
 
@@ -0,0 +1,171 @@
1
+ // @note: some sloppy impl
2
+
3
+ import { test, expect, describe } from 'vitest';
4
+ import { appRouter } from '../../src/routers/_app';
5
+ import { createTRPCContext } from '../../src/trpc';
6
+ import { prisma } from '../../src/lib/prisma';
7
+ import { caller, login1, login2, login3, session1, session2, session3, user1Caller, user2Caller, user3Caller, verification1, verification2, verification3 } from '../setup';
8
+ import { expectUnauth } from '../helpers';
9
+
10
+ describe('Auth Router', () => {
11
+ test('check returns user when authenticated', async () => {
12
+ const result = await user1Caller.auth.check();
13
+ expect(result.user).toBeDefined();
14
+ expect(result.user.id).toBeDefined();
15
+ expect(result.user.username).toBe('testuser1');
16
+ });
17
+
18
+ test('check fails when unauthenticated', () =>
19
+ expectUnauth((c) => c.auth.check()));
20
+
21
+ test('resendVerificationEmail succeeds for existing user', async () => {
22
+ await caller.auth.register({
23
+ username: 'resendtest',
24
+ email: 'resend@studious.sh',
25
+ password: 'password_is_1234',
26
+ confirmPassword: 'password_is_1234',
27
+ });
28
+ const result = await caller.auth.resendVerificationEmail({
29
+ email: 'resend@studious.sh',
30
+ });
31
+ expect(result).toBeDefined();
32
+ });
33
+
34
+ test('requestPasswordReset succeeds', async () => {
35
+ const result = await caller.auth.requestPasswordReset({
36
+ email: 'test@studious.sh',
37
+ });
38
+ expect(result).toBeDefined();
39
+ expect(result.success).toBe(true);
40
+ });
41
+
42
+ test('resetPassword works with valid token', async () => {
43
+ await caller.auth.requestPasswordReset({ email: 'test@studious.sh' });
44
+ // Reset tokens expire within 2hr; login sessions expire in 30 days
45
+ const twoHoursFromNow = new Date(Date.now() + 2 * 60 * 60 * 1000);
46
+ const resetSession = await prisma.session.findFirst({
47
+ where: {
48
+ userId: login1.user.id,
49
+ classId: null,
50
+ expiresAt: { lte: twoHoursFromNow },
51
+ createdAt: { gte: new Date(Date.now() - 60_000) }, // created in last minute
52
+ },
53
+ orderBy: { createdAt: 'desc' },
54
+ });
55
+ if (!resetSession) throw new Error('No reset token found');
56
+
57
+ const result = await caller.auth.resetPassword({
58
+ token: resetSession.id,
59
+ password: 'new_password_123',
60
+ confirmPassword: 'new_password_123',
61
+ });
62
+ expect(result).toBeDefined();
63
+
64
+ const login = await caller.auth.login({
65
+ username: 'testuser1',
66
+ password: 'new_password_123',
67
+ });
68
+ expect(login.token).toBeDefined();
69
+
70
+ await caller.auth.requestPasswordReset({ email: 'test@studious.sh' });
71
+ const restoreSession = await prisma.session.findFirst({
72
+ where: {
73
+ userId: login1.user.id,
74
+ classId: null,
75
+ expiresAt: { lte: twoHoursFromNow },
76
+ createdAt: { gte: new Date(Date.now() - 60_000) },
77
+ },
78
+ orderBy: { createdAt: 'desc' },
79
+ });
80
+ if (!restoreSession) throw new Error('No reset token found');
81
+ await caller.auth.resetPassword({
82
+ token: restoreSession.id,
83
+ password: 'password_is_1234',
84
+ confirmPassword: 'password_is_1234',
85
+ });
86
+ });
87
+
88
+ test('registration creates sessions', async () => {
89
+ expect(session1).toBeDefined();
90
+ expect(session1.id).toBeDefined();
91
+ expect(session2).toBeDefined();
92
+ expect(session2.id).toBeDefined();
93
+ });
94
+
95
+ test('email verification works', async () => {
96
+ expect(verification1).toBeDefined();
97
+ expect(verification2).toBeDefined();
98
+ });
99
+
100
+ test('login returns valid tokens', async () => {
101
+ expect(login1).toBeDefined();
102
+ expect(login1.token).toBeDefined();
103
+ expect(login1.user).toBeDefined();
104
+ expect(login2).toBeDefined();
105
+ expect(login2.token).toBeDefined();
106
+ expect(login2.user).toBeDefined();
107
+ });
108
+
109
+ test('login fails with invalid username', async () => {
110
+ await expect(
111
+ caller.auth.login({ username: 'nonexistent', password: 'any' })
112
+ ).rejects.toThrow();
113
+ });
114
+
115
+ test('login fails with wrong password', async () => {
116
+ await expect(
117
+ caller.auth.login({ username: 'testuser1', password: 'wrongpassword' })
118
+ ).rejects.toThrow();
119
+ });
120
+
121
+ test('register fails when username already exists', async () => {
122
+ await expect(
123
+ caller.auth.register({
124
+ username: 'testuser1',
125
+ email: 'newemail@studious.sh',
126
+ password: 'password_is_1234',
127
+ confirmPassword: 'password_is_1234',
128
+ })
129
+ ).rejects.toThrow();
130
+ });
131
+
132
+ test('register fails when email already exists', async () => {
133
+ await expect(
134
+ caller.auth.register({
135
+ username: 'newuser',
136
+ email: 'test@studious.sh',
137
+ password: 'password_is_1234',
138
+ confirmPassword: 'password_is_1234',
139
+ })
140
+ ).rejects.toThrow();
141
+ });
142
+
143
+ test('verify fails with invalid token', async () => {
144
+ await expect(
145
+ caller.auth.verify({ token: 'invalid-token-12345' })
146
+ ).rejects.toThrow();
147
+ });
148
+
149
+ test('resetPassword fails with invalid token', async () => {
150
+ await expect(
151
+ caller.auth.resetPassword({
152
+ token: 'invalid-token-12345',
153
+ password: 'newpass123',
154
+ confirmPassword: 'newpass123',
155
+ })
156
+ ).rejects.toThrow();
157
+ });
158
+
159
+ test('logout invalidates session', async () => {
160
+ const logout = await user3Caller.auth.logout();
161
+ expect(logout).toBeDefined();
162
+ expect(logout.success).toBe(true);
163
+
164
+ const ctx = await createTRPCContext({
165
+ req: { headers: { authorization: `Bearer ${login3.token}`, 'x-user': login3.token } } as any,
166
+ res: {} as any,
167
+ });
168
+ const router = appRouter.createCaller(ctx);
169
+ await expect(router.user.getProfile()).rejects.toThrow('Invalid or expired session');
170
+ });
171
+ });
@@ -1,8 +1,15 @@
1
- import { test, expect, describe, beforeEach, afterEach } from 'vitest';
2
- import { appRouter } from '../src/routers/_app';
3
- import { createTRPCContext } from '../src/trpc';
4
- import { prisma } from '../src/lib/prisma';
5
- import { caller, user1Caller, user2Caller } from './setup';
1
+ import { test, expect, describe, beforeEach, afterEach, vi } from 'vitest';
2
+ import { expectForbidden, expectRejects, expectUnauth } from '../helpers';
3
+ import { prisma } from '../../src/lib/prisma';
4
+ import { caller, user1Caller, user2Caller, user3Caller } from '../setup';
5
+
6
+ vi.mock('../../src/lib/googleCloudStorage.js', async (importOriginal) => {
7
+ const actual = await importOriginal<typeof import('../../src/lib/googleCloudStorage.js')>();
8
+ return {
9
+ ...actual,
10
+ copyFile: vi.fn().mockResolvedValue(undefined),
11
+ };
12
+ });
6
13
 
7
14
  describe('Class Router', () => {
8
15
  let testClass: any;
@@ -44,28 +51,19 @@ describe('Class Router', () => {
44
51
  expect(testClass.id).not.toBe(testClass2.id);
45
52
  });
46
53
 
47
- test('should fail to create class without authentication', async () => {
48
- const invalidCaller = await createTRPCContext({
49
- req: { headers: {} } as any,
50
- res: {} as any,
51
- });
52
- const router = appRouter.createCaller(invalidCaller);
53
-
54
- await expect(router.class.create({
54
+ test('should fail to create class without authentication', () =>
55
+ expectUnauth((c) => c.class.create({
55
56
  name: 'Test Class',
56
57
  subject: 'Mathematics',
57
58
  section: '10th Grade',
58
- })).rejects.toThrow();
59
- });
60
-
61
- test('should fail to create class with missing required fields', async () => {
59
+ })));
62
60
 
61
+ test('should fail to create class with missing required fields', () =>
63
62
  // @ts-expect-error - test case
64
- await expect(user1Caller.class.create({
63
+ expectRejects(() => user1Caller.class.create({
65
64
  name: '',
66
65
  color: '#3B82F6',
67
- })).rejects.toThrow();
68
- });
66
+ })));
69
67
  });
70
68
 
71
69
  describe('getAll', () => {
@@ -85,15 +83,8 @@ describe('Class Router', () => {
85
83
  expect(userClass?.id).toBe(testClass.id);
86
84
  });
87
85
 
88
- test('should fail to get classes without authentication', async () => {
89
- const invalidCaller = await createTRPCContext({
90
- req: { headers: {} } as any,
91
- res: {} as any,
92
- });
93
- const router = appRouter.createCaller(invalidCaller);
94
-
95
- await expect(router.class.getAll()).rejects.toThrow();
96
- });
86
+ test('should fail to get classes without authentication', () =>
87
+ expectUnauth((c) => c.class.getAll()));
97
88
  });
98
89
 
99
90
  describe('get', () => {
@@ -105,19 +96,11 @@ describe('Class Router', () => {
105
96
  expect(classData.class.name).toBe('Test Class 1');
106
97
  });
107
98
 
108
- test('should fail to get non-existent class', async () => {
109
- await expect(user1Caller.class.get({ classId: 'non-existent-id' })).rejects.toThrow();
110
- });
99
+ test('should fail to get non-existent class', () =>
100
+ expectRejects(() => user1Caller.class.get({ classId: 'non-existent-id' })));
111
101
 
112
- test('should fail to get class without authentication', async () => {
113
- const invalidCaller = await createTRPCContext({
114
- req: { headers: {} } as any,
115
- res: {} as any,
116
- });
117
- const router = appRouter.createCaller(invalidCaller);
118
-
119
- await expect(router.class.get({ classId: testClass.id })).rejects.toThrow();
120
- });
102
+ test('should fail to get class without authentication', () =>
103
+ expectUnauth((c) => c.class.get({ classId: testClass.id })));
121
104
  });
122
105
 
123
106
  describe('update', () => {
@@ -136,23 +119,21 @@ describe('Class Router', () => {
136
119
  expect(updatedClass.updatedClass.section).toBe('12th Grade');
137
120
  });
138
121
 
139
- test('should fail to update class user is not teacher of', async () => {
140
- await expect(user2Caller.class.update({
122
+ test('should fail to update class user is not teacher of', () =>
123
+ expectForbidden(() => user2Caller.class.update({
141
124
  classId: testClass.id,
142
125
  name: 'Updated Test Class',
143
126
  subject: 'Physics',
144
127
  section: '12th Grade',
145
- })).rejects.toThrow();
146
- });
128
+ })));
147
129
 
148
- test('should fail to update non-existent class', async () => {
149
- await expect(user1Caller.class.update({
130
+ test('should fail to update non-existent class', () =>
131
+ expectRejects(() => user1Caller.class.update({
150
132
  classId: 'non-existent-id',
151
133
  name: 'Updated Test Class',
152
134
  subject: 'Physics',
153
135
  section: '12th Grade',
154
- })).rejects.toThrow();
155
- });
136
+ })));
156
137
  });
157
138
 
158
139
  describe('delete', () => {
@@ -169,13 +150,11 @@ describe('Class Router', () => {
169
150
  }
170
151
  });
171
152
 
172
- test('should fail to delete class user is not teacher of', async () => {
173
- await expect(user2Caller.class.delete({ classId: testClass.id, id: testClass.id })).rejects.toThrow();
174
- });
153
+ test('should fail to delete class user is not teacher of', () =>
154
+ expectForbidden(() => user2Caller.class.delete({ classId: testClass.id, id: testClass.id })));
175
155
 
176
- test('should fail to delete non-existent class', async () => {
177
- await expect(user1Caller.class.delete({ classId: 'non-existent-id', id: 'non-existent-id' })).rejects.toThrow();
178
- });
156
+ test('should fail to delete non-existent class', () =>
157
+ expectRejects(() => user1Caller.class.delete({ classId: 'non-existent-id', id: 'non-existent-id' })));
179
158
  });
180
159
 
181
160
  describe('addStudent', () => {
@@ -190,13 +169,14 @@ describe('Class Router', () => {
190
169
  expect(result.newStudent).toBeDefined();
191
170
  });
192
171
 
193
- test('should fail to add student if user is not class teacher', async () => {
194
- const user1Profile = await user1Caller.user.getProfile();
195
- await expect(user2Caller.class.addStudent({
196
- classId: testClass.id,
197
- studentId: user1Profile.id,
198
- })).rejects.toThrow();
199
- });
172
+ test('should fail to add student if user is not class teacher', () =>
173
+ expectForbidden(async () => {
174
+ const user1Profile = await user1Caller.user.getProfile();
175
+ return user2Caller.class.addStudent({
176
+ classId: testClass.id,
177
+ studentId: user1Profile.id,
178
+ });
179
+ }));
200
180
  });
201
181
 
202
182
  describe('changeRole', () => {
@@ -213,14 +193,15 @@ describe('Class Router', () => {
213
193
  expect(result.user.type).toBe('teacher');
214
194
  });
215
195
 
216
- test('should fail to change role if user is not class teacher', async () => {
217
- const user1Profile = await user1Caller.user.getProfile();
218
- await expect(user2Caller.class.changeRole({
219
- classId: testClass.id,
220
- userId: user1Profile.id,
221
- type: 'teacher',
222
- })).rejects.toThrow();
223
- });
196
+ test('should fail to change role if user is not class teacher', () =>
197
+ expectForbidden(async () => {
198
+ const user1Profile = await user1Caller.user.getProfile();
199
+ return user2Caller.class.changeRole({
200
+ classId: testClass.id,
201
+ userId: user1Profile.id,
202
+ type: 'teacher',
203
+ });
204
+ }));
224
205
  });
225
206
 
226
207
  describe('removeMember', () => {
@@ -244,13 +225,14 @@ describe('Class Router', () => {
244
225
  expect(result.removedUserId).toBe(user2Profile.id);
245
226
  });
246
227
 
247
- test('should fail to remove member if user is not class teacher', async () => {
248
- const user1Profile = await user1Caller.user.getProfile();
249
- await expect(user2Caller.class.removeMember({
250
- classId: testClass.id,
251
- userId: user1Profile.id,
252
- })).rejects.toThrow();
253
- });
228
+ test('should fail to remove member if user is not class teacher', () =>
229
+ expectForbidden(async () => {
230
+ const user1Profile = await user1Caller.user.getProfile();
231
+ return user2Caller.class.removeMember({
232
+ classId: testClass.id,
233
+ userId: user1Profile.id,
234
+ });
235
+ }));
254
236
  });
255
237
 
256
238
  describe('join', () => {
@@ -273,9 +255,8 @@ describe('Class Router', () => {
273
255
  });
274
256
  });
275
257
 
276
- test('should fail to join with invalid class code', async () => {
277
- await expect(user2Caller.class.join({ classCode: 'invalid-code' })).rejects.toThrow();
278
- });
258
+ test('should fail to join with invalid class code', () =>
259
+ expectRejects(() => user2Caller.class.join({ classCode: 'invalid-code' })));
279
260
  });
280
261
 
281
262
  describe('getInviteCode', () => {
@@ -286,9 +267,8 @@ describe('Class Router', () => {
286
267
  expect(typeof result.code).toBe('string');
287
268
  });
288
269
 
289
- test('should fail to get invite code if user is not class teacher', async () => {
290
- await expect(user2Caller.class.getInviteCode({ classId: testClass.id })).rejects.toThrow();
291
- });
270
+ test('should fail to get invite code if user is not class teacher', () =>
271
+ expectForbidden(() => user2Caller.class.getInviteCode({ classId: testClass.id })));
292
272
  });
293
273
 
294
274
  describe('createInviteCode', () => {
@@ -299,8 +279,74 @@ describe('Class Router', () => {
299
279
  expect(typeof result.code).toBe('string');
300
280
  });
301
281
 
302
- test('should fail to create invite code if user is not class teacher', async () => {
303
- await expect(user2Caller.class.createInviteCode({ classId: testClass.id })).rejects.toThrow();
282
+ test('should fail to create invite code if user is not class teacher', () =>
283
+ expectForbidden(() => user2Caller.class.createInviteCode({ classId: testClass.id })));
284
+ });
285
+
286
+ describe('exportClass', () => {
287
+ test('teacher can export class', async () => {
288
+ // Ensure class has classFiles (root folder) by fetching it
289
+ await user1Caller.folder.getRootFolder({ classId: testClass.id });
290
+
291
+ const exported = await user1Caller.class.exportClass({ classId: testClass.id });
292
+
293
+ expect(exported).toBeDefined();
294
+ expect(exported.id).toBe(testClass.id);
295
+ expect(exported.name).toBe('Test Class 1');
296
+ expect(exported.subject).toBe('Mathematics');
297
+ expect(exported.assignments).toBeDefined();
298
+ expect(Array.isArray(exported.assignments)).toBe(true);
299
+ expect(exported.classFiles).toBeDefined();
300
+ });
301
+
302
+ test('non-teacher cannot export class', () =>
303
+ expectForbidden(() => user2Caller.class.exportClass({ classId: testClass.id })));
304
+ });
305
+
306
+ describe('importClass', () => {
307
+ test('teacher can import class with folder structure', async () => {
308
+ await user1Caller.folder.getRootFolder({ classId: testClass.id });
309
+ await user1Caller.folder.create({
310
+ classId: testClass.id,
311
+ name: 'Source Folder',
312
+ color: '#3B82F6',
313
+ });
314
+
315
+ const exported = await user1Caller.class.exportClass({ classId: testClass.id });
316
+ const newClass = await user1Caller.class.create({
317
+ name: 'Imported Class',
318
+ subject: 'Mathematics',
319
+ section: '10th Grade',
320
+ });
321
+
322
+ const importedId = await user1Caller.class.importClass({
323
+ classId: newClass.id,
324
+ year: new Date().getFullYear(),
325
+ classData: exported,
326
+ });
327
+
328
+ const importedClass = await user1Caller.class.get({ classId: importedId });
329
+ expect(importedClass.class.name).toBe(exported.name);
330
+ expect(importedClass.class.subject).toBe(exported.subject);
331
+
332
+ const rootFolder = await user1Caller.folder.getRootFolder({ classId: importedId });
333
+ expect(rootFolder).toBeDefined();
334
+ expect(rootFolder.name).toBe('Class Files');
335
+ });
336
+
337
+
338
+ test('non-teacher cannot import class', async () => {
339
+ await user1Caller.folder.getRootFolder({ classId: testClass.id });
340
+ const exported = await user1Caller.class.exportClass({ classId: testClass.id });
341
+ const newClassId = `import-forbidden-${Date.now()}`;
342
+
343
+ await expect(
344
+ user3Caller.class.importClass({
345
+ classId: newClassId,
346
+ year: new Date().getFullYear(),
347
+ classData: exported,
348
+ })
349
+ ).rejects.toThrow();
304
350
  });
305
351
  });
306
352
  });
@@ -0,0 +1,126 @@
1
+ import { test, expect, describe, beforeAll } from 'vitest';
2
+ import { user1Caller, user2Caller } from '../setup';
3
+
4
+ describe('Comment Router', () => {
5
+ let testClass: any;
6
+ let announcement: any;
7
+ let commentId: string;
8
+ let replyId: string;
9
+
10
+ beforeAll(async () => {
11
+ testClass = await user1Caller.class.create({
12
+ name: 'Comment Test Class',
13
+ subject: 'History',
14
+ section: '11th Grade',
15
+ });
16
+
17
+ const result = await user1Caller.announcement.create({
18
+ classId: testClass.id,
19
+ remarks: 'Test announcement for comments',
20
+ });
21
+ announcement = result.announcement;
22
+
23
+ const comment = await user1Caller.announcement.addComment({
24
+ announcementId: announcement.id,
25
+ classId: testClass.id,
26
+ content: 'Test comment on announcement',
27
+ });
28
+ commentId = comment.comment.id;
29
+ });
30
+
31
+ describe('get', () => {
32
+ test('should get a comment by ID', async () => {
33
+ const comment = await user1Caller.comment.get({ id: commentId });
34
+
35
+ expect(comment).toBeDefined();
36
+ expect(comment.id).toBe(commentId);
37
+ expect(comment.content).toBe('Test comment on announcement');
38
+ });
39
+
40
+ test('should fail for non-existent comment', async () => {
41
+ await expect(
42
+ user1Caller.comment.get({ id: 'nonexistent-id' }),
43
+ ).rejects.toThrow();
44
+ });
45
+ });
46
+
47
+ describe('replyToComment', () => {
48
+ test('should reply to a comment', async () => {
49
+ const reply = await user1Caller.comment.replyToComment({
50
+ parentCommentId: commentId,
51
+ content: 'This is a reply',
52
+ });
53
+
54
+ expect(reply).toBeDefined();
55
+ expect(reply.content).toBe('This is a reply');
56
+ expect(reply.parentCommentId).toBe(commentId);
57
+ replyId = reply.id;
58
+ });
59
+ });
60
+
61
+ describe('getReplies', () => {
62
+ test('should get replies to a comment', async () => {
63
+ const replies = await user1Caller.comment.getReplies({
64
+ commentId,
65
+ });
66
+
67
+ expect(Array.isArray(replies)).toBe(true);
68
+ expect(replies.length).toBeGreaterThanOrEqual(1);
69
+ expect(replies.some((r) => r.id === replyId)).toBe(true);
70
+ });
71
+
72
+ test('should return empty array for comment with no replies', async () => {
73
+ const replies = await user1Caller.comment.getReplies({
74
+ commentId: replyId,
75
+ });
76
+
77
+ expect(replies).toEqual([]);
78
+ });
79
+ });
80
+
81
+ describe('reactions', () => {
82
+ test('should add a reaction to a comment', async () => {
83
+ const result = await user1Caller.comment.addReaction({
84
+ id: commentId,
85
+ type: 'HEART',
86
+ });
87
+
88
+ expect(result.reaction).toBeDefined();
89
+ expect(result.reaction.type).toBe('HEART');
90
+ });
91
+
92
+ test('should get reactions for a comment', async () => {
93
+ const result = await user1Caller.comment.getReactions({
94
+ commentId,
95
+ });
96
+
97
+ expect(result).toBeDefined();
98
+ expect(result.counts.HEART).toBe(1);
99
+ expect(result.total).toBe(1);
100
+ expect(result.userReaction).toBe('HEART');
101
+ });
102
+
103
+ test('should update reaction type', async () => {
104
+ const result = await user1Caller.comment.addReaction({
105
+ id: commentId,
106
+ type: 'THUMBSUP',
107
+ });
108
+
109
+ expect(result.reaction.type).toBe('THUMBSUP');
110
+ });
111
+
112
+ test('should remove a reaction', async () => {
113
+ const result = await user1Caller.comment.removeReaction({
114
+ commentId,
115
+ });
116
+
117
+ expect(result.success).toBe(true);
118
+ });
119
+
120
+ test('should fail to remove non-existent reaction', async () => {
121
+ await expect(
122
+ user1Caller.comment.removeReaction({ commentId }),
123
+ ).rejects.toThrow();
124
+ });
125
+ });
126
+ });