@studious-lms/server 1.1.24 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (485) hide show
  1. package/.coderabbit.yaml +9 -0
  2. package/.env.example +53 -0
  3. package/.env.test.example +37 -0
  4. package/README.md +34 -7
  5. package/dist/exportType.d.ts.map +1 -1
  6. package/dist/exportType.js +4 -0
  7. package/dist/exportType.js.map +1 -0
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +212 -51
  11. package/dist/index.js.map +1 -0
  12. package/dist/instrument.d.ts +2 -0
  13. package/dist/instrument.d.ts.map +1 -0
  14. package/dist/instrument.js +18 -0
  15. package/dist/instrument.js.map +1 -0
  16. package/dist/lib/config/env.d.ts +190 -0
  17. package/dist/lib/config/env.d.ts.map +1 -0
  18. package/dist/lib/config/env.js +121 -0
  19. package/dist/lib/config/env.js.map +1 -0
  20. package/dist/lib/fileUpload.d.ts +2 -2
  21. package/dist/lib/fileUpload.d.ts.map +1 -1
  22. package/dist/lib/fileUpload.js +82 -15
  23. package/dist/lib/fileUpload.js.map +1 -0
  24. package/dist/lib/googleCloudStorage.d.ts +13 -0
  25. package/dist/lib/googleCloudStorage.d.ts.map +1 -1
  26. package/dist/lib/googleCloudStorage.js +45 -6
  27. package/dist/lib/googleCloudStorage.js.map +1 -0
  28. package/dist/lib/jsonConversion.d.ts.map +1 -1
  29. package/dist/lib/jsonConversion.js +16 -14
  30. package/dist/lib/jsonConversion.js.map +1 -0
  31. package/dist/lib/jsonStyles.d.ts.map +1 -1
  32. package/dist/lib/jsonStyles.js +4 -0
  33. package/dist/lib/jsonStyles.js.map +1 -0
  34. package/dist/lib/notificationHandler.d.ts +25 -0
  35. package/dist/lib/notificationHandler.d.ts.map +1 -0
  36. package/dist/lib/notificationHandler.js +32 -0
  37. package/dist/lib/notificationHandler.js.map +1 -0
  38. package/dist/lib/prisma.d.ts +2 -2
  39. package/dist/lib/prisma.d.ts.map +1 -1
  40. package/dist/lib/prisma.js +24 -1
  41. package/dist/lib/prisma.js.map +1 -0
  42. package/dist/lib/pusher.d.ts +4 -1
  43. package/dist/lib/pusher.d.ts.map +1 -1
  44. package/dist/lib/pusher.js +14 -6
  45. package/dist/lib/pusher.js.map +1 -0
  46. package/dist/lib/redis.d.ts +5 -0
  47. package/dist/lib/redis.d.ts.map +1 -0
  48. package/dist/lib/redis.js +53 -0
  49. package/dist/lib/redis.js.map +1 -0
  50. package/dist/lib/thumbnailGenerator.d.ts +0 -21
  51. package/dist/lib/thumbnailGenerator.d.ts.map +1 -1
  52. package/dist/lib/thumbnailGenerator.js +159 -158
  53. package/dist/lib/thumbnailGenerator.js.map +1 -0
  54. package/dist/middleware/auth.d.ts.map +1 -1
  55. package/dist/middleware/auth.js +41 -93
  56. package/dist/middleware/auth.js.map +1 -0
  57. package/dist/middleware/logging.d.ts.map +1 -1
  58. package/dist/middleware/logging.js +4 -0
  59. package/dist/middleware/logging.js.map +1 -0
  60. package/dist/middleware/security.d.ts +5 -0
  61. package/dist/middleware/security.d.ts.map +1 -0
  62. package/dist/middleware/security.js +77 -0
  63. package/dist/middleware/security.js.map +1 -0
  64. package/dist/models/agenda.d.ts +97 -0
  65. package/dist/models/agenda.d.ts.map +1 -0
  66. package/dist/models/agenda.js +40 -0
  67. package/dist/models/agenda.js.map +1 -0
  68. package/dist/models/announcement.d.ts +223 -0
  69. package/dist/models/announcement.d.ts.map +1 -0
  70. package/dist/models/announcement.js +120 -0
  71. package/dist/models/announcement.js.map +1 -0
  72. package/dist/models/assignment.d.ts +1292 -0
  73. package/dist/models/assignment.d.ts.map +1 -0
  74. package/dist/models/assignment.js +309 -0
  75. package/dist/models/assignment.js.map +1 -0
  76. package/dist/models/attendance.d.ts +180 -0
  77. package/dist/models/attendance.d.ts.map +1 -0
  78. package/dist/models/attendance.js +188 -0
  79. package/dist/models/attendance.js.map +1 -0
  80. package/dist/models/auth.d.ts +153 -0
  81. package/dist/models/auth.d.ts.map +1 -0
  82. package/dist/models/auth.js +217 -0
  83. package/dist/models/auth.js.map +1 -0
  84. package/dist/models/class.d.ts +439 -0
  85. package/dist/models/class.d.ts.map +1 -0
  86. package/dist/models/class.js +546 -0
  87. package/dist/models/class.js.map +1 -0
  88. package/dist/models/comment.d.ts +171 -0
  89. package/dist/models/comment.d.ts.map +1 -0
  90. package/dist/models/comment.js +138 -0
  91. package/dist/models/comment.js.map +1 -0
  92. package/dist/models/conversation.d.ts +164 -0
  93. package/dist/models/conversation.d.ts.map +1 -0
  94. package/dist/models/conversation.js +175 -0
  95. package/dist/models/conversation.js.map +1 -0
  96. package/dist/models/event.d.ts +295 -0
  97. package/dist/models/event.d.ts.map +1 -0
  98. package/dist/models/event.js +145 -0
  99. package/dist/models/event.js.map +1 -0
  100. package/dist/models/file.d.ts +536 -0
  101. package/dist/models/file.d.ts.map +1 -0
  102. package/dist/models/file.js +126 -0
  103. package/dist/models/file.js.map +1 -0
  104. package/dist/models/folder.d.ts +295 -0
  105. package/dist/models/folder.d.ts.map +1 -0
  106. package/dist/models/folder.js +202 -0
  107. package/dist/models/folder.js.map +1 -0
  108. package/dist/models/labChat.d.ts +243 -0
  109. package/dist/models/labChat.d.ts.map +1 -0
  110. package/dist/models/labChat.js +204 -0
  111. package/dist/models/labChat.js.map +1 -0
  112. package/dist/models/marketing.d.ts +72 -0
  113. package/dist/models/marketing.d.ts.map +1 -0
  114. package/dist/models/marketing.js +26 -0
  115. package/dist/models/marketing.js.map +1 -0
  116. package/dist/models/message.d.ts +100 -0
  117. package/dist/models/message.d.ts.map +1 -0
  118. package/dist/models/message.js +131 -0
  119. package/dist/models/message.js.map +1 -0
  120. package/dist/models/newtonChat.d.ts +72 -0
  121. package/dist/models/newtonChat.d.ts.map +1 -0
  122. package/dist/models/newtonChat.js +61 -0
  123. package/dist/models/newtonChat.js.map +1 -0
  124. package/dist/models/notification.d.ts +65 -0
  125. package/dist/models/notification.d.ts.map +1 -0
  126. package/dist/models/notification.js +46 -0
  127. package/dist/models/notification.js.map +1 -0
  128. package/dist/models/section.d.ts +102 -0
  129. package/dist/models/section.d.ts.map +1 -0
  130. package/dist/models/section.js +83 -0
  131. package/dist/models/section.js.map +1 -0
  132. package/dist/models/user.d.ts +39 -0
  133. package/dist/models/user.d.ts.map +1 -0
  134. package/dist/models/user.js +38 -0
  135. package/dist/models/user.js.map +1 -0
  136. package/dist/models/worksheet.d.ts +460 -0
  137. package/dist/models/worksheet.d.ts.map +1 -0
  138. package/dist/models/worksheet.js +200 -0
  139. package/dist/models/worksheet.js.map +1 -0
  140. package/dist/pipelines/aiLabChat.d.ts +21 -0
  141. package/dist/pipelines/aiLabChat.d.ts.map +1 -0
  142. package/dist/pipelines/aiLabChat.js +460 -0
  143. package/dist/pipelines/aiLabChat.js.map +1 -0
  144. package/dist/pipelines/aiNewtonChat.d.ts +30 -0
  145. package/dist/pipelines/aiNewtonChat.d.ts.map +1 -0
  146. package/dist/pipelines/aiNewtonChat.js +289 -0
  147. package/dist/pipelines/aiNewtonChat.js.map +1 -0
  148. package/dist/pipelines/gradeWorksheet.d.ts +30 -0
  149. package/dist/pipelines/gradeWorksheet.d.ts.map +1 -0
  150. package/dist/pipelines/gradeWorksheet.js +252 -0
  151. package/dist/pipelines/gradeWorksheet.js.map +1 -0
  152. package/dist/routers/_app.d.ts +6403 -3741
  153. package/dist/routers/_app.d.ts.map +1 -1
  154. package/dist/routers/_app.js +10 -0
  155. package/dist/routers/_app.js.map +1 -0
  156. package/dist/routers/agenda.d.ts +58 -6
  157. package/dist/routers/agenda.d.ts.map +1 -1
  158. package/dist/routers/agenda.js +6 -58
  159. package/dist/routers/agenda.js.map +1 -0
  160. package/dist/routers/announcement.d.ts +325 -6
  161. package/dist/routers/announcement.d.ts.map +1 -1
  162. package/dist/routers/announcement.js +547 -57
  163. package/dist/routers/announcement.js.map +1 -0
  164. package/dist/routers/assignment.d.ts +431 -318
  165. package/dist/routers/assignment.d.ts.map +1 -1
  166. package/dist/routers/assignment.js +104 -1559
  167. package/dist/routers/assignment.js.map +1 -0
  168. package/dist/routers/attendance.d.ts +20 -9
  169. package/dist/routers/attendance.d.ts.map +1 -1
  170. package/dist/routers/attendance.js +10 -263
  171. package/dist/routers/attendance.js.map +1 -0
  172. package/dist/routers/auth.d.ts +21 -1
  173. package/dist/routers/auth.d.ts.map +1 -1
  174. package/dist/routers/auth.js +37 -241
  175. package/dist/routers/auth.js.map +1 -0
  176. package/dist/routers/class.d.ts +198 -68
  177. package/dist/routers/class.d.ts.map +1 -1
  178. package/dist/routers/class.js +88 -909
  179. package/dist/routers/class.js.map +1 -0
  180. package/dist/routers/comment.d.ts +153 -0
  181. package/dist/routers/comment.d.ts.map +1 -0
  182. package/dist/routers/comment.js +58 -0
  183. package/dist/routers/comment.js.map +1 -0
  184. package/dist/routers/conversation.d.ts +73 -3
  185. package/dist/routers/conversation.d.ts.map +1 -1
  186. package/dist/routers/conversation.js +23 -265
  187. package/dist/routers/conversation.js.map +1 -0
  188. package/dist/routers/event.d.ts +46 -37
  189. package/dist/routers/event.d.ts.map +1 -1
  190. package/dist/routers/event.js +15 -431
  191. package/dist/routers/event.js.map +1 -0
  192. package/dist/routers/file.d.ts +4 -2
  193. package/dist/routers/file.d.ts.map +1 -1
  194. package/dist/routers/file.js +11 -295
  195. package/dist/routers/file.js.map +1 -0
  196. package/dist/routers/folder.d.ts +21 -14
  197. package/dist/routers/folder.d.ts.map +1 -1
  198. package/dist/routers/folder.js +36 -743
  199. package/dist/routers/folder.js.map +1 -0
  200. package/dist/routers/labChat.d.ts +12 -9
  201. package/dist/routers/labChat.d.ts.map +1 -1
  202. package/dist/routers/labChat.js +21 -877
  203. package/dist/routers/labChat.js.map +1 -0
  204. package/dist/routers/marketing.d.ts +2 -2
  205. package/dist/routers/marketing.d.ts.map +1 -1
  206. package/dist/routers/marketing.js +9 -54
  207. package/dist/routers/marketing.js.map +1 -0
  208. package/dist/routers/message.d.ts +2 -1
  209. package/dist/routers/message.d.ts.map +1 -1
  210. package/dist/routers/message.js +29 -519
  211. package/dist/routers/message.js.map +1 -0
  212. package/dist/routers/newtonChat.d.ts +55 -0
  213. package/dist/routers/newtonChat.d.ts.map +1 -0
  214. package/dist/routers/newtonChat.js +22 -0
  215. package/dist/routers/newtonChat.js.map +1 -0
  216. package/dist/routers/notifications.d.ts +8 -8
  217. package/dist/routers/notifications.d.ts.map +1 -1
  218. package/dist/routers/notifications.js +20 -81
  219. package/dist/routers/notifications.js.map +1 -0
  220. package/dist/routers/section.d.ts +37 -6
  221. package/dist/routers/section.d.ts.map +1 -1
  222. package/dist/routers/section.js +26 -167
  223. package/dist/routers/section.js.map +1 -0
  224. package/dist/routers/user.d.ts +1 -1
  225. package/dist/routers/user.d.ts.map +1 -1
  226. package/dist/routers/user.js +34 -204
  227. package/dist/routers/user.js.map +1 -0
  228. package/dist/routers/worksheet.d.ts +362 -0
  229. package/dist/routers/worksheet.d.ts.map +1 -0
  230. package/dist/routers/worksheet.js +153 -0
  231. package/dist/routers/worksheet.js.map +1 -0
  232. package/dist/seedDatabase.d.ts +2 -3
  233. package/dist/seedDatabase.d.ts.map +1 -1
  234. package/dist/seedDatabase.js +311 -289
  235. package/dist/seedDatabase.js.map +1 -0
  236. package/dist/server/pipelines/aiLabChat.d.ts +21 -0
  237. package/dist/server/pipelines/aiLabChat.d.ts.map +1 -0
  238. package/dist/server/pipelines/aiLabChat.js +456 -0
  239. package/dist/server/pipelines/aiLabChat.js.map +1 -0
  240. package/dist/server/pipelines/aiNewtonChat.d.ts +30 -0
  241. package/dist/server/pipelines/aiNewtonChat.d.ts.map +1 -0
  242. package/dist/server/pipelines/aiNewtonChat.js +285 -0
  243. package/dist/server/pipelines/aiNewtonChat.js.map +1 -0
  244. package/dist/server/pipelines/gradeWorksheet.d.ts +30 -0
  245. package/dist/server/pipelines/gradeWorksheet.d.ts.map +1 -0
  246. package/dist/server/pipelines/gradeWorksheet.js +248 -0
  247. package/dist/server/pipelines/gradeWorksheet.js.map +1 -0
  248. package/dist/services/agenda.d.ts +100 -0
  249. package/dist/services/agenda.d.ts.map +1 -0
  250. package/dist/services/agenda.js +21 -0
  251. package/dist/services/agenda.js.map +1 -0
  252. package/dist/services/announcement.d.ts +135 -0
  253. package/dist/services/announcement.d.ts.map +1 -0
  254. package/dist/services/announcement.js +223 -0
  255. package/dist/services/announcement.js.map +1 -0
  256. package/dist/services/assignment.d.ts +1462 -0
  257. package/dist/services/assignment.d.ts.map +1 -0
  258. package/dist/services/assignment.js +898 -0
  259. package/dist/services/assignment.js.map +1 -0
  260. package/dist/services/attendance.d.ts +93 -0
  261. package/dist/services/attendance.d.ts.map +1 -0
  262. package/dist/services/attendance.js +61 -0
  263. package/dist/services/attendance.js.map +1 -0
  264. package/dist/services/auth.d.ts +68 -0
  265. package/dist/services/auth.d.ts.map +1 -0
  266. package/dist/services/auth.js +218 -0
  267. package/dist/services/auth.js.map +1 -0
  268. package/dist/services/class.d.ts +621 -0
  269. package/dist/services/class.d.ts.map +1 -0
  270. package/dist/services/class.js +474 -0
  271. package/dist/services/class.js.map +1 -0
  272. package/dist/services/comment.d.ts +100 -0
  273. package/dist/services/comment.d.ts.map +1 -0
  274. package/dist/services/comment.js +83 -0
  275. package/dist/services/comment.js.map +1 -0
  276. package/dist/services/conversation.d.ts +159 -0
  277. package/dist/services/conversation.d.ts.map +1 -0
  278. package/dist/services/conversation.js +138 -0
  279. package/dist/services/conversation.js.map +1 -0
  280. package/dist/services/event.d.ts +216 -0
  281. package/dist/services/event.d.ts.map +1 -0
  282. package/dist/services/event.js +168 -0
  283. package/dist/services/event.js.map +1 -0
  284. package/dist/services/file.d.ts +74 -0
  285. package/dist/services/file.d.ts.map +1 -0
  286. package/dist/services/file.js +133 -0
  287. package/dist/services/file.js.map +1 -0
  288. package/dist/services/folder.d.ts +239 -0
  289. package/dist/services/folder.d.ts.map +1 -0
  290. package/dist/services/folder.js +248 -0
  291. package/dist/services/folder.js.map +1 -0
  292. package/dist/services/labChat.d.ts +165 -0
  293. package/dist/services/labChat.d.ts.map +1 -0
  294. package/dist/services/labChat.js +289 -0
  295. package/dist/services/labChat.js.map +1 -0
  296. package/dist/services/marketing.d.ts +50 -0
  297. package/dist/services/marketing.d.ts.map +1 -0
  298. package/dist/services/marketing.js +32 -0
  299. package/dist/services/marketing.js.map +1 -0
  300. package/dist/services/message.d.ts +95 -0
  301. package/dist/services/message.d.ts.map +1 -0
  302. package/dist/services/message.js +350 -0
  303. package/dist/services/message.js.map +1 -0
  304. package/dist/services/newtonChat.d.ts +22 -0
  305. package/dist/services/newtonChat.d.ts.map +1 -0
  306. package/dist/services/newtonChat.js +174 -0
  307. package/dist/services/newtonChat.js.map +1 -0
  308. package/dist/services/notification.d.ts +65 -0
  309. package/dist/services/notification.d.ts.map +1 -0
  310. package/dist/services/notification.js +33 -0
  311. package/dist/services/notification.js.map +1 -0
  312. package/dist/services/section.d.ts +53 -0
  313. package/dist/services/section.d.ts.map +1 -0
  314. package/dist/services/section.js +199 -0
  315. package/dist/services/section.js.map +1 -0
  316. package/dist/services/user.d.ts +48 -0
  317. package/dist/services/user.d.ts.map +1 -0
  318. package/dist/services/user.js +141 -0
  319. package/dist/services/user.js.map +1 -0
  320. package/dist/services/worksheet.d.ts +239 -0
  321. package/dist/services/worksheet.d.ts.map +1 -0
  322. package/dist/services/worksheet.js +235 -0
  323. package/dist/services/worksheet.js.map +1 -0
  324. package/dist/socket/handlers.d.ts.map +1 -1
  325. package/dist/socket/handlers.js +4 -0
  326. package/dist/socket/handlers.js.map +1 -0
  327. package/dist/trpc.d.ts.map +1 -1
  328. package/dist/trpc.js +4 -0
  329. package/dist/trpc.js.map +1 -0
  330. package/dist/types/trpc.d.ts.map +1 -1
  331. package/dist/types/trpc.js +4 -0
  332. package/dist/types/trpc.js.map +1 -0
  333. package/dist/utils/aiUser.d.ts +1 -3
  334. package/dist/utils/aiUser.d.ts.map +1 -1
  335. package/dist/utils/aiUser.js +8 -3
  336. package/dist/utils/aiUser.js.map +1 -0
  337. package/dist/utils/email.d.ts +12 -1
  338. package/dist/utils/email.d.ts.map +1 -1
  339. package/dist/utils/email.js +26 -4
  340. package/dist/utils/email.js.map +1 -0
  341. package/dist/utils/generateInviteCode.d.ts +1 -2
  342. package/dist/utils/generateInviteCode.d.ts.map +1 -1
  343. package/dist/utils/generateInviteCode.js +5 -2
  344. package/dist/utils/generateInviteCode.js.map +1 -0
  345. package/dist/utils/inference.d.ts +8 -0
  346. package/dist/utils/inference.d.ts.map +1 -1
  347. package/dist/utils/inference.js +78 -10
  348. package/dist/utils/inference.js.map +1 -0
  349. package/dist/utils/logger.d.ts +4 -0
  350. package/dist/utils/logger.d.ts.map +1 -1
  351. package/dist/utils/logger.js +35 -3
  352. package/dist/utils/logger.js.map +1 -0
  353. package/dist/utils/prismaErrorHandler.d.ts.map +1 -1
  354. package/dist/utils/prismaErrorHandler.js +7 -0
  355. package/dist/utils/prismaErrorHandler.js.map +1 -0
  356. package/dist/utils/prismaWrapper.d.ts +1 -0
  357. package/dist/utils/prismaWrapper.d.ts.map +1 -1
  358. package/dist/utils/prismaWrapper.js +8 -0
  359. package/dist/utils/prismaWrapper.js.map +1 -0
  360. package/docker-compose.yml +19 -0
  361. package/package.json +21 -4
  362. package/prisma/migrations/20251109122857_annuoncements_comments/migration.sql +30 -0
  363. package/prisma/migrations/20251109135555_reactions_announcements_comments/migration.sql +35 -0
  364. package/prisma/schema.prisma +180 -12
  365. package/scripts/test-pre-push.ts +14 -0
  366. package/src/index.ts +247 -52
  367. package/src/instrument.ts +15 -0
  368. package/src/lib/config/env.ts +132 -0
  369. package/src/lib/fileUpload.ts +81 -16
  370. package/src/lib/googleCloudStorage.ts +42 -6
  371. package/src/lib/jsonConversion.ts +12 -14
  372. package/src/lib/prisma.ts +23 -2
  373. package/src/lib/pusher.ts +11 -6
  374. package/src/lib/redis.ts +56 -0
  375. package/src/lib/thumbnailGenerator.ts +170 -168
  376. package/src/middleware/auth.ts +86 -137
  377. package/src/middleware/security.ts +80 -0
  378. package/src/models/agenda.ts +46 -0
  379. package/src/models/announcement.ts +134 -0
  380. package/src/models/assignment.ts +322 -0
  381. package/src/models/attendance.ts +208 -0
  382. package/src/models/auth.ts +247 -0
  383. package/src/models/class.ts +598 -0
  384. package/src/models/comment.ts +152 -0
  385. package/src/models/conversation.ts +200 -0
  386. package/src/models/event.ts +177 -0
  387. package/src/models/file.ts +129 -0
  388. package/src/models/folder.ts +225 -0
  389. package/src/models/labChat.ts +213 -0
  390. package/src/models/marketing.ts +45 -0
  391. package/src/models/message.ts +153 -0
  392. package/src/models/newtonChat.ts +70 -0
  393. package/src/models/notification.ts +54 -0
  394. package/src/models/section.ts +98 -0
  395. package/src/models/user.ts +47 -0
  396. package/src/models/worksheet.ts +294 -0
  397. package/src/pipelines/aiLabChat.ts +511 -0
  398. package/src/pipelines/aiNewtonChat.ts +347 -0
  399. package/src/pipelines/gradeWorksheet.ts +286 -0
  400. package/src/routers/_app.ts +6 -0
  401. package/src/routers/agenda.ts +3 -61
  402. package/src/routers/announcement.ts +622 -57
  403. package/src/routers/assignment.ts +157 -1688
  404. package/src/routers/attendance.ts +16 -277
  405. package/src/routers/auth.ts +79 -313
  406. package/src/routers/class.ts +265 -1038
  407. package/src/routers/comment.ts +76 -0
  408. package/src/routers/conversation.ts +53 -284
  409. package/src/routers/event.ts +50 -481
  410. package/src/routers/file.ts +45 -341
  411. package/src/routers/folder.ts +107 -836
  412. package/src/routers/labChat.ts +29 -960
  413. package/src/routers/marketing.ts +35 -77
  414. package/src/routers/message.ts +45 -571
  415. package/src/routers/newtonChat.ts +36 -0
  416. package/src/routers/notifications.ts +32 -82
  417. package/src/routers/section.ts +58 -200
  418. package/src/routers/user.ts +49 -226
  419. package/src/routers/worksheet.ts +252 -0
  420. package/src/seedDatabase.ts +330 -290
  421. package/src/services/agenda.ts +21 -0
  422. package/src/services/announcement.ts +290 -0
  423. package/src/services/assignment.ts +1198 -0
  424. package/src/services/attendance.ts +85 -0
  425. package/src/services/auth.ts +277 -0
  426. package/src/services/class.ts +622 -0
  427. package/src/services/comment.ts +106 -0
  428. package/src/services/conversation.ts +213 -0
  429. package/src/services/event.ts +231 -0
  430. package/src/services/file.ts +167 -0
  431. package/src/services/folder.ts +316 -0
  432. package/src/services/labChat.ts +352 -0
  433. package/src/services/marketing.ts +57 -0
  434. package/src/services/message.ts +461 -0
  435. package/src/services/newtonChat.ts +222 -0
  436. package/src/services/notification.ts +50 -0
  437. package/src/services/section.ts +283 -0
  438. package/src/services/user.ts +172 -0
  439. package/src/services/worksheet.ts +358 -0
  440. package/src/trpc.ts +4 -0
  441. package/src/utils/aiUser.ts +4 -3
  442. package/src/utils/email.ts +33 -4
  443. package/src/utils/generateInviteCode.ts +1 -3
  444. package/src/utils/inference.ts +89 -10
  445. package/src/utils/logger.ts +33 -3
  446. package/src/utils/prismaErrorHandler.ts +3 -0
  447. package/src/utils/prismaWrapper.ts +4 -0
  448. package/tests/globalSetup.ts +62 -0
  449. package/tests/helpers.ts +22 -0
  450. package/tests/middleware/security.test.ts +42 -0
  451. package/tests/routers/agenda.test.ts +138 -0
  452. package/tests/routers/announcement.test.ts +490 -0
  453. package/tests/routers/assignment.test.ts +837 -0
  454. package/tests/routers/attendance.test.ts +160 -0
  455. package/tests/routers/auth.test.ts +171 -0
  456. package/tests/{class.test.ts → routers/class.test.ts} +163 -92
  457. package/tests/routers/comment.test.ts +126 -0
  458. package/tests/routers/conversation.test.ts +145 -0
  459. package/tests/routers/event.test.ts +289 -0
  460. package/tests/routers/folder.test.ts +178 -0
  461. package/tests/routers/labChat.test.ts +115 -0
  462. package/tests/routers/marketing.test.ts +59 -0
  463. package/tests/routers/message.test.ts +123 -0
  464. package/tests/routers/notification.test.ts +69 -0
  465. package/tests/routers/section.test.ts +208 -0
  466. package/tests/server/rateLimit.test.ts +73 -0
  467. package/tests/setup.ts +39 -65
  468. package/tests/user.test.ts +136 -0
  469. package/tests/utils/aiUser.test.ts +22 -0
  470. package/tests/utils/generateInviteCode.test.ts +24 -0
  471. package/tests/utils/logger.test.ts +74 -0
  472. package/tests/utils/prismaErrorHandler.test.ts +101 -0
  473. package/tests/utils/prismaWrapper.test.ts +82 -0
  474. package/tests/worksheet.test.ts +181 -0
  475. package/tsconfig.json +9 -2
  476. package/vitest.config.ts +30 -1
  477. package/vitest.unit.config.ts +21 -0
  478. package/API_SPECIFICATION.md +0 -1597
  479. package/BASE64_REMOVAL_SUMMARY.md +0 -164
  480. package/CHAT_API_SPEC.md +0 -579
  481. package/LAB_CHAT_API_SPEC.md +0 -518
  482. package/dist/routers/school.d.ts +0 -208
  483. package/dist/routers/school.d.ts.map +0 -1
  484. package/dist/routers/school.js +0 -481
  485. package/tests/auth.test.ts +0 -25
