@studious-lms/server 1.16.0 → 1.16.1

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.
Files changed (151) hide show
  1. package/.github/workflows/deploy.yml +1 -1
  2. package/dist/instrument.js +3 -3
  3. package/dist/instrument.js.map +1 -1
  4. package/dist/lib/chatAttachmentPrompt.d.ts +47 -0
  5. package/dist/lib/chatAttachmentPrompt.d.ts.map +1 -0
  6. package/dist/lib/chatAttachmentPrompt.js +190 -0
  7. package/dist/lib/chatAttachmentPrompt.js.map +1 -0
  8. package/dist/lib/inferDocumentFormat.js +3 -3
  9. package/dist/lib/inferDocumentFormat.js.map +1 -1
  10. package/dist/lib/legal.d.ts +3 -0
  11. package/dist/lib/legal.d.ts.map +1 -0
  12. package/dist/lib/legal.js +6 -0
  13. package/dist/lib/legal.js.map +1 -0
  14. package/dist/models/announcement.d.ts +2 -0
  15. package/dist/models/announcement.d.ts.map +1 -1
  16. package/dist/models/auth.d.ts +8 -0
  17. package/dist/models/auth.d.ts.map +1 -1
  18. package/dist/models/auth.js +4 -2
  19. package/dist/models/auth.js.map +1 -1
  20. package/dist/models/event.d.ts +2 -0
  21. package/dist/models/event.d.ts.map +1 -1
  22. package/dist/models/file.d.ts +24 -0
  23. package/dist/models/file.d.ts.map +1 -1
  24. package/dist/models/marketing.d.ts +2 -15
  25. package/dist/models/marketing.d.ts.map +1 -1
  26. package/dist/models/marketing.js +12 -4
  27. package/dist/models/marketing.js.map +1 -1
  28. package/dist/pipelines/aiLabChat.d.ts.map +1 -1
  29. package/dist/pipelines/aiLabChat.js +39 -59
  30. package/dist/pipelines/aiLabChat.js.map +1 -1
  31. package/dist/pipelines/aiLabChatContract.d.ts +2 -2
  32. package/dist/pipelines/aiNewtonChat.d.ts +1 -1
  33. package/dist/pipelines/aiNewtonChat.d.ts.map +1 -1
  34. package/dist/pipelines/aiNewtonChat.js +20 -19
  35. package/dist/pipelines/aiNewtonChat.js.map +1 -1
  36. package/dist/pipelines/gradeWorksheet.d.ts +1 -0
  37. package/dist/pipelines/gradeWorksheet.d.ts.map +1 -1
  38. package/dist/pipelines/gradeWorksheet.js +84 -13
  39. package/dist/pipelines/gradeWorksheet.js.map +1 -1
  40. package/dist/pipelines/labChatPrompt.d.ts +4 -20
  41. package/dist/pipelines/labChatPrompt.d.ts.map +1 -1
  42. package/dist/pipelines/labChatPrompt.js +24 -64
  43. package/dist/pipelines/labChatPrompt.js.map +1 -1
  44. package/dist/routers/_app.d.ts +236 -26
  45. package/dist/routers/_app.d.ts.map +1 -1
  46. package/dist/routers/assignment.d.ts +10 -0
  47. package/dist/routers/assignment.d.ts.map +1 -1
  48. package/dist/routers/auth.d.ts +2 -0
  49. package/dist/routers/auth.d.ts.map +1 -1
  50. package/dist/routers/auth.js +15 -2
  51. package/dist/routers/auth.js.map +1 -1
  52. package/dist/routers/class.d.ts +4 -0
  53. package/dist/routers/class.d.ts.map +1 -1
  54. package/dist/routers/event.d.ts +2 -0
  55. package/dist/routers/event.d.ts.map +1 -1
  56. package/dist/routers/marketing.d.ts +0 -13
  57. package/dist/routers/marketing.d.ts.map +1 -1
  58. package/dist/routers/school.d.ts +4 -0
  59. package/dist/routers/school.d.ts.map +1 -1
  60. package/dist/routers/user.d.ts +89 -0
  61. package/dist/routers/user.d.ts.map +1 -1
  62. package/dist/routers/user.js +19 -2
  63. package/dist/routers/user.js.map +1 -1
  64. package/dist/routers/worksheet.d.ts +7 -0
  65. package/dist/routers/worksheet.d.ts.map +1 -1
  66. package/dist/routers/worksheet.js +8 -3
  67. package/dist/routers/worksheet.js.map +1 -1
  68. package/dist/seedDatabase.d.ts +2 -0
  69. package/dist/seedDatabase.d.ts.map +1 -1
  70. package/dist/services/aiChatStream.d.ts +2 -0
  71. package/dist/services/aiChatStream.d.ts.map +1 -1
  72. package/dist/services/aiChatStream.js +31 -4
  73. package/dist/services/aiChatStream.js.map +1 -1
  74. package/dist/services/aiProposalTools.d.ts.map +1 -1
  75. package/dist/services/aiProposalTools.js +9 -9
  76. package/dist/services/aiProposalTools.js.map +1 -1
  77. package/dist/services/aiTools.js +3 -3
  78. package/dist/services/aiTools.js.map +1 -1
  79. package/dist/services/assignment.d.ts +10 -0
  80. package/dist/services/assignment.d.ts.map +1 -1
  81. package/dist/services/auth.d.ts +2 -0
  82. package/dist/services/auth.d.ts.map +1 -1
  83. package/dist/services/auth.js +13 -4
  84. package/dist/services/auth.js.map +1 -1
  85. package/dist/services/class.d.ts +4 -0
  86. package/dist/services/class.d.ts.map +1 -1
  87. package/dist/services/event.d.ts +2 -0
  88. package/dist/services/event.d.ts.map +1 -1
  89. package/dist/services/marketing.d.ts +1 -14
  90. package/dist/services/marketing.d.ts.map +1 -1
  91. package/dist/services/marketing.js +5 -5
  92. package/dist/services/marketing.js.map +1 -1
  93. package/dist/services/newtonChat.d.ts.map +1 -1
  94. package/dist/services/newtonChat.js +2 -8
  95. package/dist/services/newtonChat.js.map +1 -1
  96. package/dist/services/openaiDocumentGen.d.ts +12 -0
  97. package/dist/services/openaiDocumentGen.d.ts.map +1 -1
  98. package/dist/services/openaiDocumentGen.js +173 -65
  99. package/dist/services/openaiDocumentGen.js.map +1 -1
  100. package/dist/services/school.d.ts +4 -0
  101. package/dist/services/school.d.ts.map +1 -1
  102. package/dist/services/userPrivacy.d.ts +86 -0
  103. package/dist/services/userPrivacy.d.ts.map +1 -0
  104. package/dist/services/userPrivacy.js +184 -0
  105. package/dist/services/userPrivacy.js.map +1 -0
  106. package/dist/services/worksheet.d.ts +1 -0
  107. package/dist/services/worksheet.d.ts.map +1 -1
  108. package/dist/services/worksheet.js +13 -3
  109. package/dist/services/worksheet.js.map +1 -1
  110. package/dist/utils/email.d.ts.map +1 -1
  111. package/dist/utils/email.js +7 -3
  112. package/dist/utils/email.js.map +1 -1
  113. package/dist/workers/processors/aiNewtonChat.d.ts.map +1 -1
  114. package/dist/workers/processors/aiNewtonChat.js +3 -3
  115. package/dist/workers/processors/aiNewtonChat.js.map +1 -1
  116. package/package.json +2 -1
  117. package/prisma/migrations/20260611120000_add_user_terms_acceptance/migration.sql +3 -0
  118. package/prisma/schema.prisma +3 -0
  119. package/src/instrument.ts +1 -1
  120. package/src/lib/chatAttachmentPrompt.ts +270 -0
  121. package/src/lib/inferDocumentFormat.ts +1 -1
  122. package/src/lib/legal.ts +2 -0
  123. package/src/models/auth.ts +4 -0
  124. package/src/models/marketing.ts +10 -2
  125. package/src/pipelines/aiLabChat.ts +46 -71
  126. package/src/pipelines/aiNewtonChat.ts +20 -23
  127. package/src/pipelines/gradeWorksheet.ts +87 -11
  128. package/src/pipelines/labChatPrompt.ts +29 -98
  129. package/src/routers/auth.ts +13 -0
  130. package/src/routers/user.ts +26 -0
  131. package/src/routers/worksheet.ts +11 -0
  132. package/src/services/aiChatStream.ts +30 -3
  133. package/src/services/aiProposalTools.ts +8 -7
  134. package/src/services/aiTools.ts +1 -1
  135. package/src/services/auth.ts +14 -2
  136. package/src/services/marketing.ts +3 -3
  137. package/src/services/newtonChat.ts +0 -7
  138. package/src/services/openaiDocumentGen.ts +190 -66
  139. package/src/services/userPrivacy.ts +196 -0
  140. package/src/services/worksheet.ts +20 -1
  141. package/src/utils/email.ts +5 -1
  142. package/src/workers/processors/aiNewtonChat.ts +0 -1
  143. package/tests/helpers/registerInput.ts +20 -0
  144. package/tests/integration/globalSetup.ts +10 -18
  145. package/tests/integration/routers/auth.test.ts +13 -18
  146. package/tests/integration/routers/newtonChat.test.ts +9 -8
  147. package/tests/unit/lib/chatAttachmentPrompt.test.ts +135 -0
  148. package/tests/unit/lib/inferDocumentFormat.test.ts +10 -0
  149. package/tests/unit/pipelines/labChatPrompt.test.ts +6 -6
  150. package/tests/unit/services/aiChatStream.test.ts +92 -0
  151. package/tests/unit/services/openaiDocumentGen.test.ts +65 -0
