@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.
- package/.coderabbit.yaml +9 -0
- package/.env.example +53 -0
- package/.env.test.example +37 -0
- package/README.md +34 -7
- package/dist/exportType.d.ts.map +1 -1
- package/dist/exportType.js +4 -0
- package/dist/exportType.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +212 -51
- package/dist/index.js.map +1 -0
- package/dist/instrument.d.ts +2 -0
- package/dist/instrument.d.ts.map +1 -0
- package/dist/instrument.js +18 -0
- package/dist/instrument.js.map +1 -0
- package/dist/lib/config/env.d.ts +190 -0
- package/dist/lib/config/env.d.ts.map +1 -0
- package/dist/lib/config/env.js +121 -0
- package/dist/lib/config/env.js.map +1 -0
- package/dist/lib/fileUpload.d.ts +2 -2
- package/dist/lib/fileUpload.d.ts.map +1 -1
- package/dist/lib/fileUpload.js +15 -5
- package/dist/lib/fileUpload.js.map +1 -0
- package/dist/lib/googleCloudStorage.d.ts +6 -0
- package/dist/lib/googleCloudStorage.d.ts.map +1 -1
- package/dist/lib/googleCloudStorage.js +26 -6
- package/dist/lib/googleCloudStorage.js.map +1 -0
- package/dist/lib/jsonConversion.d.ts.map +1 -1
- package/dist/lib/jsonConversion.js +16 -14
- package/dist/lib/jsonConversion.js.map +1 -0
- package/dist/lib/jsonStyles.d.ts.map +1 -1
- package/dist/lib/jsonStyles.js +4 -0
- package/dist/lib/jsonStyles.js.map +1 -0
- package/dist/lib/notificationHandler.d.ts +2 -2
- package/dist/lib/notificationHandler.d.ts.map +1 -1
- package/dist/lib/notificationHandler.js +4 -0
- package/dist/lib/notificationHandler.js.map +1 -0
- package/dist/lib/prisma.d.ts +2 -2
- package/dist/lib/prisma.d.ts.map +1 -1
- package/dist/lib/prisma.js +24 -1
- package/dist/lib/prisma.js.map +1 -0
- package/dist/lib/pusher.d.ts +4 -1
- package/dist/lib/pusher.d.ts.map +1 -1
- package/dist/lib/pusher.js +14 -6
- package/dist/lib/pusher.js.map +1 -0
- package/dist/lib/redis.d.ts +5 -0
- package/dist/lib/redis.d.ts.map +1 -0
- package/dist/lib/redis.js +53 -0
- package/dist/lib/redis.js.map +1 -0
- package/dist/lib/thumbnailGenerator.d.ts +0 -21
- package/dist/lib/thumbnailGenerator.d.ts.map +1 -1
- package/dist/lib/thumbnailGenerator.js +159 -158
- package/dist/lib/thumbnailGenerator.js.map +1 -0
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +41 -93
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/logging.d.ts.map +1 -1
- package/dist/middleware/logging.js +4 -0
- package/dist/middleware/logging.js.map +1 -0
- package/dist/middleware/security.d.ts +5 -0
- package/dist/middleware/security.d.ts.map +1 -0
- package/dist/middleware/security.js +77 -0
- package/dist/middleware/security.js.map +1 -0
- package/dist/models/agenda.d.ts +97 -0
- package/dist/models/agenda.d.ts.map +1 -0
- package/dist/models/agenda.js +40 -0
- package/dist/models/agenda.js.map +1 -0
- package/dist/models/announcement.d.ts +223 -0
- package/dist/models/announcement.d.ts.map +1 -0
- package/dist/models/announcement.js +120 -0
- package/dist/models/announcement.js.map +1 -0
- package/dist/models/assignment.d.ts +1292 -0
- package/dist/models/assignment.d.ts.map +1 -0
- package/dist/models/assignment.js +309 -0
- package/dist/models/assignment.js.map +1 -0
- package/dist/models/attendance.d.ts +180 -0
- package/dist/models/attendance.d.ts.map +1 -0
- package/dist/models/attendance.js +188 -0
- package/dist/models/attendance.js.map +1 -0
- package/dist/models/auth.d.ts +153 -0
- package/dist/models/auth.d.ts.map +1 -0
- package/dist/models/auth.js +217 -0
- package/dist/models/auth.js.map +1 -0
- package/dist/models/class.d.ts +439 -0
- package/dist/models/class.d.ts.map +1 -0
- package/dist/models/class.js +546 -0
- package/dist/models/class.js.map +1 -0
- package/dist/models/comment.d.ts +171 -0
- package/dist/models/comment.d.ts.map +1 -0
- package/dist/models/comment.js +138 -0
- package/dist/models/comment.js.map +1 -0
- package/dist/models/conversation.d.ts +164 -0
- package/dist/models/conversation.d.ts.map +1 -0
- package/dist/models/conversation.js +175 -0
- package/dist/models/conversation.js.map +1 -0
- package/dist/models/event.d.ts +295 -0
- package/dist/models/event.d.ts.map +1 -0
- package/dist/models/event.js +145 -0
- package/dist/models/event.js.map +1 -0
- package/dist/models/file.d.ts +536 -0
- package/dist/models/file.d.ts.map +1 -0
- package/dist/models/file.js +126 -0
- package/dist/models/file.js.map +1 -0
- package/dist/models/folder.d.ts +295 -0
- package/dist/models/folder.d.ts.map +1 -0
- package/dist/models/folder.js +202 -0
- package/dist/models/folder.js.map +1 -0
- package/dist/models/labChat.d.ts +243 -0
- package/dist/models/labChat.d.ts.map +1 -0
- package/dist/models/labChat.js +204 -0
- package/dist/models/labChat.js.map +1 -0
- package/dist/models/marketing.d.ts +72 -0
- package/dist/models/marketing.d.ts.map +1 -0
- package/dist/models/marketing.js +26 -0
- package/dist/models/marketing.js.map +1 -0
- package/dist/models/message.d.ts +100 -0
- package/dist/models/message.d.ts.map +1 -0
- package/dist/models/message.js +131 -0
- package/dist/models/message.js.map +1 -0
- package/dist/models/newtonChat.d.ts +72 -0
- package/dist/models/newtonChat.d.ts.map +1 -0
- package/dist/models/newtonChat.js +61 -0
- package/dist/models/newtonChat.js.map +1 -0
- package/dist/models/notification.d.ts +65 -0
- package/dist/models/notification.d.ts.map +1 -0
- package/dist/models/notification.js +46 -0
- package/dist/models/notification.js.map +1 -0
- package/dist/models/section.d.ts +102 -0
- package/dist/models/section.d.ts.map +1 -0
- package/dist/models/section.js +83 -0
- package/dist/models/section.js.map +1 -0
- package/dist/models/user.d.ts +39 -0
- package/dist/models/user.d.ts.map +1 -0
- package/dist/models/user.js +38 -0
- package/dist/models/user.js.map +1 -0
- package/dist/models/worksheet.d.ts +460 -0
- package/dist/models/worksheet.d.ts.map +1 -0
- package/dist/models/worksheet.js +200 -0
- package/dist/models/worksheet.js.map +1 -0
- package/dist/pipelines/aiLabChat.d.ts +21 -0
- package/dist/pipelines/aiLabChat.d.ts.map +1 -0
- package/dist/pipelines/aiLabChat.js +460 -0
- package/dist/pipelines/aiLabChat.js.map +1 -0
- package/dist/pipelines/aiNewtonChat.d.ts +30 -0
- package/dist/pipelines/aiNewtonChat.d.ts.map +1 -0
- package/dist/pipelines/aiNewtonChat.js +289 -0
- package/dist/pipelines/aiNewtonChat.js.map +1 -0
- package/dist/pipelines/gradeWorksheet.d.ts +30 -0
- package/dist/pipelines/gradeWorksheet.d.ts.map +1 -0
- package/dist/pipelines/gradeWorksheet.js +252 -0
- package/dist/pipelines/gradeWorksheet.js.map +1 -0
- package/dist/routers/_app.d.ts +6438 -3910
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/_app.js +10 -0
- package/dist/routers/_app.js.map +1 -0
- package/dist/routers/agenda.d.ts +58 -6
- package/dist/routers/agenda.d.ts.map +1 -1
- package/dist/routers/agenda.js +6 -58
- package/dist/routers/agenda.js.map +1 -0
- package/dist/routers/announcement.d.ts +325 -6
- package/dist/routers/announcement.d.ts.map +1 -1
- package/dist/routers/announcement.js +543 -77
- package/dist/routers/announcement.js.map +1 -0
- package/dist/routers/assignment.d.ts +419 -357
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/assignment.js +100 -1689
- package/dist/routers/assignment.js.map +1 -0
- package/dist/routers/attendance.d.ts +20 -9
- package/dist/routers/attendance.d.ts.map +1 -1
- package/dist/routers/attendance.js +10 -263
- package/dist/routers/attendance.js.map +1 -0
- package/dist/routers/auth.d.ts +21 -1
- package/dist/routers/auth.d.ts.map +1 -1
- package/dist/routers/auth.js +37 -241
- package/dist/routers/auth.js.map +1 -0
- package/dist/routers/class.d.ts +198 -68
- package/dist/routers/class.d.ts.map +1 -1
- package/dist/routers/class.js +88 -909
- package/dist/routers/class.js.map +1 -0
- package/dist/routers/comment.d.ts +153 -0
- package/dist/routers/comment.d.ts.map +1 -0
- package/dist/routers/comment.js +58 -0
- package/dist/routers/comment.js.map +1 -0
- package/dist/routers/conversation.d.ts +73 -3
- package/dist/routers/conversation.d.ts.map +1 -1
- package/dist/routers/conversation.js +23 -265
- package/dist/routers/conversation.js.map +1 -0
- package/dist/routers/event.d.ts +46 -37
- package/dist/routers/event.d.ts.map +1 -1
- package/dist/routers/event.js +15 -431
- package/dist/routers/event.js.map +1 -0
- package/dist/routers/file.d.ts +4 -2
- package/dist/routers/file.d.ts.map +1 -1
- package/dist/routers/file.js +11 -298
- package/dist/routers/file.js.map +1 -0
- package/dist/routers/folder.d.ts +21 -14
- package/dist/routers/folder.d.ts.map +1 -1
- package/dist/routers/folder.js +36 -743
- package/dist/routers/folder.js.map +1 -0
- package/dist/routers/labChat.d.ts +12 -9
- package/dist/routers/labChat.d.ts.map +1 -1
- package/dist/routers/labChat.js +21 -885
- package/dist/routers/labChat.js.map +1 -0
- package/dist/routers/marketing.d.ts +2 -2
- package/dist/routers/marketing.d.ts.map +1 -1
- package/dist/routers/marketing.js +9 -54
- package/dist/routers/marketing.js.map +1 -0
- package/dist/routers/message.d.ts +2 -1
- package/dist/routers/message.d.ts.map +1 -1
- package/dist/routers/message.js +29 -519
- package/dist/routers/message.js.map +1 -0
- package/dist/routers/newtonChat.d.ts +55 -0
- package/dist/routers/newtonChat.d.ts.map +1 -0
- package/dist/routers/newtonChat.js +22 -0
- package/dist/routers/newtonChat.js.map +1 -0
- package/dist/routers/notifications.d.ts +8 -8
- package/dist/routers/notifications.d.ts.map +1 -1
- package/dist/routers/notifications.js +20 -81
- package/dist/routers/notifications.js.map +1 -0
- package/dist/routers/section.d.ts +23 -8
- package/dist/routers/section.d.ts.map +1 -1
- package/dist/routers/section.js +23 -273
- package/dist/routers/section.js.map +1 -0
- package/dist/routers/user.d.ts +1 -1
- package/dist/routers/user.d.ts.map +1 -1
- package/dist/routers/user.js +34 -204
- package/dist/routers/user.js.map +1 -0
- package/dist/routers/worksheet.d.ts +362 -0
- package/dist/routers/worksheet.d.ts.map +1 -0
- package/dist/routers/worksheet.js +153 -0
- package/dist/routers/worksheet.js.map +1 -0
- package/dist/seedDatabase.d.ts +2 -3
- package/dist/seedDatabase.d.ts.map +1 -1
- package/dist/seedDatabase.js +309 -288
- package/dist/seedDatabase.js.map +1 -0
- package/dist/server/pipelines/aiLabChat.d.ts +21 -0
- package/dist/server/pipelines/aiLabChat.d.ts.map +1 -0
- package/dist/server/pipelines/aiLabChat.js +456 -0
- package/dist/server/pipelines/aiLabChat.js.map +1 -0
- package/dist/server/pipelines/aiNewtonChat.d.ts +30 -0
- package/dist/server/pipelines/aiNewtonChat.d.ts.map +1 -0
- package/dist/server/pipelines/aiNewtonChat.js +285 -0
- package/dist/server/pipelines/aiNewtonChat.js.map +1 -0
- package/dist/server/pipelines/gradeWorksheet.d.ts +30 -0
- package/dist/server/pipelines/gradeWorksheet.d.ts.map +1 -0
- package/dist/server/pipelines/gradeWorksheet.js +248 -0
- package/dist/server/pipelines/gradeWorksheet.js.map +1 -0
- package/dist/services/agenda.d.ts +100 -0
- package/dist/services/agenda.d.ts.map +1 -0
- package/dist/services/agenda.js +21 -0
- package/dist/services/agenda.js.map +1 -0
- package/dist/services/announcement.d.ts +135 -0
- package/dist/services/announcement.d.ts.map +1 -0
- package/dist/services/announcement.js +223 -0
- package/dist/services/announcement.js.map +1 -0
- package/dist/services/assignment.d.ts +1462 -0
- package/dist/services/assignment.d.ts.map +1 -0
- package/dist/services/assignment.js +898 -0
- package/dist/services/assignment.js.map +1 -0
- package/dist/services/attendance.d.ts +93 -0
- package/dist/services/attendance.d.ts.map +1 -0
- package/dist/services/attendance.js +61 -0
- package/dist/services/attendance.js.map +1 -0
- package/dist/services/auth.d.ts +68 -0
- package/dist/services/auth.d.ts.map +1 -0
- package/dist/services/auth.js +218 -0
- package/dist/services/auth.js.map +1 -0
- package/dist/services/class.d.ts +621 -0
- package/dist/services/class.d.ts.map +1 -0
- package/dist/services/class.js +474 -0
- package/dist/services/class.js.map +1 -0
- package/dist/services/comment.d.ts +100 -0
- package/dist/services/comment.d.ts.map +1 -0
- package/dist/services/comment.js +83 -0
- package/dist/services/comment.js.map +1 -0
- package/dist/services/conversation.d.ts +159 -0
- package/dist/services/conversation.d.ts.map +1 -0
- package/dist/services/conversation.js +138 -0
- package/dist/services/conversation.js.map +1 -0
- package/dist/services/event.d.ts +216 -0
- package/dist/services/event.d.ts.map +1 -0
- package/dist/services/event.js +168 -0
- package/dist/services/event.js.map +1 -0
- package/dist/services/file.d.ts +74 -0
- package/dist/services/file.d.ts.map +1 -0
- package/dist/services/file.js +133 -0
- package/dist/services/file.js.map +1 -0
- package/dist/services/folder.d.ts +239 -0
- package/dist/services/folder.d.ts.map +1 -0
- package/dist/services/folder.js +248 -0
- package/dist/services/folder.js.map +1 -0
- package/dist/services/labChat.d.ts +165 -0
- package/dist/services/labChat.d.ts.map +1 -0
- package/dist/services/labChat.js +289 -0
- package/dist/services/labChat.js.map +1 -0
- package/dist/services/marketing.d.ts +50 -0
- package/dist/services/marketing.d.ts.map +1 -0
- package/dist/services/marketing.js +32 -0
- package/dist/services/marketing.js.map +1 -0
- package/dist/services/message.d.ts +95 -0
- package/dist/services/message.d.ts.map +1 -0
- package/dist/services/message.js +350 -0
- package/dist/services/message.js.map +1 -0
- package/dist/services/newtonChat.d.ts +22 -0
- package/dist/services/newtonChat.d.ts.map +1 -0
- package/dist/services/newtonChat.js +174 -0
- package/dist/services/newtonChat.js.map +1 -0
- package/dist/services/notification.d.ts +65 -0
- package/dist/services/notification.d.ts.map +1 -0
- package/dist/services/notification.js +33 -0
- package/dist/services/notification.js.map +1 -0
- package/dist/services/section.d.ts +53 -0
- package/dist/services/section.d.ts.map +1 -0
- package/dist/services/section.js +199 -0
- package/dist/services/section.js.map +1 -0
- package/dist/services/user.d.ts +48 -0
- package/dist/services/user.d.ts.map +1 -0
- package/dist/services/user.js +141 -0
- package/dist/services/user.js.map +1 -0
- package/dist/services/worksheet.d.ts +239 -0
- package/dist/services/worksheet.d.ts.map +1 -0
- package/dist/services/worksheet.js +235 -0
- package/dist/services/worksheet.js.map +1 -0
- package/dist/socket/handlers.d.ts.map +1 -1
- package/dist/socket/handlers.js +4 -0
- package/dist/socket/handlers.js.map +1 -0
- package/dist/trpc.d.ts.map +1 -1
- package/dist/trpc.js +4 -0
- package/dist/trpc.js.map +1 -0
- package/dist/types/trpc.d.ts.map +1 -1
- package/dist/types/trpc.js +4 -0
- package/dist/types/trpc.js.map +1 -0
- package/dist/utils/aiUser.d.ts +1 -3
- package/dist/utils/aiUser.d.ts.map +1 -1
- package/dist/utils/aiUser.js +8 -3
- package/dist/utils/aiUser.js.map +1 -0
- package/dist/utils/email.d.ts +12 -1
- package/dist/utils/email.d.ts.map +1 -1
- package/dist/utils/email.js +26 -4
- package/dist/utils/email.js.map +1 -0
- package/dist/utils/generateInviteCode.d.ts +1 -2
- package/dist/utils/generateInviteCode.d.ts.map +1 -1
- package/dist/utils/generateInviteCode.js +5 -2
- package/dist/utils/generateInviteCode.js.map +1 -0
- package/dist/utils/inference.d.ts +8 -0
- package/dist/utils/inference.d.ts.map +1 -1
- package/dist/utils/inference.js +78 -10
- package/dist/utils/inference.js.map +1 -0
- package/dist/utils/logger.d.ts +3 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +8 -1
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/prismaErrorHandler.d.ts.map +1 -1
- package/dist/utils/prismaErrorHandler.js +7 -0
- package/dist/utils/prismaErrorHandler.js.map +1 -0
- package/dist/utils/prismaWrapper.d.ts +1 -0
- package/dist/utils/prismaWrapper.d.ts.map +1 -1
- package/dist/utils/prismaWrapper.js +8 -0
- package/dist/utils/prismaWrapper.js.map +1 -0
- package/docker-compose.yml +19 -0
- package/package.json +21 -4
- package/prisma/migrations/20251109122857_annuoncements_comments/migration.sql +30 -0
- package/prisma/migrations/20251109135555_reactions_announcements_comments/migration.sql +35 -0
- package/prisma/schema.prisma +180 -12
- package/scripts/test-pre-push.ts +14 -0
- package/src/index.ts +247 -52
- package/src/instrument.ts +15 -0
- package/src/lib/config/env.ts +132 -0
- package/src/lib/fileUpload.ts +13 -6
- package/src/lib/googleCloudStorage.ts +23 -6
- package/src/lib/jsonConversion.ts +12 -14
- package/src/lib/prisma.ts +23 -2
- package/src/lib/pusher.ts +11 -6
- package/src/lib/redis.ts +56 -0
- package/src/lib/thumbnailGenerator.ts +170 -168
- package/src/middleware/auth.ts +86 -137
- package/src/middleware/security.ts +80 -0
- package/src/models/agenda.ts +46 -0
- package/src/models/announcement.ts +134 -0
- package/src/models/assignment.ts +322 -0
- package/src/models/attendance.ts +208 -0
- package/src/models/auth.ts +247 -0
- package/src/models/class.ts +598 -0
- package/src/models/comment.ts +152 -0
- package/src/models/conversation.ts +200 -0
- package/src/models/event.ts +177 -0
- package/src/models/file.ts +129 -0
- package/src/models/folder.ts +225 -0
- package/src/models/labChat.ts +213 -0
- package/src/models/marketing.ts +45 -0
- package/src/models/message.ts +153 -0
- package/src/models/newtonChat.ts +70 -0
- package/src/models/notification.ts +54 -0
- package/src/models/section.ts +98 -0
- package/src/models/user.ts +47 -0
- package/src/models/worksheet.ts +294 -0
- package/src/pipelines/aiLabChat.ts +511 -0
- package/src/pipelines/aiNewtonChat.ts +347 -0
- package/src/pipelines/gradeWorksheet.ts +286 -0
- package/src/routers/_app.ts +6 -0
- package/src/routers/agenda.ts +3 -61
- package/src/routers/announcement.ts +616 -79
- package/src/routers/assignment.ts +148 -1827
- package/src/routers/attendance.ts +16 -277
- package/src/routers/auth.ts +79 -313
- package/src/routers/class.ts +265 -1038
- package/src/routers/comment.ts +76 -0
- package/src/routers/conversation.ts +53 -284
- package/src/routers/event.ts +50 -481
- package/src/routers/file.ts +45 -344
- package/src/routers/folder.ts +107 -836
- package/src/routers/labChat.ts +29 -969
- package/src/routers/marketing.ts +35 -77
- package/src/routers/message.ts +45 -571
- package/src/routers/newtonChat.ts +36 -0
- package/src/routers/notifications.ts +32 -82
- package/src/routers/section.ts +58 -322
- package/src/routers/user.ts +49 -226
- package/src/routers/worksheet.ts +252 -0
- package/src/seedDatabase.ts +328 -289
- package/src/services/agenda.ts +21 -0
- package/src/services/announcement.ts +290 -0
- package/src/services/assignment.ts +1198 -0
- package/src/services/attendance.ts +85 -0
- package/src/services/auth.ts +277 -0
- package/src/services/class.ts +622 -0
- package/src/services/comment.ts +106 -0
- package/src/services/conversation.ts +213 -0
- package/src/services/event.ts +231 -0
- package/src/services/file.ts +167 -0
- package/src/services/folder.ts +316 -0
- package/src/services/labChat.ts +352 -0
- package/src/services/marketing.ts +57 -0
- package/src/services/message.ts +461 -0
- package/src/services/newtonChat.ts +222 -0
- package/src/services/notification.ts +50 -0
- package/src/services/section.ts +283 -0
- package/src/services/user.ts +172 -0
- package/src/services/worksheet.ts +358 -0
- package/src/trpc.ts +4 -0
- package/src/utils/aiUser.ts +4 -3
- package/src/utils/email.ts +33 -4
- package/src/utils/generateInviteCode.ts +1 -3
- package/src/utils/inference.ts +89 -10
- package/src/utils/logger.ts +4 -1
- package/src/utils/prismaErrorHandler.ts +3 -0
- package/src/utils/prismaWrapper.ts +4 -0
- package/tests/globalSetup.ts +62 -0
- package/tests/helpers.ts +22 -0
- package/tests/middleware/security.test.ts +42 -0
- package/tests/routers/agenda.test.ts +138 -0
- package/tests/routers/announcement.test.ts +490 -0
- package/tests/routers/assignment.test.ts +837 -0
- package/tests/routers/attendance.test.ts +160 -0
- package/tests/routers/auth.test.ts +171 -0
- package/tests/{class.test.ts → routers/class.test.ts} +163 -92
- package/tests/routers/comment.test.ts +126 -0
- package/tests/routers/conversation.test.ts +145 -0
- package/tests/routers/event.test.ts +289 -0
- package/tests/routers/folder.test.ts +178 -0
- package/tests/routers/labChat.test.ts +115 -0
- package/tests/routers/marketing.test.ts +59 -0
- package/tests/routers/message.test.ts +123 -0
- package/tests/routers/notification.test.ts +69 -0
- package/tests/routers/section.test.ts +208 -0
- package/tests/server/rateLimit.test.ts +73 -0
- package/tests/setup.ts +39 -59
- package/tests/user.test.ts +136 -0
- package/tests/utils/aiUser.test.ts +22 -0
- package/tests/utils/generateInviteCode.test.ts +24 -0
- package/tests/utils/logger.test.ts +74 -0
- package/tests/utils/prismaErrorHandler.test.ts +101 -0
- package/tests/utils/prismaWrapper.test.ts +82 -0
- package/tests/worksheet.test.ts +181 -0
- package/tsconfig.json +9 -2
- package/vitest.config.ts +30 -1
- package/vitest.unit.config.ts +21 -0
- package/API_SPECIFICATION.md +0 -1597
- package/BASE64_REMOVAL_SUMMARY.md +0 -164
- package/CHAT_API_SPEC.md +0 -579
- package/LAB_CHAT_API_SPEC.md +0 -518
- package/dist/routers/school.d.ts +0 -208
- package/dist/routers/school.d.ts.map +0 -1
- package/dist/routers/school.js +0 -481
- package/src/lib/notificationHandler.ts +0 -36
- package/tests/auth.test.ts +0 -25
package/src/middleware/auth.ts
CHANGED
|
@@ -1,45 +1,33 @@
|
|
|
1
|
-
import { TRPCError } from
|
|
2
|
-
import
|
|
3
|
-
import type { MiddlewareContext } from
|
|
1
|
+
import { TRPCError } from "@trpc/server";
|
|
2
|
+
import * as Sentry from "@sentry/node";
|
|
3
|
+
import type { MiddlewareContext } from "../types/trpc.js";
|
|
4
|
+
import {
|
|
5
|
+
findUserBySessionToken,
|
|
6
|
+
findTeacherClassesByUserId,
|
|
7
|
+
findClassWithMember,
|
|
8
|
+
findClassWithTeacher,
|
|
9
|
+
} from "../models/auth.js";
|
|
4
10
|
|
|
5
11
|
export const createAuthMiddleware = (t: any) => {
|
|
6
|
-
|
|
7
|
-
// Auth middleware
|
|
8
12
|
const isAuthed = t.middleware(async ({ next, ctx }: MiddlewareContext) => {
|
|
9
|
-
const
|
|
10
|
-
// Get user from request headers
|
|
11
|
-
const userHeader = ctx.req.headers['x-user'];
|
|
13
|
+
const userHeader = ctx.req.headers["x-user"];
|
|
12
14
|
|
|
13
15
|
if (!userHeader) {
|
|
14
16
|
throw new TRPCError({
|
|
15
|
-
code:
|
|
16
|
-
message:
|
|
17
|
+
code: "UNAUTHORIZED",
|
|
18
|
+
message: "Not authenticated - no token found",
|
|
17
19
|
});
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
try {
|
|
21
|
-
const token =
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const user = await prisma.user.findFirst({
|
|
25
|
-
where: {
|
|
26
|
-
sessions: {
|
|
27
|
-
some: {
|
|
28
|
-
id: token
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
select: {
|
|
33
|
-
id: true,
|
|
34
|
-
username: true,
|
|
35
|
-
// institutionId: true,
|
|
36
|
-
}
|
|
37
|
-
});
|
|
23
|
+
const token =
|
|
24
|
+
typeof userHeader === "string" ? userHeader : userHeader[0];
|
|
25
|
+
const user = await findUserBySessionToken(token);
|
|
38
26
|
|
|
39
27
|
if (!user) {
|
|
40
28
|
throw new TRPCError({
|
|
41
|
-
code:
|
|
42
|
-
message:
|
|
29
|
+
code: "UNAUTHORIZED",
|
|
30
|
+
message: "Invalid or expired session",
|
|
43
31
|
});
|
|
44
32
|
}
|
|
45
33
|
|
|
@@ -50,135 +38,96 @@ export const createAuthMiddleware = (t: any) => {
|
|
|
50
38
|
},
|
|
51
39
|
});
|
|
52
40
|
} catch (error) {
|
|
53
|
-
|
|
41
|
+
if (error instanceof TRPCError) {
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
Sentry.captureException(error);
|
|
45
|
+
console.error(error);
|
|
54
46
|
throw new TRPCError({
|
|
55
|
-
code:
|
|
56
|
-
message:
|
|
47
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
48
|
+
message: "Internal server error",
|
|
57
49
|
});
|
|
58
50
|
}
|
|
59
51
|
});
|
|
60
52
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Get all classes where user is a teacher
|
|
71
|
-
const teacherClasses = await prisma.class.findMany({
|
|
72
|
-
where: {
|
|
73
|
-
teachers: {
|
|
74
|
-
some: {
|
|
75
|
-
id: ctx.user.id
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
select: {
|
|
80
|
-
id: true
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
return next({
|
|
85
|
-
ctx: {
|
|
86
|
-
...ctx,
|
|
87
|
-
isTeacher: teacherClasses.length > 0,
|
|
88
|
-
teacherClassIds: teacherClasses.map((c: { id: string }) => c.id)
|
|
53
|
+
const addComputedFlags = t.middleware(
|
|
54
|
+
async ({ next, ctx }: MiddlewareContext) => {
|
|
55
|
+
if (!ctx.user) {
|
|
56
|
+
throw new TRPCError({
|
|
57
|
+
code: "UNAUTHORIZED",
|
|
58
|
+
message: "Not authenticated",
|
|
59
|
+
});
|
|
89
60
|
}
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
61
|
|
|
93
|
-
|
|
94
|
-
const isMemberInClass = t.middleware(async ({ next, ctx, input }: MiddlewareContext) => {
|
|
95
|
-
if (!ctx.user) {
|
|
96
|
-
throw new TRPCError({
|
|
97
|
-
code: 'UNAUTHORIZED',
|
|
98
|
-
message: 'Not authenticated',
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const classId = (input as { classId: string })?.classId;
|
|
62
|
+
const teacherClasses = await findTeacherClassesByUserId(ctx.user.id);
|
|
103
63
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
64
|
+
return next({
|
|
65
|
+
ctx: {
|
|
66
|
+
...ctx,
|
|
67
|
+
isTeacher: teacherClasses.length > 0,
|
|
68
|
+
teacherClassIds: teacherClasses.map((c) => c.id),
|
|
69
|
+
},
|
|
108
70
|
});
|
|
109
71
|
}
|
|
72
|
+
);
|
|
110
73
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
id: ctx.user.id
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
teachers: {
|
|
124
|
-
some: {
|
|
125
|
-
id: ctx.user.id
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
]
|
|
74
|
+
const isMemberInClass = t.middleware(
|
|
75
|
+
async ({ next, ctx, input }: MiddlewareContext) => {
|
|
76
|
+
if (!ctx.user) {
|
|
77
|
+
throw new TRPCError({
|
|
78
|
+
code: "UNAUTHORIZED",
|
|
79
|
+
message: "Not authenticated",
|
|
80
|
+
});
|
|
130
81
|
}
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
if (!isMember) {
|
|
134
|
-
throw new TRPCError({
|
|
135
|
-
code: 'FORBIDDEN',
|
|
136
|
-
message: 'Not a member in this class',
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
82
|
|
|
140
|
-
|
|
141
|
-
|
|
83
|
+
const classId = (input as { classId: string })?.classId;
|
|
84
|
+
if (!classId) {
|
|
85
|
+
throw new TRPCError({
|
|
86
|
+
code: "BAD_REQUEST",
|
|
87
|
+
message: "classId is required",
|
|
88
|
+
});
|
|
89
|
+
}
|
|
142
90
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
}
|
|
91
|
+
const isMember = await findClassWithMember(classId, ctx.user.id);
|
|
92
|
+
if (!isMember) {
|
|
93
|
+
throw new TRPCError({
|
|
94
|
+
code: "FORBIDDEN",
|
|
95
|
+
message: "Not a member in this class",
|
|
96
|
+
});
|
|
97
|
+
}
|
|
151
98
|
|
|
152
|
-
|
|
153
|
-
if (!classId) {
|
|
154
|
-
throw new TRPCError({
|
|
155
|
-
code: 'BAD_REQUEST',
|
|
156
|
-
message: 'classId is required',
|
|
157
|
-
});
|
|
99
|
+
return next();
|
|
158
100
|
}
|
|
101
|
+
);
|
|
159
102
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
103
|
+
const isTeacherInClass = t.middleware(
|
|
104
|
+
async ({ next, ctx, input }: MiddlewareContext) => {
|
|
105
|
+
if (!ctx.user) {
|
|
106
|
+
throw new TRPCError({
|
|
107
|
+
code: "UNAUTHORIZED",
|
|
108
|
+
message: "Not authenticated",
|
|
109
|
+
});
|
|
168
110
|
}
|
|
169
|
-
});
|
|
170
111
|
|
|
112
|
+
const classId = (input as { classId: string })?.classId;
|
|
113
|
+
if (!classId) {
|
|
114
|
+
throw new TRPCError({
|
|
115
|
+
code: "BAD_REQUEST",
|
|
116
|
+
message: "classId is required",
|
|
117
|
+
});
|
|
118
|
+
}
|
|
171
119
|
|
|
120
|
+
const isTeacher = await findClassWithTeacher(classId, ctx.user.id);
|
|
121
|
+
if (!isTeacher) {
|
|
122
|
+
throw new TRPCError({
|
|
123
|
+
code: "FORBIDDEN",
|
|
124
|
+
message: "Not a teacher in this class",
|
|
125
|
+
});
|
|
126
|
+
}
|
|
172
127
|
|
|
173
|
-
|
|
174
|
-
throw new TRPCError({
|
|
175
|
-
code: 'FORBIDDEN',
|
|
176
|
-
message: 'Not a teacher in this class',
|
|
177
|
-
});
|
|
128
|
+
return next();
|
|
178
129
|
}
|
|
179
|
-
|
|
180
|
-
return next();
|
|
181
|
-
});
|
|
130
|
+
);
|
|
182
131
|
|
|
183
132
|
return {
|
|
184
133
|
isAuthed,
|
|
@@ -186,4 +135,4 @@ export const createAuthMiddleware = (t: any) => {
|
|
|
186
135
|
isMemberInClass,
|
|
187
136
|
isTeacherInClass,
|
|
188
137
|
};
|
|
189
|
-
};
|
|
138
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import helmet from 'helmet';
|
|
2
|
+
import rateLimit from 'express-rate-limit';
|
|
3
|
+
import type { Request, Response } from 'express';
|
|
4
|
+
|
|
5
|
+
const isDevelopment = process.env.NODE_ENV === 'development';
|
|
6
|
+
|
|
7
|
+
// Custom handler for rate limit errors that returns JSON
|
|
8
|
+
// This format can be intercepted on the frontend with:
|
|
9
|
+
// error.data?.code === 'TOO_MANY_REQUESTS' || error.data?.httpStatus === 429
|
|
10
|
+
const rateLimitHandler = (req: Request, res: Response) => {
|
|
11
|
+
// Return JSON structure that can be intercepted on frontend with:
|
|
12
|
+
// error.data?.code === 'TOO_MANY_REQUESTS' || error.data?.httpStatus === 429
|
|
13
|
+
// When tRPC wraps this, the response body becomes error.data, so we put code/httpStatus at top level
|
|
14
|
+
res.status(429).json({
|
|
15
|
+
code: 'TOO_MANY_REQUESTS',
|
|
16
|
+
message: 'Too many requests, please try again later.',
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// General API rate limiter - applies to all routes
|
|
21
|
+
export const generalLimiter = rateLimit({
|
|
22
|
+
windowMs: 10 * 60, // 10 minutes
|
|
23
|
+
max: 100, // Limit each IP to 100 requests per windowMs
|
|
24
|
+
message: 'Too many requests from this IP, please try again later.',
|
|
25
|
+
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
|
|
26
|
+
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
|
|
27
|
+
handler: rateLimitHandler,
|
|
28
|
+
skip: (req) => {
|
|
29
|
+
// Skip rate limiting for health checks
|
|
30
|
+
return req.path === '/health' || req.method === 'OPTIONS';
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Stricter rate limiter for authentication endpoints
|
|
35
|
+
export const authLimiter = rateLimit({
|
|
36
|
+
windowMs: 5 * 60 * 1000, // 5 minutes
|
|
37
|
+
max: 5, // Limit each IP to 5 login attempts per windowMs
|
|
38
|
+
message: 'Too many authentication attempts, please try again later.',
|
|
39
|
+
standardHeaders: true,
|
|
40
|
+
legacyHeaders: false,
|
|
41
|
+
skipSuccessfulRequests: true, // Don't count successful requests
|
|
42
|
+
handler: rateLimitHandler,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// File upload rate limiter
|
|
46
|
+
export const uploadLimiter = rateLimit({
|
|
47
|
+
windowMs: 30 * 60 * 1000, // 30 minutes
|
|
48
|
+
max: 50, // Limit each IP to 50 uploads per hour
|
|
49
|
+
message: 'Too many file uploads, please try again later.',
|
|
50
|
+
standardHeaders: true,
|
|
51
|
+
legacyHeaders: false,
|
|
52
|
+
handler: rateLimitHandler,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Helmet configuration
|
|
56
|
+
export const helmetConfig = helmet({
|
|
57
|
+
contentSecurityPolicy: {
|
|
58
|
+
directives: {
|
|
59
|
+
defaultSrc: ["'self'"],
|
|
60
|
+
styleSrc: ["'self'", "'unsafe-inline'"], // Allow inline styles for tRPC panel
|
|
61
|
+
// Allow inline scripts only in development (for tRPC panel)
|
|
62
|
+
// In production, keep strict CSP without unsafe-inline
|
|
63
|
+
scriptSrc: isDevelopment
|
|
64
|
+
? ["'self'", "'unsafe-inline'"]
|
|
65
|
+
: ["'self'"],
|
|
66
|
+
imgSrc: ["'self'", "data:", "https:"], // Allow images from any HTTPS source
|
|
67
|
+
connectSrc: ["'self'", "https://*.sentry.io"], // Allow Sentry connections
|
|
68
|
+
fontSrc: ["'self'", "data:"],
|
|
69
|
+
objectSrc: ["'none'"],
|
|
70
|
+
mediaSrc: ["'self'"],
|
|
71
|
+
frameSrc: ["'none'"],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
crossOriginEmbedderPolicy: false, // Disable if you need to embed resources
|
|
75
|
+
hsts: {
|
|
76
|
+
maxAge: 31536000, // 1 year
|
|
77
|
+
includeSubDomains: true,
|
|
78
|
+
preload: true,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agenda model – personal and class events for a date range.
|
|
3
|
+
*/
|
|
4
|
+
import { prisma } from "../lib/prisma.js";
|
|
5
|
+
|
|
6
|
+
/** @returns Personal events (no class) for user in date range. */
|
|
7
|
+
export function findPersonalEvents(
|
|
8
|
+
userId: string,
|
|
9
|
+
rangeStart: Date,
|
|
10
|
+
rangeEnd: Date,
|
|
11
|
+
) {
|
|
12
|
+
return prisma.event.findMany({
|
|
13
|
+
where: {
|
|
14
|
+
userId,
|
|
15
|
+
startTime: { gte: rangeStart, lte: rangeEnd },
|
|
16
|
+
class: { is: null },
|
|
17
|
+
},
|
|
18
|
+
include: {
|
|
19
|
+
assignmentsAttached: true,
|
|
20
|
+
class: true,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** @returns Class events for user's classes in date range. */
|
|
26
|
+
export function findClassEvents(
|
|
27
|
+
userId: string,
|
|
28
|
+
rangeStart: Date,
|
|
29
|
+
rangeEnd: Date,
|
|
30
|
+
) {
|
|
31
|
+
return prisma.event.findMany({
|
|
32
|
+
where: {
|
|
33
|
+
class: {
|
|
34
|
+
OR: [
|
|
35
|
+
{ teachers: { some: { id: userId } } },
|
|
36
|
+
{ students: { some: { id: userId } } },
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
startTime: { gte: rangeStart, lte: rangeEnd },
|
|
40
|
+
},
|
|
41
|
+
include: {
|
|
42
|
+
assignmentsAttached: true,
|
|
43
|
+
class: true,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Announcement model – announcements, attachments, comments.
|
|
3
|
+
*/
|
|
4
|
+
import { prisma } from "../lib/prisma.js";
|
|
5
|
+
|
|
6
|
+
export const announcementSelect = {
|
|
7
|
+
id: true,
|
|
8
|
+
teacher: {
|
|
9
|
+
select: {
|
|
10
|
+
id: true,
|
|
11
|
+
username: true,
|
|
12
|
+
profile: {
|
|
13
|
+
select: {
|
|
14
|
+
displayName: true,
|
|
15
|
+
profilePicture: true,
|
|
16
|
+
profilePictureThumbnail: true,
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
remarks: true,
|
|
22
|
+
createdAt: true,
|
|
23
|
+
modifiedAt: true,
|
|
24
|
+
attachments: {
|
|
25
|
+
select: {
|
|
26
|
+
id: true,
|
|
27
|
+
name: true,
|
|
28
|
+
type: true,
|
|
29
|
+
size: true,
|
|
30
|
+
path: true,
|
|
31
|
+
uploadedAt: true,
|
|
32
|
+
thumbnailId: true,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** @returns Announcements for a class. */
|
|
38
|
+
export function findAnnouncementsByClassId(classId: string) {
|
|
39
|
+
return prisma.announcement.findMany({
|
|
40
|
+
where: { classId },
|
|
41
|
+
select: {
|
|
42
|
+
...announcementSelect,
|
|
43
|
+
_count: {
|
|
44
|
+
select: { comments: true },
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
orderBy: { createdAt: "desc" },
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** @returns Announcement by ID and class. */
|
|
52
|
+
export function findAnnouncementByIdAndClass(id: string, classId: string) {
|
|
53
|
+
return prisma.announcement.findFirst({
|
|
54
|
+
where: { id, classId },
|
|
55
|
+
select: announcementSelect,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** @returns Announcement by ID with class and teachers. */
|
|
60
|
+
export function findAnnouncementWithClass(id: string) {
|
|
61
|
+
return prisma.announcement.findUnique({
|
|
62
|
+
where: { id },
|
|
63
|
+
include: {
|
|
64
|
+
class: {
|
|
65
|
+
include: { teachers: true },
|
|
66
|
+
},
|
|
67
|
+
attachments: {
|
|
68
|
+
select: {
|
|
69
|
+
id: true,
|
|
70
|
+
name: true,
|
|
71
|
+
type: true,
|
|
72
|
+
path: true,
|
|
73
|
+
size: true,
|
|
74
|
+
uploadStatus: true,
|
|
75
|
+
thumbnail: { select: { path: true } },
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Create an announcement. */
|
|
83
|
+
export function createAnnouncement(data: {
|
|
84
|
+
remarks: string;
|
|
85
|
+
teacherId: string;
|
|
86
|
+
classId: string;
|
|
87
|
+
}) {
|
|
88
|
+
return prisma.announcement.create({
|
|
89
|
+
data: {
|
|
90
|
+
remarks: data.remarks,
|
|
91
|
+
teacher: { connect: { id: data.teacherId } },
|
|
92
|
+
class: { connect: { id: data.classId } },
|
|
93
|
+
},
|
|
94
|
+
select: announcementSelect,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Update announcement attachments. */
|
|
99
|
+
export function updateAnnouncementAttachments(
|
|
100
|
+
id: string,
|
|
101
|
+
data: {
|
|
102
|
+
connect?: { id: string }[];
|
|
103
|
+
deleteMany?: { id: { in: string[] } };
|
|
104
|
+
}
|
|
105
|
+
) {
|
|
106
|
+
return prisma.announcement.update({
|
|
107
|
+
where: { id },
|
|
108
|
+
data: {
|
|
109
|
+
...(data.connect?.length && {
|
|
110
|
+
attachments: { connect: data.connect },
|
|
111
|
+
}),
|
|
112
|
+
...(data.deleteMany && {
|
|
113
|
+
attachments: { deleteMany: data.deleteMany },
|
|
114
|
+
}),
|
|
115
|
+
},
|
|
116
|
+
select: announcementSelect,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Update announcement remarks. */
|
|
121
|
+
export function updateAnnouncement(id: string, data: { remarks?: string }) {
|
|
122
|
+
return prisma.announcement.update({
|
|
123
|
+
where: { id },
|
|
124
|
+
data,
|
|
125
|
+
select: announcementSelect,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Delete an announcement. */
|
|
130
|
+
export function deleteAnnouncement(id: string) {
|
|
131
|
+
return prisma.announcement.delete({
|
|
132
|
+
where: { id },
|
|
133
|
+
});
|
|
134
|
+
}
|