@studious-lms/server 1.2.53 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.coderabbit.yaml +9 -0
- package/.env.example +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +102 -8
- package/dist/index.js.map +1 -1
- package/dist/lib/config/env.d.ts +21 -0
- package/dist/lib/config/env.d.ts.map +1 -1
- package/dist/lib/config/env.js +8 -2
- package/dist/lib/config/env.js.map +1 -1
- package/dist/lib/fileUpload.d.ts.map +1 -1
- package/dist/lib/fileUpload.js +2 -2
- package/dist/lib/fileUpload.js.map +1 -1
- package/dist/lib/googleCloudStorage.d.ts +6 -0
- package/dist/lib/googleCloudStorage.d.ts.map +1 -1
- package/dist/lib/googleCloudStorage.js +19 -2
- package/dist/lib/googleCloudStorage.js.map +1 -1
- package/dist/lib/pusher.d.ts +4 -1
- package/dist/lib/pusher.d.ts.map +1 -1
- package/dist/lib/pusher.js +6 -3
- package/dist/lib/pusher.js.map +1 -1
- 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 +157 -160
- package/dist/lib/thumbnailGenerator.js.map +1 -1
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +33 -95
- package/dist/middleware/auth.js.map +1 -1
- 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 +1393 -1267
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/agenda.d.ts +22 -22
- package/dist/routers/agenda.d.ts.map +1 -1
- package/dist/routers/agenda.js +4 -65
- package/dist/routers/agenda.js.map +1 -1
- package/dist/routers/announcement.d.ts +16 -16
- package/dist/routers/announcement.d.ts.map +1 -1
- package/dist/routers/announcement.js +37 -446
- package/dist/routers/announcement.js.map +1 -1
- package/dist/routers/assignment.d.ts +300 -378
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/assignment.js +78 -1868
- package/dist/routers/assignment.js.map +1 -1
- package/dist/routers/attendance.d.ts +19 -9
- package/dist/routers/attendance.d.ts.map +1 -1
- package/dist/routers/attendance.js +7 -264
- package/dist/routers/attendance.js.map +1 -1
- package/dist/routers/auth.d.ts +2 -2
- package/dist/routers/auth.d.ts.map +1 -1
- package/dist/routers/auth.js +29 -354
- package/dist/routers/auth.js.map +1 -1
- package/dist/routers/class.d.ts +139 -68
- package/dist/routers/class.d.ts.map +1 -1
- package/dist/routers/class.js +82 -1052
- package/dist/routers/class.js.map +1 -1
- package/dist/routers/comment.d.ts +6 -42
- package/dist/routers/comment.d.ts.map +1 -1
- package/dist/routers/comment.js +24 -244
- package/dist/routers/comment.js.map +1 -1
- package/dist/routers/conversation.d.ts +45 -7
- package/dist/routers/conversation.d.ts.map +1 -1
- package/dist/routers/conversation.js +19 -327
- package/dist/routers/conversation.js.map +1 -1
- package/dist/routers/event.d.ts +36 -36
- package/dist/routers/event.d.ts.map +1 -1
- package/dist/routers/event.js +13 -433
- package/dist/routers/event.js.map +1 -1
- package/dist/routers/file.d.ts +2 -2
- package/dist/routers/file.d.ts.map +1 -1
- package/dist/routers/file.js +9 -323
- package/dist/routers/file.js.map +1 -1
- package/dist/routers/folder.d.ts +21 -14
- package/dist/routers/folder.d.ts.map +1 -1
- package/dist/routers/folder.js +34 -745
- package/dist/routers/folder.js.map +1 -1
- package/dist/routers/labChat.d.ts +11 -10
- package/dist/routers/labChat.d.ts.map +1 -1
- package/dist/routers/labChat.js +19 -570
- package/dist/routers/labChat.js.map +1 -1
- package/dist/routers/marketing.d.ts +1 -1
- package/dist/routers/marketing.d.ts.map +1 -1
- package/dist/routers/marketing.js +7 -56
- package/dist/routers/marketing.js.map +1 -1
- package/dist/routers/message.d.ts +2 -2
- package/dist/routers/message.d.ts.map +1 -1
- package/dist/routers/message.js +27 -522
- package/dist/routers/message.js.map +1 -1
- package/dist/routers/newtonChat.d.ts +1 -1
- package/dist/routers/newtonChat.d.ts.map +1 -1
- package/dist/routers/newtonChat.js +7 -246
- package/dist/routers/newtonChat.js.map +1 -1
- package/dist/routers/notifications.d.ts +4 -4
- package/dist/routers/notifications.d.ts.map +1 -1
- package/dist/routers/notifications.js +18 -83
- package/dist/routers/notifications.js.map +1 -1
- package/dist/routers/section.d.ts +4 -4
- package/dist/routers/section.d.ts.map +1 -1
- package/dist/routers/section.js +14 -286
- package/dist/routers/section.js.map +1 -1
- package/dist/routers/user.d.ts +1 -1
- package/dist/routers/user.d.ts.map +1 -1
- package/dist/routers/user.js +32 -207
- package/dist/routers/user.js.map +1 -1
- package/dist/routers/worksheet.d.ts +51 -38
- package/dist/routers/worksheet.d.ts.map +1 -1
- package/dist/routers/worksheet.js +79 -394
- package/dist/routers/worksheet.js.map +1 -1
- package/dist/seedDatabase.d.ts +1 -1
- package/dist/server/pipelines/gradeWorksheet.d.ts +6 -6
- package/dist/server/pipelines/gradeWorksheet.d.ts.map +1 -1
- package/dist/server/pipelines/gradeWorksheet.js +12 -5
- package/dist/server/pipelines/gradeWorksheet.js.map +1 -1
- 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/utils/aiUser.d.ts +1 -3
- package/dist/utils/aiUser.d.ts.map +1 -1
- package/dist/utils/aiUser.js +6 -5
- package/dist/utils/aiUser.js.map +1 -1
- package/dist/utils/email.d.ts +3 -0
- package/dist/utils/email.d.ts.map +1 -1
- package/dist/utils/email.js +7 -4
- package/dist/utils/email.js.map +1 -1
- package/dist/utils/generateInviteCode.d.ts +1 -2
- package/dist/utils/generateInviteCode.d.ts.map +1 -1
- package/dist/utils/generateInviteCode.js +3 -4
- package/dist/utils/generateInviteCode.js.map +1 -1
- package/dist/utils/inference.d.ts +3 -0
- package/dist/utils/inference.d.ts.map +1 -1
- package/dist/utils/inference.js +7 -4
- package/dist/utils/inference.js.map +1 -1
- package/dist/utils/logger.d.ts +3 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +5 -2
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/prismaErrorHandler.d.ts.map +1 -1
- package/dist/utils/prismaErrorHandler.js +5 -2
- package/dist/utils/prismaErrorHandler.js.map +1 -1
- package/dist/utils/prismaWrapper.d.ts +1 -0
- package/dist/utils/prismaWrapper.d.ts.map +1 -1
- package/dist/utils/prismaWrapper.js +6 -2
- package/dist/utils/prismaWrapper.js.map +1 -1
- package/docker-compose.yml +5 -0
- package/package.json +4 -3
- package/src/index.ts +119 -12
- package/src/lib/config/env.ts +6 -0
- package/src/lib/fileUpload.ts +0 -1
- package/src/lib/googleCloudStorage.ts +17 -0
- package/src/lib/pusher.ts +5 -1
- package/src/lib/redis.ts +56 -0
- package/src/lib/thumbnailGenerator.ts +170 -168
- package/src/middleware/auth.ts +80 -137
- 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/{server/pipelines → pipelines}/aiLabChat.ts +11 -7
- package/src/{server/pipelines → pipelines}/aiNewtonChat.ts +9 -5
- package/src/{server/pipelines → pipelines}/gradeWorksheet.ts +25 -14
- package/src/routers/agenda.ts +3 -66
- package/src/routers/announcement.ts +54 -495
- package/src/routers/assignment.ts +126 -2018
- package/src/routers/attendance.ts +15 -276
- package/src/routers/auth.ts +79 -442
- package/src/routers/class.ts +263 -1187
- package/src/routers/comment.ts +61 -288
- package/src/routers/conversation.ts +51 -360
- package/src/routers/event.ts +50 -481
- package/src/routers/file.ts +45 -368
- package/src/routers/folder.ts +107 -836
- package/src/routers/labChat.ts +29 -605
- package/src/routers/marketing.ts +35 -77
- package/src/routers/message.ts +45 -571
- package/src/routers/newtonChat.ts +17 -278
- package/src/routers/notifications.ts +32 -82
- package/src/routers/section.ts +46 -330
- package/src/routers/user.ts +49 -227
- package/src/routers/worksheet.ts +215 -503
- 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/utils/aiUser.ts +4 -3
- package/src/utils/email.ts +5 -3
- package/src/utils/generateInviteCode.ts +1 -3
- package/src/utils/inference.ts +5 -2
- package/src/utils/logger.ts +3 -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/{attendance.test.ts → routers/attendance.test.ts} +6 -14
- package/tests/routers/auth.test.ts +171 -0
- package/tests/{class.test.ts → routers/class.test.ts} +131 -85
- package/tests/routers/comment.test.ts +126 -0
- package/tests/routers/conversation.test.ts +145 -0
- package/tests/{event.test.ts → routers/event.test.ts} +93 -32
- 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/{section.test.ts → routers/section.test.ts} +5 -13
- package/tests/server/rateLimit.test.ts +73 -0
- package/tests/setup.ts +18 -92
- package/tests/user.test.ts +9 -31
- 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/vitest.config.ts +6 -3
- package/vitest.unit.config.ts +21 -0
- package/TODO.md +0 -2
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/clover.xml +0 -12110
- package/coverage/coverage-final.json +0 -44
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -221
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/server/index.html +0 -116
- package/coverage/server/src/exportType.ts.html +0 -109
- package/coverage/server/src/index.html +0 -161
- package/coverage/server/src/index.ts.html +0 -1702
- package/coverage/server/src/instrument.ts.html +0 -130
- package/coverage/server/src/lib/config/env.ts.html +0 -448
- package/coverage/server/src/lib/config/index.html +0 -116
- package/coverage/server/src/lib/fileUpload.ts.html +0 -1138
- package/coverage/server/src/lib/googleCloudStorage.ts.html +0 -334
- package/coverage/server/src/lib/index.html +0 -206
- package/coverage/server/src/lib/jsonConversion.ts.html +0 -2323
- package/coverage/server/src/lib/jsonStyles.ts.html +0 -193
- package/coverage/server/src/lib/notificationHandler.ts.html +0 -193
- package/coverage/server/src/lib/pusher.ts.html +0 -121
- package/coverage/server/src/lib/thumbnailGenerator.ts.html +0 -592
- package/coverage/server/src/middleware/auth.ts.html +0 -646
- package/coverage/server/src/middleware/index.html +0 -146
- package/coverage/server/src/middleware/logging.ts.html +0 -244
- package/coverage/server/src/middleware/security.ts.html +0 -271
- package/coverage/server/src/routers/_app.ts.html +0 -232
- package/coverage/server/src/routers/agenda.ts.html +0 -319
- package/coverage/server/src/routers/announcement.ts.html +0 -3481
- package/coverage/server/src/routers/assignment.ts.html +0 -7633
- package/coverage/server/src/routers/attendance.ts.html +0 -1030
- package/coverage/server/src/routers/auth.ts.html +0 -1081
- package/coverage/server/src/routers/class.ts.html +0 -3535
- package/coverage/server/src/routers/comment.ts.html +0 -991
- package/coverage/server/src/routers/conversation.ts.html +0 -982
- package/coverage/server/src/routers/event.ts.html +0 -1609
- package/coverage/server/src/routers/file.ts.html +0 -1144
- package/coverage/server/src/routers/folder.ts.html +0 -2797
- package/coverage/server/src/routers/index.html +0 -386
- package/coverage/server/src/routers/labChat.ts.html +0 -3073
- package/coverage/server/src/routers/marketing.ts.html +0 -340
- package/coverage/server/src/routers/message.ts.html +0 -1912
- package/coverage/server/src/routers/notifications.ts.html +0 -364
- package/coverage/server/src/routers/section.ts.html +0 -1120
- package/coverage/server/src/routers/user.ts.html +0 -862
- package/coverage/server/src/routers/worksheet.ts.html +0 -1729
- package/coverage/server/src/trpc.ts.html +0 -397
- package/coverage/server/src/types/index.html +0 -116
- package/coverage/server/src/types/trpc.ts.html +0 -127
- package/coverage/server/src/utils/aiUser.ts.html +0 -280
- package/coverage/server/src/utils/email.ts.html +0 -121
- package/coverage/server/src/utils/generateInviteCode.ts.html +0 -106
- package/coverage/server/src/utils/index.html +0 -206
- package/coverage/server/src/utils/inference.ts.html +0 -709
- package/coverage/server/src/utils/logger.ts.html +0 -664
- package/coverage/server/src/utils/prismaErrorHandler.ts.html +0 -907
- package/coverage/server/src/utils/prismaWrapper.ts.html +0 -355
- package/coverage/server/vitest.config.ts.html +0 -196
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -210
- package/src/lib/notificationHandler.ts +0 -36
- package/tests/announcement.test.ts +0 -164
- package/tests/assignment.test.ts +0 -296
- package/tests/auth.test.ts +0 -48
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worksheet service – CRUD for worksheets, questions, submissions, and AI grading.
|
|
3
|
+
* Integrates with gradeWorksheet pipeline for auto-grading.
|
|
4
|
+
*/
|
|
5
|
+
import { TRPCError } from "@trpc/server";
|
|
6
|
+
import { GenerationStatus, type WorksheetQuestionType } from "@prisma/client";
|
|
7
|
+
import { prisma } from "../lib/prisma.js";
|
|
8
|
+
import { cancelGradePipeline, regradeWorksheetPipeline } from "../pipelines/gradeWorksheet.js";
|
|
9
|
+
import {
|
|
10
|
+
findWorksheetById,
|
|
11
|
+
findWorksheetByIdMinimal,
|
|
12
|
+
findWorksheetsByClassId,
|
|
13
|
+
createWorksheet,
|
|
14
|
+
updateWorksheet,
|
|
15
|
+
deleteWorksheet,
|
|
16
|
+
findQuestionsByWorksheetId,
|
|
17
|
+
createWorksheetQuestion,
|
|
18
|
+
updateWorksheetQuestion,
|
|
19
|
+
deleteWorksheetQuestion,
|
|
20
|
+
findSubmissionById,
|
|
21
|
+
findOrCreateWorksheetResponse,
|
|
22
|
+
findWorksheetResponseWithResponses,
|
|
23
|
+
findWorksheetQuestionById,
|
|
24
|
+
updateStudentQuestionProgress,
|
|
25
|
+
createStudentQuestionProgress,
|
|
26
|
+
findStudentQuestionProgress,
|
|
27
|
+
updateStudentQuestionProgressForGrading,
|
|
28
|
+
createStudentQuestionProgressForGrading,
|
|
29
|
+
createComment,
|
|
30
|
+
} from "../models/worksheet.js";
|
|
31
|
+
|
|
32
|
+
type MCQOptions = { id: string; text: string; isCorrect: boolean }[];
|
|
33
|
+
|
|
34
|
+
/** Get worksheet with questions and mark schemes. */
|
|
35
|
+
export async function getWorksheet(worksheetId: string) {
|
|
36
|
+
const worksheet = await findWorksheetById(worksheetId);
|
|
37
|
+
if (!worksheet) {
|
|
38
|
+
throw new TRPCError({
|
|
39
|
+
code: "NOT_FOUND",
|
|
40
|
+
message: "Worksheet not found",
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return worksheet;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Check if worksheet exists. */
|
|
47
|
+
export async function worksheetExists(id: string) {
|
|
48
|
+
const worksheet = await findWorksheetByIdMinimal(id);
|
|
49
|
+
return !!worksheet;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function listWorksheets(classId: string) {
|
|
53
|
+
const worksheets = await findWorksheetsByClassId(classId);
|
|
54
|
+
return worksheets.map((w) => ({
|
|
55
|
+
...w,
|
|
56
|
+
questionCount: w.questions.length,
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function updateWorksheetRecord(
|
|
61
|
+
worksheetId: string,
|
|
62
|
+
data: { name?: string }
|
|
63
|
+
) {
|
|
64
|
+
return updateWorksheet(worksheetId, data);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function deleteWorksheetRecord(worksheetId: string) {
|
|
68
|
+
return deleteWorksheet(worksheetId);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function createWorksheetRecord(classId: string, name: string) {
|
|
72
|
+
return createWorksheet({ classId, name });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function addQuestionToWorksheet(
|
|
76
|
+
worksheetId: string,
|
|
77
|
+
data: {
|
|
78
|
+
question: string;
|
|
79
|
+
answer: string;
|
|
80
|
+
points?: number;
|
|
81
|
+
options?: object;
|
|
82
|
+
markScheme?: object;
|
|
83
|
+
type: WorksheetQuestionType;
|
|
84
|
+
}
|
|
85
|
+
) {
|
|
86
|
+
const worksheet = await findWorksheetByIdMinimal(worksheetId);
|
|
87
|
+
if (!worksheet) {
|
|
88
|
+
throw new TRPCError({
|
|
89
|
+
code: "NOT_FOUND",
|
|
90
|
+
message: "Worksheet not found",
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return createWorksheetQuestion({
|
|
94
|
+
worksheetId,
|
|
95
|
+
...data,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function reorderWorksheetQuestions(
|
|
100
|
+
worksheetId: string,
|
|
101
|
+
movedId: string,
|
|
102
|
+
position: "before" | "after",
|
|
103
|
+
targetId: string
|
|
104
|
+
) {
|
|
105
|
+
const worksheet = await findWorksheetByIdMinimal(worksheetId);
|
|
106
|
+
if (!worksheet) {
|
|
107
|
+
throw new TRPCError({
|
|
108
|
+
code: "NOT_FOUND",
|
|
109
|
+
message: "Worksheet not found",
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const questions = await findQuestionsByWorksheetId(worksheetId);
|
|
114
|
+
const movedIdx = questions.findIndex((q) => q.id === movedId);
|
|
115
|
+
if (movedIdx === -1) {
|
|
116
|
+
throw new TRPCError({
|
|
117
|
+
code: "NOT_FOUND",
|
|
118
|
+
message: "Moved question not found",
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
const targetIdx = questions.findIndex((q) => q.id === targetId);
|
|
122
|
+
if (targetIdx === -1) {
|
|
123
|
+
throw new TRPCError({
|
|
124
|
+
code: "NOT_FOUND",
|
|
125
|
+
message: "Target question not found",
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const withoutMoved = questions.filter((q) => q.id !== movedId);
|
|
130
|
+
const next =
|
|
131
|
+
position === "before"
|
|
132
|
+
? [
|
|
133
|
+
...withoutMoved.slice(0, targetIdx).map((item) => ({ id: item.id })),
|
|
134
|
+
{ id: movedId },
|
|
135
|
+
...withoutMoved.slice(targetIdx).map((item) => ({ id: item.id })),
|
|
136
|
+
]
|
|
137
|
+
: [
|
|
138
|
+
...withoutMoved
|
|
139
|
+
.slice(0, targetIdx + 1)
|
|
140
|
+
.map((item) => ({ id: item.id })),
|
|
141
|
+
{ id: movedId },
|
|
142
|
+
...withoutMoved
|
|
143
|
+
.slice(targetIdx + 1)
|
|
144
|
+
.map((item) => ({ id: item.id })),
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
await prisma.$transaction(
|
|
148
|
+
next.map((item, index) =>
|
|
149
|
+
prisma.worksheetQuestion.update({
|
|
150
|
+
where: { id: item.id },
|
|
151
|
+
data: { order: index },
|
|
152
|
+
})
|
|
153
|
+
)
|
|
154
|
+
);
|
|
155
|
+
return next;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export async function updateWorksheetQuestionRecord(
|
|
159
|
+
worksheetId: string,
|
|
160
|
+
questionId: string,
|
|
161
|
+
data: {
|
|
162
|
+
question?: string;
|
|
163
|
+
answer?: string;
|
|
164
|
+
points?: number;
|
|
165
|
+
options?: object;
|
|
166
|
+
markScheme?: object;
|
|
167
|
+
type?: WorksheetQuestionType;
|
|
168
|
+
}
|
|
169
|
+
) {
|
|
170
|
+
const worksheet = await findWorksheetByIdMinimal(worksheetId);
|
|
171
|
+
if (!worksheet) {
|
|
172
|
+
throw new TRPCError({
|
|
173
|
+
code: "NOT_FOUND",
|
|
174
|
+
message: "Worksheet not found",
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
return updateWorksheetQuestion(questionId, data);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function deleteWorksheetQuestionRecord(
|
|
181
|
+
worksheetId: string,
|
|
182
|
+
questionId: string
|
|
183
|
+
) {
|
|
184
|
+
const worksheet = await findWorksheetByIdMinimal(worksheetId);
|
|
185
|
+
if (!worksheet) {
|
|
186
|
+
throw new TRPCError({
|
|
187
|
+
code: "NOT_FOUND",
|
|
188
|
+
message: "Worksheet not found",
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
return deleteWorksheetQuestion(questionId);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export async function getWorksheetSubmission(
|
|
195
|
+
worksheetId: string,
|
|
196
|
+
submissionId: string
|
|
197
|
+
) {
|
|
198
|
+
const submission = await findSubmissionById(submissionId);
|
|
199
|
+
if (!submission) {
|
|
200
|
+
throw new TRPCError({
|
|
201
|
+
code: "NOT_FOUND",
|
|
202
|
+
message: "Submission not found",
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
return findOrCreateWorksheetResponse(
|
|
206
|
+
submissionId,
|
|
207
|
+
worksheetId,
|
|
208
|
+
submission.studentId
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export async function answerWorksheetQuestion(
|
|
213
|
+
worksheetResponseId: string,
|
|
214
|
+
questionId: string,
|
|
215
|
+
response: string,
|
|
216
|
+
userId: string
|
|
217
|
+
) {
|
|
218
|
+
const worksheetResponse = await findWorksheetResponseWithResponses(
|
|
219
|
+
worksheetResponseId,
|
|
220
|
+
questionId
|
|
221
|
+
);
|
|
222
|
+
if (!worksheetResponse) {
|
|
223
|
+
throw new TRPCError({
|
|
224
|
+
code: "NOT_FOUND",
|
|
225
|
+
message: "Worksheet response not found",
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const question = await findWorksheetQuestionById(questionId);
|
|
230
|
+
const isMarkableByAlgo =
|
|
231
|
+
question?.type === "MULTIPLE_CHOICE" || question?.type === "TRUE_FALSE";
|
|
232
|
+
const marksAwardedIfCorrect = question?.points || 0;
|
|
233
|
+
const correctAnswer = isMarkableByAlgo
|
|
234
|
+
? question?.type === "MULTIPLE_CHOICE"
|
|
235
|
+
? (question?.options as MCQOptions)?.find((o) => o.isCorrect)?.id
|
|
236
|
+
: question?.answer?.toString()
|
|
237
|
+
: null;
|
|
238
|
+
|
|
239
|
+
const existingResponse = worksheetResponse.responses[0];
|
|
240
|
+
|
|
241
|
+
if (existingResponse) {
|
|
242
|
+
await updateStudentQuestionProgress(existingResponse.id, {
|
|
243
|
+
response,
|
|
244
|
+
...(isMarkableByAlgo && {
|
|
245
|
+
isCorrect: response === correctAnswer,
|
|
246
|
+
}),
|
|
247
|
+
...(isMarkableByAlgo && {
|
|
248
|
+
points: response === correctAnswer ? marksAwardedIfCorrect : 0,
|
|
249
|
+
}),
|
|
250
|
+
status: GenerationStatus.NOT_STARTED,
|
|
251
|
+
});
|
|
252
|
+
} else {
|
|
253
|
+
await createStudentQuestionProgress({
|
|
254
|
+
studentId: worksheetResponse.studentId,
|
|
255
|
+
questionId,
|
|
256
|
+
response,
|
|
257
|
+
studentWorksheetResponseId: worksheetResponseId,
|
|
258
|
+
...(isMarkableByAlgo && {
|
|
259
|
+
isCorrect: response === correctAnswer,
|
|
260
|
+
}),
|
|
261
|
+
...(isMarkableByAlgo && {
|
|
262
|
+
points: response === correctAnswer ? marksAwardedIfCorrect : 0,
|
|
263
|
+
}),
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const updated = await prisma.studentWorksheetResponse.findUnique({
|
|
268
|
+
where: { id: worksheetResponseId },
|
|
269
|
+
include: { responses: true },
|
|
270
|
+
});
|
|
271
|
+
return updated!;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export async function cancelGrading(
|
|
275
|
+
worksheetResponseId: string,
|
|
276
|
+
progressId: string
|
|
277
|
+
) {
|
|
278
|
+
return cancelGradePipeline(worksheetResponseId, progressId);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export async function regradeQuestion(
|
|
282
|
+
worksheetResponseId: string,
|
|
283
|
+
progressId: string
|
|
284
|
+
) {
|
|
285
|
+
return regradeWorksheetPipeline(worksheetResponseId, progressId);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export async function gradeAnswer(
|
|
289
|
+
questionId: string,
|
|
290
|
+
studentWorksheetResponseId: string,
|
|
291
|
+
data: {
|
|
292
|
+
responseId?: string;
|
|
293
|
+
response?: string;
|
|
294
|
+
isCorrect: boolean;
|
|
295
|
+
feedback?: string;
|
|
296
|
+
markschemeState?: object;
|
|
297
|
+
points?: number;
|
|
298
|
+
}
|
|
299
|
+
) {
|
|
300
|
+
if (data.responseId) {
|
|
301
|
+
return updateStudentQuestionProgressForGrading(data.responseId, {
|
|
302
|
+
isCorrect: data.isCorrect,
|
|
303
|
+
feedback: data.feedback,
|
|
304
|
+
markschemeState: data.markschemeState,
|
|
305
|
+
points: data.points,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const worksheetResponse = await prisma.studentWorksheetResponse.findUnique({
|
|
310
|
+
where: { id: studentWorksheetResponseId },
|
|
311
|
+
select: { studentId: true },
|
|
312
|
+
});
|
|
313
|
+
if (!worksheetResponse) {
|
|
314
|
+
throw new TRPCError({
|
|
315
|
+
code: "NOT_FOUND",
|
|
316
|
+
message: "Student worksheet response not found",
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const existing = await findStudentQuestionProgress(
|
|
321
|
+
worksheetResponse.studentId,
|
|
322
|
+
questionId,
|
|
323
|
+
studentWorksheetResponseId
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
if (existing) {
|
|
327
|
+
return updateStudentQuestionProgressForGrading(existing.id, {
|
|
328
|
+
isCorrect: data.isCorrect,
|
|
329
|
+
response: data.response,
|
|
330
|
+
feedback: data.feedback,
|
|
331
|
+
markschemeState: data.markschemeState,
|
|
332
|
+
points: data.points,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return createStudentQuestionProgressForGrading({
|
|
337
|
+
studentId: worksheetResponse.studentId,
|
|
338
|
+
questionId,
|
|
339
|
+
studentWorksheetResponseId,
|
|
340
|
+
response: data.response || "",
|
|
341
|
+
isCorrect: data.isCorrect,
|
|
342
|
+
feedback: data.feedback,
|
|
343
|
+
markschemeState: data.markschemeState,
|
|
344
|
+
points: data.points || 0,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export async function addCommentToResponse(
|
|
349
|
+
responseId: string,
|
|
350
|
+
comment: string,
|
|
351
|
+
authorId: string
|
|
352
|
+
) {
|
|
353
|
+
return createComment({
|
|
354
|
+
studentQuestionProgressId: responseId,
|
|
355
|
+
content: comment,
|
|
356
|
+
authorId,
|
|
357
|
+
});
|
|
358
|
+
}
|
package/src/utils/aiUser.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI user util – ensures AI assistant user exists, provides ID and isAIUser check.
|
|
3
|
+
*/
|
|
1
4
|
import { prisma } from '../lib/prisma.js';
|
|
2
5
|
import { logger } from './logger.js';
|
|
3
6
|
|
|
4
7
|
const AI_USER_ID = 'AI_ASSISTANT';
|
|
5
8
|
|
|
6
|
-
/**
|
|
7
|
-
* Ensure AI assistant user exists in the database
|
|
8
|
-
*/
|
|
9
|
+
/** Ensure AI assistant user exists in the database. */
|
|
9
10
|
export async function ensureAIUserExists(): Promise<void> {
|
|
10
11
|
try {
|
|
11
12
|
// Check if AI user already exists
|
package/src/utils/email.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email util – nodemailer transport and sendMail wrapper. Supports EMAIL_DRY_RUN.
|
|
3
|
+
*/
|
|
1
4
|
import nodemailer from 'nodemailer';
|
|
2
5
|
import { env } from '../lib/config/env.js';
|
|
3
6
|
import { logger } from './logger.js';
|
|
4
7
|
|
|
5
|
-
|
|
6
8
|
type sendMailProps = {
|
|
7
9
|
from: string;
|
|
8
10
|
to: string;
|
|
@@ -13,7 +15,7 @@ type sendMailProps = {
|
|
|
13
15
|
|
|
14
16
|
export const transport = nodemailer.createTransport({
|
|
15
17
|
host: env.EMAIL_HOST,
|
|
16
|
-
port:
|
|
18
|
+
port: env.EMAIL_PORT,
|
|
17
19
|
secure: false,
|
|
18
20
|
auth: {
|
|
19
21
|
user: env.EMAIL_USER,
|
|
@@ -30,7 +32,7 @@ export const sendMail = async ({ from, to, subject, text }: sendMailProps) => {
|
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
await transport.sendMail({
|
|
33
|
-
from
|
|
35
|
+
from: `"Studious" <${from}>`,
|
|
34
36
|
to,
|
|
35
37
|
subject,
|
|
36
38
|
text,
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* @returns {string} The invite code with length 5
|
|
2
|
+
* Generate invite code util – random 5-char alphanumeric code for class invites.
|
|
4
3
|
*/
|
|
5
|
-
|
|
6
4
|
export const generateInviteCode = () => {
|
|
7
5
|
return (Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)).slice(0, 5);
|
|
8
6
|
}
|
package/src/utils/inference.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inference util – OpenAI client, inference() for structured output, sendAIMessage for chat.
|
|
3
|
+
*/
|
|
1
4
|
import OpenAI from 'openai';
|
|
2
5
|
import { logger } from './logger.js';
|
|
3
6
|
import { prisma } from '../lib/prisma.js';
|
|
4
|
-
import { pusher } from '../lib/pusher.js';
|
|
7
|
+
import { chatChannel, pusher } from '../lib/pusher.js';
|
|
5
8
|
import { ensureAIUserExists, getAIUserId } from './aiUser.js';
|
|
6
9
|
import { env } from '../lib/config/env.js';
|
|
7
10
|
import { ZodSchema } from 'zod';
|
|
@@ -103,7 +106,7 @@ export async function sendAIMessage(
|
|
|
103
106
|
|
|
104
107
|
// Broadcast via Pusher
|
|
105
108
|
try {
|
|
106
|
-
await pusher.trigger(
|
|
109
|
+
await pusher.trigger(chatChannel(conversationId), 'new-message', {
|
|
107
110
|
id: aiMessage.id,
|
|
108
111
|
content: aiMessage.content,
|
|
109
112
|
senderId: getAIUserId(),
|
package/src/utils/logger.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prisma wrapper – wraps Prisma operations with error handling and tRPC error mapping.
|
|
3
|
+
*/
|
|
1
4
|
import { TRPCError } from '@trpc/server';
|
|
2
5
|
import { handlePrismaError } from './prismaErrorHandler.js';
|
|
3
6
|
|
|
7
|
+
/** Execute a Prisma operation and convert errors to tRPC errors. */
|
|
4
8
|
export async function withPrismaErrorHandling<T>(
|
|
5
9
|
operation: () => Promise<T>,
|
|
6
10
|
context: string = 'database operation'
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { config } from 'dotenv';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
import type { TestProject } from 'vitest/node';
|
|
4
|
+
|
|
5
|
+
config({ path: resolve(process.cwd(), '.env.test'), override: true });
|
|
6
|
+
|
|
7
|
+
import { prisma } from '../src/lib/prisma';
|
|
8
|
+
import { appRouter } from '../src/routers/_app';
|
|
9
|
+
import { createTRPCContext } from '../src/trpc';
|
|
10
|
+
import { clearDatabase } from '../src/seedDatabase';
|
|
11
|
+
import { execSync } from 'child_process';
|
|
12
|
+
|
|
13
|
+
async function runSetup() {
|
|
14
|
+
const ctx = await createTRPCContext({
|
|
15
|
+
req: { headers: {} } as any,
|
|
16
|
+
res: {} as any,
|
|
17
|
+
});
|
|
18
|
+
const caller = appRouter.createCaller(ctx);
|
|
19
|
+
|
|
20
|
+
execSync('npm run test:pre');
|
|
21
|
+
|
|
22
|
+
const user1 = await caller.auth.register({
|
|
23
|
+
username: 'testuser1',
|
|
24
|
+
email: 'test@studious.sh',
|
|
25
|
+
password: 'password_is_1234',
|
|
26
|
+
confirmPassword: 'password_is_1234',
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const user2 = await caller.auth.register({
|
|
30
|
+
username: 'testuser2',
|
|
31
|
+
email: 'test2@studious.sh',
|
|
32
|
+
password: 'password_is_1234',
|
|
33
|
+
confirmPassword: 'password_is_1234',
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const user3 = await caller.auth.register({
|
|
37
|
+
username: 'testuser3',
|
|
38
|
+
email: 'test3@studious.sh',
|
|
39
|
+
password: 'password_is_1234',
|
|
40
|
+
confirmPassword: 'password_is_1234',
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const session1 = await prisma.session.findFirst({ where: { userId: user1.user.id } });
|
|
44
|
+
const session2 = await prisma.session.findFirst({ where: { userId: user2.user.id } });
|
|
45
|
+
const session3 = await prisma.session.findFirst({ where: { userId: user3.user.id } });
|
|
46
|
+
|
|
47
|
+
if (!session1 || !session2 || !session3) {
|
|
48
|
+
throw new Error('Failed to create sessions for test users');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
await caller.auth.verify({ token: session1.id });
|
|
52
|
+
await caller.auth.verify({ token: session2.id });
|
|
53
|
+
await caller.auth.verify({ token: session3.id });
|
|
54
|
+
|
|
55
|
+
await prisma.$disconnect();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default async function setup(project: TestProject) {
|
|
59
|
+
project.onTestsRerun(runSetup);
|
|
60
|
+
await runSetup();
|
|
61
|
+
}
|
|
62
|
+
|
package/tests/helpers.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createTRPCContext } from '../src/trpc';
|
|
2
|
+
import { appRouter } from '../src/routers/_app';
|
|
3
|
+
|
|
4
|
+
export async function createUnauthCaller() {
|
|
5
|
+
const ctx = await createTRPCContext({
|
|
6
|
+
req: { headers: {} } as any,
|
|
7
|
+
res: {} as any,
|
|
8
|
+
});
|
|
9
|
+
return appRouter.createCaller(ctx);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const expectRejects = (fn: () => Promise<unknown>) =>
|
|
13
|
+
expect(fn()).rejects.toThrow();
|
|
14
|
+
|
|
15
|
+
/** Expect a call to throw (e.g. unauthenticated). */
|
|
16
|
+
export async function expectUnauth(call: (caller: Awaited<ReturnType<typeof createUnauthCaller>>) => Promise<unknown>) {
|
|
17
|
+
const unauth = await createUnauthCaller();
|
|
18
|
+
await expectRejects(() => call(unauth));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Expect a call to throw (e.g. not teacher, not member, forbidden). Same as expectRejects but semantic. */
|
|
22
|
+
export const expectForbidden = expectRejects;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach } from 'vitest';
|
|
2
|
+
import express from 'express';
|
|
3
|
+
import request from 'supertest';
|
|
4
|
+
import {
|
|
5
|
+
generalLimiter,
|
|
6
|
+
authLimiter,
|
|
7
|
+
uploadLimiter,
|
|
8
|
+
helmetConfig,
|
|
9
|
+
} from '../../src/middleware/security.js';
|
|
10
|
+
|
|
11
|
+
describe('security middleware', () => {
|
|
12
|
+
test('exports all middleware', () => {
|
|
13
|
+
expect(typeof generalLimiter).toBe('function');
|
|
14
|
+
expect(typeof authLimiter).toBe('function');
|
|
15
|
+
expect(typeof uploadLimiter).toBe('function');
|
|
16
|
+
expect(typeof helmetConfig).toBe('function');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('generalLimiter skip logic', () => {
|
|
20
|
+
let app: express.Application;
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
app = express();
|
|
24
|
+
app.use(generalLimiter);
|
|
25
|
+
app.get('/health', (_req, res) => res.status(200).json({ ok: true }));
|
|
26
|
+
app.get('/api/test', (_req, res) => res.status(200).json({ ok: true }));
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('/health is not rate limited (skip)', async () => {
|
|
30
|
+
const requests = Array(15)
|
|
31
|
+
.fill(null)
|
|
32
|
+
.map(() => request(app).get('/health'));
|
|
33
|
+
const responses = await Promise.all(requests);
|
|
34
|
+
expect(responses.every((r) => r.status === 200)).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('OPTIONS is skipped', async () => {
|
|
38
|
+
const res = await request(app).options('/api/test');
|
|
39
|
+
expect(res.status).not.toBe(429);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
});
|