@studious-lms/server 1.13.0 → 1.15.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/.dockerignore +28 -0
- package/.env.example +5 -0
- package/.github/workflows/deploy.yml +123 -0
- package/Dockerfile +54 -0
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/instrument.js +3 -3
- package/dist/instrument.js.map +1 -1
- package/dist/lib/config/cors.d.ts.map +1 -1
- package/dist/lib/config/cors.js +31 -11
- package/dist/lib/config/cors.js.map +1 -1
- package/dist/lib/config/env.d.ts +17 -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/googleCloudStorage.d.ts +10 -0
- package/dist/lib/googleCloudStorage.d.ts.map +1 -1
- package/dist/lib/googleCloudStorage.js +22 -2
- package/dist/lib/googleCloudStorage.js.map +1 -1
- package/dist/lib/inferDocumentFormat.d.ts +10 -0
- package/dist/lib/inferDocumentFormat.d.ts.map +1 -0
- package/dist/lib/inferDocumentFormat.js +48 -0
- package/dist/lib/inferDocumentFormat.js.map +1 -0
- package/dist/lib/legal.d.ts +3 -0
- package/dist/lib/legal.d.ts.map +1 -0
- package/dist/lib/legal.js +6 -0
- package/dist/lib/legal.js.map +1 -0
- package/dist/lib/prisma.d.ts +104 -5
- package/dist/lib/prisma.d.ts.map +1 -1
- package/dist/lib/prisma.js +33 -2
- package/dist/lib/prisma.js.map +1 -1
- package/dist/models/agenda.d.ts +20 -20
- package/dist/models/announcement.d.ts +211 -101
- package/dist/models/announcement.d.ts.map +1 -1
- package/dist/models/assignment.d.ts +560 -362
- package/dist/models/assignment.d.ts.map +1 -1
- package/dist/models/attendance.d.ts +197 -71
- package/dist/models/attendance.d.ts.map +1 -1
- package/dist/models/auth.d.ts +356 -114
- package/dist/models/auth.d.ts.map +1 -1
- package/dist/models/auth.js +4 -2
- package/dist/models/auth.js.map +1 -1
- package/dist/models/class.d.ts +327 -201
- package/dist/models/class.d.ts.map +1 -1
- package/dist/models/class.js +2 -2
- package/dist/models/class.js.map +1 -1
- package/dist/models/cloudStorage.d.ts +146 -56
- package/dist/models/cloudStorage.d.ts.map +1 -1
- package/dist/models/comment.d.ts +262 -110
- package/dist/models/comment.d.ts.map +1 -1
- package/dist/models/conversation.d.ts +206 -80
- package/dist/models/conversation.d.ts.map +1 -1
- package/dist/models/department.d.ts +196 -70
- package/dist/models/department.d.ts.map +1 -1
- package/dist/models/event.d.ts +305 -141
- package/dist/models/event.d.ts.map +1 -1
- package/dist/models/file.d.ts +422 -272
- package/dist/models/file.d.ts.map +1 -1
- package/dist/models/folder.d.ts +433 -181
- package/dist/models/folder.d.ts.map +1 -1
- package/dist/models/labChat.d.ts +205 -79
- package/dist/models/labChat.d.ts.map +1 -1
- package/dist/models/marketing.d.ts +79 -38
- package/dist/models/marketing.d.ts.map +1 -1
- package/dist/models/marketing.js +12 -4
- package/dist/models/marketing.js.map +1 -1
- package/dist/models/message.d.ts +97 -43
- package/dist/models/message.d.ts.map +1 -1
- package/dist/models/newtonChat.d.ts +84 -30
- package/dist/models/newtonChat.d.ts.map +1 -1
- package/dist/models/notification.d.ts +57 -21
- package/dist/models/notification.d.ts.map +1 -1
- package/dist/models/school.d.ts +349 -133
- package/dist/models/school.d.ts.map +1 -1
- package/dist/models/section.d.ts +216 -72
- package/dist/models/section.d.ts.map +1 -1
- package/dist/models/tag.d.ts +156 -48
- package/dist/models/tag.d.ts.map +1 -1
- package/dist/models/user.d.ts +82 -28
- package/dist/models/user.d.ts.map +1 -1
- package/dist/models/worksheet.d.ts +1032 -423
- package/dist/models/worksheet.d.ts.map +1 -1
- package/dist/models/worksheet.js +8 -2
- package/dist/models/worksheet.js.map +1 -1
- package/dist/pipelines/aiLabChat.d.ts +7 -9
- package/dist/pipelines/aiLabChat.d.ts.map +1 -1
- package/dist/pipelines/aiLabChat.js +194 -125
- package/dist/pipelines/aiLabChat.js.map +1 -1
- package/dist/pipelines/aiLabChatContract.d.ts +233 -389
- package/dist/pipelines/aiLabChatContract.d.ts.map +1 -1
- package/dist/pipelines/aiLabChatContract.js +54 -70
- package/dist/pipelines/aiLabChatContract.js.map +1 -1
- package/dist/pipelines/aiNewtonChat.d.ts.map +1 -1
- package/dist/pipelines/aiNewtonChat.js +41 -41
- package/dist/pipelines/aiNewtonChat.js.map +1 -1
- package/dist/pipelines/gradeWorksheet.d.ts +8 -8
- package/dist/pipelines/labChatPrompt.d.ts +18 -11
- package/dist/pipelines/labChatPrompt.d.ts.map +1 -1
- package/dist/pipelines/labChatPrompt.js +93 -75
- package/dist/pipelines/labChatPrompt.js.map +1 -1
- package/dist/pipelines/pdfToWorksheet.d.ts +1 -1
- package/dist/routers/_app.d.ts +2601 -2151
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/_app.js +4 -2
- package/dist/routers/_app.js.map +1 -1
- package/dist/routers/admin.d.ts +13 -13
- package/dist/routers/agenda.d.ts +18 -18
- package/dist/routers/announcement.d.ts +60 -60
- package/dist/routers/assignment.d.ts +393 -383
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/attendance.d.ts +10 -10
- package/dist/routers/auth.d.ts +4 -2
- package/dist/routers/auth.d.ts.map +1 -1
- package/dist/routers/auth.js +15 -2
- package/dist/routers/auth.js.map +1 -1
- package/dist/routers/class.d.ts +157 -153
- package/dist/routers/class.d.ts.map +1 -1
- package/dist/routers/comment.d.ts +18 -18
- package/dist/routers/conversation.d.ts +11 -11
- package/dist/routers/department.d.ts +34 -34
- package/dist/routers/discussion.d.ts +68 -56
- package/dist/routers/discussion.d.ts.map +1 -1
- package/dist/routers/discussion.js +6 -2
- package/dist/routers/discussion.js.map +1 -1
- package/dist/routers/embedding.d.ts +73 -0
- package/dist/routers/embedding.d.ts.map +1 -0
- package/dist/routers/embedding.js +151 -0
- package/dist/routers/embedding.js.map +1 -0
- package/dist/routers/event.d.ts +57 -55
- package/dist/routers/event.d.ts.map +1 -1
- package/dist/routers/file.d.ts +14 -14
- package/dist/routers/folder.d.ts +38 -38
- package/dist/routers/labChat.d.ts +16 -16
- package/dist/routers/marketing.d.ts +1 -14
- package/dist/routers/marketing.d.ts.map +1 -1
- package/dist/routers/message.d.ts +13 -13
- package/dist/routers/newtonChat.d.ts +4 -4
- package/dist/routers/notifications.d.ts +5 -5
- package/dist/routers/permissions.d.ts +14 -14
- package/dist/routers/school.d.ts +59 -55
- package/dist/routers/school.d.ts.map +1 -1
- package/dist/routers/search.d.ts +22 -1
- package/dist/routers/search.d.ts.map +1 -1
- package/dist/routers/search.js +9 -3
- package/dist/routers/search.js.map +1 -1
- package/dist/routers/section.d.ts +4 -4
- package/dist/routers/studentProgress.d.ts +1 -1
- package/dist/routers/tag.d.ts +7 -7
- package/dist/routers/user.d.ts +99 -10
- package/dist/routers/user.d.ts.map +1 -1
- package/dist/routers/user.js +19 -2
- package/dist/routers/user.js.map +1 -1
- package/dist/routers/worksheet.d.ts +93 -55
- package/dist/routers/worksheet.d.ts.map +1 -1
- package/dist/routers/worksheet.js +26 -2
- package/dist/routers/worksheet.js.map +1 -1
- package/dist/routes/aiStream.d.ts +2 -0
- package/dist/routes/aiStream.d.ts.map +1 -0
- package/dist/routes/aiStream.js +726 -0
- package/dist/routes/aiStream.js.map +1 -0
- package/dist/seedDatabase.d.ts +3 -1
- package/dist/seedDatabase.d.ts.map +1 -1
- package/dist/seedDatabase.js +6 -6
- package/dist/seedDatabase.js.map +1 -1
- package/dist/services/adminInstitution.d.ts +8 -8
- package/dist/services/adminRunJob.d.ts +4 -4
- package/dist/services/agenda.d.ts +18 -18
- package/dist/services/aiChatStream.d.ts +88 -0
- package/dist/services/aiChatStream.d.ts.map +1 -0
- package/dist/services/aiChatStream.js +254 -0
- package/dist/services/aiChatStream.js.map +1 -0
- package/dist/services/aiModifierTools.d.ts +16 -0
- package/dist/services/aiModifierTools.d.ts.map +1 -0
- package/dist/services/aiModifierTools.js +274 -0
- package/dist/services/aiModifierTools.js.map +1 -0
- package/dist/services/aiProposalTools.d.ts +32 -0
- package/dist/services/aiProposalTools.d.ts.map +1 -0
- package/dist/services/aiProposalTools.js +207 -0
- package/dist/services/aiProposalTools.js.map +1 -0
- package/dist/services/aiStream.d.ts +22 -0
- package/dist/services/aiStream.d.ts.map +1 -0
- package/dist/services/aiStream.js +142 -0
- package/dist/services/aiStream.js.map +1 -0
- package/dist/services/aiTools.d.ts +16 -0
- package/dist/services/aiTools.d.ts.map +1 -0
- package/dist/services/aiTools.js +271 -0
- package/dist/services/aiTools.js.map +1 -0
- package/dist/services/announcement.d.ts +42 -42
- package/dist/services/announcement.d.ts.map +1 -1
- package/dist/services/announcement.js +7 -2
- package/dist/services/announcement.js.map +1 -1
- package/dist/services/assignment.d.ts +355 -345
- package/dist/services/assignment.d.ts.map +1 -1
- package/dist/services/assignment.js +30 -9
- package/dist/services/assignment.js.map +1 -1
- package/dist/services/attendance.d.ts +10 -10
- package/dist/services/auth.d.ts +4 -2
- package/dist/services/auth.d.ts.map +1 -1
- package/dist/services/auth.js +13 -4
- package/dist/services/auth.js.map +1 -1
- package/dist/services/class.d.ts +146 -142
- package/dist/services/class.d.ts.map +1 -1
- package/dist/services/class.js +24 -4
- package/dist/services/class.js.map +1 -1
- package/dist/services/comment.d.ts +18 -18
- package/dist/services/conversation.d.ts +11 -11
- package/dist/services/department.d.ts +33 -33
- package/dist/services/discussion.d.ts +58 -58
- package/dist/services/discussion.d.ts.map +1 -1
- package/dist/services/discussion.js +15 -8
- package/dist/services/discussion.js.map +1 -1
- package/dist/services/discussionSummary.d.ts +21 -0
- package/dist/services/discussionSummary.d.ts.map +1 -0
- package/dist/services/discussionSummary.js +179 -0
- package/dist/services/discussionSummary.js.map +1 -0
- package/dist/services/embedding.d.ts +85 -0
- package/dist/services/embedding.d.ts.map +1 -0
- package/dist/services/embedding.js +372 -0
- package/dist/services/embedding.js.map +1 -0
- package/dist/services/event.d.ts +55 -53
- package/dist/services/event.d.ts.map +1 -1
- package/dist/services/file.d.ts +28 -28
- package/dist/services/folder.d.ts +36 -36
- package/dist/services/folder.d.ts.map +1 -1
- package/dist/services/folder.js +32 -2
- package/dist/services/folder.js.map +1 -1
- package/dist/services/labChat.d.ts +16 -16
- package/dist/services/marketing.d.ts +2 -15
- package/dist/services/marketing.d.ts.map +1 -1
- package/dist/services/marketing.js +5 -5
- package/dist/services/marketing.js.map +1 -1
- package/dist/services/message.d.ts +11 -11
- package/dist/services/message.d.ts.map +1 -1
- package/dist/services/message.js +2 -2
- package/dist/services/message.js.map +1 -1
- package/dist/services/newtonChat.d.ts +4 -4
- package/dist/services/notification.d.ts +5 -5
- package/dist/services/notification.d.ts.map +1 -1
- package/dist/services/openaiDocumentGen.d.ts +37 -0
- package/dist/services/openaiDocumentGen.d.ts.map +1 -0
- package/dist/services/openaiDocumentGen.js +262 -0
- package/dist/services/openaiDocumentGen.js.map +1 -0
- package/dist/services/permissions.d.ts +14 -14
- package/dist/services/school.d.ts +54 -50
- package/dist/services/school.d.ts.map +1 -1
- package/dist/services/search.d.ts +13 -1
- package/dist/services/search.d.ts.map +1 -1
- package/dist/services/search.js +145 -2
- package/dist/services/search.js.map +1 -1
- package/dist/services/section.d.ts +3 -3
- package/dist/services/tag.d.ts +4 -4
- package/dist/services/user.d.ts +4 -4
- package/dist/services/user.d.ts.map +1 -1
- package/dist/services/user.js +10 -15
- package/dist/services/user.js.map +1 -1
- package/dist/services/userPrivacy.d.ts +86 -0
- package/dist/services/userPrivacy.d.ts.map +1 -0
- package/dist/services/userPrivacy.js +184 -0
- package/dist/services/userPrivacy.js.map +1 -0
- package/dist/services/worksheet.d.ts +86 -57
- package/dist/services/worksheet.d.ts.map +1 -1
- package/dist/services/worksheet.js +57 -5
- package/dist/services/worksheet.js.map +1 -1
- package/dist/utils/email.d.ts.map +1 -1
- package/dist/utils/email.js +7 -3
- package/dist/utils/email.js.map +1 -1
- package/dist/utils/streamBroadcast.d.ts +11 -0
- package/dist/utils/streamBroadcast.d.ts.map +1 -0
- package/dist/utils/streamBroadcast.js +32 -0
- package/dist/utils/streamBroadcast.js.map +1 -0
- package/dist/workers/connection.d.ts +15 -0
- package/dist/workers/connection.d.ts.map +1 -1
- package/dist/workers/connection.js +20 -2
- package/dist/workers/connection.js.map +1 -1
- package/dist/workers/index.js +4 -2
- package/dist/workers/index.js.map +1 -1
- package/dist/workers/processors/createRunJobWorker.d.ts.map +1 -1
- package/dist/workers/processors/createRunJobWorker.js +31 -8
- package/dist/workers/processors/createRunJobWorker.js.map +1 -1
- package/dist/workers/processors/embedContent.d.ts +8 -0
- package/dist/workers/processors/embedContent.d.ts.map +1 -0
- package/dist/workers/processors/embedContent.js +67 -0
- package/dist/workers/processors/embedContent.js.map +1 -0
- package/dist/workers/queues.d.ts +6 -0
- package/dist/workers/queues.d.ts.map +1 -1
- package/dist/workers/queues.js +5 -3
- package/dist/workers/queues.js.map +1 -1
- package/package.json +5 -1
- package/prisma/migrations/20260609215135_init/migration.sql +1412 -0
- package/prisma/migrations/20260610130000_add_worksheet_tags/migration.sql +13 -0
- package/prisma/migrations/20260610140000_expand_embedding_source_types/migration.sql +4 -0
- package/prisma/migrations/20260611120000_add_user_terms_acceptance/migration.sql +3 -0
- package/prisma/schema.prisma +44 -7
- package/src/index.ts +4 -0
- package/src/instrument.ts +1 -1
- package/src/lib/config/cors.ts +32 -9
- package/src/lib/config/env.ts +6 -0
- package/src/lib/googleCloudStorage.ts +21 -0
- package/src/lib/inferDocumentFormat.ts +58 -0
- package/src/lib/legal.ts +2 -0
- package/src/lib/prisma.ts +40 -1
- package/src/models/auth.ts +4 -0
- package/src/models/class.ts +3 -2
- package/src/models/marketing.ts +10 -2
- package/src/models/worksheet.ts +5 -2
- package/src/pipelines/aiLabChat.ts +245 -146
- package/src/pipelines/aiLabChatContract.ts +59 -69
- package/src/pipelines/aiNewtonChat.ts +46 -43
- package/src/pipelines/labChatPrompt.ts +115 -90
- package/src/routers/_app.ts +2 -0
- package/src/routers/auth.ts +13 -0
- package/src/routers/discussion.ts +7 -0
- package/src/routers/embedding.ts +164 -0
- package/src/routers/search.ts +10 -1
- package/src/routers/user.ts +26 -0
- package/src/routers/worksheet.ts +27 -0
- package/src/routes/aiStream.ts +827 -0
- package/src/seedDatabase.ts +4 -4
- package/src/services/aiChatStream.ts +341 -0
- package/src/services/aiModifierTools.ts +341 -0
- package/src/services/aiProposalTools.ts +274 -0
- package/src/services/aiStream.ts +175 -0
- package/src/services/aiTools.ts +290 -0
- package/src/services/announcement.ts +7 -0
- package/src/services/assignment.ts +31 -7
- package/src/services/auth.ts +14 -2
- package/src/services/class.ts +27 -2
- package/src/services/discussion.ts +22 -8
- package/src/services/discussionSummary.ts +226 -0
- package/src/services/embedding.ts +446 -0
- package/src/services/folder.ts +31 -0
- package/src/services/marketing.ts +3 -3
- package/src/services/message.ts +2 -2
- package/src/services/openaiDocumentGen.ts +319 -0
- package/src/services/search.ts +163 -1
- package/src/services/user.ts +9 -17
- package/src/services/userPrivacy.ts +196 -0
- package/src/services/worksheet.ts +77 -3
- package/src/utils/email.ts +5 -1
- package/src/utils/streamBroadcast.ts +48 -0
- package/src/workers/connection.ts +19 -0
- package/src/workers/index.ts +2 -0
- package/src/workers/processors/createRunJobWorker.ts +32 -6
- package/src/workers/processors/embedContent.ts +86 -0
- package/src/workers/queues.ts +5 -1
- package/tests/helpers/registerInput.ts +20 -0
- package/tests/integration/globalSetup.ts +10 -18
- package/tests/integration/routers/auth.test.ts +13 -18
- package/tests/unit/lib/inferDocumentFormat.test.ts +33 -0
- package/tests/unit/pipelines/aiLabChatContract.test.ts +38 -21
- package/tests/unit/pipelines/labChatPrompt.test.ts +23 -36
- package/tests/unit/services/aiChatStream.test.ts +92 -0
- package/tests/unit/services/openaiDocumentGen.test.ts +65 -0
- package/prisma/migrations/20250807062924_init/migration.sql +0 -436
- package/prisma/migrations/20250919063704_init2/migration.sql +0 -24
- package/prisma/migrations/20250920143543_add_profile_fields/migration.sql +0 -15
- package/prisma/migrations/20250922165147_add_chat_models/migration.sql +0 -68
- package/prisma/migrations/20250922165314_add_chat_roles/migration.sql +0 -5
- package/prisma/migrations/20250922171242_refactor_notification_system/migration.sql +0 -20
- package/prisma/migrations/20250922172208_add_mentions_system/migration.sql +0 -21
- package/prisma/migrations/20250925072732_add_lab_chat_system/migration.sql +0 -25
- package/prisma/migrations/20251020151505_add_upload_tracking_fields/migration.sql +0 -57
- package/prisma/migrations/20251109122857_annuoncements_comments/migration.sql +0 -30
- package/prisma/migrations/20251109135555_reactions_announcements_comments/migration.sql +0 -35
- package/prisma/migrations/20260410124000_add_submission_recommendation_state/migration.sql +0 -14
- package/prisma/migrations/20260415103500_add_session_type/migration.sql +0 -33
- package/prisma/migrations/20260415133000_add_ai_usage_logs/migration.sql +0 -19
- package/prisma/migrations/20260417083000_add_conversation_member_hidden_at/migration.sql +0 -2
- package/prisma/migrations/20260502230500_assignment_submission_comments/migration.sql +0 -60
- package/prisma/migrations/20260504071500_school_memberships/migration.sql +0 -81
- package/prisma/migrations/20260506162500_consolidate_run_job_and_unify_job_status/migration.sql +0 -166
- package/prisma/migrations/20260509220000_institution_overhaul/migration.sql +0 -184
- package/prisma/migrations/20260510170000_add_folder_permissions/migration.sql +0 -93
- package/prisma/migrations/20260510183000_add_discussions/migration.sql +0 -151
- /package/.github/workflows/{npm.yml → npm-publish.yml} +0 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
-- CreateTable (implicit many-to-many join for ClassTag <-> Worksheet)
|
|
2
|
+
CREATE TABLE "_WorksheetToTag" (
|
|
3
|
+
"A" TEXT NOT NULL,
|
|
4
|
+
"B" TEXT NOT NULL,
|
|
5
|
+
CONSTRAINT "_WorksheetToTag_A_fkey" FOREIGN KEY ("A") REFERENCES "DiscussionTag"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
|
6
|
+
CONSTRAINT "_WorksheetToTag_B_fkey" FOREIGN KEY ("B") REFERENCES "Worksheet"("id") ON DELETE CASCADE ON UPDATE CASCADE
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
-- CreateIndex
|
|
10
|
+
CREATE UNIQUE INDEX "_WorksheetToTag_AB_unique" ON "_WorksheetToTag"("A", "B");
|
|
11
|
+
|
|
12
|
+
-- CreateIndex
|
|
13
|
+
CREATE INDEX "_WorksheetToTag_B_index" ON "_WorksheetToTag"("B");
|
package/prisma/schema.prisma
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
|
|
6
6
|
|
|
7
7
|
generator client {
|
|
8
|
-
provider
|
|
8
|
+
provider = "prisma-client-js"
|
|
9
|
+
previewFeatures = ["postgresqlExtensions"]
|
|
9
10
|
// output = "../generated/prisma"
|
|
10
11
|
}
|
|
11
12
|
|
|
@@ -15,9 +16,10 @@ generator client {
|
|
|
15
16
|
// }
|
|
16
17
|
|
|
17
18
|
datasource db {
|
|
18
|
-
provider
|
|
19
|
-
url
|
|
20
|
-
directUrl
|
|
19
|
+
provider = "postgresql"
|
|
20
|
+
url = env("DATABASE_URL")
|
|
21
|
+
directUrl = env("DIRECT_URL")
|
|
22
|
+
extensions = [vector]
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
enum AssignmentType {
|
|
@@ -194,6 +196,9 @@ model User {
|
|
|
194
196
|
profile UserProfile?
|
|
195
197
|
verified Boolean @default(false)
|
|
196
198
|
|
|
199
|
+
termsAcceptedAt DateTime?
|
|
200
|
+
termsVersion String?
|
|
201
|
+
|
|
197
202
|
teacherIn Class[] @relation("UserTeacherToClass")
|
|
198
203
|
studentIn Class[] @relation("UserStudentToClass")
|
|
199
204
|
|
|
@@ -321,7 +326,8 @@ model Class {
|
|
|
321
326
|
tags Tag[] @relation("ClassTags")
|
|
322
327
|
permissions Permission[] @relation("ClassPermissions")
|
|
323
328
|
discussions Discussion[]
|
|
324
|
-
|
|
329
|
+
classTags ClassTag[]
|
|
330
|
+
embeddings Embedding[]
|
|
325
331
|
|
|
326
332
|
discussionsEnabled Boolean @default(true)
|
|
327
333
|
studentsCanCreateDiscussions Boolean @default(true)
|
|
@@ -542,7 +548,7 @@ model Discussion {
|
|
|
542
548
|
isLocked Boolean @default(false)
|
|
543
549
|
allowReplies Boolean @default(true)
|
|
544
550
|
attachments File[] @relation("DiscussionFiles")
|
|
545
|
-
tags
|
|
551
|
+
tags ClassTag[] @relation("DiscussionToTag")
|
|
546
552
|
replies DiscussionReply[]
|
|
547
553
|
reactions Reaction[]
|
|
548
554
|
reads DiscussionRead[]
|
|
@@ -575,15 +581,17 @@ model DiscussionReply {
|
|
|
575
581
|
@@index([parentId])
|
|
576
582
|
}
|
|
577
583
|
|
|
578
|
-
model
|
|
584
|
+
model ClassTag {
|
|
579
585
|
id String @id @default(uuid())
|
|
580
586
|
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
|
|
581
587
|
classId String
|
|
582
588
|
name String
|
|
583
589
|
color String @default("#3B82F6")
|
|
584
590
|
discussions Discussion[] @relation("DiscussionToTag")
|
|
591
|
+
worksheets Worksheet[] @relation("WorksheetToTag")
|
|
585
592
|
|
|
586
593
|
@@unique([classId, name])
|
|
594
|
+
@@map("DiscussionTag")
|
|
587
595
|
}
|
|
588
596
|
|
|
589
597
|
model DiscussionRead {
|
|
@@ -861,6 +869,7 @@ model Worksheet {
|
|
|
861
869
|
runJob RunJob? @relation("WorksheetRunJob", fields: [runJobId], references: [id], onDelete: SetNull)
|
|
862
870
|
|
|
863
871
|
status JobStatus? @default(NOT_STARTED)
|
|
872
|
+
tags ClassTag[] @relation("WorksheetToTag")
|
|
864
873
|
|
|
865
874
|
@@unique([classId, sourceUrl])
|
|
866
875
|
}
|
|
@@ -962,6 +971,34 @@ enum RunJobType {
|
|
|
962
971
|
AI_LAB_CHAT
|
|
963
972
|
AI_NEWTON_CHAT
|
|
964
973
|
STUDENT_PROGRESS_CHAT
|
|
974
|
+
EMBED_CONTENT
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
enum EmbeddingSourceType {
|
|
978
|
+
FILE
|
|
979
|
+
ASSIGNMENT
|
|
980
|
+
WORKSHEET
|
|
981
|
+
ANNOUNCEMENT
|
|
982
|
+
SECTION
|
|
983
|
+
DISCUSSION
|
|
984
|
+
SUBMISSION
|
|
985
|
+
SYLLABUS
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
model Embedding {
|
|
989
|
+
id String @id @default(uuid())
|
|
990
|
+
classId String
|
|
991
|
+
sourceType EmbeddingSourceType
|
|
992
|
+
sourceId String
|
|
993
|
+
content String
|
|
994
|
+
vector Unsupported("vector(1536)")
|
|
995
|
+
metadata Json? @default("{}")
|
|
996
|
+
createdAt DateTime @default(now())
|
|
997
|
+
|
|
998
|
+
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
|
|
999
|
+
|
|
1000
|
+
@@index([classId])
|
|
1001
|
+
@@index([sourceType, sourceId])
|
|
965
1002
|
}
|
|
966
1003
|
|
|
967
1004
|
enum RunJobStatus {
|
package/src/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ import { v4 as uuidv4 } from 'uuid';
|
|
|
27
27
|
|
|
28
28
|
import "./instrument.js";
|
|
29
29
|
import { openAIClient } from './utils/inference.js';
|
|
30
|
+
import { aiStreamRouter } from './routes/aiStream.js';
|
|
30
31
|
|
|
31
32
|
const app = express();
|
|
32
33
|
|
|
@@ -313,6 +314,9 @@ io.engine.on('connection_error', (err: Error) => {
|
|
|
313
314
|
// Setup socket handlers
|
|
314
315
|
setupSocketHandlers(io);
|
|
315
316
|
|
|
317
|
+
// AI streaming routes
|
|
318
|
+
app.use('/api/ai', aiStreamRouter);
|
|
319
|
+
|
|
316
320
|
// File serving endpoint for secure file access
|
|
317
321
|
app.get('/api/files/:fileId', async (req, res) => {
|
|
318
322
|
try {
|
package/src/instrument.ts
CHANGED
|
@@ -8,7 +8,7 @@ if (env.NODE_ENV !== 'test') {
|
|
|
8
8
|
environment: env.NODE_ENV || 'development',
|
|
9
9
|
// Setting this option to true will send default PII data to Sentry.
|
|
10
10
|
// For example, automatic IP address collection on events
|
|
11
|
-
sendDefaultPii:
|
|
11
|
+
sendDefaultPii: false,
|
|
12
12
|
// @todo: disable in test environment
|
|
13
13
|
enabled: true, // Explicitly disable in test environment
|
|
14
14
|
});
|
package/src/lib/config/cors.ts
CHANGED
|
@@ -5,18 +5,24 @@ type CorsEnv = {
|
|
|
5
5
|
CORS_ALLOWED_ORIGIN_PATTERNS?: string;
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
// First-party Studious domains. These are always allowed, regardless of
|
|
9
|
+
// NODE_ENV, so local/dev servers can still serve the real frontends.
|
|
10
|
+
const studiousOrigins = [
|
|
9
11
|
'https://www.studious.sh',
|
|
10
12
|
'https://studious.sh',
|
|
11
13
|
'https://dev.studious.sh',
|
|
12
|
-
|
|
13
|
-
'
|
|
14
|
-
'
|
|
15
|
-
'
|
|
16
|
-
'
|
|
14
|
+
'https://superuser.studious.sh',
|
|
15
|
+
'https://*.studious.sh',
|
|
16
|
+
'https://www.studious.ac',
|
|
17
|
+
'https://studious.ac',
|
|
18
|
+
'https://dev.studious.ac',
|
|
19
|
+
'https://superuser.studious.ac',
|
|
20
|
+
'https://*.studious.ac',
|
|
17
21
|
];
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
// Local development frontends.
|
|
24
|
+
// @todo: gate these behind a non-production check before real prod.
|
|
25
|
+
const localhostOrigins = [
|
|
20
26
|
'http://localhost:3000',
|
|
21
27
|
'http://localhost:3001',
|
|
22
28
|
'http://127.0.0.1:3000',
|
|
@@ -40,6 +46,17 @@ const parseList = (value?: string) =>
|
|
|
40
46
|
|
|
41
47
|
const isDefined = <T>(value: T | undefined | null): value is T => Boolean(value);
|
|
42
48
|
|
|
49
|
+
const hasWildcard = (value: string) => value.includes('*');
|
|
50
|
+
|
|
51
|
+
// Turn a wildcard origin like `https://*.studious.ac` into an anchored regex.
|
|
52
|
+
// `*` matches one or more subdomain labels (e.g. `app` or `a.b`).
|
|
53
|
+
const wildcardOriginToRegExp = (value: string) => {
|
|
54
|
+
const escaped = value
|
|
55
|
+
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
56
|
+
.replace(/\\\*/g, '[a-z0-9-]+(?:\\.[a-z0-9-]+)*');
|
|
57
|
+
return new RegExp(`^${escaped}$`, 'i');
|
|
58
|
+
};
|
|
59
|
+
|
|
43
60
|
const toCorsOrigin = (value: string) => {
|
|
44
61
|
try {
|
|
45
62
|
return new URL(value).origin;
|
|
@@ -79,14 +96,20 @@ export const isValidCorsOriginPatternList = (patterns?: string) => {
|
|
|
79
96
|
};
|
|
80
97
|
|
|
81
98
|
export const createCorsOriginMatcher = (env: CorsEnv) => {
|
|
82
|
-
const
|
|
83
|
-
...
|
|
99
|
+
const configuredOrigins = Array.from(new Set([
|
|
100
|
+
...studiousOrigins,
|
|
101
|
+
...localhostOrigins,
|
|
84
102
|
env.NEXT_PUBLIC_APP_URL ? toCorsOrigin(env.NEXT_PUBLIC_APP_URL) : undefined,
|
|
85
103
|
...parseCorsOrigins(env.CORS_ALLOWED_ORIGINS),
|
|
86
104
|
].filter(isDefined)));
|
|
87
105
|
|
|
106
|
+
// Wildcard entries (e.g. `https://*.studious.ac`) can't be matched by exact
|
|
107
|
+
// string comparison, so they're compiled into regex patterns instead.
|
|
108
|
+
const allowedOrigins = configuredOrigins.filter((origin) => !hasWildcard(origin));
|
|
109
|
+
|
|
88
110
|
const allowedOriginPatterns = [
|
|
89
111
|
vercelAppOriginPattern,
|
|
112
|
+
...configuredOrigins.filter(hasWildcard).map(wildcardOriginToRegExp),
|
|
90
113
|
...parseCorsOriginPatterns(env.CORS_ALLOWED_ORIGIN_PATTERNS),
|
|
91
114
|
];
|
|
92
115
|
|
package/src/lib/config/env.ts
CHANGED
|
@@ -35,6 +35,12 @@ const baseSchema = z.object({
|
|
|
35
35
|
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
|
|
36
36
|
PORT: z.string().transform(Number).default('3001'),
|
|
37
37
|
DATABASE_URL: z.string().url('DATABASE_URL must be a valid URL'),
|
|
38
|
+
/**
|
|
39
|
+
* Optional BullMQ key prefix. Defaults to `bull:<NODE_ENV>` (see
|
|
40
|
+
* `getBullPrefix`). Set explicitly to isolate queues when multiple
|
|
41
|
+
* databases share one Redis instance (e.g. several devs on one Upstash).
|
|
42
|
+
*/
|
|
43
|
+
BULL_PREFIX: z.string().optional(),
|
|
38
44
|
});
|
|
39
45
|
|
|
40
46
|
// Production/development schema with all required vars
|
|
@@ -98,4 +98,25 @@ export async function copyFile(sourcePath: string, destPath: string): Promise<vo
|
|
|
98
98
|
message: 'Failed to copy file in storage',
|
|
99
99
|
});
|
|
100
100
|
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Returns the public URL for a GCS object.
|
|
105
|
+
* The object must be publicly readable (via ACL or bucket-level IAM).
|
|
106
|
+
*/
|
|
107
|
+
export function getPublicUrl(filePath: string): string {
|
|
108
|
+
return `https://storage.googleapis.com/${env.GOOGLE_CLOUD_BUCKET_NAME}/${filePath}`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Best-effort attempt to make a GCS object publicly readable.
|
|
113
|
+
* Silently ignores errors (e.g. uniform bucket-level access enabled).
|
|
114
|
+
*/
|
|
115
|
+
export async function makeFilePublic(filePath: string): Promise<void> {
|
|
116
|
+
try {
|
|
117
|
+
await bucket.file(filePath).makePublic();
|
|
118
|
+
} catch {
|
|
119
|
+
// Bucket may use uniform access or already have a public IAM policy – either way, the
|
|
120
|
+
// public URL will work if the bucket allows it; nothing to do here.
|
|
121
|
+
}
|
|
101
122
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { DocumentFormat } from '../services/openaiDocumentGen.js';
|
|
2
|
+
import { normalizeDocumentFormat } from '../services/openaiDocumentGen.js';
|
|
3
|
+
|
|
4
|
+
type FormatRule = { format: DocumentFormat; pattern: RegExp };
|
|
5
|
+
|
|
6
|
+
/** Ordered most-specific first. */
|
|
7
|
+
const FORMAT_RULES: FormatRule[] = [
|
|
8
|
+
{ format: 'pptx', pattern: /\b(presentation|powerpoint|power point|slides?|slide deck|deck|pptx|\.pptx)\b/i },
|
|
9
|
+
{ format: 'xlsx', pattern: /\b(spreadsheet|excel|workbook|xlsx|gradebook|grade book|\.xlsx)\b/i },
|
|
10
|
+
{ format: 'csv', pattern: /\b(csv|comma[- ]separated|\.csv)\b/i },
|
|
11
|
+
{ format: 'docx', pattern: /\b(word doc(ument)?|docx|editable handout|\.docx)\b/i },
|
|
12
|
+
{ format: 'png', pattern: /\b(diagram|chart|graph|plot|figure|infographic|\.png)\b/i },
|
|
13
|
+
{ format: 'md', pattern: /\b(markdown|readme|\.md)\b/i },
|
|
14
|
+
{ format: 'html', pattern: /\b(html|web ?page|\.html)\b/i },
|
|
15
|
+
{ format: 'txt', pattern: /\b(plain text|text file|\.txt)\b/i },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
function inferFromText(...parts: Array<string | null | undefined>): DocumentFormat | null {
|
|
19
|
+
const hay = parts.filter(Boolean).join('\n');
|
|
20
|
+
if (!hay.trim()) return null;
|
|
21
|
+
|
|
22
|
+
for (const { format, pattern } of FORMAT_RULES) {
|
|
23
|
+
if (pattern.test(hay)) return format;
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the output format for a generated document.
|
|
30
|
+
*
|
|
31
|
+
* The lab model often passes `format: "pdf"` even for presentations/spreadsheets.
|
|
32
|
+
* Keyword signals from the teacher request, title, and body take precedence over
|
|
33
|
+
* a generic pdf default; a non-pdf explicit format is always respected.
|
|
34
|
+
*/
|
|
35
|
+
export function inferDocumentFormat(
|
|
36
|
+
title: string,
|
|
37
|
+
content: string,
|
|
38
|
+
explicit?: string | null,
|
|
39
|
+
userRequest?: string | null,
|
|
40
|
+
): DocumentFormat {
|
|
41
|
+
const explicitNorm = explicit?.trim()
|
|
42
|
+
? normalizeDocumentFormat(explicit)
|
|
43
|
+
: null;
|
|
44
|
+
|
|
45
|
+
const fromKeywords = inferFromText(userRequest, title, content);
|
|
46
|
+
|
|
47
|
+
// Non-pdf explicit choice wins (model or tool args).
|
|
48
|
+
if (explicitNorm && explicitNorm !== 'pdf') {
|
|
49
|
+
return explicitNorm;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Override pdf when the request clearly asks for slides, spreadsheets, etc.
|
|
53
|
+
if (fromKeywords && fromKeywords !== 'pdf') {
|
|
54
|
+
return fromKeywords;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return explicitNorm ?? 'pdf';
|
|
58
|
+
}
|
package/src/lib/legal.ts
ADDED
package/src/lib/prisma.ts
CHANGED
|
@@ -30,6 +30,19 @@ const getDatabaseUrl = () => {
|
|
|
30
30
|
}
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Resolve a profile-picture value stored in the DB to a browser-loadable URL.
|
|
35
|
+
* - `null` / empty → `null`
|
|
36
|
+
* - already an HTTP URL (e.g. dicebear) → returned as-is
|
|
37
|
+
* - raw GCS object path → converted to a public GCS URL
|
|
38
|
+
*/
|
|
39
|
+
function resolveProfilePictureUrl(raw: string | null): string | null {
|
|
40
|
+
if (!raw) return null;
|
|
41
|
+
if (raw.startsWith('http')) return raw;
|
|
42
|
+
const bucket = env.GOOGLE_CLOUD_BUCKET_NAME;
|
|
43
|
+
return `https://storage.googleapis.com/${bucket}/${raw}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
33
46
|
const prismaClientSingleton = () => {
|
|
34
47
|
return new PrismaClient({
|
|
35
48
|
datasources: {
|
|
@@ -37,6 +50,23 @@ const prismaClientSingleton = () => {
|
|
|
37
50
|
url: getDatabaseUrl(),
|
|
38
51
|
},
|
|
39
52
|
},
|
|
53
|
+
}).$extends({
|
|
54
|
+
result: {
|
|
55
|
+
userProfile: {
|
|
56
|
+
profilePicture: {
|
|
57
|
+
needs: { profilePicture: true },
|
|
58
|
+
compute(profile) {
|
|
59
|
+
return resolveProfilePictureUrl(profile.profilePicture);
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
profilePictureThumbnail: {
|
|
63
|
+
needs: { profilePictureThumbnail: true },
|
|
64
|
+
compute(profile) {
|
|
65
|
+
return resolveProfilePictureUrl(profile.profilePictureThumbnail);
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
40
70
|
});
|
|
41
71
|
};
|
|
42
72
|
|
|
@@ -53,4 +83,13 @@ if (env.NODE_ENV !== 'production') {
|
|
|
53
83
|
|
|
54
84
|
process.on('beforeExit', async () => {
|
|
55
85
|
await prisma.$disconnect();
|
|
56
|
-
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
/** The concrete (extended) Prisma client type used throughout the app. */
|
|
89
|
+
export type AppPrismaClient = typeof prisma;
|
|
90
|
+
|
|
91
|
+
/** Transaction client type compatible with the extended Prisma client. */
|
|
92
|
+
export type AppTransactionClient = Omit<
|
|
93
|
+
AppPrismaClient,
|
|
94
|
+
'$connect' | '$disconnect' | '$on' | '$transaction' | '$use' | '$extends'
|
|
95
|
+
>;
|
package/src/models/auth.ts
CHANGED
|
@@ -102,6 +102,8 @@ export function createUser(data: {
|
|
|
102
102
|
email: string;
|
|
103
103
|
password: string;
|
|
104
104
|
verified?: boolean;
|
|
105
|
+
termsAcceptedAt?: Date;
|
|
106
|
+
termsVersion?: string;
|
|
105
107
|
}) {
|
|
106
108
|
return prisma.user.create({
|
|
107
109
|
data: {
|
|
@@ -109,6 +111,8 @@ export function createUser(data: {
|
|
|
109
111
|
email: data.email,
|
|
110
112
|
password: data.password,
|
|
111
113
|
verified: data.verified ?? false,
|
|
114
|
+
termsAcceptedAt: data.termsAcceptedAt,
|
|
115
|
+
termsVersion: data.termsVersion,
|
|
112
116
|
profile: { create: {} },
|
|
113
117
|
},
|
|
114
118
|
select: {
|
package/src/models/class.ts
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { Assignment, Class, Folder, GradingBoundary, MarkScheme, Section, SessionType, Worksheet } from "@prisma/client";
|
|
8
|
-
import type {
|
|
8
|
+
import type { WorksheetQuestion } from "@prisma/client";
|
|
9
|
+
import type { AppPrismaClient, AppTransactionClient } from "../lib/prisma.js";
|
|
9
10
|
|
|
10
11
|
import { prisma } from "../lib/prisma.js";
|
|
11
12
|
import { v4 as uuidv4 } from 'uuid';
|
|
@@ -477,7 +478,7 @@ async function recursivelyCopyFilesToGcs(folderId: string): Promise<Map<string,
|
|
|
477
478
|
}
|
|
478
479
|
|
|
479
480
|
async function recursivelyCreateFiles(
|
|
480
|
-
tx:
|
|
481
|
+
tx: AppPrismaClient | AppTransactionClient,
|
|
481
482
|
folderId: string,
|
|
482
483
|
targetFolderId: string | undefined,
|
|
483
484
|
pathMap: Map<string, string>
|
package/src/models/marketing.ts
CHANGED
|
@@ -27,10 +27,18 @@ export function createSchoolDevelopmentProgram(data: {
|
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
/**
|
|
31
|
-
export function
|
|
30
|
+
/** Public status lookup — omits contact PII (email, phone, address). */
|
|
31
|
+
export function findSchoolDevelopmentProgramStatusById(id: string) {
|
|
32
32
|
return prisma.schoolDevelopementProgram.findUnique({
|
|
33
33
|
where: { id },
|
|
34
|
+
select: {
|
|
35
|
+
id: true,
|
|
36
|
+
name: true,
|
|
37
|
+
city: true,
|
|
38
|
+
country: true,
|
|
39
|
+
status: true,
|
|
40
|
+
submittedAt: true,
|
|
41
|
+
},
|
|
34
42
|
});
|
|
35
43
|
}
|
|
36
44
|
|
package/src/models/worksheet.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Worksheet model – worksheets, questions, submissions, student progress, grading.
|
|
3
3
|
*/
|
|
4
|
-
import type { Prisma } from "@prisma/client";
|
|
5
4
|
import { prisma } from "../lib/prisma.js";
|
|
5
|
+
import type { AppTransactionClient } from "../lib/prisma.js";
|
|
6
6
|
import type { WorksheetQuestionType, JobStatus } from "@prisma/client";
|
|
7
7
|
import {
|
|
8
8
|
classMemberOrSchoolAdminWhere,
|
|
@@ -45,6 +45,7 @@ export function findWorksheetByIdForClassMember(id: string, userId: string) {
|
|
|
45
45
|
include: {
|
|
46
46
|
questions: { orderBy: { order: "asc" as const } },
|
|
47
47
|
class: true,
|
|
48
|
+
tags: true,
|
|
48
49
|
},
|
|
49
50
|
});
|
|
50
51
|
}
|
|
@@ -59,6 +60,7 @@ export function findWorksheetByIdForTeacher(id: string, userId: string) {
|
|
|
59
60
|
include: {
|
|
60
61
|
questions: { orderBy: { order: "asc" as const } },
|
|
61
62
|
class: true,
|
|
63
|
+
tags: true,
|
|
62
64
|
},
|
|
63
65
|
});
|
|
64
66
|
}
|
|
@@ -69,6 +71,7 @@ export function findWorksheetsByClassId(classId: string) {
|
|
|
69
71
|
where: { classId },
|
|
70
72
|
include: {
|
|
71
73
|
questions: { select: { id: true } },
|
|
74
|
+
tags: true,
|
|
72
75
|
},
|
|
73
76
|
orderBy: { createdAt: "desc" },
|
|
74
77
|
});
|
|
@@ -121,7 +124,7 @@ export function deleteWorksheet(id: string) {
|
|
|
121
124
|
* Does not copy student responses or assignment links.
|
|
122
125
|
*/
|
|
123
126
|
export async function duplicateWorksheetWithClient(
|
|
124
|
-
tx:
|
|
127
|
+
tx: AppTransactionClient,
|
|
125
128
|
sourceWorksheetId: string,
|
|
126
129
|
targetClassId: string,
|
|
127
130
|
name: string
|