@@ -120,4 +120,4 @@ jobs:
120
120
  --image ${{ needs.build.outputs.image_uri }} \
121
121
  --region ${{ matrix.region }} \
122
122
  --command node \
123
- --args dist/worker.js
123
+ --args dist/workers/index.js
@@ -1,5 +1,5 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="eedba78e-175f-5c0b-809f-3af467929bd2")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="5806dd47-56ef-5a55-a383-1c966636efbf")}catch(e){}}();
3
3
  import * as Sentry from "@sentry/node";
4
4
  import { env } from "./lib/config/env.js";
5
5
  // Only initialize Sentry in non-test environments
@@ -9,10 +9,10 @@ if (env.NODE_ENV !== 'test') {
9
9
  environment: env.NODE_ENV || 'development',
10
10
  // Setting this option to true will send default PII data to Sentry.
11
11
  // For example, automatic IP address collection on events
12
- sendDefaultPii: true,
12
+ sendDefaultPii: false,
13
13
  // @todo: disable in test environment
14
14
  enabled: true, // Explicitly disable in test environment
15
15
  });
16
16
  }
17
17
  //# sourceMappingURL=instrument.js.map
18
- //# debugId=eedba78e-175f-5c0b-809f-3af467929bd2
18
+ //# debugId=5806dd47-56ef-5a55-a383-1c966636efbf
@@ -1 +1 @@
1
- {"version":3,"file":"instrument.js","sources":["instrument.ts"],"sourceRoot":"/","sourcesContent":["import * as Sentry from \"@sentry/node\";\nimport { env } from \"./lib/config/env.js\";\n\n// Only initialize Sentry in non-test environments\nif (env.NODE_ENV !== 'test') {\n Sentry.init({\n dsn: env.SENTRY_DSN,\n environment: env.NODE_ENV || 'development',\n // Setting this option to true will send default PII data to Sentry.\n // For example, automatic IP address collection on events\n sendDefaultPii: true,\n // @todo: disable in test environment\n enabled: true, // Explicitly disable in test environment\n });\n}\n"],"names":[],"mappings":";;AAAA,OAAO,KAAK,MAAM,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAE1C,kDAAkD;AAClD,IAAI,GAAG,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;IAC5B,MAAM,CAAC,IAAI,CAAC;QACV,GAAG,EAAE,GAAG,CAAC,UAAU;QACnB,WAAW,EAAE,GAAG,CAAC,QAAQ,IAAI,aAAa;QAC1C,oEAAoE;QACpE,yDAAyD;QACzD,cAAc,EAAE,IAAI;QACpB,qCAAqC;QACrC,OAAO,EAAE,IAAI,EAAE,yCAAyC;KACzD,CAAC,CAAC;AACL,CAAC","debug_id":"eedba78e-175f-5c0b-809f-3af467929bd2"}
1
+ {"version":3,"file":"instrument.js","sources":["instrument.ts"],"sourceRoot":"/","sourcesContent":["import * as Sentry from \"@sentry/node\";\nimport { env } from \"./lib/config/env.js\";\n\n// Only initialize Sentry in non-test environments\nif (env.NODE_ENV !== 'test') {\n Sentry.init({\n dsn: env.SENTRY_DSN,\n environment: env.NODE_ENV || 'development',\n // Setting this option to true will send default PII data to Sentry.\n // For example, automatic IP address collection on events\n sendDefaultPii: false,\n // @todo: disable in test environment\n enabled: true, // Explicitly disable in test environment\n });\n}\n"],"names":[],"mappings":";;AAAA,OAAO,KAAK,MAAM,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAE1C,kDAAkD;AAClD,IAAI,GAAG,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;IAC5B,MAAM,CAAC,IAAI,CAAC;QACV,GAAG,EAAE,GAAG,CAAC,UAAU;QACnB,WAAW,EAAE,GAAG,CAAC,QAAQ,IAAI,aAAa;QAC1C,oEAAoE;QACpE,yDAAyD;QACzD,cAAc,EAAE,KAAK;QACrB,qCAAqC;QACrC,OAAO,EAAE,IAAI,EAAE,yCAAyC;KACzD,CAAC,CAAC;AACL,CAAC","debug_id":"5806dd47-56ef-5a55-a383-1c966636efbf"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Shared chat attachment handling for Newton + Lab pipelines.
3
+ *
4
+ * - Images + PDFs: inlined as multimodal parts (bytes from GCS).
5
+ * - Office docs, spreadsheets, slides, text, csv, json, html: text extracted
6
+ * via officeparser / UTF-8 decode and appended to the user message.
7
+ * - Audio/video/zip: filename mention only (no extraction).
8
+ */
9
+ import type { ModelMessage, UserContent } from 'ai';
10
+ /** Model used for AI chats that may include document attachments. */
11
+ export declare const AI_CHAT_MODEL = "gpt-5.5";
12
+ export type ChatAttachmentRef = {
13
+ id: string;
14
+ name: string;
15
+ type: string;
16
+ path: string;
17
+ };
18
+ export type LoadedChatAttachment = ChatAttachmentRef & {
19
+ data?: Uint8Array;
20
+ extractedText?: string;
21
+ };
22
+ export type ChatHistoryMessage = {
23
+ id?: string;
24
+ content: string;
25
+ senderId: string;
26
+ createdAt?: Date | string | number;
27
+ sender?: {
28
+ username?: string | null;
29
+ profile?: {
30
+ displayName?: string | null;
31
+ } | null;
32
+ } | null;
33
+ attachments?: ChatAttachmentRef[];
34
+ };
35
+ /** Download bytes from GCS and extract text for every supported attachment type. */
36
+ export declare function loadChatAttachments(attachments: ChatAttachmentRef[]): Promise<LoadedChatAttachment[]>;
37
+ /** @deprecated Use {@link loadChatAttachments} */
38
+ export declare const loadPromptAttachments: typeof loadChatAttachments;
39
+ export declare function buildUserMessageContentWithAttachments(textContent: string, attachments: LoadedChatAttachment[]): UserContent;
40
+ export declare function sortChatMessagesChronologically<T extends ChatHistoryMessage>(messages: T[]): T[];
41
+ /** Build ModelMessage[] with shared attachment loading for Newton + Lab. */
42
+ export declare function buildChatModelMessages({ messages, isAIUser, defaultSenderLabel, }: {
43
+ messages: ChatHistoryMessage[];
44
+ isAIUser: (senderId: string) => boolean;
45
+ defaultSenderLabel: string;
46
+ }): Promise<ModelMessage[]>;
47
+ //# sourceMappingURL=chatAttachmentPrompt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chatAttachmentPrompt.d.ts","sourceRoot":"/","sources":["lib/chatAttachmentPrompt.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAuB,YAAY,EAAY,WAAW,EAAE,MAAM,IAAI,CAAC;AAKnF,qEAAqE;AACrE,eAAO,MAAM,aAAa,YAAY,CAAC;AAKvC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,iBAAiB,GAAG;IACrD,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;IACnC,MAAM,CAAC,EAAE;QACP,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,OAAO,CAAC,EAAE;YAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;SAAE,GAAG,IAAI,CAAC;KAClD,GAAG,IAAI,CAAC;IACT,WAAW,CAAC,EAAE,iBAAiB,EAAE,CAAC;CACnC,CAAC;AAyEF,oFAAoF;AACpF,wBAAsB,mBAAmB,CACvC,WAAW,EAAE,iBAAiB,EAAE,GAC/B,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAyBjC;AAED,kDAAkD;AAClD,eAAO,MAAM,qBAAqB,4BAAsB,CAAC;AAEzD,wBAAgB,sCAAsC,CACpD,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,oBAAoB,EAAE,GAClC,WAAW,CA4Db;AAED,wBAAgB,+BAA+B,CAAC,CAAC,SAAS,kBAAkB,EAC1E,QAAQ,EAAE,CAAC,EAAE,GACZ,CAAC,EAAE,CAmBL;AAED,4EAA4E;AAC5E,wBAAsB,sBAAsB,CAAC,EAC3C,QAAQ,EACR,QAAQ,EACR,kBAAkB,GACnB,EAAE;IACD,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,kBAAkB,EAAE,MAAM,CAAC;CAC5B,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAyB1B"}
@@ -0,0 +1,190 @@
1
+
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="b2f1ec36-e878-51c6-b49c-0473d9a71c3e")}catch(e){}}();
3
+ import { OfficeParser } from 'officeparser';
4
+ import { bucket } from './googleCloudStorage.js';
5
+ import { logger } from '../utils/logger.js';
6
+ /** Model used for AI chats that may include document attachments. */
7
+ export const AI_CHAT_MODEL = 'gpt-5.5';
8
+ const MAX_EXTRACTED_CHARS = 80000;
9
+ const IMAGE_TYPE_PREFIX = 'image/';
10
+ const TEXT_DECODE_TYPES = new Set([
11
+ 'text/plain',
12
+ 'text/csv',
13
+ 'text/markdown',
14
+ 'text/html',
15
+ 'application/json',
16
+ ]);
17
+ const MENTION_ONLY_PREFIXES = ['audio/', 'video/'];
18
+ const MENTION_ONLY_TYPES = new Set(['application/zip']);
19
+ function extension(name) {
20
+ return name.split('.').pop()?.toLowerCase() ?? '';
21
+ }
22
+ function isPdf(attachment) {
23
+ return attachment.type === 'application/pdf' || extension(attachment.name) === 'pdf';
24
+ }
25
+ function isImage(attachment) {
26
+ return attachment.type.startsWith(IMAGE_TYPE_PREFIX);
27
+ }
28
+ function isMentionOnly(attachment) {
29
+ if (MENTION_ONLY_TYPES.has(attachment.type))
30
+ return true;
31
+ return MENTION_ONLY_PREFIXES.some((prefix) => attachment.type.startsWith(prefix));
32
+ }
33
+ function isUtf8Text(attachment) {
34
+ if (TEXT_DECODE_TYPES.has(attachment.type))
35
+ return true;
36
+ const ext = extension(attachment.name);
37
+ return ['txt', 'csv', 'md', 'html', 'json'].includes(ext);
38
+ }
39
+ function shouldDownload(attachment) {
40
+ return Boolean(attachment.path) && !isMentionOnly(attachment);
41
+ }
42
+ async function extractTextFromBytes(bytes, name, type) {
43
+ if (isUtf8Text({ type, name })) {
44
+ return new TextDecoder('utf-8', { fatal: false })
45
+ .decode(bytes)
46
+ .slice(0, MAX_EXTRACTED_CHARS);
47
+ }
48
+ if (isImage({ type }) || isPdf({ type, name })) {
49
+ return null;
50
+ }
51
+ if (isMentionOnly({ type, name })) {
52
+ return null;
53
+ }
54
+ try {
55
+ const ast = await OfficeParser.parseOffice(Buffer.from(bytes), {
56
+ includeRawContent: false,
57
+ });
58
+ const text = ast.toText().trim();
59
+ return text ? text.slice(0, MAX_EXTRACTED_CHARS) : null;
60
+ }
61
+ catch (error) {
62
+ logger.warn('Failed to extract chat attachment text', { name, type, error });
63
+ return null;
64
+ }
65
+ }
66
+ /** Download bytes from GCS and extract text for every supported attachment type. */
67
+ export async function loadChatAttachments(attachments) {
68
+ return Promise.all(attachments.map(async (attachment) => {
69
+ if (!shouldDownload(attachment)) {
70
+ return { ...attachment };
71
+ }
72
+ try {
73
+ const [bytes] = await bucket.file(attachment.path).download();
74
+ const data = new Uint8Array(bytes);
75
+ const extractedText = (await extractTextFromBytes(data, attachment.name, attachment.type)) ??
76
+ undefined;
77
+ return { ...attachment, data, extractedText };
78
+ }
79
+ catch (error) {
80
+ logger.warn('Failed to load chat attachment from storage', {
81
+ attachmentId: attachment.id,
82
+ path: attachment.path,
83
+ type: attachment.type,
84
+ error,
85
+ });
86
+ return { ...attachment };
87
+ }
88
+ }));
89
+ }
90
+ /** @deprecated Use {@link loadChatAttachments} */
91
+ export const loadPromptAttachments = loadChatAttachments;
92
+ export function buildUserMessageContentWithAttachments(textContent, attachments) {
93
+ if (attachments.length === 0) {
94
+ return textContent;
95
+ }
96
+ const parts = [];
97
+ let fullText = textContent;
98
+ for (const attachment of attachments) {
99
+ if (attachment.extractedText) {
100
+ fullText += `\n\n[Content of ${attachment.name}]:\n${attachment.extractedText}`;
101
+ }
102
+ }
103
+ const unhandled = attachments.filter((a) => {
104
+ if (a.extractedText)
105
+ return false;
106
+ if (isImage(a) && a.data)
107
+ return false;
108
+ if (isPdf(a) && a.data)
109
+ return false;
110
+ return true;
111
+ });
112
+ if (unhandled.length > 0) {
113
+ const fileList = unhandled
114
+ .map((a) => `${a.name} (${a.type})`)
115
+ .join(', ');
116
+ fullText = fullText
117
+ ? `${fullText}\n[Attached files (content not extracted): ${fileList}]`
118
+ : `[Attached files (content not extracted): ${fileList}]`;
119
+ }
120
+ if (fullText.trim()) {
121
+ parts.push({ type: 'text', text: fullText });
122
+ }
123
+ for (const image of attachments) {
124
+ if (isImage(image) && image.data) {
125
+ parts.push({ type: 'image', image: image.data });
126
+ }
127
+ }
128
+ for (const file of attachments) {
129
+ if (isPdf(file) && file.data) {
130
+ parts.push({
131
+ type: 'file',
132
+ data: file.data,
133
+ mediaType: 'application/pdf',
134
+ filename: file.name,
135
+ });
136
+ }
137
+ }
138
+ if (parts.length === 0) {
139
+ return textContent || '[Attached files]';
140
+ }
141
+ if (parts.length === 1 && parts[0].type === 'text') {
142
+ return parts[0].text;
143
+ }
144
+ return parts;
145
+ }
146
+ export function sortChatMessagesChronologically(messages) {
147
+ return messages
148
+ .map((message, index) => ({ message, index }))
149
+ .sort((left, right) => {
150
+ const leftTime = left.message.createdAt
151
+ ? new Date(left.message.createdAt).getTime()
152
+ : null;
153
+ const rightTime = right.message.createdAt
154
+ ? new Date(right.message.createdAt).getTime()
155
+ : null;
156
+ if (leftTime != null && rightTime != null) {
157
+ return leftTime - rightTime;
158
+ }
159
+ if (leftTime != null)
160
+ return -1;
161
+ if (rightTime != null)
162
+ return 1;
163
+ return left.index - right.index;
164
+ })
165
+ .map(({ message }) => message);
166
+ }
167
+ /** Build ModelMessage[] with shared attachment loading for Newton + Lab. */
168
+ export async function buildChatModelMessages({ messages, isAIUser, defaultSenderLabel, }) {
169
+ const modelMessages = [];
170
+ for (const message of sortChatMessagesChronologically(messages)) {
171
+ if (isAIUser(message.senderId)) {
172
+ modelMessages.push({ role: 'assistant', content: message.content });
173
+ continue;
174
+ }
175
+ const senderName = message.sender?.profile?.displayName ||
176
+ message.sender?.username ||
177
+ defaultSenderLabel;
178
+ const label = message.content.trim()
179
+ ? `${senderName}: ${message.content}`
180
+ : senderName;
181
+ const attachments = await loadChatAttachments(message.attachments ?? []);
182
+ modelMessages.push({
183
+ role: 'user',
184
+ content: buildUserMessageContentWithAttachments(label, attachments),
185
+ });
186
+ }
187
+ return modelMessages;
188
+ }
189
+ //# sourceMappingURL=chatAttachmentPrompt.js.map
190
+ //# debugId=b2f1ec36-e878-51c6-b49c-0473d9a71c3e
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chatAttachmentPrompt.js","sources":["lib/chatAttachmentPrompt.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Shared chat attachment handling for Newton + Lab pipelines.\n *\n * - Images + PDFs: inlined as multimodal parts (bytes from GCS).\n * - Office docs, spreadsheets, slides, text, csv, json, html: text extracted\n * via officeparser / UTF-8 decode and appended to the user message.\n * - Audio/video/zip: filename mention only (no extraction).\n */\nimport type { FilePart, ImagePart, ModelMessage, TextPart, UserContent } from 'ai';\nimport { OfficeParser } from 'officeparser';\nimport { bucket } from './googleCloudStorage.js';\nimport { logger } from '../utils/logger.js';\n\n/** Model used for AI chats that may include document attachments. */\nexport const AI_CHAT_MODEL = 'gpt-5.5';\n\nconst MAX_EXTRACTED_CHARS = 80_000;\nconst IMAGE_TYPE_PREFIX = 'image/';\n\nexport type ChatAttachmentRef = {\n id: string;\n name: string;\n type: string;\n path: string;\n};\n\nexport type LoadedChatAttachment = ChatAttachmentRef & {\n data?: Uint8Array;\n extractedText?: string;\n};\n\nexport type ChatHistoryMessage = {\n id?: string;\n content: string;\n senderId: string;\n createdAt?: Date | string | number;\n sender?: {\n username?: string | null;\n profile?: { displayName?: string | null } | null;\n } | null;\n attachments?: ChatAttachmentRef[];\n};\n\ntype UserContentPart = TextPart | ImagePart | FilePart;\n\nconst TEXT_DECODE_TYPES = new Set([\n 'text/plain',\n 'text/csv',\n 'text/markdown',\n 'text/html',\n 'application/json',\n]);\n\nconst MENTION_ONLY_PREFIXES = ['audio/', 'video/'];\nconst MENTION_ONLY_TYPES = new Set(['application/zip']);\n\nfunction extension(name: string): string {\n return name.split('.').pop()?.toLowerCase() ?? '';\n}\n\nfunction isPdf(attachment: { type: string; name: string }): boolean {\n return attachment.type === 'application/pdf' || extension(attachment.name) === 'pdf';\n}\n\nfunction isImage(attachment: { type: string }): boolean {\n return attachment.type.startsWith(IMAGE_TYPE_PREFIX);\n}\n\nfunction isMentionOnly(attachment: { type: string; name: string }): boolean {\n if (MENTION_ONLY_TYPES.has(attachment.type)) return true;\n return MENTION_ONLY_PREFIXES.some((prefix) => attachment.type.startsWith(prefix));\n}\n\nfunction isUtf8Text(attachment: { type: string; name: string }): boolean {\n if (TEXT_DECODE_TYPES.has(attachment.type)) return true;\n const ext = extension(attachment.name);\n return ['txt', 'csv', 'md', 'html', 'json'].includes(ext);\n}\n\nfunction shouldDownload(attachment: ChatAttachmentRef): boolean {\n return Boolean(attachment.path) && !isMentionOnly(attachment);\n}\n\nasync function extractTextFromBytes(\n bytes: Uint8Array,\n name: string,\n type: string,\n): Promise<string | null> {\n if (isUtf8Text({ type, name })) {\n return new TextDecoder('utf-8', { fatal: false })\n .decode(bytes)\n .slice(0, MAX_EXTRACTED_CHARS);\n }\n\n if (isImage({ type }) || isPdf({ type, name })) {\n return null;\n }\n\n if (isMentionOnly({ type, name })) {\n return null;\n }\n\n try {\n const ast = await OfficeParser.parseOffice(Buffer.from(bytes), {\n includeRawContent: false,\n });\n const text = ast.toText().trim();\n return text ? text.slice(0, MAX_EXTRACTED_CHARS) : null;\n } catch (error) {\n logger.warn('Failed to extract chat attachment text', { name, type, error });\n return null;\n }\n}\n\n/** Download bytes from GCS and extract text for every supported attachment type. */\nexport async function loadChatAttachments(\n attachments: ChatAttachmentRef[],\n): Promise<LoadedChatAttachment[]> {\n return Promise.all(\n attachments.map(async (attachment) => {\n if (!shouldDownload(attachment)) {\n return { ...attachment };\n }\n\n try {\n const [bytes] = await bucket.file(attachment.path).download();\n const data = new Uint8Array(bytes);\n const extractedText =\n (await extractTextFromBytes(data, attachment.name, attachment.type)) ??\n undefined;\n return { ...attachment, data, extractedText };\n } catch (error) {\n logger.warn('Failed to load chat attachment from storage', {\n attachmentId: attachment.id,\n path: attachment.path,\n type: attachment.type,\n error,\n });\n return { ...attachment };\n }\n }),\n );\n}\n\n/** @deprecated Use {@link loadChatAttachments} */\nexport const loadPromptAttachments = loadChatAttachments;\n\nexport function buildUserMessageContentWithAttachments(\n textContent: string,\n attachments: LoadedChatAttachment[],\n): UserContent {\n if (attachments.length === 0) {\n return textContent;\n }\n\n const parts: UserContentPart[] = [];\n let fullText = textContent;\n\n for (const attachment of attachments) {\n if (attachment.extractedText) {\n fullText += `\\n\\n[Content of ${attachment.name}]:\\n${attachment.extractedText}`;\n }\n }\n\n const unhandled = attachments.filter((a) => {\n if (a.extractedText) return false;\n if (isImage(a) && a.data) return false;\n if (isPdf(a) && a.data) return false;\n return true;\n });\n\n if (unhandled.length > 0) {\n const fileList = unhandled\n .map((a) => `${a.name} (${a.type})`)\n .join(', ');\n fullText = fullText\n ? `${fullText}\\n[Attached files (content not extracted): ${fileList}]`\n : `[Attached files (content not extracted): ${fileList}]`;\n }\n\n if (fullText.trim()) {\n parts.push({ type: 'text', text: fullText });\n }\n\n for (const image of attachments) {\n if (isImage(image) && image.data) {\n parts.push({ type: 'image', image: image.data });\n }\n }\n\n for (const file of attachments) {\n if (isPdf(file) && file.data) {\n parts.push({\n type: 'file',\n data: file.data,\n mediaType: 'application/pdf',\n filename: file.name,\n });\n }\n }\n\n if (parts.length === 0) {\n return textContent || '[Attached files]';\n }\n\n if (parts.length === 1 && parts[0].type === 'text') {\n return parts[0].text;\n }\n\n return parts;\n}\n\nexport function sortChatMessagesChronologically<T extends ChatHistoryMessage>(\n messages: T[],\n): T[] {\n return messages\n .map((message, index) => ({ message, index }))\n .sort((left, right) => {\n const leftTime = left.message.createdAt\n ? new Date(left.message.createdAt).getTime()\n : null;\n const rightTime = right.message.createdAt\n ? new Date(right.message.createdAt).getTime()\n : null;\n\n if (leftTime != null && rightTime != null) {\n return leftTime - rightTime;\n }\n if (leftTime != null) return -1;\n if (rightTime != null) return 1;\n return left.index - right.index;\n })\n .map(({ message }) => message);\n}\n\n/** Build ModelMessage[] with shared attachment loading for Newton + Lab. */\nexport async function buildChatModelMessages({\n messages,\n isAIUser,\n defaultSenderLabel,\n}: {\n messages: ChatHistoryMessage[];\n isAIUser: (senderId: string) => boolean;\n defaultSenderLabel: string;\n}): Promise<ModelMessage[]> {\n const modelMessages: ModelMessage[] = [];\n\n for (const message of sortChatMessagesChronologically(messages)) {\n if (isAIUser(message.senderId)) {\n modelMessages.push({ role: 'assistant', content: message.content });\n continue;\n }\n\n const senderName =\n message.sender?.profile?.displayName ||\n message.sender?.username ||\n defaultSenderLabel;\n const label = message.content.trim()\n ? `${senderName}: ${message.content}`\n : senderName;\n const attachments = await loadChatAttachments(message.attachments ?? []);\n\n modelMessages.push({\n role: 'user',\n content: buildUserMessageContentWithAttachments(label, attachments),\n });\n }\n\n return modelMessages;\n}\n"],"names":[],"mappings":"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","debug_id":"b2f1ec36-e878-51c6-b49c-0473d9a71c3e"}
@@ -1,5 +1,5 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="4c92c09b-87d0-5b2a-b970-6fe3b548d588")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="64850cb0-dd7d-5df3-8b52-d0a2d8e27737")}catch(e){}}();
3
3
  import { normalizeDocumentFormat } from '../services/openaiDocumentGen.js';
