@studious-lms/server 1.1.26 → 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 (486) 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 +15 -5
  23. package/dist/lib/fileUpload.js.map +1 -0
  24. package/dist/lib/googleCloudStorage.d.ts +6 -0
  25. package/dist/lib/googleCloudStorage.d.ts.map +1 -1
  26. package/dist/lib/googleCloudStorage.js +26 -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 +2 -2
  35. package/dist/lib/notificationHandler.d.ts.map +1 -1
  36. package/dist/lib/notificationHandler.js +4 -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 +6438 -3910
  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 +543 -77
  163. package/dist/routers/announcement.js.map +1 -0
  164. package/dist/routers/assignment.d.ts +419 -357
  165. package/dist/routers/assignment.d.ts.map +1 -1
  166. package/dist/routers/assignment.js +100 -1689
  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 -298
  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 -885
  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 +23 -8
  221. package/dist/routers/section.d.ts.map +1 -1
  222. package/dist/routers/section.js +23 -273
  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 +309 -288
  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 +3 -0
  350. package/dist/utils/logger.d.ts.map +1 -1
  351. package/dist/utils/logger.js +8 -1
  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 +13 -6
  370. package/src/lib/googleCloudStorage.ts +23 -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 +616 -79
  403. package/src/routers/assignment.ts +148 -1827
  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 -344
  411. package/src/routers/folder.ts +107 -836
  412. package/src/routers/labChat.ts +29 -969
  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 -322
  418. package/src/routers/user.ts +49 -226
  419. package/src/routers/worksheet.ts +252 -0
  420. package/src/seedDatabase.ts +328 -289
  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 +4 -1
  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 -59
  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/src/lib/notificationHandler.ts +0 -36
  486. package/tests/auth.test.ts +0 -25
@@ -1,143 +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
- import { sendNotifications } from "../lib/notificationHandler.js";
6
- import { logger } from "../utils/logger.js";
7
- const AnnouncementSelect = {
8
- id: true,
9
- teacher: {
10
- select: {
11
- id: true,
12
- username: true,
13
- },
14
- },
15
- remarks: true,
16
- createdAt: true,
17
- };
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
+ });
18
29
  export const announcementRouter = createTRPCRouter({
19
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
20
37
  .input(z.object({
21
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(),
22
42
  }))
23
- .query(async ({ ctx, input }) => {
24
- 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({
25
80
  where: {
26
- classId: input.classId,
81
+ id: classId,
82
+ teachers: {
83
+ some: {
84
+ id: ctx.user.id,
85
+ },
86
+ },
27
87
  },
28
- select: AnnouncementSelect,
29
- orderBy: {
30
- 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
+ },
31
130
  },
32
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);
33
139
  return {
34
- announcements,
140
+ success: true,
141
+ message: uploadSuccess ? "Upload confirmed successfully" : "Upload failed",
35
142
  };
36
143
  }),
37
- create: protectedTeacherProcedure
144
+ // Comment endpoints
145
+ addComment: protectedClassMemberProcedure
38
146
  .input(z.object({
147
+ announcementId: z.string(),
39
148
  classId: z.string(),
40
- remarks: z.string(),
149
+ content: z.string().min(1, "Comment cannot be empty"),
150
+ parentCommentId: z.string().optional(),
41
151
  }))
42
152
  .mutation(async ({ ctx, input }) => {
43
- const classId = input.classId;
44
- const remarks = input.remarks;
45
- const classData = await prisma.class.findUnique({
46
- where: { id: classId },
47
- include: {
48
- students: {
49
- select: { id: true }
50
- }
51
- }
52
- });
53
- if (!classData) {
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) {
54
161
  throw new TRPCError({
55
162
  code: "NOT_FOUND",
56
- message: "Class not found",
163
+ message: "Announcement not found",
57
164
  });
58
165
  }
59
- const announcement = await prisma.announcement.create({
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({
60
182
  data: {
61
- remarks: remarks,
62
- teacher: {
63
- connect: {
64
- id: ctx.user?.id,
65
- },
183
+ content: input.content,
184
+ author: {
185
+ connect: { id: ctx.user.id },
186
+ },
187
+ announcement: {
188
+ connect: { id: input.announcementId },
66
189
  },
67
- class: {
68
- connect: {
69
- id: 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
+ },
70
208
  },
71
209
  },
72
210
  },
73
- select: AnnouncementSelect,
74
211
  });
75
- sendNotifications(classData.students.map(student => student.id), {
76
- title: `🔔 Announcement for ${classData.name}`,
77
- content: remarks
78
- }).catch(error => {
79
- logger.error('Failed to send announcement notifications:');
80
- });
81
- return {
82
- announcement,
83
- };
212
+ return { comment };
84
213
  }),
85
- update: protectedProcedure
214
+ updateComment: protectedProcedure
86
215
  .input(z.object({
87
216
  id: z.string(),
88
- data: z.object({
89
- content: z.string(),
90
- }),
217
+ content: z.string().min(1, "Comment cannot be empty"),
91
218
  }))
92
219
  .mutation(async ({ ctx, input }) => {
93
- 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({
94
227
  where: { id: input.id },
95
- include: {
96
- class: {
97
- include: {
98
- teachers: true,
99
- },
100
- },
101
- },
102
228
  });
103
- if (!announcement) {
229
+ if (!comment) {
104
230
  throw new TRPCError({
105
231
  code: "NOT_FOUND",
106
- 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",
107
240
  });
108
241
  }
109
- const updatedAnnouncement = await prisma.announcement.update({
242
+ const updatedComment = await prisma.comment.update({
110
243
  where: { id: input.id },
111
244
  data: {
112
- 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
+ },
113
261
  },
114
262
  });
115
- return { announcement: updatedAnnouncement };
263
+ return { comment: updatedComment };
116
264
  }),
117
- delete: protectedProcedure
265
+ deleteComment: protectedProcedure
118
266
  .input(z.object({
119
267
  id: z.string(),
120
268
  }))
121
269
  .mutation(async ({ ctx, input }) => {
122
- 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({
123
277
  where: { id: input.id },
124
278
  include: {
125
- class: {
279
+ announcement: {
126
280
  include: {
127
- teachers: true,
281
+ class: {
282
+ include: {
283
+ teachers: true,
284
+ },
285
+ },
128
286
  },
129
287
  },
130
288
  },
131
289
  });
132
- if (!announcement) {
290
+ if (!comment) {
133
291
  throw new TRPCError({
134
292
  code: "NOT_FOUND",
135
- message: "Announcement not found",
293
+ message: "Comment not found",
136
294
  });
137
295
  }
138
- await prisma.announcement.delete({
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",
304
+ });
305
+ }
306
+ await prisma.comment.delete({
139
307
  where: { id: input.id },
140
308
  });
141
309
  return { success: true };
142
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
+ }),
143
607
  });
608
+ //# sourceMappingURL=announcement.js.map
609
+ //# debugId=a4b6352a-4a02-5817-b542-3f6ab5651e28