@@ -1,119 +1,609 @@
1
+
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="a4b6352a-4a02-5817-b542-3f6ab5651e28")}catch(e){}}();
1
3
  import { z } from "zod";
2
4
  import { createTRPCRouter, protectedClassMemberProcedure, protectedTeacherProcedure, protectedProcedure } from "../trpc.js";
3
5
  import { prisma } from "../lib/prisma.js";
4
6
  import { TRPCError } from "@trpc/server";
5
- const AnnouncementSelect = {
6
- id: true,
7
- teacher: {
8
- select: {
9
- id: true,
10
- username: true,
11
- },
12
- },
13
- remarks: true,
14
- createdAt: true,
15
- };
7
+ import { getAllAnnouncements, getAnnouncement, createAnnouncementRecord, updateAnnouncementRecord, deleteAnnouncementRecord, } from "../services/announcement.js";
8
+ import { findAnnouncementByIdAndClass } from "../models/announcement.js";
9
+ import { findCommentWithAnnouncement, findReactionByUserAndComment, upsertReaction, deleteReactionById, } from "../models/comment.js";
10
+ import { getReactions as getCommentReactions } from "../services/comment.js";
11
+ import { createDirectUploadFiles, confirmDirectUpload } from "../lib/fileUpload.js";
12
+ // Schema for direct file uploads (no base64 data)
13
+ const directFileSchema = z.object({
14
+ name: z.string(),
15
+ type: z.string(),
16
+ size: z.number(),
17
+ });
18
+ // Schemas for file upload endpoints
19
+ const getAnnouncementUploadUrlsSchema = z.object({
20
+ announcementId: z.string(),
21
+ classId: z.string(),
22
+ files: z.array(directFileSchema),
23
+ });
24
+ const confirmAnnouncementUploadSchema = z.object({
25
+ fileId: z.string(),
26
+ uploadSuccess: z.boolean(),
27
+ errorMessage: z.string().optional(),
28
+ });
16
29
  export const announcementRouter = createTRPCRouter({
17
30
  getAll: protectedClassMemberProcedure
31
+ .input(z.object({ classId: z.string() }))
32
+ .query(({ input }) => getAllAnnouncements(input.classId)),
33
+ get: protectedClassMemberProcedure
34
+ .input(z.object({ id: z.string(), classId: z.string() }))
35
+ .query(({ input }) => getAnnouncement(input.id, input.classId)),
36
+ create: protectedTeacherProcedure
18
37
  .input(z.object({
19
38
  classId: z.string(),
39
+ remarks: z.string().min(1, "Remarks cannot be empty"),
40
+ files: z.array(directFileSchema).optional(),
41
+ existingFileIds: z.array(z.string()).optional(),
20
42
  }))
21
- .query(async ({ ctx, input }) => {
22
- const announcements = await prisma.announcement.findMany({
43
+ .mutation(({ ctx, input }) => createAnnouncementRecord(ctx.user.id, {
44
+ classId: input.classId,
45
+ remarks: input.remarks,
46
+ files: input.files,
47
+ existingFileIds: input.existingFileIds,
48
+ })),
49
+ update: protectedTeacherProcedure
50
+ .input(z.object({
51
+ id: z.string(),
52
+ classId: z.string(),
53
+ data: z.object({
54
+ remarks: z.string().min(1, "Remarks cannot be empty").optional(),
55
+ files: z.array(directFileSchema).optional(),
56
+ existingFileIds: z.array(z.string()).optional(),
57
+ removedAttachments: z.array(z.string()).optional(),
58
+ }),
59
+ }))
60
+ .mutation(({ ctx, input }) => updateAnnouncementRecord(ctx.user.id, {
61
+ id: input.id,
62
+ classId: input.classId,
63
+ data: input.data,
64
+ })),
65
+ delete: protectedTeacherProcedure
66
+ .input(z.object({ id: z.string(), classId: z.string() }))
67
+ .mutation(({ ctx, input }) => deleteAnnouncementRecord(ctx.user.id, input.id, input.classId)),
68
+ getAnnouncementUploadUrls: protectedTeacherProcedure
69
+ .input(getAnnouncementUploadUrlsSchema)
70
+ .mutation(async ({ ctx, input }) => {
71
+ const { announcementId, classId, files } = input;
72
+ if (!ctx.user) {
73
+ throw new TRPCError({
74
+ code: "UNAUTHORIZED",
75
+ message: "You must be logged in to upload files",
76
+ });
77
+ }
78
+ // Verify user is a teacher of the class
79
+ const classData = await prisma.class.findFirst({
23
80
  where: {
24
- classId: input.classId,
81
+ id: classId,
82
+ teachers: {
83
+ some: {
84
+ id: ctx.user.id,
85
+ },
86
+ },
25
87
  },
26
- select: AnnouncementSelect,
27
- orderBy: {
28
- createdAt: 'desc',
88
+ });
89
+ if (!classData) {
90
+ throw new TRPCError({
91
+ code: "NOT_FOUND",
92
+ message: "Class not found or you are not a teacher",
93
+ });
94
+ }
95
+ const announcement = await findAnnouncementByIdAndClass(announcementId, classId);
96
+ if (!announcement) {
97
+ throw new TRPCError({
98
+ code: "NOT_FOUND",
99
+ message: "Announcement not found",
100
+ });
101
+ }
102
+ // Create direct upload files
103
+ const directUploadFiles = await createDirectUploadFiles(files, ctx.user.id, undefined, // No specific directory
104
+ undefined, // No assignment ID
105
+ undefined, // No submission ID
106
+ announcementId);
107
+ return {
108
+ success: true,
109
+ uploadFiles: directUploadFiles,
110
+ };
111
+ }),
112
+ confirmAnnouncementUpload: protectedTeacherProcedure
113
+ .input(confirmAnnouncementUploadSchema)
114
+ .mutation(async ({ ctx, input }) => {
115
+ const { fileId, uploadSuccess, errorMessage } = input;
116
+ if (!ctx.user) {
117
+ throw new TRPCError({
118
+ code: "UNAUTHORIZED",
119
+ message: "You must be logged in",
120
+ });
121
+ }
122
+ // Verify file belongs to user and is an announcement file
123
+ const file = await prisma.file.findFirst({
124
+ where: {
125
+ id: fileId,
126
+ userId: ctx.user.id,
127
+ announcement: {
128
+ isNot: null,
129
+ },
29
130
  },
30
131
  });
132
+ if (!file) {
133
+ throw new TRPCError({
134
+ code: "NOT_FOUND",
135
+ message: "File not found or you don't have permission",
136
+ });
137
+ }
138
+ await confirmDirectUpload(fileId, uploadSuccess, errorMessage);
31
139
  return {
32
- announcements,
140
+ success: true,
141
+ message: uploadSuccess ? "Upload confirmed successfully" : "Upload failed",
33
142
  };
34
143
  }),
35
- create: protectedTeacherProcedure
144
+ // Comment endpoints
145
+ addComment: protectedClassMemberProcedure
36
146
  .input(z.object({
147
+ announcementId: z.string(),
37
148
  classId: z.string(),
38
- remarks: z.string(),
149
+ content: z.string().min(1, "Comment cannot be empty"),
150
+ parentCommentId: z.string().optional(),
39
151
  }))
40
152
  .mutation(async ({ ctx, input }) => {
41
- const announcement = await prisma.announcement.create({
153
+ if (!ctx.user) {
154
+ throw new TRPCError({
155
+ code: "UNAUTHORIZED",
156
+ message: "User must be authenticated",
157
+ });
158
+ }
159
+ const announcement = await findAnnouncementByIdAndClass(input.announcementId, input.classId);
160
+ if (!announcement) {
161
+ throw new TRPCError({
162
+ code: "NOT_FOUND",
163
+ message: "Announcement not found",
164
+ });
165
+ }
166
+ // If replying to a comment, verify parent comment exists and belongs to the same announcement
167
+ if (input.parentCommentId) {
168
+ const parentComment = await prisma.comment.findFirst({
169
+ where: {
170
+ id: input.parentCommentId,
171
+ announcementId: input.announcementId,
172
+ },
173
+ });
174
+ if (!parentComment) {
175
+ throw new TRPCError({
176
+ code: "NOT_FOUND",
177
+ message: "Parent comment not found",
178
+ });
179
+ }
180
+ }
181
+ const comment = await prisma.comment.create({
42
182
  data: {
43
- remarks: input.remarks,
44
- teacher: {
45
- connect: {
46
- id: ctx.user?.id,
47
- },
183
+ content: input.content,
184
+ author: {
185
+ connect: { id: ctx.user.id },
186
+ },
187
+ announcement: {
188
+ connect: { id: input.announcementId },
48
189
  },
49
- class: {
50
- connect: {
51
- id: input.classId,
190
+ ...(input.parentCommentId && {
191
+ parentComment: {
192
+ connect: { id: input.parentCommentId },
193
+ },
194
+ }),
195
+ },
196
+ include: {
197
+ author: {
198
+ select: {
199
+ id: true,
200
+ username: true,
201
+ profile: {
202
+ select: {
203
+ displayName: true,
204
+ profilePicture: true,
205
+ profilePictureThumbnail: true,
206
+ },
207
+ },
52
208
  },
53
209
  },
54
210
  },
55
- select: AnnouncementSelect,
56
211
  });
57
- return {
58
- announcement,
59
- };
212
+ return { comment };
60
213
  }),
61
- update: protectedProcedure
214
+ updateComment: protectedProcedure
62
215
  .input(z.object({
63
216
  id: z.string(),
64
- data: z.object({
65
- content: z.string(),
66
- }),
217
+ content: z.string().min(1, "Comment cannot be empty"),
67
218
  }))
68
219
  .mutation(async ({ ctx, input }) => {
69
- const announcement = await prisma.announcement.findUnique({
220
+ if (!ctx.user) {
221
+ throw new TRPCError({
222
+ code: "UNAUTHORIZED",
223
+ message: "User must be authenticated",
224
+ });
225
+ }
226
+ const comment = await prisma.comment.findUnique({
70
227
  where: { id: input.id },
71
- include: {
72
- class: {
73
- include: {
74
- teachers: true,
75
- },
76
- },
77
- },
78
228
  });
79
- if (!announcement) {
229
+ if (!comment) {
80
230
  throw new TRPCError({
81
231
  code: "NOT_FOUND",
82
- message: "Announcement not found",
232
+ message: "Comment not found",
233
+ });
234
+ }
235
+ // Only the author can update their comment
236
+ if (comment.authorId !== ctx.user.id) {
237
+ throw new TRPCError({
238
+ code: "FORBIDDEN",
239
+ message: "Only the comment author can update this comment",
83
240
  });
84
241
  }
85
- const updatedAnnouncement = await prisma.announcement.update({
242
+ const updatedComment = await prisma.comment.update({
86
243
  where: { id: input.id },
87
244
  data: {
88
- remarks: input.data.content,
245
+ content: input.content,
246
+ },
247
+ include: {
248
+ author: {
249
+ select: {
250
+ id: true,
251
+ username: true,
252
+ profile: {
253
+ select: {
254
+ displayName: true,
255
+ profilePicture: true,
256
+ profilePictureThumbnail: true,
257
+ },
258
+ },
259
+ },
260
+ },
89
261
  },
90
262
  });
91
- return { announcement: updatedAnnouncement };
263
+ return { comment: updatedComment };
92
264
  }),
93
- delete: protectedProcedure
265
+ deleteComment: protectedProcedure
94
266
  .input(z.object({
95
267
  id: z.string(),
96
268
  }))
97
269
  .mutation(async ({ ctx, input }) => {
98
- const announcement = await prisma.announcement.findUnique({
270
+ if (!ctx.user) {
271
+ throw new TRPCError({
272
+ code: "UNAUTHORIZED",
273
+ message: "User must be authenticated",
274
+ });
275
+ }
276
+ const comment = await prisma.comment.findUnique({
99
277
  where: { id: input.id },
100
278
  include: {
101
- class: {
279
+ announcement: {
102
280
  include: {
103
- teachers: true,
281
+ class: {
282
+ include: {
283
+ teachers: true,
284
+ },
285
+ },
104
286
  },
105
287
  },
106
288
  },
107
289
  });
108
- if (!announcement) {
290
+ if (!comment) {
109
291
  throw new TRPCError({
110
292
  code: "NOT_FOUND",
111
- message: "Announcement not found",
293
+ message: "Comment not found",
294
+ });
295
+ }
296
+ // Only the author or a class teacher can delete comments
297
+ const userId = ctx.user.id;
298
+ const isAuthor = comment.authorId === userId;
299
+ const isClassTeacher = comment.announcement.class.teachers.some((teacher) => teacher.id === userId);
300
+ if (!isAuthor && !isClassTeacher) {
301
+ throw new TRPCError({
302
+ code: "FORBIDDEN",
303
+ message: "Only the comment author or class teachers can delete comments",
112
304
  });
113
305
  }
114
- await prisma.announcement.delete({
306
+ await prisma.comment.delete({
115
307
  where: { id: input.id },
116
308
  });
117
309
  return { success: true };
118
310
  }),
311
+ getComments: protectedClassMemberProcedure
312
+ .input(z.object({
313
+ announcementId: z.string(),
314
+ classId: z.string(),
315
+ }))
316
+ .query(async ({ ctx, input }) => {
317
+ const announcement = await findAnnouncementByIdAndClass(input.announcementId, input.classId);
318
+ if (!announcement) {
319
+ throw new TRPCError({
320
+ code: "NOT_FOUND",
321
+ message: "Announcement not found",
322
+ });
323
+ }
324
+ // Get all top-level comments (no parent)
325
+ const comments = await prisma.comment.findMany({
326
+ where: {
327
+ announcementId: input.announcementId,
328
+ parentCommentId: null,
329
+ },
330
+ include: {
331
+ author: {
332
+ select: {
333
+ id: true,
334
+ username: true,
335
+ profile: {
336
+ select: {
337
+ displayName: true,
338
+ profilePicture: true,
339
+ profilePictureThumbnail: true,
340
+ },
341
+ },
342
+ },
343
+ },
344
+ replies: {
345
+ include: {
346
+ author: {
347
+ select: {
348
+ id: true,
349
+ username: true,
350
+ profile: {
351
+ select: {
352
+ displayName: true,
353
+ profilePicture: true,
354
+ profilePictureThumbnail: true,
355
+ },
356
+ },
357
+ },
358
+ },
359
+ },
360
+ orderBy: {
361
+ createdAt: 'asc',
362
+ },
363
+ },
364
+ },
365
+ orderBy: {
366
+ createdAt: 'asc',
367
+ },
368
+ });
369
+ return { comments };
370
+ }),
371
+ // Reaction endpoints
372
+ addReaction: protectedClassMemberProcedure
373
+ .input(z.object({
374
+ announcementId: z.string().optional(),
375
+ commentId: z.string().optional(),
376
+ classId: z.string(),
377
+ type: z.enum(['THUMBSUP', 'CELEBRATE', 'CARE', 'HEART', 'IDEA', 'HAPPY']),
378
+ }))
379
+ .mutation(async ({ ctx, input }) => {
380
+ if (!ctx.user) {
381
+ throw new TRPCError({
382
+ code: "UNAUTHORIZED",
383
+ message: "User must be authenticated",
384
+ });
385
+ }
386
+ // Exactly one of announcementId or commentId must be provided
387
+ if (!input.announcementId && !input.commentId) {
388
+ throw new TRPCError({
389
+ code: "BAD_REQUEST",
390
+ message: "Either announcementId or commentId must be provided",
391
+ });
392
+ }
393
+ if (input.announcementId && input.commentId) {
394
+ throw new TRPCError({
395
+ code: "BAD_REQUEST",
396
+ message: "Cannot react to both announcement and comment at the same time",
397
+ });
398
+ }
399
+ const userId = ctx.user.id;
400
+ if (input.announcementId) {
401
+ const announcement = await findAnnouncementByIdAndClass(input.announcementId, input.classId);
402
+ if (!announcement) {
403
+ throw new TRPCError({
404
+ code: "NOT_FOUND",
405
+ message: "Announcement not found",
406
+ });
407
+ }
408
+ // Upsert reaction: update if exists, create if not
409
+ const reaction = await prisma.reaction.upsert({
410
+ where: {
411
+ userId_announcementId: {
412
+ userId,
413
+ announcementId: input.announcementId,
414
+ },
415
+ },
416
+ update: {
417
+ type: input.type,
418
+ },
419
+ create: {
420
+ type: input.type,
421
+ userId,
422
+ announcementId: input.announcementId,
423
+ },
424
+ include: {
425
+ user: {
426
+ select: {
427
+ id: true,
428
+ username: true,
429
+ profile: {
430
+ select: {
431
+ displayName: true,
432
+ profilePicture: true,
433
+ profilePictureThumbnail: true,
434
+ },
435
+ },
436
+ },
437
+ },
438
+ },
439
+ });
440
+ return { reaction };
441
+ }
442
+ else if (input.commentId) {
443
+ const comment = await findCommentWithAnnouncement(input.commentId);
444
+ if (!comment) {
445
+ throw new TRPCError({
446
+ code: "NOT_FOUND",
447
+ message: "Comment not found",
448
+ });
449
+ }
450
+ if (comment.announcement.classId !== input.classId) {
451
+ throw new TRPCError({
452
+ code: "FORBIDDEN",
453
+ message: "Comment does not belong to this class",
454
+ });
455
+ }
456
+ const reaction = await upsertReaction({
457
+ userId,
458
+ commentId: input.commentId,
459
+ type: input.type,
460
+ });
461
+ return { reaction };
462
+ }
463
+ throw new TRPCError({
464
+ code: "INTERNAL_SERVER_ERROR",
465
+ message: "Unexpected error",
466
+ });
467
+ }),
468
+ removeReaction: protectedProcedure
469
+ .input(z.object({
470
+ announcementId: z.string().optional(),
471
+ commentId: z.string().optional(),
472
+ }))
473
+ .mutation(async ({ ctx, input }) => {
474
+ if (!ctx.user) {
475
+ throw new TRPCError({
476
+ code: "UNAUTHORIZED",
477
+ message: "User must be authenticated",
478
+ });
479
+ }
480
+ // Exactly one of announcementId or commentId must be provided
481
+ if (!input.announcementId && !input.commentId) {
482
+ throw new TRPCError({
483
+ code: "BAD_REQUEST",
484
+ message: "Either announcementId or commentId must be provided",
485
+ });
486
+ }
487
+ const userId = ctx.user.id;
488
+ if (input.announcementId) {
489
+ const reaction = await prisma.reaction.findUnique({
490
+ where: {
491
+ userId_announcementId: {
492
+ userId,
493
+ announcementId: input.announcementId,
494
+ },
495
+ },
496
+ });
497
+ if (!reaction) {
498
+ throw new TRPCError({
499
+ code: "NOT_FOUND",
500
+ message: "Reaction not found",
501
+ });
502
+ }
503
+ await prisma.reaction.delete({
504
+ where: { id: reaction.id },
505
+ });
506
+ return { success: true };
507
+ }
508
+ else if (input.commentId) {
509
+ const reaction = await findReactionByUserAndComment(userId, input.commentId);
510
+ if (!reaction) {
511
+ throw new TRPCError({
512
+ code: "NOT_FOUND",
513
+ message: "Reaction not found",
514
+ });
515
+ }
516
+ await deleteReactionById(reaction.id);
517
+ return { success: true };
518
+ }
519
+ throw new TRPCError({
520
+ code: "INTERNAL_SERVER_ERROR",
521
+ message: "Unexpected error",
522
+ });
523
+ }),
524
+ getReactions: protectedClassMemberProcedure
525
+ .input(z.object({
526
+ announcementId: z.string().optional(),
527
+ commentId: z.string().optional(),
528
+ classId: z.string(),
529
+ }))
530
+ .query(async ({ ctx, input }) => {
531
+ if (!ctx.user) {
532
+ throw new TRPCError({
533
+ code: "UNAUTHORIZED",
534
+ message: "User must be authenticated",
535
+ });
536
+ }
537
+ // Exactly one of announcementId or commentId must be provided
538
+ if (!input.announcementId && !input.commentId) {
539
+ throw new TRPCError({
540
+ code: "BAD_REQUEST",
541
+ message: "Either announcementId or commentId must be provided",
542
+ });
543
+ }
544
+ const userId = ctx.user.id;
545
+ if (input.announcementId) {
546
+ const announcement = await findAnnouncementByIdAndClass(input.announcementId, input.classId);
547
+ if (!announcement) {
548
+ throw new TRPCError({
549
+ code: "NOT_FOUND",
550
+ message: "Announcement not found",
551
+ });
552
+ }
553
+ // Get reaction counts by type
554
+ const reactionCounts = await prisma.reaction.groupBy({
555
+ by: ['type'],
556
+ where: { announcementId: input.announcementId },
557
+ _count: { type: true },
558
+ });
559
+ // Get current user's reaction
560
+ const userReaction = await prisma.reaction.findUnique({
561
+ where: {
562
+ userId_announcementId: {
563
+ userId,
564
+ announcementId: input.announcementId,
565
+ },
566
+ },
567
+ });
568
+ // Format counts
569
+ const counts = {
570
+ THUMBSUP: 0,
571
+ CELEBRATE: 0,
572
+ CARE: 0,
573
+ HEART: 0,
574
+ IDEA: 0,
575
+ HAPPY: 0,
576
+ };
577
+ reactionCounts.forEach((item) => {
578
+ counts[item.type] = item._count.type;
579
+ });
580
+ return {
581
+ counts,
582
+ userReaction: userReaction?.type || null,
583
+ total: reactionCounts.reduce((sum, item) => sum + item._count.type, 0),
584
+ };
585
+ }
586
+ else if (input.commentId) {
587
+ const comment = await findCommentWithAnnouncement(input.commentId);
588
+ if (!comment) {
589
+ throw new TRPCError({
590
+ code: "NOT_FOUND",
591
+ message: "Comment not found",
592
+ });
593
+ }
594
+ if (comment.announcement.classId !== input.classId) {
595
+ throw new TRPCError({
596
+ code: "FORBIDDEN",
597
+ message: "Comment does not belong to this class",
598
+ });
599
+ }
600
+ return getCommentReactions(userId, input.commentId);
601
+ }
602
+ throw new TRPCError({
603
+ code: "INTERNAL_SERVER_ERROR",
604
+ message: "Unexpected error",
605
+ });
606
+ }),
119
607
  });
608
+ //# sourceMappingURL=announcement.js.map
609
+ //# debugId=a4b6352a-4a02-5817-b542-3f6ab5651e28