4
4
  /** Ordered most-specific first. */
5
5
  const FORMAT_RULES = [
@@ -7,7 +7,7 @@ const FORMAT_RULES = [
7
7
  { format: 'xlsx', pattern: /\b(spreadsheet|excel|workbook|xlsx|gradebook|grade book|\.xlsx)\b/i },
8
8
  { format: 'csv', pattern: /\b(csv|comma[- ]separated|\.csv)\b/i },
9
9
  { format: 'docx', pattern: /\b(word doc(ument)?|docx|editable handout|\.docx)\b/i },
10
- { format: 'png', pattern: /\b(diagram|chart|graph|plot|figure|infographic|\.png)\b/i },
10
+ { format: 'png', pattern: /\b(diagram|flowchart|chart|graph|plot|figure|infographic|visual|illustration|mind ?map|timeline|\.png)\b/i },
11
11
  { format: 'md', pattern: /\b(markdown|readme|\.md)\b/i },
12
12
  { format: 'html', pattern: /\b(html|web ?page|\.html)\b/i },
13
13
  { format: 'txt', pattern: /\b(plain text|text file|\.txt)\b/i },
@@ -45,4 +45,4 @@ export function inferDocumentFormat(title, content, explicit, userRequest) {
45
45
  return explicitNorm ?? 'pdf';
46
46
  }
47
47
  //# sourceMappingURL=inferDocumentFormat.js.map
48
- //# debugId=4c92c09b-87d0-5b2a-b970-6fe3b548d588
48
+ //# debugId=64850cb0-dd7d-5df3-8b52-d0a2d8e27737
@@ -1 +1 @@
1
- {"version":3,"file":"inferDocumentFormat.js","sources":["lib/inferDocumentFormat.ts"],"sourceRoot":"/","sourcesContent":["import type { DocumentFormat } from '../services/openaiDocumentGen.js';\nimport { normalizeDocumentFormat } from '../services/openaiDocumentGen.js';\n\ntype FormatRule = { format: DocumentFormat; pattern: RegExp };\n\n/** Ordered most-specific first. */\nconst FORMAT_RULES: FormatRule[] = [\n { format: 'pptx', pattern: /\\b(presentation|powerpoint|power point|slides?|slide deck|deck|pptx|\\.pptx)\\b/i },\n { format: 'xlsx', pattern: /\\b(spreadsheet|excel|workbook|xlsx|gradebook|grade book|\\.xlsx)\\b/i },\n { format: 'csv', pattern: /\\b(csv|comma[- ]separated|\\.csv)\\b/i },\n { format: 'docx', pattern: /\\b(word doc(ument)?|docx|editable handout|\\.docx)\\b/i },\n { format: 'png', pattern: /\\b(diagram|chart|graph|plot|figure|infographic|\\.png)\\b/i },\n { format: 'md', pattern: /\\b(markdown|readme|\\.md)\\b/i },\n { format: 'html', pattern: /\\b(html|web ?page|\\.html)\\b/i },\n { format: 'txt', pattern: /\\b(plain text|text file|\\.txt)\\b/i },\n];\n\nfunction inferFromText(...parts: Array<string | null | undefined>): DocumentFormat | null {\n const hay = parts.filter(Boolean).join('\\n');\n if (!hay.trim()) return null;\n\n for (const { format, pattern } of FORMAT_RULES) {\n if (pattern.test(hay)) return format;\n }\n return null;\n}\n\n/**\n * Resolve the output format for a generated document.\n *\n * The lab model often passes `format: \"pdf\"` even for presentations/spreadsheets.\n * Keyword signals from the teacher request, title, and body take precedence over\n * a generic pdf default; a non-pdf explicit format is always respected.\n */\nexport function inferDocumentFormat(\n title: string,\n content: string,\n explicit?: string | null,\n userRequest?: string | null,\n): DocumentFormat {\n const explicitNorm = explicit?.trim()\n ? normalizeDocumentFormat(explicit)\n : null;\n\n const fromKeywords = inferFromText(userRequest, title, content);\n\n // Non-pdf explicit choice wins (model or tool args).\n if (explicitNorm && explicitNorm !== 'pdf') {\n return explicitNorm;\n }\n\n // Override pdf when the request clearly asks for slides, spreadsheets, etc.\n if (fromKeywords && fromKeywords !== 'pdf') {\n return fromKeywords;\n }\n\n return explicitNorm ?? 'pdf';\n}\n"],"names":[],"mappings":";;AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAI3E,mCAAmC;AACnC,MAAM,YAAY,GAAiB;IACjC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,gFAAgF,EAAE;IAC7G,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,oEAAoE,EAAE;IACjG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,qCAAqC,EAAE;IACjE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,sDAAsD,EAAE;IACnF,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,0DAA0D,EAAE;IACtF,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,6BAA6B,EAAE;IACxD,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,8BAA8B,EAAE;IAC3D,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,mCAAmC,EAAE;CAChE,CAAC;AAEF,SAAS,aAAa,CAAC,GAAG,KAAuC;IAC/D,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7C,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE;QAAE,OAAO,IAAI,CAAC;IAE7B,KAAK,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,YAAY,EAAE,CAAC;QAC/C,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,OAAO,MAAM,CAAC;IACvC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CACjC,KAAa,EACb,OAAe,EACf,QAAwB,EACxB,WAA2B;IAE3B,MAAM,YAAY,GAAG,QAAQ,EAAE,IAAI,EAAE;QACnC,CAAC,CAAC,uBAAuB,CAAC,QAAQ,CAAC;QACnC,CAAC,CAAC,IAAI,CAAC;IAET,MAAM,YAAY,GAAG,aAAa,CAAC,WAAW,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IAEhE,qDAAqD;IACrD,IAAI,YAAY,IAAI,YAAY,KAAK,KAAK,EAAE,CAAC;QAC3C,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,4EAA4E;IAC5E,IAAI,YAAY,IAAI,YAAY,KAAK,KAAK,EAAE,CAAC;QAC3C,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,OAAO,YAAY,IAAI,KAAK,CAAC;AAC/B,CAAC","debug_id":"4c92c09b-87d0-5b2a-b970-6fe3b548d588"}
1
+ {"version":3,"file":"inferDocumentFormat.js","sources":["lib/inferDocumentFormat.ts"],"sourceRoot":"/","sourcesContent":["import type { DocumentFormat } from '../services/openaiDocumentGen.js';\nimport { normalizeDocumentFormat } from '../services/openaiDocumentGen.js';\n\ntype FormatRule = { format: DocumentFormat; pattern: RegExp };\n\n/** Ordered most-specific first. */\nconst FORMAT_RULES: FormatRule[] = [\n { format: 'pptx', pattern: /\\b(presentation|powerpoint|power point|slides?|slide deck|deck|pptx|\\.pptx)\\b/i },\n { format: 'xlsx', pattern: /\\b(spreadsheet|excel|workbook|xlsx|gradebook|grade book|\\.xlsx)\\b/i },\n { format: 'csv', pattern: /\\b(csv|comma[- ]separated|\\.csv)\\b/i },\n { format: 'docx', pattern: /\\b(word doc(ument)?|docx|editable handout|\\.docx)\\b/i },\n { format: 'png', pattern: /\\b(diagram|flowchart|chart|graph|plot|figure|infographic|visual|illustration|mind ?map|timeline|\\.png)\\b/i },\n { format: 'md', pattern: /\\b(markdown|readme|\\.md)\\b/i },\n { format: 'html', pattern: /\\b(html|web ?page|\\.html)\\b/i },\n { format: 'txt', pattern: /\\b(plain text|text file|\\.txt)\\b/i },\n];\n\nfunction inferFromText(...parts: Array<string | null | undefined>): DocumentFormat | null {\n const hay = parts.filter(Boolean).join('\\n');\n if (!hay.trim()) return null;\n\n for (const { format, pattern } of FORMAT_RULES) {\n if (pattern.test(hay)) return format;\n }\n return null;\n}\n\n/**\n * Resolve the output format for a generated document.\n *\n * The lab model often passes `format: \"pdf\"` even for presentations/spreadsheets.\n * Keyword signals from the teacher request, title, and body take precedence over\n * a generic pdf default; a non-pdf explicit format is always respected.\n */\nexport function inferDocumentFormat(\n title: string,\n content: string,\n explicit?: string | null,\n userRequest?: string | null,\n): DocumentFormat {\n const explicitNorm = explicit?.trim()\n ? normalizeDocumentFormat(explicit)\n : null;\n\n const fromKeywords = inferFromText(userRequest, title, content);\n\n // Non-pdf explicit choice wins (model or tool args).\n if (explicitNorm && explicitNorm !== 'pdf') {\n return explicitNorm;\n }\n\n // Override pdf when the request clearly asks for slides, spreadsheets, etc.\n if (fromKeywords && fromKeywords !== 'pdf') {\n return fromKeywords;\n }\n\n return explicitNorm ?? 'pdf';\n}\n"],"names":[],"mappings":";;AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAI3E,mCAAmC;AACnC,MAAM,YAAY,GAAiB;IACjC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,gFAAgF,EAAE;IAC7G,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,oEAAoE,EAAE;IACjG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,qCAAqC,EAAE;IACjE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,sDAAsD,EAAE;IACnF,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,2GAA2G,EAAE;IACvI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,6BAA6B,EAAE;IACxD,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,8BAA8B,EAAE;IAC3D,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,mCAAmC,EAAE;CAChE,CAAC;AAEF,SAAS,aAAa,CAAC,GAAG,KAAuC;IAC/D,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7C,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE;QAAE,OAAO,IAAI,CAAC;IAE7B,KAAK,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,YAAY,EAAE,CAAC;QAC/C,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,OAAO,MAAM,CAAC;IACvC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CACjC,KAAa,EACb,OAAe,EACf,QAAwB,EACxB,WAA2B;IAE3B,MAAM,YAAY,GAAG,QAAQ,EAAE,IAAI,EAAE;QACnC,CAAC,CAAC,uBAAuB,CAAC,QAAQ,CAAC;QACnC,CAAC,CAAC,IAAI,CAAC;IAET,MAAM,YAAY,GAAG,aAAa,CAAC,WAAW,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IAEhE,qDAAqD;IACrD,IAAI,YAAY,IAAI,YAAY,KAAK,KAAK,EAAE,CAAC;QAC3C,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,4EAA4E;IAC5E,IAAI,YAAY,IAAI,YAAY,KAAK,KAAK,EAAE,CAAC;QAC3C,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,OAAO,YAAY,IAAI,KAAK,CAAC;AAC/B,CAAC","debug_id":"64850cb0-dd7d-5df3-8b52-d0a2d8e27737"}
@@ -0,0 +1,3 @@
1
+ /** Must match the "Last updated" month in the client privacy/terms copy. */
2
+ export declare const CURRENT_TERMS_VERSION = "2025-11";
3
+ //# sourceMappingURL=legal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legal.d.ts","sourceRoot":"/","sources":["lib/legal.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,eAAO,MAAM,qBAAqB,YAAY,CAAC"}
@@ -0,0 +1,6 @@
1
+ /** Must match the "Last updated" month in the client privacy/terms copy. */
2
+
3
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="ad0b3a8e-96d8-5634-84c5-fec20e40859b")}catch(e){}}();
4
+ export const CURRENT_TERMS_VERSION = "2025-11";
5
+ //# sourceMappingURL=legal.js.map
6
+ //# debugId=ad0b3a8e-96d8-5634-84c5-fec20e40859b
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legal.js","sources":["lib/legal.ts"],"sourceRoot":"/","sourcesContent":["/** Must match the \"Last updated\" month in the client privacy/terms copy. */\nexport const CURRENT_TERMS_VERSION = \"2025-11\";\n"],"names":[],"mappings":"AAAA,4EAA4E;;;AAC5E,MAAM,CAAC,MAAM,qBAAqB,GAAG,SAAS,CAAC","debug_id":"ad0b3a8e-96d8-5634-84c5-fec20e40859b"}
@@ -154,6 +154,8 @@ export declare function findAnnouncementWithClass(id: string): import("@prisma/c
154
154
  email: string;
155
155
  password: string;
156
156
  verified: boolean;
157
+ termsAcceptedAt: Date | null;
158
+ termsVersion: string | null;
157
159
  schoolId: string | null;
158
160
  }[];
