@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.
- 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 +461 -0
- package/dist/models/class.d.ts.map +1 -0
- package/dist/models/class.js +645 -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 +76 -0
- package/dist/pipelines/aiLabChat.d.ts.map +1 -0
- package/dist/pipelines/aiLabChat.js +599 -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 +1523 -1315
- 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 +160 -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 +21 -11
- package/dist/routers/labChat.d.ts.map +1 -1
- package/dist/routers/labChat.js +22 -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 +13 -2
- package/dist/routers/message.d.ts.map +1 -1
- package/dist/routers/message.js +32 -520
- 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 +68 -55
- 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 +643 -0
- package/dist/services/class.d.ts.map +1 -0
- package/dist/services/class.js +486 -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 +169 -0
- package/dist/services/labChat.d.ts.map +1 -0
- package/dist/services/labChat.js +381 -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 +103 -0
- package/dist/services/message.d.ts.map +1 -0
- package/dist/services/message.js +422 -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/prisma/schema.prisma +1 -1
- 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 +703 -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 +684 -0
- 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 +35 -604
- package/src/routers/marketing.ts +35 -77
- package/src/routers/message.ts +54 -567
- 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 +629 -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 +458 -0
- package/src/services/marketing.ts +57 -0
- package/src/services/message.ts +554 -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/src/server/pipelines/aiLabChat.ts +0 -507
- package/tests/announcement.test.ts +0 -164
- package/tests/assignment.test.ts +0 -296
- package/tests/auth.test.ts +0 -48
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User service – profile get/update, profile picture upload URLs.
|
|
3
|
+
*/
|
|
4
|
+
import { TRPCError } from "@trpc/server";
|
|
5
|
+
import { logger } from "../utils/logger.js";
|
|
6
|
+
import { env } from "../lib/config/env.js";
|
|
7
|
+
import { findUserById, findUserProfileByUserId, upsertUserProfile } from "../models/user.js";
|
|
8
|
+
|
|
9
|
+
function getFileUrl(filePath: string | null): string | null {
|
|
10
|
+
if (!filePath) return null;
|
|
11
|
+
if (filePath.startsWith("http")) {
|
|
12
|
+
return filePath;
|
|
13
|
+
}
|
|
14
|
+
const backendUrl = env.BACKEND_URL || "http://localhost:3001";
|
|
15
|
+
return `${backendUrl}/api/files/${encodeURIComponent(filePath)}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Get user profile with display info and profile picture URLs. */
|
|
19
|
+
export async function getProfile(userId: string) {
|
|
20
|
+
const user = await findUserById(userId);
|
|
21
|
+
if (!user) {
|
|
22
|
+
throw new TRPCError({
|
|
23
|
+
code: "NOT_FOUND",
|
|
24
|
+
message: "User not found",
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const userProfile = await findUserProfileByUserId(userId);
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
id: user.id,
|
|
32
|
+
username: user.username,
|
|
33
|
+
profile: userProfile
|
|
34
|
+
? {
|
|
35
|
+
displayName: (userProfile as any).displayName || null,
|
|
36
|
+
bio: (userProfile as any).bio || null,
|
|
37
|
+
location: (userProfile as any).location || null,
|
|
38
|
+
website: (userProfile as any).website || null,
|
|
39
|
+
profilePicture: getFileUrl((userProfile as any).profilePicture),
|
|
40
|
+
profilePictureThumbnail: getFileUrl(
|
|
41
|
+
(userProfile as any).profilePictureThumbnail
|
|
42
|
+
),
|
|
43
|
+
}
|
|
44
|
+
: {
|
|
45
|
+
displayName: null,
|
|
46
|
+
bio: null,
|
|
47
|
+
location: null,
|
|
48
|
+
website: null,
|
|
49
|
+
profilePicture: null,
|
|
50
|
+
profilePictureThumbnail: null,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Update user profile (display name, bio, etc.) and/or profile picture. */
|
|
56
|
+
export async function updateProfile(
|
|
57
|
+
userId: string,
|
|
58
|
+
input: {
|
|
59
|
+
profile?: {
|
|
60
|
+
displayName?: string | null;
|
|
61
|
+
bio?: string | null;
|
|
62
|
+
location?: string | null;
|
|
63
|
+
website?: string | null;
|
|
64
|
+
};
|
|
65
|
+
profilePicture?: {
|
|
66
|
+
filePath: string;
|
|
67
|
+
fileName: string;
|
|
68
|
+
fileType: string;
|
|
69
|
+
fileSize: number;
|
|
70
|
+
};
|
|
71
|
+
dicebearAvatar?: { url: string };
|
|
72
|
+
}
|
|
73
|
+
) {
|
|
74
|
+
let profilePictureUrl: string | null = null;
|
|
75
|
+
let profilePictureThumbnail: string | null = null;
|
|
76
|
+
|
|
77
|
+
if (input.profilePicture) {
|
|
78
|
+
try {
|
|
79
|
+
profilePictureUrl = input.profilePicture.filePath;
|
|
80
|
+
profilePictureThumbnail = null;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
logger.error("Profile picture processing failed", {
|
|
83
|
+
userId,
|
|
84
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
85
|
+
});
|
|
86
|
+
throw new TRPCError({
|
|
87
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
88
|
+
message: "Failed to process profile picture. Please try again.",
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (input.dicebearAvatar) {
|
|
94
|
+
profilePictureUrl = input.dicebearAvatar.url;
|
|
95
|
+
profilePictureThumbnail = null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const updateData: any = {};
|
|
99
|
+
if (input.profile) {
|
|
100
|
+
if (input.profile.displayName !== undefined && input.profile.displayName !== null) {
|
|
101
|
+
updateData.displayName = input.profile.displayName;
|
|
102
|
+
}
|
|
103
|
+
if (input.profile.bio !== undefined && input.profile.bio !== null) {
|
|
104
|
+
updateData.bio = input.profile.bio;
|
|
105
|
+
}
|
|
106
|
+
if (input.profile.location !== undefined && input.profile.location !== null) {
|
|
107
|
+
updateData.location = input.profile.location;
|
|
108
|
+
}
|
|
109
|
+
if (input.profile.website !== undefined && input.profile.website !== null) {
|
|
110
|
+
updateData.website = input.profile.website;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (profilePictureUrl !== null) updateData.profilePicture = profilePictureUrl;
|
|
114
|
+
if (profilePictureThumbnail !== null)
|
|
115
|
+
updateData.profilePictureThumbnail = profilePictureThumbnail;
|
|
116
|
+
|
|
117
|
+
const updatedProfile = await upsertUserProfile(userId, updateData);
|
|
118
|
+
const user = await findUserById(userId);
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
id: userId,
|
|
122
|
+
username: user?.username || "",
|
|
123
|
+
profile: {
|
|
124
|
+
displayName: (updatedProfile as any).displayName || null,
|
|
125
|
+
bio: (updatedProfile as any).bio || null,
|
|
126
|
+
location: (updatedProfile as any).location || null,
|
|
127
|
+
website: (updatedProfile as any).website || null,
|
|
128
|
+
profilePicture: getFileUrl((updatedProfile as any).profilePicture),
|
|
129
|
+
profilePictureThumbnail: getFileUrl(
|
|
130
|
+
(updatedProfile as any).profilePictureThumbnail
|
|
131
|
+
),
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function getUploadUrl(
|
|
137
|
+
userId: string,
|
|
138
|
+
fileName: string,
|
|
139
|
+
fileType: string
|
|
140
|
+
) {
|
|
141
|
+
try {
|
|
142
|
+
const fileExtension = fileName.split(".").pop();
|
|
143
|
+
const uniqueFilename = `${userId}-${Date.now()}.${fileExtension}`;
|
|
144
|
+
const filePath = `users/${userId}/profile/${uniqueFilename}`;
|
|
145
|
+
|
|
146
|
+
const backendUrl = env.BACKEND_URL || "http://localhost:3001";
|
|
147
|
+
const uploadUrl = `${backendUrl}/api/upload/${encodeURIComponent(filePath)}`;
|
|
148
|
+
|
|
149
|
+
logger.info("Generated upload URL", {
|
|
150
|
+
userId,
|
|
151
|
+
filePath,
|
|
152
|
+
fileName: uniqueFilename,
|
|
153
|
+
fileType,
|
|
154
|
+
uploadUrl,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
uploadUrl,
|
|
159
|
+
filePath,
|
|
160
|
+
fileName: uniqueFilename,
|
|
161
|
+
};
|
|
162
|
+
} catch (error) {
|
|
163
|
+
logger.error("Failed to generate upload URL", {
|
|
164
|
+
userId,
|
|
165
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
166
|
+
});
|
|
167
|
+
throw new TRPCError({
|
|
168
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
169
|
+
message: "Failed to generate upload URL",
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -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
|
+
|