@studious-lms/server 1.1.24 → 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 +82 -15
- package/dist/lib/fileUpload.js.map +1 -0
- package/dist/lib/googleCloudStorage.d.ts +13 -0
- package/dist/lib/googleCloudStorage.d.ts.map +1 -1
- package/dist/lib/googleCloudStorage.js +45 -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 +25 -0
- package/dist/lib/notificationHandler.d.ts.map +1 -0
- package/dist/lib/notificationHandler.js +32 -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 +6403 -3741
- 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 +547 -57
- package/dist/routers/announcement.js.map +1 -0
- package/dist/routers/assignment.d.ts +431 -318
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/assignment.js +104 -1559
- 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 -295
- 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 -877
- 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 +37 -6
- package/dist/routers/section.d.ts.map +1 -1
- package/dist/routers/section.js +26 -167
- 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 +311 -289
- 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 +4 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +35 -3
- 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 +81 -16
- package/src/lib/googleCloudStorage.ts +42 -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 +622 -57
- package/src/routers/assignment.ts +157 -1688
- 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 -341
- package/src/routers/folder.ts +107 -836
- package/src/routers/labChat.ts +29 -960
- 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 -200
- package/src/routers/user.ts +49 -226
- package/src/routers/worksheet.ts +252 -0
- package/src/seedDatabase.ts +330 -290
- 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 +33 -3
- 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 -65
- 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/tests/auth.test.ts +0 -25
|
@@ -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/trpc.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { createAuthMiddleware } from './middleware/auth.js';
|
|
|
7
7
|
import { Request, Response } from 'express';
|
|
8
8
|
import { z } from 'zod';
|
|
9
9
|
import { handlePrismaError, PrismaErrorInfo } from './utils/prismaErrorHandler.js';
|
|
10
|
+
import { generalLimiter } from './middleware/security.js';
|
|
10
11
|
|
|
11
12
|
interface CreateContextOptions {
|
|
12
13
|
req: Request;
|
|
@@ -89,11 +90,14 @@ const { isAuthed, isMemberInClass, isTeacherInClass } = createAuthMiddleware(t);
|
|
|
89
90
|
export const createTRPCRouter = t.router;
|
|
90
91
|
export const publicProcedure = t.procedure.use(loggingMiddleware);
|
|
91
92
|
|
|
93
|
+
|
|
92
94
|
// Protected procedures
|
|
93
95
|
export const protectedProcedure = publicProcedure.use(isAuthed);
|
|
96
|
+
|
|
94
97
|
export const protectedClassMemberProcedure = protectedProcedure
|
|
95
98
|
.input(z.object({ classId: z.string() }).passthrough())
|
|
96
99
|
.use(isMemberInClass);
|
|
100
|
+
|
|
97
101
|
export const protectedTeacherProcedure = protectedProcedure
|
|
98
102
|
.input(z.object({ classId: z.string() }).passthrough())
|
|
99
103
|
.use(isTeacherInClass);
|
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,11 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email util – nodemailer transport and sendMail wrapper. Supports EMAIL_DRY_RUN.
|
|
3
|
+
*/
|
|
1
4
|
import nodemailer from 'nodemailer';
|
|
5
|
+
import { env } from '../lib/config/env.js';
|
|
6
|
+
import { logger } from './logger.js';
|
|
7
|
+
|
|
8
|
+
type sendMailProps = {
|
|
9
|
+
from: string;
|
|
10
|
+
to: string;
|
|
11
|
+
subject: string;
|
|
12
|
+
text: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
2
15
|
|
|
3
16
|
export const transport = nodemailer.createTransport({
|
|
4
|
-
host:
|
|
5
|
-
port:
|
|
17
|
+
host: env.EMAIL_HOST,
|
|
18
|
+
port: env.EMAIL_PORT,
|
|
6
19
|
secure: false,
|
|
7
20
|
auth: {
|
|
8
|
-
user:
|
|
9
|
-
pass:
|
|
21
|
+
user: env.EMAIL_USER,
|
|
22
|
+
pass: env.EMAIL_PASS,
|
|
10
23
|
},
|
|
11
24
|
});
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
export const sendMail = async ({ from, to, subject, text }: sendMailProps) => {
|
|
28
|
+
// Wrapper function for sending emails
|
|
29
|
+
if (env.EMAIL_DRY_RUN == "true") {
|
|
30
|
+
logger.info(`Email dry run enabled. Would have sent email to ${to} from ${from} with subject ${subject} and text ${text}`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
await transport.sendMail({
|
|
35
|
+
from: `"Studious" <${from}>`,
|
|
36
|
+
to,
|
|
37
|
+
subject,
|
|
38
|
+
text,
|
|
39
|
+
});
|
|
40
|
+
};
|
|
@@ -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,19 +1,24 @@
|
|
|
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';
|
|
9
|
+
import { env } from '../lib/config/env.js';
|
|
10
|
+
import { ZodSchema } from 'zod';
|
|
11
|
+
import { zodTextFormat } from "openai/helpers/zod";
|
|
6
12
|
|
|
7
|
-
// Initialize inference client (Cohere via OpenAI SDK)
|
|
8
13
|
|
|
9
|
-
logger.info('Inference API Key', { apiKey: process.env.INFERENCE_API_KEY });
|
|
10
|
-
logger.info('Inference API Base URL', { baseURL: process.env.INFERENCE_API_BASE_URL });
|
|
11
14
|
|
|
12
15
|
export const inferenceClient = new OpenAI({
|
|
13
|
-
apiKey:
|
|
14
|
-
baseURL:
|
|
16
|
+
apiKey: env.INFERENCE_API_KEY,
|
|
17
|
+
baseURL: env.INFERENCE_API_BASE_URL,
|
|
15
18
|
});
|
|
16
19
|
|
|
20
|
+
export const openAIClient = new OpenAI();
|
|
21
|
+
|
|
17
22
|
// Types for lab chat context
|
|
18
23
|
export interface LabChatContext {
|
|
19
24
|
subject: string;
|
|
@@ -46,6 +51,7 @@ export async function sendAIMessage(
|
|
|
46
51
|
attachments?: {
|
|
47
52
|
connect: { id: string }[];
|
|
48
53
|
};
|
|
54
|
+
meta?: Record<string, any>;
|
|
49
55
|
customSender?: {
|
|
50
56
|
displayName: string;
|
|
51
57
|
profilePicture?: string | null;
|
|
@@ -57,6 +63,7 @@ export async function sendAIMessage(
|
|
|
57
63
|
senderId: string;
|
|
58
64
|
conversationId: string;
|
|
59
65
|
createdAt: Date;
|
|
66
|
+
meta?: Record<string, any>;
|
|
60
67
|
}> {
|
|
61
68
|
// Ensure AI user exists
|
|
62
69
|
await ensureAIUserExists();
|
|
@@ -72,6 +79,9 @@ export async function sendAIMessage(
|
|
|
72
79
|
connect: options.attachments.connect,
|
|
73
80
|
},
|
|
74
81
|
}),
|
|
82
|
+
...(options.meta && {
|
|
83
|
+
meta: options.meta,
|
|
84
|
+
}),
|
|
75
85
|
},
|
|
76
86
|
include: {
|
|
77
87
|
attachments: true,
|
|
@@ -96,7 +106,7 @@ export async function sendAIMessage(
|
|
|
96
106
|
|
|
97
107
|
// Broadcast via Pusher
|
|
98
108
|
try {
|
|
99
|
-
await pusher.trigger(
|
|
109
|
+
await pusher.trigger(chatChannel(conversationId), 'new-message', {
|
|
100
110
|
id: aiMessage.id,
|
|
101
111
|
content: aiMessage.content,
|
|
102
112
|
senderId: getAIUserId(),
|
|
@@ -104,6 +114,7 @@ export async function sendAIMessage(
|
|
|
104
114
|
createdAt: aiMessage.createdAt,
|
|
105
115
|
sender: senderInfo,
|
|
106
116
|
mentionedUserIds: [],
|
|
117
|
+
meta: aiMessage.meta,
|
|
107
118
|
attachments: aiMessage.attachments.map(attachment => ({
|
|
108
119
|
id: attachment.id,
|
|
109
120
|
attachmentId: attachment.id,
|
|
@@ -123,9 +134,77 @@ export async function sendAIMessage(
|
|
|
123
134
|
senderId: getAIUserId(),
|
|
124
135
|
conversationId: aiMessage.conversationId,
|
|
125
136
|
createdAt: aiMessage.createdAt,
|
|
137
|
+
meta: aiMessage.meta as Record<string, any>,
|
|
126
138
|
};
|
|
127
139
|
}
|
|
128
140
|
|
|
141
|
+
export async function inference<T>(
|
|
142
|
+
content: string | OpenAI.Chat.Completions.ChatCompletionMessageParam[],
|
|
143
|
+
format?: ZodSchema
|
|
144
|
+
): Promise<T> {
|
|
145
|
+
try {
|
|
146
|
+
|
|
147
|
+
if (!format) {
|
|
148
|
+
const completion = await openAIClient.chat.completions.create({
|
|
149
|
+
model: 'gpt-5-nano',
|
|
150
|
+
messages: typeof content === 'string' ? [
|
|
151
|
+
{
|
|
152
|
+
role: 'user',
|
|
153
|
+
content: content,
|
|
154
|
+
},
|
|
155
|
+
] : content as Array<{ role: 'user' | 'assistant' | 'system'; content: string }>,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
return completion.choices[0]?.message?.content as T;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const completion = await openAIClient.responses.parse({
|
|
162
|
+
model: 'gpt-5-nano',
|
|
163
|
+
input: typeof content === 'string' ? [
|
|
164
|
+
{
|
|
165
|
+
role: 'user',
|
|
166
|
+
content: content,
|
|
167
|
+
},
|
|
168
|
+
] : content as Array<{ role: 'user' | 'assistant' | 'system'; content: string }>,
|
|
169
|
+
...(format ? { text: {
|
|
170
|
+
format: zodTextFormat(format, "newton_response_format"),
|
|
171
|
+
},
|
|
172
|
+
} : {}),
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
console.log({
|
|
176
|
+
model: 'gpt-5-nano',
|
|
177
|
+
input: typeof content === 'string' ? [
|
|
178
|
+
{
|
|
179
|
+
role: 'user',
|
|
180
|
+
content: content,
|
|
181
|
+
},
|
|
182
|
+
] : content as Array<{ role: 'user' | 'assistant' | 'system'; content: string }>,
|
|
183
|
+
...(format ? { text: {
|
|
184
|
+
format: zodTextFormat(format, "newton_response_format"),
|
|
185
|
+
},
|
|
186
|
+
} : {}),
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
if (!completion) {
|
|
191
|
+
throw new Error('No response generated from inference API');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// if (format) {
|
|
195
|
+
// if (typeof completion.output === 'string') {
|
|
196
|
+
// return JSON.parse(completion.output);
|
|
197
|
+
// }
|
|
198
|
+
// return JSON.parse(completion.output);
|
|
199
|
+
// }
|
|
200
|
+
|
|
201
|
+
return completion.output_parsed;
|
|
202
|
+
} catch (error) {
|
|
203
|
+
logger.error('Failed to generate inference response', { error });
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
129
208
|
/**
|
|
130
209
|
* Simple inference function for general use
|
|
131
210
|
*/
|
|
@@ -137,10 +216,10 @@ export async function generateInferenceResponse(
|
|
|
137
216
|
maxTokens?: number;
|
|
138
217
|
} = {}
|
|
139
218
|
): Promise<InferenceResponse> {
|
|
140
|
-
const { model = '
|
|
219
|
+
const { model = 'gpt-5-nano', maxTokens = 500 } = options;
|
|
141
220
|
|
|
142
221
|
try {
|
|
143
|
-
const completion = await
|
|
222
|
+
const completion = await openAIClient.chat.completions.create({
|
|
144
223
|
model,
|
|
145
224
|
messages: [
|
|
146
225
|
{
|
|
@@ -180,7 +259,7 @@ export async function generateInferenceResponse(
|
|
|
180
259
|
* Validate inference configuration
|
|
181
260
|
*/
|
|
182
261
|
export function validateInferenceConfig(): boolean {
|
|
183
|
-
if (!
|
|
262
|
+
if (!env.INFERENCE_API_KEY) {
|
|
184
263
|
logger.error('Inference API key not configured for Cohere');
|
|
185
264
|
return false;
|
|
186
265
|
}
|
package/src/utils/logger.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger util – structured logging with levels, modes, and colored output.
|
|
3
|
+
*/
|
|
1
4
|
export enum LogLevel {
|
|
2
5
|
INFO = 'info',
|
|
3
6
|
WARN = 'warn',
|
|
@@ -26,7 +29,24 @@ const colors = {
|
|
|
26
29
|
magenta: '\x1b[35m',
|
|
27
30
|
cyan: '\x1b[36m',
|
|
28
31
|
white: '\x1b[37m',
|
|
29
|
-
gray: '\x1b[90m'
|
|
32
|
+
gray: '\x1b[90m',
|
|
33
|
+
// Background colors
|
|
34
|
+
bgRed: '\x1b[41m',
|
|
35
|
+
bgGreen: '\x1b[42m',
|
|
36
|
+
bgYellow: '\x1b[43m',
|
|
37
|
+
bgBlue: '\x1b[44m',
|
|
38
|
+
bgMagenta: '\x1b[45m',
|
|
39
|
+
bgCyan: '\x1b[46m',
|
|
40
|
+
bgWhite: '\x1b[47m',
|
|
41
|
+
bgGray: '\x1b[100m',
|
|
42
|
+
// Bright background colors
|
|
43
|
+
bgBrightRed: '\x1b[101m',
|
|
44
|
+
bgBrightGreen: '\x1b[102m',
|
|
45
|
+
bgBrightYellow: '\x1b[103m',
|
|
46
|
+
bgBrightBlue: '\x1b[104m',
|
|
47
|
+
bgBrightMagenta: '\x1b[105m',
|
|
48
|
+
bgBrightCyan: '\x1b[106m',
|
|
49
|
+
bgBrightWhite: '\x1b[107m'
|
|
30
50
|
};
|
|
31
51
|
|
|
32
52
|
class Logger {
|
|
@@ -34,6 +54,7 @@ class Logger {
|
|
|
34
54
|
private isDevelopment: boolean;
|
|
35
55
|
private mode: LogMode;
|
|
36
56
|
private levelColors: Record<LogLevel, string>;
|
|
57
|
+
private levelBgColors: Record<LogLevel, string>;
|
|
37
58
|
private levelEmojis: Record<LogLevel, string>;
|
|
38
59
|
|
|
39
60
|
private constructor() {
|
|
@@ -41,7 +62,7 @@ class Logger {
|
|
|
41
62
|
// this.isDevelopment = process.env.NODE_ENV === 'development';
|
|
42
63
|
this.isDevelopment = true;
|
|
43
64
|
|
|
44
|
-
this.mode = (process.env.LOG_MODE as LogMode) || 'normal';
|
|
65
|
+
this.mode = (process.env.NODE_ENV === 'test' ? 'silent' : ((process.env.LOG_MODE as LogMode) || 'normal'));
|
|
45
66
|
|
|
46
67
|
|
|
47
68
|
this.levelColors = {
|
|
@@ -51,6 +72,13 @@ class Logger {
|
|
|
51
72
|
[LogLevel.DEBUG]: colors.magenta
|
|
52
73
|
};
|
|
53
74
|
|
|
75
|
+
this.levelBgColors = {
|
|
76
|
+
[LogLevel.INFO]: colors.bgBlue,
|
|
77
|
+
[LogLevel.WARN]: colors.bgYellow,
|
|
78
|
+
[LogLevel.ERROR]: colors.bgRed,
|
|
79
|
+
[LogLevel.DEBUG]: colors.bgMagenta
|
|
80
|
+
};
|
|
81
|
+
|
|
54
82
|
this.levelEmojis = {
|
|
55
83
|
[LogLevel.INFO]: 'ℹ️',
|
|
56
84
|
[LogLevel.WARN]: '⚠️',
|
|
@@ -95,10 +123,12 @@ class Logger {
|
|
|
95
123
|
private formatMessage(logMessage: LogMessage): string {
|
|
96
124
|
const { level, message, timestamp, context } = logMessage;
|
|
97
125
|
const color = this.levelColors[level];
|
|
126
|
+
const bgColor = this.levelBgColors[level];
|
|
98
127
|
const emoji = this.levelEmojis[level];
|
|
99
128
|
|
|
100
129
|
const timestampStr = colors.gray + `[${timestamp}]` + colors.reset;
|
|
101
|
-
|
|
130
|
+
// Use background color for level badge like Vitest
|
|
131
|
+
const levelStr = colors.white + bgColor + ` ${level.toUpperCase()} ` + colors.reset;
|
|
102
132
|
const emojiStr = emoji + ' ';
|
|
103
133
|
const messageStr = colors.bright + message + colors.reset;
|
|
104
134
|
|