159
161
  } & {
@@ -1 +1 @@
1
- {"version":3,"file":"announcement.d.ts","sourceRoot":"/","sources":["models/announcement.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6B9B,CAAC;AAEF,0CAA0C;AAC1C,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;KAWzD;AAED,6CAA6C;AAC7C,wBAAgB,4BAA4B,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAKvE;AAED,yEAAyE;AACzE,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAoCnD;AAED,8BAA8B;AAC9B,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GASA;AAED,uCAAuC;AACvC,wBAAgB,6BAA6B,CAC3C,EAAE,EAAE,MAAM,EACV,IAAI,EAAE;IACJ,OAAO,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC3B,UAAU,CAAC,EAAE;QAAE,EAAE,EAAE;YAAE,EAAE,EAAE,MAAM,EAAE,CAAA;SAAE,CAAA;KAAE,CAAC;CACvC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAcF;AAED,mCAAmC;AACnC,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAMxE;AAED,8BAA8B;AAC9B,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAI5C"}
1
+ {"version":3,"file":"announcement.d.ts","sourceRoot":"/","sources":["models/announcement.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6B9B,CAAC;AAEF,0CAA0C;AAC1C,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;KAWzD;AAED,6CAA6C;AAC7C,wBAAgB,4BAA4B,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAKvE;AAED,yEAAyE;AACzE,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAoCnD;AAED,8BAA8B;AAC9B,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GASA;AAED,uCAAuC;AACvC,wBAAgB,6BAA6B,CAC3C,EAAE,EAAE,MAAM,EACV,IAAI,EAAE;IACJ,OAAO,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC3B,UAAU,CAAC,EAAE;QAAE,EAAE,EAAE;YAAE,EAAE,EAAE,MAAM,EAAE,CAAA;SAAE,CAAA;KAAE,CAAC;CACvC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAcF;AAED,mCAAmC;AACnC,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAMxE;AAED,8BAA8B;AAC9B,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAI5C"}
@@ -146,6 +146,8 @@ export declare function createUser(data: {
146
146
  email: string;
147
147
  password: string;
148
148
  verified?: boolean;
149
+ termsAcceptedAt?: Date;
150
+ termsVersion?: string;
149
151
  }): import("@prisma/client/runtime/library.js").DynamicModelExtensionFluentApi<import(".prisma/client").Prisma.TypeMap<import("@prisma/client/runtime/library.js").InternalArgs & {
150
152
  result: {
151
153
  userProfile: {
@@ -249,6 +251,8 @@ export declare function deleteUserById(id: string): import("@prisma/client/runti
249
251
  email: string;
250
252
  password: string;
251
253
  verified: boolean;
254
+ termsAcceptedAt: Date | null;
255
+ termsVersion: string | null;
252
256
  schoolId: string | null;
253
257
  }>;
254
258
  /** @returns Session by ID and type. */
@@ -352,6 +356,8 @@ export declare function updateUserVerified(userId: string, verified: boolean): i
352
356
  email: string;
353
357
  password: string;
354
358
  verified: boolean;
359
+ termsAcceptedAt: Date | null;
360
+ termsVersion: string | null;
355
361
  schoolId: string | null;
356
362
  }>;
357
363
  /** Update user password. */
@@ -385,6 +391,8 @@ export declare function updateUserPassword(userId: string, password: string): im
385
391
  email: string;
386
392
  password: string;
387
393
  verified: boolean;
394
+ termsAcceptedAt: Date | null;
395
+ termsVersion: string | null;
388
396
  schoolId: string | null;
389
397
  }>;
390
398
  /** Delete a session. */
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"/","sources":["models/auth.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAuB7C,+DAA+D;AAC/D,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAWlD;AAED,0CAA0C;AAC1C,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAYxE;AAED,2CAA2C;AAC3C,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAiB5C;AAED,wCAAwC;AACxC,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAmBtC;AAED,wCAAwC;AACxC,wBAAgB,UAAU,CAAC,IAAI,EAAE;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAeA;AAED,wBAAwB;AACxB,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,WAAW,CAAC;IAClB,SAAS,EAAE,IAAI,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAIA;AAED,6DAA6D;AAC7D,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,MAAM,uEAU1D;AAED,2DAA2D;AAC3D,wBAAgB,gCAAgC,CAC9C,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,IAAI,yHASpB;AAED,qBAAqB;AACrB,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAIxC;AAED,uCAAuC;AACvC,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAInE;AAED,iDAAiD;AACjD,wBAAgB,8BAA8B,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAO3E;AAED,gCAAgC;AAChC,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAKnE;AAED,4BAA4B;AAC5B,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAKlE;AAED,wBAAwB;AACxB,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,MAAM;;;;;;;GAKjD;AAED,iGAAiG;AACjG,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,MAAM,gBAqCzD;AAED,kDAAkD;AAClD,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM;;KAKxD;AAED,oDAAoD;AACpD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAOlE;AAED,2DAA2D;AAC3D,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAOtE"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"/","sources":["models/auth.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAuB7C,+DAA+D;AAC/D,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAWlD;AAED,0CAA0C;AAC1C,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAYxE;AAED,2CAA2C;AAC3C,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAiB5C;AAED,wCAAwC;AACxC,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAmBtC;AAED,wCAAwC;AACxC,wBAAgB,UAAU,CAAC,IAAI,EAAE;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,IAAI,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiBA;AAED,wBAAwB;AACxB,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,WAAW,CAAC;IAClB,SAAS,EAAE,IAAI,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAIA;AAED,6DAA6D;AAC7D,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,MAAM,uEAU1D;AAED,2DAA2D;AAC3D,wBAAgB,gCAAgC,CAC9C,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,IAAI,yHASpB;AAED,qBAAqB;AACrB,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAIxC;AAED,uCAAuC;AACvC,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAInE;AAED,iDAAiD;AACjD,wBAAgB,8BAA8B,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAO3E;AAED,gCAAgC;AAChC,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAKnE;AAED,4BAA4B;AAC5B,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAKlE;AAED,wBAAwB;AACxB,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,MAAM;;;;;;;GAKjD;AAED,iGAAiG;AACjG,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,MAAM,gBAqCzD;AAED,kDAAkD;AAClD,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM;;KAKxD;AAED,oDAAoD;AACpD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAOlE;AAED,2DAA2D;AAC3D,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAOtE"}
@@ -3,7 +3,7 @@
3
3
  * findUserBySessionToken checks Redis first, then DB.
4
4
  */
5
5
 
6
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="0a142abe-c24b-5549-932f-108cd325b0ee")}catch(e){}}();
6
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="1f454e5f-571d-5200-843d-700eec8d74e3")}catch(e){}}();
7
7
  import { SessionType } from "@prisma/client";
