@studious-lms/server 1.2.53 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (479) hide show
  1. package/.coderabbit.yaml +9 -0
  2. package/.env.example +9 -1
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +102 -8
  5. package/dist/index.js.map +1 -1
  6. package/dist/lib/config/env.d.ts +21 -0
  7. package/dist/lib/config/env.d.ts.map +1 -1
  8. package/dist/lib/config/env.js +8 -2
  9. package/dist/lib/config/env.js.map +1 -1
  10. package/dist/lib/fileUpload.d.ts.map +1 -1
  11. package/dist/lib/fileUpload.js +2 -2
  12. package/dist/lib/fileUpload.js.map +1 -1
  13. package/dist/lib/googleCloudStorage.d.ts +6 -0
  14. package/dist/lib/googleCloudStorage.d.ts.map +1 -1
  15. package/dist/lib/googleCloudStorage.js +19 -2
  16. package/dist/lib/googleCloudStorage.js.map +1 -1
  17. package/dist/lib/pusher.d.ts +4 -1
  18. package/dist/lib/pusher.d.ts.map +1 -1
  19. package/dist/lib/pusher.js +6 -3
  20. package/dist/lib/pusher.js.map +1 -1
  21. package/dist/lib/redis.d.ts +5 -0
  22. package/dist/lib/redis.d.ts.map +1 -0
  23. package/dist/lib/redis.js +53 -0
  24. package/dist/lib/redis.js.map +1 -0
  25. package/dist/lib/thumbnailGenerator.d.ts +0 -21
  26. package/dist/lib/thumbnailGenerator.d.ts.map +1 -1
  27. package/dist/lib/thumbnailGenerator.js +157 -160
  28. package/dist/lib/thumbnailGenerator.js.map +1 -1
  29. package/dist/middleware/auth.d.ts.map +1 -1
  30. package/dist/middleware/auth.js +33 -95
  31. package/dist/middleware/auth.js.map +1 -1
  32. package/dist/models/agenda.d.ts +97 -0
  33. package/dist/models/agenda.d.ts.map +1 -0
  34. package/dist/models/agenda.js +40 -0
  35. package/dist/models/agenda.js.map +1 -0
  36. package/dist/models/announcement.d.ts +223 -0
  37. package/dist/models/announcement.d.ts.map +1 -0
  38. package/dist/models/announcement.js +120 -0
  39. package/dist/models/announcement.js.map +1 -0
  40. package/dist/models/assignment.d.ts +1292 -0
  41. package/dist/models/assignment.d.ts.map +1 -0
  42. package/dist/models/assignment.js +309 -0
  43. package/dist/models/assignment.js.map +1 -0
  44. package/dist/models/attendance.d.ts +180 -0
  45. package/dist/models/attendance.d.ts.map +1 -0
  46. package/dist/models/attendance.js +188 -0
  47. package/dist/models/attendance.js.map +1 -0
  48. package/dist/models/auth.d.ts +153 -0
  49. package/dist/models/auth.d.ts.map +1 -0
  50. package/dist/models/auth.js +217 -0
  51. package/dist/models/auth.js.map +1 -0
  52. package/dist/models/class.d.ts +461 -0
  53. package/dist/models/class.d.ts.map +1 -0
  54. package/dist/models/class.js +645 -0
  55. package/dist/models/class.js.map +1 -0
  56. package/dist/models/comment.d.ts +171 -0
  57. package/dist/models/comment.d.ts.map +1 -0
  58. package/dist/models/comment.js +138 -0
  59. package/dist/models/comment.js.map +1 -0
  60. package/dist/models/conversation.d.ts +164 -0
  61. package/dist/models/conversation.d.ts.map +1 -0
  62. package/dist/models/conversation.js +175 -0
  63. package/dist/models/conversation.js.map +1 -0
  64. package/dist/models/event.d.ts +295 -0
  65. package/dist/models/event.d.ts.map +1 -0
  66. package/dist/models/event.js +145 -0
  67. package/dist/models/event.js.map +1 -0
  68. package/dist/models/file.d.ts +536 -0
  69. package/dist/models/file.d.ts.map +1 -0
  70. package/dist/models/file.js +126 -0
  71. package/dist/models/file.js.map +1 -0
  72. package/dist/models/folder.d.ts +295 -0
  73. package/dist/models/folder.d.ts.map +1 -0
  74. package/dist/models/folder.js +202 -0
  75. package/dist/models/folder.js.map +1 -0
  76. package/dist/models/labChat.d.ts +243 -0
  77. package/dist/models/labChat.d.ts.map +1 -0
  78. package/dist/models/labChat.js +204 -0
  79. package/dist/models/labChat.js.map +1 -0
  80. package/dist/models/marketing.d.ts +72 -0
  81. package/dist/models/marketing.d.ts.map +1 -0
  82. package/dist/models/marketing.js +26 -0
  83. package/dist/models/marketing.js.map +1 -0
  84. package/dist/models/message.d.ts +100 -0
  85. package/dist/models/message.d.ts.map +1 -0
  86. package/dist/models/message.js +131 -0
  87. package/dist/models/message.js.map +1 -0
  88. package/dist/models/newtonChat.d.ts +72 -0
  89. package/dist/models/newtonChat.d.ts.map +1 -0
  90. package/dist/models/newtonChat.js +61 -0
  91. package/dist/models/newtonChat.js.map +1 -0
  92. package/dist/models/notification.d.ts +65 -0
  93. package/dist/models/notification.d.ts.map +1 -0
  94. package/dist/models/notification.js +46 -0
  95. package/dist/models/notification.js.map +1 -0
  96. package/dist/models/section.d.ts +102 -0
  97. package/dist/models/section.d.ts.map +1 -0
  98. package/dist/models/section.js +83 -0
  99. package/dist/models/section.js.map +1 -0
  100. package/dist/models/user.d.ts +39 -0
  101. package/dist/models/user.d.ts.map +1 -0
  102. package/dist/models/user.js +38 -0
  103. package/dist/models/user.js.map +1 -0
  104. package/dist/models/worksheet.d.ts +460 -0
  105. package/dist/models/worksheet.d.ts.map +1 -0
  106. package/dist/models/worksheet.js +200 -0
  107. package/dist/models/worksheet.js.map +1 -0
  108. package/dist/pipelines/aiLabChat.d.ts +76 -0
  109. package/dist/pipelines/aiLabChat.d.ts.map +1 -0
  110. package/dist/pipelines/aiLabChat.js +599 -0
  111. package/dist/pipelines/aiLabChat.js.map +1 -0
  112. package/dist/pipelines/aiNewtonChat.d.ts +30 -0
  113. package/dist/pipelines/aiNewtonChat.d.ts.map +1 -0
  114. package/dist/pipelines/aiNewtonChat.js +289 -0
  115. package/dist/pipelines/aiNewtonChat.js.map +1 -0
  116. package/dist/pipelines/gradeWorksheet.d.ts +30 -0
  117. package/dist/pipelines/gradeWorksheet.d.ts.map +1 -0
  118. package/dist/pipelines/gradeWorksheet.js +252 -0
  119. package/dist/pipelines/gradeWorksheet.js.map +1 -0
  120. package/dist/routers/_app.d.ts +1523 -1315
  121. package/dist/routers/_app.d.ts.map +1 -1
  122. package/dist/routers/agenda.d.ts +22 -22
  123. package/dist/routers/agenda.d.ts.map +1 -1
  124. package/dist/routers/agenda.js +4 -65
  125. package/dist/routers/agenda.js.map +1 -1
  126. package/dist/routers/announcement.d.ts +16 -16
  127. package/dist/routers/announcement.d.ts.map +1 -1
  128. package/dist/routers/announcement.js +37 -446
  129. package/dist/routers/announcement.js.map +1 -1
  130. package/dist/routers/assignment.d.ts +300 -378
  131. package/dist/routers/assignment.d.ts.map +1 -1
  132. package/dist/routers/assignment.js +78 -1868
  133. package/dist/routers/assignment.js.map +1 -1
  134. package/dist/routers/attendance.d.ts +19 -9
  135. package/dist/routers/attendance.d.ts.map +1 -1
  136. package/dist/routers/attendance.js +7 -264
  137. package/dist/routers/attendance.js.map +1 -1
  138. package/dist/routers/auth.d.ts +2 -2
  139. package/dist/routers/auth.d.ts.map +1 -1
  140. package/dist/routers/auth.js +29 -354
  141. package/dist/routers/auth.js.map +1 -1
  142. package/dist/routers/class.d.ts +160 -68
  143. package/dist/routers/class.d.ts.map +1 -1
  144. package/dist/routers/class.js +82 -1052
  145. package/dist/routers/class.js.map +1 -1
  146. package/dist/routers/comment.d.ts +6 -42
  147. package/dist/routers/comment.d.ts.map +1 -1
  148. package/dist/routers/comment.js +24 -244
  149. package/dist/routers/comment.js.map +1 -1
  150. package/dist/routers/conversation.d.ts +45 -7
  151. package/dist/routers/conversation.d.ts.map +1 -1
  152. package/dist/routers/conversation.js +19 -327
  153. package/dist/routers/conversation.js.map +1 -1
  154. package/dist/routers/event.d.ts +36 -36
  155. package/dist/routers/event.d.ts.map +1 -1
  156. package/dist/routers/event.js +13 -433
  157. package/dist/routers/event.js.map +1 -1
  158. package/dist/routers/file.d.ts +2 -2
  159. package/dist/routers/file.d.ts.map +1 -1
  160. package/dist/routers/file.js +9 -323
  161. package/dist/routers/file.js.map +1 -1
  162. package/dist/routers/folder.d.ts +21 -14
  163. package/dist/routers/folder.d.ts.map +1 -1
  164. package/dist/routers/folder.js +34 -745
  165. package/dist/routers/folder.js.map +1 -1
  166. package/dist/routers/labChat.d.ts +21 -11
  167. package/dist/routers/labChat.d.ts.map +1 -1
  168. package/dist/routers/labChat.js +22 -570
  169. package/dist/routers/labChat.js.map +1 -1
  170. package/dist/routers/marketing.d.ts +1 -1
  171. package/dist/routers/marketing.d.ts.map +1 -1
  172. package/dist/routers/marketing.js +7 -56
  173. package/dist/routers/marketing.js.map +1 -1
  174. package/dist/routers/message.d.ts +13 -2
  175. package/dist/routers/message.d.ts.map +1 -1
  176. package/dist/routers/message.js +32 -520
  177. package/dist/routers/message.js.map +1 -1
  178. package/dist/routers/newtonChat.d.ts +1 -1
  179. package/dist/routers/newtonChat.d.ts.map +1 -1
  180. package/dist/routers/newtonChat.js +7 -246
  181. package/dist/routers/newtonChat.js.map +1 -1
  182. package/dist/routers/notifications.d.ts +4 -4
  183. package/dist/routers/notifications.d.ts.map +1 -1
  184. package/dist/routers/notifications.js +18 -83
  185. package/dist/routers/notifications.js.map +1 -1
  186. package/dist/routers/section.d.ts +4 -4
  187. package/dist/routers/section.d.ts.map +1 -1
  188. package/dist/routers/section.js +14 -286
  189. package/dist/routers/section.js.map +1 -1
  190. package/dist/routers/user.d.ts +1 -1
  191. package/dist/routers/user.d.ts.map +1 -1
  192. package/dist/routers/user.js +32 -207
  193. package/dist/routers/user.js.map +1 -1
  194. package/dist/routers/worksheet.d.ts +68 -55
  195. package/dist/routers/worksheet.d.ts.map +1 -1
  196. package/dist/routers/worksheet.js +79 -394
  197. package/dist/routers/worksheet.js.map +1 -1
  198. package/dist/seedDatabase.d.ts +1 -1
  199. package/dist/server/pipelines/gradeWorksheet.d.ts +6 -6
  200. package/dist/server/pipelines/gradeWorksheet.d.ts.map +1 -1
  201. package/dist/server/pipelines/gradeWorksheet.js +12 -5
  202. package/dist/server/pipelines/gradeWorksheet.js.map +1 -1
  203. package/dist/services/agenda.d.ts +100 -0
  204. package/dist/services/agenda.d.ts.map +1 -0
  205. package/dist/services/agenda.js +21 -0
  206. package/dist/services/agenda.js.map +1 -0
  207. package/dist/services/announcement.d.ts +135 -0
  208. package/dist/services/announcement.d.ts.map +1 -0
  209. package/dist/services/announcement.js +223 -0
  210. package/dist/services/announcement.js.map +1 -0
  211. package/dist/services/assignment.d.ts +1462 -0
  212. package/dist/services/assignment.d.ts.map +1 -0
  213. package/dist/services/assignment.js +898 -0
  214. package/dist/services/assignment.js.map +1 -0
  215. package/dist/services/attendance.d.ts +93 -0
  216. package/dist/services/attendance.d.ts.map +1 -0
  217. package/dist/services/attendance.js +61 -0
  218. package/dist/services/attendance.js.map +1 -0
  219. package/dist/services/auth.d.ts +68 -0
  220. package/dist/services/auth.d.ts.map +1 -0
  221. package/dist/services/auth.js +218 -0
  222. package/dist/services/auth.js.map +1 -0
  223. package/dist/services/class.d.ts +643 -0
  224. package/dist/services/class.d.ts.map +1 -0
  225. package/dist/services/class.js +486 -0
  226. package/dist/services/class.js.map +1 -0
  227. package/dist/services/comment.d.ts +100 -0
  228. package/dist/services/comment.d.ts.map +1 -0
  229. package/dist/services/comment.js +83 -0
  230. package/dist/services/comment.js.map +1 -0
  231. package/dist/services/conversation.d.ts +159 -0
  232. package/dist/services/conversation.d.ts.map +1 -0
  233. package/dist/services/conversation.js +138 -0
  234. package/dist/services/conversation.js.map +1 -0
  235. package/dist/services/event.d.ts +216 -0
  236. package/dist/services/event.d.ts.map +1 -0
  237. package/dist/services/event.js +168 -0
  238. package/dist/services/event.js.map +1 -0
  239. package/dist/services/file.d.ts +74 -0
  240. package/dist/services/file.d.ts.map +1 -0
  241. package/dist/services/file.js +133 -0
  242. package/dist/services/file.js.map +1 -0
  243. package/dist/services/folder.d.ts +239 -0
  244. package/dist/services/folder.d.ts.map +1 -0
  245. package/dist/services/folder.js +248 -0
  246. package/dist/services/folder.js.map +1 -0
  247. package/dist/services/labChat.d.ts +169 -0
  248. package/dist/services/labChat.d.ts.map +1 -0
  249. package/dist/services/labChat.js +381 -0
  250. package/dist/services/labChat.js.map +1 -0
  251. package/dist/services/marketing.d.ts +50 -0
  252. package/dist/services/marketing.d.ts.map +1 -0
  253. package/dist/services/marketing.js +32 -0
  254. package/dist/services/marketing.js.map +1 -0
  255. package/dist/services/message.d.ts +103 -0
  256. package/dist/services/message.d.ts.map +1 -0
  257. package/dist/services/message.js +422 -0
  258. package/dist/services/message.js.map +1 -0
  259. package/dist/services/newtonChat.d.ts +22 -0
  260. package/dist/services/newtonChat.d.ts.map +1 -0
  261. package/dist/services/newtonChat.js +174 -0
  262. package/dist/services/newtonChat.js.map +1 -0
  263. package/dist/services/notification.d.ts +65 -0
  264. package/dist/services/notification.d.ts.map +1 -0
  265. package/dist/services/notification.js +33 -0
  266. package/dist/services/notification.js.map +1 -0
  267. package/dist/services/section.d.ts +53 -0
  268. package/dist/services/section.d.ts.map +1 -0
  269. package/dist/services/section.js +199 -0
  270. package/dist/services/section.js.map +1 -0
  271. package/dist/services/user.d.ts +48 -0
  272. package/dist/services/user.d.ts.map +1 -0
  273. package/dist/services/user.js +141 -0
  274. package/dist/services/user.js.map +1 -0
  275. package/dist/services/worksheet.d.ts +239 -0
  276. package/dist/services/worksheet.d.ts.map +1 -0
  277. package/dist/services/worksheet.js +235 -0
  278. package/dist/services/worksheet.js.map +1 -0
  279. package/dist/utils/aiUser.d.ts +1 -3
  280. package/dist/utils/aiUser.d.ts.map +1 -1
  281. package/dist/utils/aiUser.js +6 -5
  282. package/dist/utils/aiUser.js.map +1 -1
  283. package/dist/utils/email.d.ts +3 -0
  284. package/dist/utils/email.d.ts.map +1 -1
  285. package/dist/utils/email.js +7 -4
  286. package/dist/utils/email.js.map +1 -1
  287. package/dist/utils/generateInviteCode.d.ts +1 -2
  288. package/dist/utils/generateInviteCode.d.ts.map +1 -1
  289. package/dist/utils/generateInviteCode.js +3 -4
  290. package/dist/utils/generateInviteCode.js.map +1 -1
  291. package/dist/utils/inference.d.ts +3 -0
  292. package/dist/utils/inference.d.ts.map +1 -1
  293. package/dist/utils/inference.js +7 -4
  294. package/dist/utils/inference.js.map +1 -1
  295. package/dist/utils/logger.d.ts +3 -0
  296. package/dist/utils/logger.d.ts.map +1 -1
  297. package/dist/utils/logger.js +5 -2
  298. package/dist/utils/logger.js.map +1 -1
  299. package/dist/utils/prismaErrorHandler.d.ts.map +1 -1
  300. package/dist/utils/prismaErrorHandler.js +5 -2
  301. package/dist/utils/prismaErrorHandler.js.map +1 -1
  302. package/dist/utils/prismaWrapper.d.ts +1 -0
  303. package/dist/utils/prismaWrapper.d.ts.map +1 -1
  304. package/dist/utils/prismaWrapper.js +6 -2
  305. package/dist/utils/prismaWrapper.js.map +1 -1
  306. package/docker-compose.yml +5 -0
  307. package/package.json +4 -3
  308. package/prisma/schema.prisma +1 -1
  309. package/src/index.ts +119 -12
  310. package/src/lib/config/env.ts +6 -0
  311. package/src/lib/fileUpload.ts +0 -1
  312. package/src/lib/googleCloudStorage.ts +17 -0
  313. package/src/lib/pusher.ts +5 -1
  314. package/src/lib/redis.ts +56 -0
  315. package/src/lib/thumbnailGenerator.ts +170 -168
  316. package/src/middleware/auth.ts +80 -137
  317. package/src/models/agenda.ts +46 -0
  318. package/src/models/announcement.ts +134 -0
  319. package/src/models/assignment.ts +322 -0
  320. package/src/models/attendance.ts +208 -0
  321. package/src/models/auth.ts +247 -0
  322. package/src/models/class.ts +703 -0
  323. package/src/models/comment.ts +152 -0
  324. package/src/models/conversation.ts +200 -0
  325. package/src/models/event.ts +177 -0
  326. package/src/models/file.ts +129 -0
  327. package/src/models/folder.ts +225 -0
  328. package/src/models/labChat.ts +213 -0
  329. package/src/models/marketing.ts +45 -0
  330. package/src/models/message.ts +153 -0
  331. package/src/models/newtonChat.ts +70 -0
  332. package/src/models/notification.ts +54 -0
  333. package/src/models/section.ts +98 -0
  334. package/src/models/user.ts +47 -0
  335. package/src/models/worksheet.ts +294 -0
  336. package/src/pipelines/aiLabChat.ts +684 -0
  337. package/src/{server/pipelines → pipelines}/aiNewtonChat.ts +9 -5
  338. package/src/{server/pipelines → pipelines}/gradeWorksheet.ts +25 -14
  339. package/src/routers/agenda.ts +3 -66
  340. package/src/routers/announcement.ts +54 -495
  341. package/src/routers/assignment.ts +126 -2018
  342. package/src/routers/attendance.ts +15 -276
  343. package/src/routers/auth.ts +79 -442
  344. package/src/routers/class.ts +263 -1187
  345. package/src/routers/comment.ts +61 -288
  346. package/src/routers/conversation.ts +51 -360
  347. package/src/routers/event.ts +50 -481
  348. package/src/routers/file.ts +45 -368
  349. package/src/routers/folder.ts +107 -836
  350. package/src/routers/labChat.ts +35 -604
  351. package/src/routers/marketing.ts +35 -77
  352. package/src/routers/message.ts +54 -567
  353. package/src/routers/newtonChat.ts +17 -278
  354. package/src/routers/notifications.ts +32 -82
  355. package/src/routers/section.ts +46 -330
  356. package/src/routers/user.ts +49 -227
  357. package/src/routers/worksheet.ts +215 -503
  358. package/src/services/agenda.ts +21 -0
  359. package/src/services/announcement.ts +290 -0
  360. package/src/services/assignment.ts +1198 -0
  361. package/src/services/attendance.ts +85 -0
  362. package/src/services/auth.ts +277 -0
  363. package/src/services/class.ts +629 -0
  364. package/src/services/comment.ts +106 -0
  365. package/src/services/conversation.ts +213 -0
  366. package/src/services/event.ts +231 -0
  367. package/src/services/file.ts +167 -0
  368. package/src/services/folder.ts +316 -0
  369. package/src/services/labChat.ts +458 -0
  370. package/src/services/marketing.ts +57 -0
  371. package/src/services/message.ts +554 -0
  372. package/src/services/newtonChat.ts +222 -0
  373. package/src/services/notification.ts +50 -0
  374. package/src/services/section.ts +283 -0
  375. package/src/services/user.ts +172 -0
  376. package/src/services/worksheet.ts +358 -0
  377. package/src/utils/aiUser.ts +4 -3
  378. package/src/utils/email.ts +5 -3
  379. package/src/utils/generateInviteCode.ts +1 -3
  380. package/src/utils/inference.ts +5 -2
  381. package/src/utils/logger.ts +3 -1
  382. package/src/utils/prismaErrorHandler.ts +3 -0
  383. package/src/utils/prismaWrapper.ts +4 -0
  384. package/tests/globalSetup.ts +62 -0
  385. package/tests/helpers.ts +22 -0
  386. package/tests/middleware/security.test.ts +42 -0
  387. package/tests/routers/agenda.test.ts +138 -0
  388. package/tests/routers/announcement.test.ts +490 -0
  389. package/tests/routers/assignment.test.ts +837 -0
  390. package/tests/{attendance.test.ts → routers/attendance.test.ts} +6 -14
  391. package/tests/routers/auth.test.ts +171 -0
  392. package/tests/{class.test.ts → routers/class.test.ts} +131 -85
  393. package/tests/routers/comment.test.ts +126 -0
  394. package/tests/routers/conversation.test.ts +145 -0
  395. package/tests/{event.test.ts → routers/event.test.ts} +93 -32
  396. package/tests/routers/folder.test.ts +178 -0
  397. package/tests/routers/labChat.test.ts +115 -0
  398. package/tests/routers/marketing.test.ts +59 -0
  399. package/tests/routers/message.test.ts +123 -0
  400. package/tests/routers/notification.test.ts +69 -0
  401. package/tests/{section.test.ts → routers/section.test.ts} +5 -13
  402. package/tests/server/rateLimit.test.ts +73 -0
  403. package/tests/setup.ts +18 -92
  404. package/tests/user.test.ts +9 -31
  405. package/tests/utils/aiUser.test.ts +22 -0
  406. package/tests/utils/generateInviteCode.test.ts +24 -0
  407. package/tests/utils/logger.test.ts +74 -0
  408. package/tests/utils/prismaErrorHandler.test.ts +101 -0
  409. package/tests/utils/prismaWrapper.test.ts +82 -0
  410. package/tests/worksheet.test.ts +181 -0
  411. package/vitest.config.ts +6 -3
  412. package/vitest.unit.config.ts +21 -0
  413. package/TODO.md +0 -2
  414. package/coverage/base.css +0 -224
  415. package/coverage/block-navigation.js +0 -87
  416. package/coverage/clover.xml +0 -12110
  417. package/coverage/coverage-final.json +0 -44
  418. package/coverage/favicon.png +0 -0
  419. package/coverage/index.html +0 -221
  420. package/coverage/prettify.css +0 -1
  421. package/coverage/prettify.js +0 -2
  422. package/coverage/server/index.html +0 -116
  423. package/coverage/server/src/exportType.ts.html +0 -109
  424. package/coverage/server/src/index.html +0 -161
  425. package/coverage/server/src/index.ts.html +0 -1702
  426. package/coverage/server/src/instrument.ts.html +0 -130
  427. package/coverage/server/src/lib/config/env.ts.html +0 -448
  428. package/coverage/server/src/lib/config/index.html +0 -116
  429. package/coverage/server/src/lib/fileUpload.ts.html +0 -1138
  430. package/coverage/server/src/lib/googleCloudStorage.ts.html +0 -334
  431. package/coverage/server/src/lib/index.html +0 -206
  432. package/coverage/server/src/lib/jsonConversion.ts.html +0 -2323
  433. package/coverage/server/src/lib/jsonStyles.ts.html +0 -193
  434. package/coverage/server/src/lib/notificationHandler.ts.html +0 -193
  435. package/coverage/server/src/lib/pusher.ts.html +0 -121
  436. package/coverage/server/src/lib/thumbnailGenerator.ts.html +0 -592
  437. package/coverage/server/src/middleware/auth.ts.html +0 -646
  438. package/coverage/server/src/middleware/index.html +0 -146
  439. package/coverage/server/src/middleware/logging.ts.html +0 -244
  440. package/coverage/server/src/middleware/security.ts.html +0 -271
  441. package/coverage/server/src/routers/_app.ts.html +0 -232
  442. package/coverage/server/src/routers/agenda.ts.html +0 -319
  443. package/coverage/server/src/routers/announcement.ts.html +0 -3481
  444. package/coverage/server/src/routers/assignment.ts.html +0 -7633
  445. package/coverage/server/src/routers/attendance.ts.html +0 -1030
  446. package/coverage/server/src/routers/auth.ts.html +0 -1081
  447. package/coverage/server/src/routers/class.ts.html +0 -3535
  448. package/coverage/server/src/routers/comment.ts.html +0 -991
  449. package/coverage/server/src/routers/conversation.ts.html +0 -982
  450. package/coverage/server/src/routers/event.ts.html +0 -1609
  451. package/coverage/server/src/routers/file.ts.html +0 -1144
  452. package/coverage/server/src/routers/folder.ts.html +0 -2797
  453. package/coverage/server/src/routers/index.html +0 -386
  454. package/coverage/server/src/routers/labChat.ts.html +0 -3073
  455. package/coverage/server/src/routers/marketing.ts.html +0 -340
  456. package/coverage/server/src/routers/message.ts.html +0 -1912
  457. package/coverage/server/src/routers/notifications.ts.html +0 -364
  458. package/coverage/server/src/routers/section.ts.html +0 -1120
  459. package/coverage/server/src/routers/user.ts.html +0 -862
  460. package/coverage/server/src/routers/worksheet.ts.html +0 -1729
  461. package/coverage/server/src/trpc.ts.html +0 -397
  462. package/coverage/server/src/types/index.html +0 -116
  463. package/coverage/server/src/types/trpc.ts.html +0 -127
  464. package/coverage/server/src/utils/aiUser.ts.html +0 -280
  465. package/coverage/server/src/utils/email.ts.html +0 -121
  466. package/coverage/server/src/utils/generateInviteCode.ts.html +0 -106
  467. package/coverage/server/src/utils/index.html +0 -206
  468. package/coverage/server/src/utils/inference.ts.html +0 -709
  469. package/coverage/server/src/utils/logger.ts.html +0 -664
  470. package/coverage/server/src/utils/prismaErrorHandler.ts.html +0 -907
  471. package/coverage/server/src/utils/prismaWrapper.ts.html +0 -355
  472. package/coverage/server/vitest.config.ts.html +0 -196
  473. package/coverage/sort-arrow-sprite.png +0 -0
  474. package/coverage/sorter.js +0 -210
  475. package/src/lib/notificationHandler.ts +0 -36
  476. package/src/server/pipelines/aiLabChat.ts +0 -507
  477. package/tests/announcement.test.ts +0 -164
  478. package/tests/assignment.test.ts +0 -296
  479. package/tests/auth.test.ts +0 -48