8
8
  import { getRedis } from "../lib/redis.js";
9
9
  import { prisma } from "../lib/prisma.js";
@@ -94,6 +94,8 @@ export function createUser(data) {
94
94
  email: data.email,
95
95
  password: data.password,
96
96
  verified: data.verified ?? false,
97
+ termsAcceptedAt: data.termsAcceptedAt,
98
+ termsVersion: data.termsVersion,
97
99
  profile: { create: {} },
98
100
  },
99
101
  select: {
@@ -233,4 +235,4 @@ export function findClassWithTeacher(classId, teacherId) {
233
235
  });
234
236
  }
235
237
  //# sourceMappingURL=auth.js.map
236
- //# debugId=0a142abe-c24b-5549-932f-108cd325b0ee
238
+ //# debugId=1f454e5f-571d-5200-843d-700eec8d74e3
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sources":["models/auth.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Auth model – user lookup, session CRUD, Redis session cache.\n * findUserBySessionToken checks Redis first, then DB.\n */\nimport { SessionType } from \"@prisma/client\";\nimport { getRedis } from \"../lib/redis.js\";\nimport { prisma } from \"../lib/prisma.js\";\nimport {\n classMemberOrSchoolAdminWhere,\n classTeacherOrSchoolAdminWhere,\n} from \"./access.js\";\n\nconst AUTH_SESSION_CACHE_PREFIX = \"auth-session:\";\n\nfunction getAuthSessionCacheKey(token: string) {\n return `${AUTH_SESSION_CACHE_PREFIX}${token}`;\n}\n\nfunction getSessionTtlSeconds(expiresAt?: Date | null) {\n if (!expiresAt) {\n return null;\n }\n\n const ttlSeconds = Math.floor((expiresAt.getTime() - Date.now()) / 1000);\n return ttlSeconds > 0 ? ttlSeconds : null;\n}\n\n/** @returns User by username (includes password for login). */\nexport function findUserByUsername(username: string) {\n return prisma.user.findFirst({\n where: { username },\n select: {\n id: true,\n username: true,\n password: true,\n email: true,\n verified: true,\n },\n });\n}\n\n/** @returns User by username or email. */\nexport function findUserByUsernameOrEmail(username: string, email: string) {\n return prisma.user.findFirst({\n where: {\n OR: [{ username }, { email }],\n },\n select: {\n id: true,\n username: true,\n email: true,\n verified: true,\n },\n });\n}\n\n/** @returns User by email with profile. */\nexport function findUserByEmail(email: string) {\n return prisma.user.findFirst({\n where: { email },\n select: {\n id: true,\n email: true,\n profile: {\n select: {\n displayName: true,\n bio: true,\n location: true,\n website: true,\n profilePicture: true,\n },\n },\n },\n });\n}\n\n/** @returns User by ID with profile. */\nexport function findUserById(id: string) {\n return prisma.user.findUnique({\n where: { id },\n select: {\n id: true,\n username: true,\n email: true,\n profile: {\n select: {\n displayName: true,\n bio: true,\n location: true,\n website: true,\n profilePicture: true,\n profilePictureThumbnail: true,\n },\n },\n },\n });\n}\n\n/** Create a user with empty profile. */\nexport function createUser(data: {\n username: string;\n email: string;\n password: string;\n verified?: boolean;\n}) {\n return prisma.user.create({\n data: {\n username: data.username,\n email: data.email,\n password: data.password,\n verified: data.verified ?? false,\n profile: { create: {} },\n },\n select: {\n id: true,\n username: true,\n email: true,\n },\n });\n}\n\n/** Create a session. */\nexport function createSession(data: {\n id: string;\n type: SessionType;\n expiresAt: Date;\n userId?: string;\n classId?: string;\n}) {\n return prisma.session.create({\n data,\n });\n}\n\n/** Delete all sessions for user. Invalidates Redis cache. */\nexport async function deleteSessionsByUserId(userId: string) {\n const sessions = await prisma.session.findMany({\n where: { userId },\n });\n for (const session of sessions) {\n await getRedis()?.del(getAuthSessionCacheKey(session.id));\n }\n return prisma.session.deleteMany({\n where: { userId },\n });\n}\n\n/** Delete password reset sessions expiring before date. */\nexport function deleteShortLivedSessionsByUserId(\n userId: string,\n expiresBefore: Date\n) {\n return prisma.session.deleteMany({\n where: {\n userId,\n type: SessionType.PASSWORD_RESET,\n expiresAt: { lte: expiresBefore },\n },\n });\n}\n\n/** Delete a user. */\nexport function deleteUserById(id: string) {\n return prisma.user.delete({\n where: { id },\n });\n}\n\n/** @returns Session by ID and type. */\nexport function findSessionByIdAndType(id: string, type: SessionType) {\n return prisma.session.findFirst({\n where: { id, type },\n });\n}\n\n/** @returns Session with user by ID and type. */\nexport function findSessionByIdWithUserAndType(id: string, type: SessionType) {\n return prisma.session.findFirst({\n where: { id, type },\n include: {\n user: { select: { id: true } },\n },\n });\n}\n\n/** Set user verified status. */\nexport function updateUserVerified(userId: string, verified: boolean) {\n return prisma.user.update({\n where: { id: userId },\n data: { verified },\n });\n}\n\n/** Update user password. */\nexport function updateUserPassword(userId: string, password: string) {\n return prisma.user.update({\n where: { id: userId },\n data: { password },\n });\n}\n\n/** Delete a session. */\nexport async function deleteSessionById(id: string) {\n await getRedis()?.del(getAuthSessionCacheKey(id));\n return prisma.session.delete({\n where: { id },\n });\n}\n\n/** @returns User from valid auth session token. Checks Redis first, then DB. Caches in Redis. */\nexport async function findUserBySessionToken(token: string) {\n const cacheKey = getAuthSessionCacheKey(token);\n const cachedUser = await getRedis()?.get(cacheKey);\n\n if (cachedUser) {\n return JSON.parse(cachedUser);\n }\n\n const session = await prisma.session.findFirst({\n where: {\n id: token,\n type: SessionType.AUTH,\n userId: { not: null },\n expiresAt: { gt: new Date() },\n },\n select: {\n expiresAt: true,\n user: {\n select: {\n id: true,\n username: true,\n },\n },\n },\n });\n\n if (!session?.user) {\n return null;\n }\n\n const ttlSeconds = getSessionTtlSeconds(session.expiresAt);\n const r = getRedis();\n if (r && ttlSeconds) {\n await r.setex(cacheKey, ttlSeconds, JSON.stringify(session.user));\n }\n\n return session.user;\n}\n\n/** @returns Class IDs where user is a teacher. */\nexport function findTeacherClassesByUserId(userId: string) {\n return prisma.class.findMany({\n where: classTeacherOrSchoolAdminWhere(userId),\n select: { id: true },\n });\n}\n\n/** @returns Class if user is student or teacher. */\nexport function findClassWithMember(classId: string, userId: string) {\n return prisma.class.findFirst({\n where: {\n id: classId,\n ...classMemberOrSchoolAdminWhere(userId),\n },\n });\n}\n\n/** @returns Class if user is a teacher or school admin. */\nexport function findClassWithTeacher(classId: string, teacherId: string) {\n return prisma.class.findFirst({\n where: {\n id: classId,\n ...classTeacherOrSchoolAdminWhere(teacherId),\n },\n });\n}\n"],"names":[],"mappings":"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","debug_id":"0a142abe-c24b-5549-932f-108cd325b0ee"}
1
+ {"version":3,"file":"auth.js","sources":["models/auth.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Auth model – user lookup, session CRUD, Redis session cache.\n * findUserBySessionToken checks Redis first, then DB.\n */\nimport { SessionType } from \"@prisma/client\";\nimport { getRedis } from \"../lib/redis.js\";\nimport { prisma } from \"../lib/prisma.js\";\nimport {\n classMemberOrSchoolAdminWhere,\n classTeacherOrSchoolAdminWhere,\n} from \"./access.js\";\n\nconst AUTH_SESSION_CACHE_PREFIX = \"auth-session:\";\n\nfunction getAuthSessionCacheKey(token: string) {\n return `${AUTH_SESSION_CACHE_PREFIX}${token}`;\n}\n\nfunction getSessionTtlSeconds(expiresAt?: Date | null) {\n if (!expiresAt) {\n return null;\n }\n\n const ttlSeconds = Math.floor((expiresAt.getTime() - Date.now()) / 1000);\n return ttlSeconds > 0 ? ttlSeconds : null;\n}\n\n/** @returns User by username (includes password for login). */\nexport function findUserByUsername(username: string) {\n return prisma.user.findFirst({\n where: { username },\n select: {\n id: true,\n username: true,\n password: true,\n email: true,\n verified: true,\n },\n });\n}\n\n/** @returns User by username or email. */\nexport function findUserByUsernameOrEmail(username: string, email: string) {\n return prisma.user.findFirst({\n where: {\n OR: [{ username }, { email }],\n },\n select: {\n id: true,\n username: true,\n email: true,\n verified: true,\n },\n });\n}\n\n/** @returns User by email with profile. */\nexport function findUserByEmail(email: string) {\n return prisma.user.findFirst({\n where: { email },\n select: {\n id: true,\n email: true,\n profile: {\n select: {\n displayName: true,\n bio: true,\n location: true,\n website: true,\n profilePicture: true,\n },\n },\n },\n });\n}\n\n/** @returns User by ID with profile. */\nexport function findUserById(id: string) {\n return prisma.user.findUnique({\n where: { id },\n select: {\n id: true,\n username: true,\n email: true,\n profile: {\n select: {\n displayName: true,\n bio: true,\n location: true,\n website: true,\n profilePicture: true,\n profilePictureThumbnail: true,\n },\n },\n },\n });\n}\n\n/** Create a user with empty profile. */\nexport function createUser(data: {\n username: string;\n email: string;\n password: string;\n verified?: boolean;\n termsAcceptedAt?: Date;\n termsVersion?: string;\n}) {\n return prisma.user.create({\n data: {\n username: data.username,\n email: data.email,\n password: data.password,\n verified: data.verified ?? false,\n termsAcceptedAt: data.termsAcceptedAt,\n termsVersion: data.termsVersion,\n profile: { create: {} },\n },\n select: {\n id: true,\n username: true,\n email: true,\n },\n });\n}\n\n/** Create a session. */\nexport function createSession(data: {\n id: string;\n type: SessionType;\n expiresAt: Date;\n userId?: string;\n classId?: string;\n}) {\n return prisma.session.create({\n data,\n });\n}\n\n/** Delete all sessions for user. Invalidates Redis cache. */\nexport async function deleteSessionsByUserId(userId: string) {\n const sessions = await prisma.session.findMany({\n where: { userId },\n });\n for (const session of sessions) {\n await getRedis()?.del(getAuthSessionCacheKey(session.id));\n }\n return prisma.session.deleteMany({\n where: { userId },\n });\n}\n\n/** Delete password reset sessions expiring before date. */\nexport function deleteShortLivedSessionsByUserId(\n userId: string,\n expiresBefore: Date\n) {\n return prisma.session.deleteMany({\n where: {\n userId,\n type: SessionType.PASSWORD_RESET,\n expiresAt: { lte: expiresBefore },\n },\n });\n}\n\n/** Delete a user. */\nexport function deleteUserById(id: string) {\n return prisma.user.delete({\n where: { id },\n });\n}\n\n/** @returns Session by ID and type. */\nexport function findSessionByIdAndType(id: string, type: SessionType) {\n return prisma.session.findFirst({\n where: { id, type },\n });\n}\n\n/** @returns Session with user by ID and type. */\nexport function findSessionByIdWithUserAndType(id: string, type: SessionType) {\n return prisma.session.findFirst({\n where: { id, type },\n include: {\n user: { select: { id: true } },\n },\n });\n}\n\n/** Set user verified status. */\nexport function updateUserVerified(userId: string, verified: boolean) {\n return prisma.user.update({\n where: { id: userId },\n data: { verified },\n });\n}\n\n/** Update user password. */\nexport function updateUserPassword(userId: string, password: string) {\n return prisma.user.update({\n where: { id: userId },\n data: { password },\n });\n}\n\n/** Delete a session. */\nexport async function deleteSessionById(id: string) {\n await getRedis()?.del(getAuthSessionCacheKey(id));\n return prisma.session.delete({\n where: { id },\n });\n}\n\n/** @returns User from valid auth session token. Checks Redis first, then DB. Caches in Redis. */\nexport async function findUserBySessionToken(token: string) {\n const cacheKey = getAuthSessionCacheKey(token);\n const cachedUser = await getRedis()?.get(cacheKey);\n\n if (cachedUser) {\n return JSON.parse(cachedUser);\n }\n\n const session = await prisma.session.findFirst({\n where: {\n id: token,\n type: SessionType.AUTH,\n userId: { not: null },\n expiresAt: { gt: new Date() },\n },\n select: {\n expiresAt: true,\n user: {\n select: {\n id: true,\n username: true,\n },\n },\n },\n });\n\n if (!session?.user) {\n return null;\n }\n\n const ttlSeconds = getSessionTtlSeconds(session.expiresAt);\n const r = getRedis();\n if (r && ttlSeconds) {\n await r.setex(cacheKey, ttlSeconds, JSON.stringify(session.user));\n }\n\n return session.user;\n}\n\n/** @returns Class IDs where user is a teacher. */\nexport function findTeacherClassesByUserId(userId: string) {\n return prisma.class.findMany({\n where: classTeacherOrSchoolAdminWhere(userId),\n select: { id: true },\n });\n}\n\n/** @returns Class if user is student or teacher. */\nexport function findClassWithMember(classId: string, userId: string) {\n return prisma.class.findFirst({\n where: {\n id: classId,\n ...classMemberOrSchoolAdminWhere(userId),\n },\n });\n}\n\n/** @returns Class if user is a teacher or school admin. */\nexport function findClassWithTeacher(classId: string, teacherId: string) {\n return prisma.class.findFirst({\n where: {\n id: classId,\n ...classTeacherOrSchoolAdminWhere(teacherId),\n },\n });\n}\n"],"names":[],"mappings":"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","debug_id":"1f454e5f-571d-5200-843d-700eec8d74e3"}
@@ -30,6 +30,8 @@ export declare function findEventById(id: string): import("@prisma/client/runtim
30
30
  email: string;
31
31
  password: string;
32
32
  verified: boolean;
33
+ termsAcceptedAt: Date | null;
34
+ termsVersion: string | null;
33
35
  schoolId: string | null;
34
36
  } | null;
35
37
  class: ({
@@ -1 +1 @@
1
- {"version":3,"file":"event.d.ts","sourceRoot":"/","sources":["models/event.ts"],"names":[],"mappings":"AA+DA,mEAAmE;AACnE,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAKvC;AAED,2DAA2D;AAC3D,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAOvE;AAED,+BAA+B;AAC/B,wBAAgB,WAAW,CAAC,IAAI,EAAE;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,IAAI,CAAC;IAChB,OAAO,EAAE,IAAI,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAKA;AAED,uBAAuB;AACvB,wBAAgB,WAAW,CACzB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE;IACJ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAMF;AAED,uBAAuB;AACvB,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAIzC;AAED,8CAA8C;AAC9C,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAyBjD;AAED,mDAAmD;AACnD,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAyB3D;AAED,qCAAqC;AACrC,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAOhB;AAED,wCAAwC;AACxC,wBAAgB,yBAAyB,CAAC,YAAY,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAM7D;AAED,sDAAsD;AACtD,wBAAgB,gCAAgC,CAAC,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;KAqB/D"}
1
+ {"version":3,"file":"event.d.ts","sourceRoot":"/","sources":["models/event.ts"],"names":[],"mappings":"AA+DA,mEAAmE;AACnE,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAKvC;AAED,2DAA2D;AAC3D,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAOvE;AAED,+BAA+B;AAC/B,wBAAgB,WAAW,CAAC,IAAI,EAAE;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,IAAI,CAAC;IAChB,OAAO,EAAE,IAAI,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAKA;AAED,uBAAuB;AACvB,wBAAgB,WAAW,CACzB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE;IACJ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAMF;AAED,uBAAuB;AACvB,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAIzC;AAED,8CAA8C;AAC9C,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAyBjD;AAED,mDAAmD;AACnD,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAyB3D;AAED,qCAAqC;AACrC,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAOhB;AAED,wCAAwC;AACxC,wBAAgB,yBAAyB,CAAC,YAAY,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAM7D;AAED,sDAAsD;AACtD,wBAAgB,gCAAgC,CAAC,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;KAqB/D"}