@@ -1,13 +1,14 @@
1
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]="a7728c55-4567-50bb-acc9-7f592bac0ec1")}catch(e){}}();
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){}}();
3
3
  import { z } from "zod";
4
4
  import { createTRPCRouter, protectedClassMemberProcedure, protectedTeacherProcedure, protectedProcedure } from "../trpc.js";
5
5
  import { prisma } from "../lib/prisma.js";
6
6
  import { TRPCError } from "@trpc/server";
7
- import { sendNotifications } from "../lib/notificationHandler.js";
8
- import { logger } from "../utils/logger.js";
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";
9
11
  import { createDirectUploadFiles, confirmDirectUpload } from "../lib/fileUpload.js";
10
- import { deleteFile } from "../lib/googleCloudStorage.js";
11
12
  // Schema for direct file uploads (no base64 data)
12
13
  const directFileSchema = z.object({
13
14
  name: z.string(),
@@ -25,91 +26,13 @@ const confirmAnnouncementUploadSchema = z.object({
25
26
  uploadSuccess: z.boolean(),
26
27
  errorMessage: z.string().optional(),
27
28
  });
28
- const AnnouncementSelect = {
29
- id: true,
30
- teacher: {
31
- select: {
32
- id: true,
33
- username: true,
34
- profile: {
35
- select: {
36
- displayName: true,
37
- profilePicture: true,
38
- profilePictureThumbnail: true,
39
- },
40
- },
41
- },
42
- },
43
- remarks: true,
44
- createdAt: true,
45
- modifiedAt: true,
46
- attachments: {
47
- select: {
48
- id: true,
49
- name: true,
50
- type: true,
51
- size: true,
52
- path: true,
53
- uploadedAt: true,
54
- thumbnailId: true,
55
- },
56
- },
57
- };
58
29
  export const announcementRouter = createTRPCRouter({
59
30
  getAll: protectedClassMemberProcedure
60
- .input(z.object({
61
- classId: z.string(),
62
- }))
63
- .query(async ({ ctx, input }) => {
64
- const announcements = await prisma.announcement.findMany({
65
- where: {
66
- classId: input.classId,
67
- },
68
- select: {
69
- ...AnnouncementSelect,
70
- _count: {
71
- select: {
72
- comments: true,
73
- },
74
- },
75
- },
76
- orderBy: {
77
- createdAt: 'desc',
78
- },
79
- });
80
- // Transform to include comment count
81
- const announcementsWithCounts = announcements.map(announcement => ({
82
- ...announcement,
83
- commentCount: announcement._count.comments,
84
- _count: undefined,
85
- }));
86
- return {
87
- announcements: announcementsWithCounts,
88
- };
89
- }),
31
+ .input(z.object({ classId: z.string() }))
32
+ .query(({ input }) => getAllAnnouncements(input.classId)),
90
33
  get: protectedClassMemberProcedure
91
- .input(z.object({
92
- id: z.string(),
93
- classId: z.string(),
94
- }))
95
- .query(async ({ ctx, input }) => {
96
- const announcement = await prisma.announcement.findUnique({
97
- where: {
98
- id: input.id,
99
- classId: input.classId,
100
- },
101
- select: AnnouncementSelect,
102
- });
103
- if (!announcement) {
104
- throw new TRPCError({
105
- code: "NOT_FOUND",
106
- message: "Announcement not found",
107
- });
108
- }
109
- return {
110
- announcement,
111
- };
112
- }),
34
+ .input(z.object({ id: z.string(), classId: z.string() }))
35
+ .query(({ input }) => getAnnouncement(input.id, input.classId)),
113
36
  create: protectedTeacherProcedure
114
37
  .input(z.object({
115
38
  classId: z.string(),
@@ -117,83 +40,12 @@ export const announcementRouter = createTRPCRouter({
117
40
  files: z.array(directFileSchema).optional(),
118
41
  existingFileIds: z.array(z.string()).optional(),
119
42
  }))
120
- .mutation(async ({ ctx, input }) => {
121
- const { classId, remarks, files, existingFileIds } = input;
122
- if (!ctx.user) {
123
- throw new TRPCError({
124
- code: "UNAUTHORIZED",
125
- message: "User must be authenticated",
126
- });
127
- }
128
- const classData = await prisma.class.findUnique({
129
- where: { id: classId },
130
- include: {
131
- students: {
132
- select: { id: true }
133
- }
134
- }
135
- });
136
- if (!classData) {
137
- throw new TRPCError({
138
- code: "NOT_FOUND",
139
- message: "Class not found",
140
- });
141
- }
142
- const announcement = await prisma.announcement.create({
143
- data: {
144
- remarks: remarks,
145
- teacher: {
146
- connect: {
147
- id: ctx.user.id,
148
- },
149
- },
150
- class: {
151
- connect: {
152
- id: classId,
153
- },
154
- },
155
- },
156
- select: AnnouncementSelect,
157
- });
158
- // Handle file attachments
159
- // NOTE: Files are now handled via direct upload endpoints
160
- // The files field in the schema is for metadata only
161
- // Actual file uploads should use getAnnouncementUploadUrls endpoint
162
- // However, if files are provided here, we create the file records and return upload URLs
163
- let directUploadFiles = [];
164
- if (files && files.length > 0) {
165
- // Create direct upload files - this creates file records with upload URLs
166
- // Files are automatically connected to the announcement via announcementId
167
- directUploadFiles = await createDirectUploadFiles(files, ctx.user.id, undefined, undefined, undefined, announcement.id);
168
- }
169
- // Connect existing files if provided
170
- if (existingFileIds && existingFileIds.length > 0) {
171
- await prisma.announcement.update({
172
- where: { id: announcement.id },
173
- data: {
174
- attachments: {
175
- connect: existingFileIds.map(fileId => ({ id: fileId }))
176
- }
177
- }
178
- });
179
- }
180
- // Fetch announcement with attachments
181
- const announcementWithAttachments = await prisma.announcement.findUnique({
182
- where: { id: announcement.id },
183
- select: AnnouncementSelect,
184
- });
185
- sendNotifications(classData.students.map(student => student.id), {
186
- title: `🔔 Announcement for ${classData.name}`,
187
- content: remarks
188
- }).catch(error => {
189
- logger.error('Failed to send announcement notifications:', error);
190
- });
191
- return {
192
- announcement: announcementWithAttachments || announcement,
193
- // Return upload URLs if files were provided
194
- uploadFiles: directUploadFiles.length > 0 ? directUploadFiles : undefined,
195
- };
196
- }),
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
+ })),
197
49
  update: protectedTeacherProcedure
198
50
  .input(z.object({
199
51
  id: z.string(),
@@ -205,158 +57,14 @@ export const announcementRouter = createTRPCRouter({
205
57
  removedAttachments: z.array(z.string()).optional(),
206
58
  }),
207
59
  }))
208
- .mutation(async ({ ctx, input }) => {
209
- if (!ctx.user) {
210
- throw new TRPCError({
211
- code: "UNAUTHORIZED",
212
- message: "User must be authenticated",
213
- });
214
- }
215
- const announcement = await prisma.announcement.findUnique({
216
- where: { id: input.id },
217
- include: {
218
- class: {
219
- include: {
220
- teachers: true,
221
- },
222
- },
223
- attachments: {
224
- select: {
225
- id: true,
226
- name: true,
227
- type: true,
228
- path: true,
229
- size: true,
230
- uploadStatus: true,
231
- thumbnail: {
232
- select: {
233
- path: true
234
- }
235
- }
236
- },
237
- },
238
- },
239
- });
240
- if (!announcement) {
241
- throw new TRPCError({
242
- code: "NOT_FOUND",
243
- message: "Announcement not found",
244
- });
245
- }
246
- // Authorization check: user must be the creator OR a teacher in the class
247
- const userId = ctx.user.id;
248
- const isCreator = announcement.teacherId === userId;
249
- const isClassTeacher = announcement.class.teachers.some((teacher) => teacher.id === userId);
250
- if (!isCreator && !isClassTeacher) {
251
- throw new TRPCError({
252
- code: "FORBIDDEN",
253
- message: "Only the announcement creator or class teachers can update announcements",
254
- });
255
- }
256
- // Handle file attachments
257
- // NOTE: Files are now handled via direct upload endpoints
258
- let directUploadFiles = [];
259
- if (input.data.files && input.data.files.length > 0) {
260
- // Create direct upload files - this creates file records with upload URLs
261
- // Files are automatically connected to the announcement via announcementId
262
- directUploadFiles = await createDirectUploadFiles(input.data.files, userId, undefined, undefined, undefined, input.id);
263
- }
264
- // Delete removed attachments from storage before updating database
265
- if (input.data.removedAttachments && input.data.removedAttachments.length > 0) {
266
- const filesToDelete = announcement.attachments.filter((file) => input.data.removedAttachments.includes(file.id));
267
- // Delete files from storage (only if they were actually uploaded)
268
- await Promise.all(filesToDelete.map(async (file) => {
269
- try {
270
- // Only delete from GCS if the file was successfully uploaded
271
- if (file.uploadStatus === 'COMPLETED') {
272
- // Delete the main file
273
- await deleteFile(file.path);
274
- // Delete thumbnail if it exists
275
- if (file.thumbnail?.path) {
276
- await deleteFile(file.thumbnail.path);
277
- }
278
- }
279
- }
280
- catch (error) {
281
- logger.warn(`Failed to delete file ${file.path}:`, {
282
- error: error instanceof Error ? {
283
- name: error.name,
284
- message: error.message,
285
- stack: error.stack,
286
- } : error
287
- });
288
- }
289
- }));
290
- }
291
- const updatedAnnouncement = await prisma.announcement.update({
292
- where: { id: input.id },
293
- data: {
294
- ...(input.data.remarks && { remarks: input.data.remarks }),
295
- // Note: directUploadFiles are already connected via createDirectUploadFiles
296
- ...(input.data.existingFileIds && input.data.existingFileIds.length > 0 && {
297
- attachments: {
298
- connect: input.data.existingFileIds.map(fileId => ({ id: fileId }))
299
- }
300
- }),
301
- ...(input.data.removedAttachments && input.data.removedAttachments.length > 0 && {
302
- attachments: {
303
- deleteMany: {
304
- id: { in: input.data.removedAttachments }
305
- }
306
- }
307
- }),
308
- },
309
- select: AnnouncementSelect,
310
- });
311
- return {
312
- announcement: updatedAnnouncement,
313
- // Return upload URLs if new files were provided
314
- uploadFiles: directUploadFiles.length > 0 ? directUploadFiles : undefined,
315
- };
316
- }),
60
+ .mutation(({ ctx, input }) => updateAnnouncementRecord(ctx.user.id, {
61
+ id: input.id,
62
+ classId: input.classId,
63
+ data: input.data,
64
+ })),
317
65
  delete: protectedTeacherProcedure
318
- .input(z.object({
319
- id: z.string(),
320
- classId: z.string(),
321
- }))
322
- .mutation(async ({ ctx, input }) => {
323
- if (!ctx.user) {
324
- throw new TRPCError({
325
- code: "UNAUTHORIZED",
326
- message: "User must be authenticated",
327
- });
328
- }
329
- const announcement = await prisma.announcement.findUnique({
330
- where: { id: input.id },
331
- include: {
332
- class: {
333
- include: {
334
- teachers: true,
335
- },
336
- },
337
- },
338
- });
339
- if (!announcement) {
340
- throw new TRPCError({
341
- code: "NOT_FOUND",
342
- message: "Announcement not found",
343
- });
344
- }
345
- // Authorization check: user must be the creator OR a teacher in the class
346
- const userId = ctx.user.id;
347
- const isCreator = announcement.teacherId === userId;
348
- const isClassTeacher = announcement.class.teachers.some((teacher) => teacher.id === userId);
349
- if (!isCreator && !isClassTeacher) {
350
- throw new TRPCError({
351
- code: "FORBIDDEN",
352
- message: "Only the announcement creator or class teachers can delete announcements",
353
- });
354
- }
355
- await prisma.announcement.delete({
356
- where: { id: input.id },
357
- });
358
- return { success: true };
359
- }),
66
+ .input(z.object({ id: z.string(), classId: z.string() }))
67
+ .mutation(({ ctx, input }) => deleteAnnouncementRecord(ctx.user.id, input.id, input.classId)),
360
68
  getAnnouncementUploadUrls: protectedTeacherProcedure
361
69
  .input(getAnnouncementUploadUrlsSchema)
362
70
  .mutation(async ({ ctx, input }) => {
@@ -384,13 +92,7 @@ export const announcementRouter = createTRPCRouter({
384
92
  message: "Class not found or you are not a teacher",
385
93
  });
386
94
  }
387
- // Verify announcement exists and belongs to the class
388
- const announcement = await prisma.announcement.findFirst({
389
- where: {
390
- id: announcementId,
391
- classId: classId,
392
- },
393
- });
95
+ const announcement = await findAnnouncementByIdAndClass(announcementId, classId);
394
96
  if (!announcement) {
395
97
  throw new TRPCError({
396
98
  code: "NOT_FOUND",
@@ -454,13 +156,7 @@ export const announcementRouter = createTRPCRouter({
454
156
  message: "User must be authenticated",
455
157
  });
456
158
  }
457
- // Verify announcement exists and belongs to the class
458
- const announcement = await prisma.announcement.findFirst({
459
- where: {
460
- id: input.announcementId,
461
- classId: input.classId,
462
- },
463
- });
159
+ const announcement = await findAnnouncementByIdAndClass(input.announcementId, input.classId);
464
160
  if (!announcement) {
465
161
  throw new TRPCError({
466
162
  code: "NOT_FOUND",
@@ -618,13 +314,7 @@ export const announcementRouter = createTRPCRouter({
618
314
  classId: z.string(),
619
315
  }))
620
316
  .query(async ({ ctx, input }) => {
621
- // Verify announcement exists and belongs to the class
622
- const announcement = await prisma.announcement.findFirst({
623
- where: {
624
- id: input.announcementId,
625
- classId: input.classId,
626
- },
627
- });
317
+ const announcement = await findAnnouncementByIdAndClass(input.announcementId, input.classId);
628
318
  if (!announcement) {
629
319
  throw new TRPCError({
630
320
  code: "NOT_FOUND",
@@ -707,14 +397,8 @@ export const announcementRouter = createTRPCRouter({
707
397
  });
708
398
  }
709
399
  const userId = ctx.user.id;
710
- // Verify the announcement or comment exists and belongs to the class
711
400
  if (input.announcementId) {
712
- const announcement = await prisma.announcement.findFirst({
713
- where: {
714
- id: input.announcementId,
715
- classId: input.classId,
716
- },
717
- });
401
+ const announcement = await findAnnouncementByIdAndClass(input.announcementId, input.classId);
718
402
  if (!announcement) {
719
403
  throw new TRPCError({
720
404
  code: "NOT_FOUND",
@@ -756,17 +440,7 @@ export const announcementRouter = createTRPCRouter({
756
440
  return { reaction };
757
441
  }
758
442
  else if (input.commentId) {
759
- // Verify comment exists and get its announcement to check class
760
- const comment = await prisma.comment.findUnique({
761
- where: { id: input.commentId },
762
- include: {
763
- announcement: {
764
- select: {
765
- classId: true,
766
- },
767
- },
768
- },
769
- });
443
+ const comment = await findCommentWithAnnouncement(input.commentId);
770
444
  if (!comment) {
771
445
  throw new TRPCError({
772
446
  code: "NOT_FOUND",
@@ -779,37 +453,10 @@ export const announcementRouter = createTRPCRouter({
779
453
  message: "Comment does not belong to this class",
780
454
  });
781
455
  }
782
- // Upsert reaction: update if exists, create if not
783
- const reaction = await prisma.reaction.upsert({
784
- where: {
785
- userId_commentId: {
786
- userId,
787
- commentId: input.commentId,
788
- },
789
- },
790
- update: {
791
- type: input.type,
792
- },
793
- create: {
794
- type: input.type,
795
- userId,
796
- commentId: input.commentId,
797
- },
798
- include: {
799
- user: {
800
- select: {
801
- id: true,
802
- username: true,
803
- profile: {
804
- select: {
805
- displayName: true,
806
- profilePicture: true,
807
- profilePictureThumbnail: true,
808
- },
809
- },
810
- },
811
- },
812
- },
456
+ const reaction = await upsertReaction({
457
+ userId,
458
+ commentId: input.commentId,
459
+ type: input.type,
813
460
  });
814
461
  return { reaction };
815
462
  }
@@ -859,23 +506,14 @@ export const announcementRouter = createTRPCRouter({
859
506
  return { success: true };
860
507
  }
861
508
  else if (input.commentId) {
862
- const reaction = await prisma.reaction.findUnique({
863
- where: {
864
- userId_commentId: {
865
- userId,
866
- commentId: input.commentId,
867
- },
868
- },
869
- });
509
+ const reaction = await findReactionByUserAndComment(userId, input.commentId);
870
510
  if (!reaction) {
871
511
  throw new TRPCError({
872
512
  code: "NOT_FOUND",
873
513
  message: "Reaction not found",
874
514
  });
875
515
  }
876
- await prisma.reaction.delete({
877
- where: { id: reaction.id },
878
- });
516
+ await deleteReactionById(reaction.id);
879
517
  return { success: true };
880
518
  }
881
519
  throw new TRPCError({
@@ -905,13 +543,7 @@ export const announcementRouter = createTRPCRouter({
905
543
  }
906
544
  const userId = ctx.user.id;
907
545
  if (input.announcementId) {
908
- // Verify announcement exists
909
- const announcement = await prisma.announcement.findFirst({
910
- where: {
911
- id: input.announcementId,
912
- classId: input.classId,
913
- },
914
- });
546
+ const announcement = await findAnnouncementByIdAndClass(input.announcementId, input.classId);
915
547
  if (!announcement) {
916
548
  throw new TRPCError({
917
549
  code: "NOT_FOUND",
@@ -952,17 +584,7 @@ export const announcementRouter = createTRPCRouter({
952
584
  };
953
585
  }
954
586
  else if (input.commentId) {
955
- // Verify comment exists
956
- const comment = await prisma.comment.findUnique({
957
- where: { id: input.commentId },
958
- include: {
959
- announcement: {
960
- select: {
961
- classId: true,
962
- },
963
- },
964
- },
965
- });
587
+ const comment = await findCommentWithAnnouncement(input.commentId);
966
588
  if (!comment) {
967
589
  throw new TRPCError({
968
590
  code: "NOT_FOUND",
@@ -975,38 +597,7 @@ export const announcementRouter = createTRPCRouter({
975
597
  message: "Comment does not belong to this class",
976
598
  });
977
599
  }
978
- // Get reaction counts by type
979
- const reactionCounts = await prisma.reaction.groupBy({
980
- by: ['type'],
981
- where: { commentId: input.commentId },
982
- _count: { type: true },
983
- });
984
- // Get current user's reaction
985
- const userReaction = await prisma.reaction.findUnique({
986
- where: {
987
- userId_commentId: {
988
- userId,
989
- commentId: input.commentId,
990
- },
991
- },
992
- });
993
- // Format counts
994
- const counts = {
995
- THUMBSUP: 0,
996
- CELEBRATE: 0,
997
- CARE: 0,
998
- HEART: 0,
999
- IDEA: 0,
1000
- HAPPY: 0,
1001
- };
1002
- reactionCounts.forEach((item) => {
1003
- counts[item.type] = item._count.type;
1004
- });
1005
- return {
1006
- counts,
1007
- userReaction: userReaction?.type || null,
1008
- total: reactionCounts.reduce((sum, item) => sum + item._count.type, 0),
1009
- };
600
+ return getCommentReactions(userId, input.commentId);
1010
601
  }
1011
602
  throw new TRPCError({
1012
603
  code: "INTERNAL_SERVER_ERROR",
@@ -1015,4 +606,4 @@ export const announcementRouter = createTRPCRouter({
1015
606
  }),
1016
607
  });
1017
608
  //# sourceMappingURL=announcement.js.map
1018
- //# debugId=a7728c55-4567-50bb-acc9-7f592bac0ec1
609
+ //# debugId=a4b6352a-4a02-5817-b542-3f6ab5651e28