@studious-lms/server 1.15.0 → 1.16.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.
Files changed (113) 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/models/announcement.d.ts +0 -2
  5. package/dist/models/announcement.d.ts.map +1 -1
  6. package/dist/models/auth.d.ts +0 -8
  7. package/dist/models/auth.d.ts.map +1 -1
  8. package/dist/models/auth.js +2 -4
  9. package/dist/models/auth.js.map +1 -1
  10. package/dist/models/event.d.ts +0 -2
  11. package/dist/models/event.d.ts.map +1 -1
  12. package/dist/models/file.d.ts +0 -24
  13. package/dist/models/file.d.ts.map +1 -1
  14. package/dist/models/marketing.d.ts +15 -2
  15. package/dist/models/marketing.d.ts.map +1 -1
  16. package/dist/models/marketing.js +4 -12
  17. package/dist/models/marketing.js.map +1 -1
  18. package/dist/pipelines/aiLabChat.d.ts.map +1 -1
  19. package/dist/pipelines/aiLabChat.js +26 -34
  20. package/dist/pipelines/aiLabChat.js.map +1 -1
  21. package/dist/pipelines/gradeWorksheet.d.ts.map +1 -1
  22. package/dist/pipelines/gradeWorksheet.js +75 -36
  23. package/dist/pipelines/gradeWorksheet.js.map +1 -1
  24. package/dist/pipelines/labChatPrompt.d.ts.map +1 -1
  25. package/dist/pipelines/labChatPrompt.js +3 -6
  26. package/dist/pipelines/labChatPrompt.js.map +1 -1
  27. package/dist/routers/_app.d.ts +26 -222
  28. package/dist/routers/_app.d.ts.map +1 -1
  29. package/dist/routers/assignment.d.ts +0 -10
  30. package/dist/routers/assignment.d.ts.map +1 -1
  31. package/dist/routers/auth.d.ts +0 -2
  32. package/dist/routers/auth.d.ts.map +1 -1
  33. package/dist/routers/auth.js +2 -15
  34. package/dist/routers/auth.js.map +1 -1
  35. package/dist/routers/class.d.ts +0 -4
  36. package/dist/routers/class.d.ts.map +1 -1
  37. package/dist/routers/event.d.ts +0 -2
  38. package/dist/routers/event.d.ts.map +1 -1
  39. package/dist/routers/marketing.d.ts +13 -0
  40. package/dist/routers/marketing.d.ts.map +1 -1
  41. package/dist/routers/school.d.ts +0 -4
  42. package/dist/routers/school.d.ts.map +1 -1
  43. package/dist/routers/user.d.ts +0 -89
  44. package/dist/routers/user.d.ts.map +1 -1
  45. package/dist/routers/user.js +2 -19
  46. package/dist/routers/user.js.map +1 -1
  47. package/dist/seedDatabase.d.ts +0 -2
  48. package/dist/seedDatabase.d.ts.map +1 -1
  49. package/dist/services/aiChatStream.d.ts +0 -2
  50. package/dist/services/aiChatStream.d.ts.map +1 -1
  51. package/dist/services/aiChatStream.js +4 -31
  52. package/dist/services/aiChatStream.js.map +1 -1
  53. package/dist/services/aiProposalTools.d.ts.map +1 -1
  54. package/dist/services/aiProposalTools.js +7 -7
  55. package/dist/services/aiProposalTools.js.map +1 -1
  56. package/dist/services/aiTools.js +3 -3
  57. package/dist/services/aiTools.js.map +1 -1
  58. package/dist/services/assignment.d.ts +0 -10
  59. package/dist/services/assignment.d.ts.map +1 -1
  60. package/dist/services/auth.d.ts +0 -2
  61. package/dist/services/auth.d.ts.map +1 -1
  62. package/dist/services/auth.js +4 -13
  63. package/dist/services/auth.js.map +1 -1
  64. package/dist/services/class.d.ts +0 -4
  65. package/dist/services/class.d.ts.map +1 -1
  66. package/dist/services/event.d.ts +0 -2
  67. package/dist/services/event.d.ts.map +1 -1
  68. package/dist/services/marketing.d.ts +14 -1
  69. package/dist/services/marketing.d.ts.map +1 -1
  70. package/dist/services/marketing.js +5 -5
  71. package/dist/services/marketing.js.map +1 -1
  72. package/dist/services/openaiDocumentGen.d.ts +0 -12
  73. package/dist/services/openaiDocumentGen.d.ts.map +1 -1
  74. package/dist/services/openaiDocumentGen.js +63 -130
  75. package/dist/services/openaiDocumentGen.js.map +1 -1
  76. package/dist/services/school.d.ts +0 -4
  77. package/dist/services/school.d.ts.map +1 -1
  78. package/dist/utils/email.d.ts.map +1 -1
  79. package/dist/utils/email.js +3 -7
  80. package/dist/utils/email.js.map +1 -1
  81. package/package.json +1 -1
  82. package/prisma/schema.prisma +0 -3
  83. package/src/instrument.ts +1 -1
  84. package/src/models/auth.ts +0 -4
  85. package/src/models/marketing.ts +2 -10
  86. package/src/pipelines/aiLabChat.ts +28 -41
  87. package/src/pipelines/gradeWorksheet.ts +95 -38
  88. package/src/pipelines/labChatPrompt.ts +1 -4
  89. package/src/routers/auth.ts +0 -13
  90. package/src/routers/user.ts +0 -26
  91. package/src/services/aiChatStream.ts +3 -30
  92. package/src/services/aiProposalTools.ts +5 -6
  93. package/src/services/aiTools.ts +1 -1
  94. package/src/services/auth.ts +2 -14
  95. package/src/services/marketing.ts +3 -3
  96. package/src/services/openaiDocumentGen.ts +64 -146
  97. package/src/utils/email.ts +1 -5
  98. package/tests/integration/globalSetup.ts +18 -10
  99. package/tests/integration/routers/auth.test.ts +18 -13
  100. package/dist/lib/legal.d.ts +0 -3
  101. package/dist/lib/legal.d.ts.map +0 -1
  102. package/dist/lib/legal.js +0 -6
  103. package/dist/lib/legal.js.map +0 -1
  104. package/dist/services/userPrivacy.d.ts +0 -86
  105. package/dist/services/userPrivacy.d.ts.map +0 -1
  106. package/dist/services/userPrivacy.js +0 -184
  107. package/dist/services/userPrivacy.js.map +0 -1
  108. package/prisma/migrations/20260611120000_add_user_terms_acceptance/migration.sql +0 -3
  109. package/src/lib/legal.ts +0 -2
  110. package/src/services/userPrivacy.ts +0 -196
  111. package/tests/helpers/registerInput.ts +0 -20
  112. package/tests/unit/services/aiChatStream.test.ts +0 -92
  113. package/tests/unit/services/openaiDocumentGen.test.ts +0 -65
@@ -8,34 +8,10 @@ import { logger } from '../utils/logger.js';
8
8
 
9
9
  const DOCUMENT_GEN_MODEL = 'gpt-4o-mini';
10
10
 
11
- /**
12
- * Extensions OpenAI Code Interpreter can read/write in its sandbox.
13
- * @see https://developers.openai.com/api/docs/guides/tools-code-interpreter
14
- */
15
11
  export const SUPPORTED_DOCUMENT_FORMATS = [
16
- 'pdf',
17
- 'docx',
18
- 'doc',
19
- 'xlsx',
20
- 'pptx',
21
- 'csv',
22
- 'md',
23
- 'html',
24
- 'txt',
25
- 'json',
26
- 'xml',
27
- 'tex',
28
- 'png',
29
- 'jpeg',
30
- 'jpg',
31
- 'gif',
32
- 'zip',
33
- 'tar',
34
- 'py',
35
- 'js',
36
- 'ts',
37
- 'css',
38
- 'sh',
12
+ 'pdf', 'docx', 'doc', 'xlsx', 'pptx', 'csv', 'md', 'html', 'txt', 'json',
13
+ 'xml', 'tex', 'png', 'jpeg', 'jpg', 'gif', 'zip', 'tar', 'py', 'js', 'ts',
14
+ 'css', 'sh',
39
15
  ] as const;
40
16
 
41
17
  export type DocumentFormat = (typeof SUPPORTED_DOCUMENT_FORMATS)[number];
@@ -67,36 +43,34 @@ const FORMAT_MIME: Record<DocumentFormat, string> = {
67
43
  };
68
44
 
69
45
  const FORMAT_HINTS: Record<DocumentFormat, string> = {
70
- pdf: 'fpdf2 or reportlab — structured document with headings and body text',
71
- docx: 'python-docx — Word document with headings and paragraphs',
72
- doc: 'python-docx saving as .doc if supported, otherwise .docx',
73
- xlsx: 'openpyxl or pandas — spreadsheet with labelled columns/rows',
74
- pptx: 'python-pptx — slides with title + bullet content per slide',
75
- csv: 'pandas or csv module — tabular data with headers',
76
- md: 'write the markdown file directly',
77
- html: 'write a complete, styled HTML page',
78
- txt: 'write plain text with clear section breaks',
79
- json: 'json.dumps with readable indentation',
80
- xml: 'well-formed XML document',
81
- tex: 'LaTeX source that compiles to the requested content',
82
- png: 'matplotlib or Pillow — chart, diagram, or visual',
83
- jpeg: 'matplotlib or Pillow — save as JPEG',
84
- jpg: 'matplotlib or Pillow — save as JPEG',
85
- gif: 'matplotlib or Pillow — save as GIF',
86
- zip: 'zipfile — bundle multiple related files if needed',
87
- tar: 'tarfile — archive if multiple files are required',
88
- py: 'valid Python source code file',
89
- js: 'valid JavaScript source file',
90
- ts: 'valid TypeScript source file',
91
- css: 'stylesheet file',
92
- sh: 'shell script file',
46
+ pdf: 'fpdf2 or reportlab',
47
+ docx: 'python-docx',
48
+ doc: 'python-docx',
49
+ xlsx: 'openpyxl or pandas',
50
+ pptx: 'python-pptx',
51
+ csv: 'pandas or csv module',
52
+ md: 'write markdown directly',
53
+ html: 'write a complete HTML page',
54
+ txt: 'write plain text',
55
+ json: 'json.dumps with indentation',
56
+ xml: 'well-formed XML',
57
+ tex: 'LaTeX source',
58
+ png: 'matplotlib or Pillow',
59
+ jpeg: 'matplotlib or Pillow',
60
+ jpg: 'matplotlib or Pillow',
61
+ gif: 'matplotlib or Pillow',
62
+ zip: 'zipfile',
63
+ tar: 'tarfile',
64
+ py: 'Python source',
65
+ js: 'JavaScript source',
66
+ ts: 'TypeScript source',
67
+ css: 'CSS stylesheet',
68
+ sh: 'shell script',
93
69
  };
94
70
 
95
71
  export interface GenerateDocumentInput {
96
72
  title: string;
97
- /** Markdown or plain-text source material for the output file. */
98
73
  content: string;
99
- /** Output format. Defaults to pdf. */
100
74
  format?: DocumentFormat | string;
101
75
  }
102
76
 
@@ -113,9 +87,7 @@ export interface ContainerGeneratedFile {
113
87
  filename: string;
114
88
  }
115
89
 
116
- export function normalizeDocumentFormat(
117
- format?: string | null,
118
- ): DocumentFormat {
90
+ export function normalizeDocumentFormat(format?: string | null): DocumentFormat {
119
91
  const raw = (format ?? 'pdf').toLowerCase().replace(/^\./, '');
120
92
  if ((SUPPORTED_DOCUMENT_FORMATS as readonly string[]).includes(raw)) {
121
93
  return raw as DocumentFormat;
@@ -127,25 +99,13 @@ export function mimeTypeForFormat(format: DocumentFormat): string {
127
99
  return FORMAT_MIME[format];
128
100
  }
129
101
 
130
- export function extensionForFormat(format: DocumentFormat): string {
131
- return format === 'jpg' ? 'jpg' : format;
132
- }
133
-
134
- /** Pull generated-file citations for a given extension out of a Responses output. */
135
102
  export function extractContainerGeneratedFiles(
136
103
  response: Response,
137
104
  extension: DocumentFormat,
138
105
  ): ContainerGeneratedFile[] {
106
+ const suffix = `.${extension}`;
139
107
  const seen = new Set<string>();
140
108
  const files: ContainerGeneratedFile[] = [];
141
- const suffix = `.${extension}`;
142
-
143
- const add = (containerId: string, fileId: string, filename: string) => {
144
- const key = `${containerId}:${fileId}`;
145
- if (seen.has(key)) return;
146
- seen.add(key);
147
- files.push({ containerId, fileId, filename });
148
- };
149
109
 
150
110
  for (const item of response.output ?? []) {
151
111
  if (item.type !== 'message') continue;
@@ -154,13 +114,18 @@ export function extractContainerGeneratedFiles(
154
114
  for (const ann of block.annotations ?? []) {
155
115
  if (ann.type !== 'container_file_citation') continue;
156
116
  const name = ann.filename?.toLowerCase() ?? '';
157
- if (name.endsWith(suffix)) {
158
- add(ann.container_id, ann.file_id, ann.filename);
159
- }
117
+ if (!name.endsWith(suffix)) continue;
118
+ const key = `${ann.container_id}:${ann.file_id}`;
119
+ if (seen.has(key)) continue;
120
+ seen.add(key);
121
+ files.push({
122
+ containerId: ann.container_id,
123
+ fileId: ann.file_id,
124
+ filename: ann.filename,
125
+ });
160
126
  }
161
127
  }
162
128
  }
163
-
164
129
  return files;
165
130
  }
166
131
 
@@ -193,46 +158,13 @@ async function listContainerGeneratedFiles(
193
158
  return files;
194
159
  }
195
160
 
196
- async function downloadContainerFile(
197
- client: OpenAI,
198
- file: ContainerGeneratedFile,
199
- ): Promise<Buffer | null> {
200
- try {
201
- const res = await client.containers.files.content.retrieve(file.fileId, {
202
- container_id: file.containerId,
203
- });
204
- const bytes = await res.arrayBuffer();
205
- return Buffer.from(bytes);
206
- } catch (error) {
207
- logger.error('Failed to download container file', { error, file });
208
- return null;
209
- }
210
- }
211
-
212
161
  function safeBasename(title: string): string {
213
- const base = title
214
- .replace(/[^a-zA-Z0-9\s\-_]/g, '')
215
- .replace(/\s+/g, '_')
216
- .substring(0, 60);
217
- return base || 'document';
218
- }
219
-
220
- function buildDocumentGenInstructions(format: DocumentFormat): string {
221
- return [
222
- 'You are a document generator for K-12 and university teachers.',
223
- 'When given a title, output format, and source content:',
224
- '1. Use the python tool to create exactly ONE output file of the requested type.',
225
- `2. For this format use: ${FORMAT_HINTS[format]}.`,
226
- `3. You MUST output a ${format.toUpperCase()} file — not any other format.`,
227
- `4. The saved filename MUST end with .${extensionForFormat(format)}.`,
228
- '5. Structure content clearly — headings, sections, lists, tables/charts as appropriate.',
229
- '6. Include the document title prominently where the format allows.',
230
- '7. Put ONLY the educational content in the file — no meta commentary.',
231
- '8. You MUST save the file to disk in the working directory before you finish.',
232
- ...(format !== 'pdf'
233
- ? [`9. Do NOT create a PDF when ${format.toUpperCase()} was requested.`]
234
- : []),
235
- ].join(' ');
162
+ return (
163
+ title
164
+ .replace(/[^a-zA-Z0-9\s\-_]/g, '')
165
+ .replace(/\s+/g, '_')
166
+ .substring(0, 60) || 'document'
167
+ );
236
168
  }
237
169
 
238
170
  /** Generate a file via OpenAI Code Interpreter. */
@@ -241,32 +173,33 @@ export async function generateDocumentWithOpenAI(
241
173
  ): Promise<GeneratedDocumentResult | null> {
242
174
  const client = new OpenAI();
243
175
  const format = normalizeDocumentFormat(input.format);
244
- const ext = extensionForFormat(format);
176
+ const ext = format === 'jpg' ? 'jpg' : format;
245
177
  const filename = `${safeBasename(input.title)}.${ext}`;
246
178
 
247
179
  try {
248
180
  const response = await client.responses.create({
249
181
  model: DOCUMENT_GEN_MODEL,
250
- instructions: buildDocumentGenInstructions(format),
251
- tools: [
252
- {
253
- type: 'code_interpreter',
254
- container: { type: 'auto' },
255
- },
256
- ],
182
+ instructions: [
183
+ 'You generate classroom-ready files for teachers using the python tool.',
184
+ `You MUST output a ${ext.toUpperCase()} file — not any other format.`,
185
+ `Use: ${FORMAT_HINTS[format]}.`,
186
+ `The saved filename MUST end with .${ext}.`,
187
+ 'Save exactly one output file with the requested filename.',
188
+ 'Include only educational content — no chat commentary.',
189
+ ...(format !== 'pdf' ? [`Do NOT create a PDF when ${ext.toUpperCase()} was requested.`] : []),
190
+ ].join(' '),
191
+ tools: [{ type: 'code_interpreter', container: { type: 'auto' } }],
257
192
  tool_choice: 'required',
258
193
  input: [
259
- `Create a ${ext.toUpperCase()} file using the python tool.`,
260
- `Output filename (exact): ${filename}`,
261
- `Output format: ${ext}`,
262
- `Document title: ${input.title}`,
263
- 'Source content (markdown or plain text — adapt to the output format):',
194
+ `Create a ${ext.toUpperCase()} file.`,
195
+ `Filename (exact): ${filename}`,
196
+ `Title: ${input.title}`,
197
+ 'Content:',
264
198
  input.content,
265
199
  ].join('\n\n'),
266
200
  });
267
201
 
268
202
  let matches = extractContainerGeneratedFiles(response, format);
269
-
270
203
  if (matches.length === 0) {
271
204
  const containerId = containerIdFromResponse(response);
272
205
  if (containerId) {
@@ -275,10 +208,9 @@ export async function generateDocumentWithOpenAI(
275
208
  }
276
209
 
277
210
  if (matches.length === 0) {
278
- logger.error('OpenAI document gen produced no matching files', {
211
+ logger.error('OpenAI document gen: no output file', {
279
212
  title: input.title,
280
213
  format,
281
- outputTypes: (response.output ?? []).map((o) => o.type),
282
214
  });
283
215
  return null;
284
216
  }
@@ -287,8 +219,10 @@ export async function generateDocumentWithOpenAI(
287
219
  matches.find((f) => f.filename.toLowerCase() === filename.toLowerCase()) ??
288
220
  matches[0];
289
221
 
290
- const bytes = await downloadContainerFile(client, preferred);
291
- if (!bytes) return null;
222
+ const res = await client.containers.files.content.retrieve(preferred.fileId, {
223
+ container_id: preferred.containerId,
224
+ });
225
+ const bytes = Buffer.from(await res.arrayBuffer());
292
226
 
293
227
  return {
294
228
  bytes,
@@ -297,23 +231,7 @@ export async function generateDocumentWithOpenAI(
297
231
  format,
298
232
  };
299
233
  } catch (error) {
300
- logger.error('OpenAI document generation failed', {
301
- error,
302
- title: input.title,
303
- format,
304
- });
234
+ logger.error('OpenAI document generation failed', { error, title: input.title, format });
305
235
  return null;
306
236
  }
307
237
  }
308
-
309
- /** @deprecated Use {@link generateDocumentWithOpenAI} */
310
- export async function generatePdfWithOpenAI(
311
- input: Omit<GenerateDocumentInput, 'format'>,
312
- ): Promise<Buffer | null> {
313
- const result = await generateDocumentWithOpenAI({ ...input, format: 'pdf' });
314
- return result?.bytes ?? null;
315
- }
316
-
317
- /** @deprecated Use {@link extractContainerGeneratedFiles} */
318
- export const extractContainerPdfFiles = (response: Response) =>
319
- extractContainerGeneratedFiles(response, 'pdf');
@@ -27,11 +27,7 @@ export const transport = nodemailer.createTransport({
27
27
  export const sendMail = async ({ from, to, subject, text }: sendMailProps) => {
28
28
  // Wrapper function for sending emails
29
29
  if (env.EMAIL_DRY_RUN == "true") {
30
- logger.info("Email dry run enabled. Would have sent email", {
31
- to,
32
- from,
33
- subject,
34
- });
30
+ logger.info(`Email dry run enabled. Would have sent email to ${to} from ${from} with subject ${subject} and text ${text}`);
35
31
  return;
36
32
  }
37
33
 
@@ -10,7 +10,6 @@ import { createTRPCContext } from '../../src/trpc';
10
10
  import { clearDatabase } from '../../src/seedDatabase';
11
11
  import { execSync } from 'child_process';
12
12
  import { SessionType } from '@prisma/client';
13
- import { registerInput } from '../helpers/registerInput.js';
14
13
 
15
14
  async function runSetup() {
16
15
  const ctx = await createTRPCContext({
@@ -21,17 +20,26 @@ async function runSetup() {
21
20
 
22
21
  execSync('npm run test:pre');
23
22
 
24
- const user1 = await caller.auth.register(
25
- registerInput({ username: 'testuser1', email: 'test@studious.sh' }),
26
- );
23
+ const user1 = await caller.auth.register({
24
+ username: 'testuser1',
25
+ email: 'test@studious.sh',
26
+ password: 'password_is_1234',
27
+ confirmPassword: 'password_is_1234',
28
+ });
27
29
 
28
- const user2 = await caller.auth.register(
29
- registerInput({ username: 'testuser2', email: 'test2@studious.sh' }),
30
- );
30
+ const user2 = await caller.auth.register({
31
+ username: 'testuser2',
32
+ email: 'test2@studious.sh',
33
+ password: 'password_is_1234',
34
+ confirmPassword: 'password_is_1234',
35
+ });
31
36
 
32
- const user3 = await caller.auth.register(
33
- registerInput({ username: 'testuser3', email: 'test3@studious.sh' }),
34
- );
37
+ const user3 = await caller.auth.register({
38
+ username: 'testuser3',
39
+ email: 'test3@studious.sh',
40
+ password: 'password_is_1234',
41
+ confirmPassword: 'password_is_1234',
42
+ });
35
43
 
36
44
  const session1 = await prisma.session.findFirst({
37
45
  where: { userId: user1.user.id, type: SessionType.EMAIL_VERIFICATION },
@@ -6,7 +6,6 @@ import { test, expect, describe } from 'vitest';
6
6
  import { appRouter } from '../../../src/routers/_app';
7
7
  import { createTRPCContext } from '../../../src/trpc';
8
8
  import { prisma } from '../../../src/lib/prisma';
9
- import { registerInput } from '../../helpers/registerInput.js';
10
9
  import {
11
10
  caller,
12
11
  login1,
@@ -65,9 +64,12 @@ describe('Auth Router', () => {
65
64
  test('check fails when unauthenticated', () => expectUnauth((c) => c.auth.check()));
66
65
 
67
66
  test('resendVerificationEmail succeeds for existing user', async () => {
68
- await caller.auth.register(
69
- registerInput({ username: 'resendtest', email: 'resend@studious.sh' }),
70
- );
67
+ await caller.auth.register({
68
+ username: 'resendtest',
69
+ email: 'resend@studious.sh',
70
+ password: 'password_is_1234',
71
+ confirmPassword: 'password_is_1234',
72
+ });
71
73
  const result = await caller.auth.resendVerificationEmail({
72
74
  email: 'resend@studious.sh',
73
75
  });
@@ -225,20 +227,23 @@ describe('Auth Router', () => {
225
227
 
226
228
  test('register fails when username already exists', async () => {
227
229
  await expect(
228
- caller.auth.register(
229
- registerInput({
230
- username: 'testuser1',
231
- email: 'newemail@studious.sh',
232
- }),
233
- )
230
+ caller.auth.register({
231
+ username: 'testuser1',
232
+ email: 'newemail@studious.sh',
233
+ password: 'password_is_1234',
234
+ confirmPassword: 'password_is_1234',
235
+ })
234
236
  ).rejects.toThrow();
235
237
  });
236
238
 
237
239
  test('register fails when email already exists', async () => {
238
240
  await expect(
239
- caller.auth.register(
240
- registerInput({ username: 'newuser', email: 'test@studious.sh' }),
241
- )
241
+ caller.auth.register({
242
+ username: 'newuser',
243
+ email: 'test@studious.sh',
244
+ password: 'password_is_1234',
245
+ confirmPassword: 'password_is_1234',
246
+ })
242
247
  ).rejects.toThrow();
243
248
  });
244
249
 
@@ -1,3 +0,0 @@
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
@@ -1 +0,0 @@
1
- {"version":3,"file":"legal.d.ts","sourceRoot":"/","sources":["lib/legal.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,eAAO,MAAM,qBAAqB,YAAY,CAAC"}
package/dist/lib/legal.js DELETED
@@ -1,6 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,86 +0,0 @@
1
- /** Export a portable snapshot of the authenticated user's personal data. */
2
- export declare function exportUserData(userId: string): Promise<{
3
- exportedAt: string;
4
- user: {
5
- id: string;
6
- username: string;
7
- email: string;
8
- verified: boolean;
9
- termsAcceptedAt: Date | null;
10
- termsVersion: string | null;
11
- profile: {
12
- id: string;
13
- userId: string;
14
- displayName: string | null;
15
- bio: string | null;
16
- location: string | null;
17
- website: string | null;
18
- createdAt: Date;
19
- updatedAt: Date;
20
- profilePicture: string | null;
21
- profilePictureThumbnail: string | null;
22
- } | null;
23
- teacherIn: {
24
- section: string;
25
- id: string;
26
- name: string;
27
- subject: string;
28
- }[];
29
- studentIn: {
30
- section: string;
31
- id: string;
32
- name: string;
33
- subject: string;
34
- }[];
35
- submissions: {
36
- assignment: {
37
- id: string;
38
- classId: string;
39
- title: string;
40
- };
41
- id: string;
42
- createdAt: Date;
43
- extendedResponse: string | null;
44
- gradeReceived: number | null;
45
- submittedAt: Date | null;
46
- }[];
47
- events: {
48
- id: string;
49
- location: string | null;
50
- name: string | null;
51
- startTime: Date;
52
- endTime: Date;
53
- remarks: string | null;
54
- }[];
55
- notificationsReceived: {
56
- id: string;
57
- createdAt: Date;
58
- title: string;
59
- content: string;
60
- read: boolean;
61
- }[];
62
- schoolMemberships: {
63
- status: import(".prisma/client").$Enums.SchoolMembershipStatus;
64
- school: {
65
- id: string;
66
- name: string;
67
- };
68
- role: import(".prisma/client").$Enums.SchoolMembershipRole;
69
- }[];
70
- sentMessages: {
71
- id: string;
72
- createdAt: Date;
73
- content: string;
74
- conversationId: string;
75
- }[];
76
- };
77
- }>;
78
- /**
79
- * Erase personal account data by anonymizing the user record and removing
80
- * sessions and memberships. Educational records tied to classes (e.g.
81
- * submissions) remain with an anonymized identity where required.
82
- */
83
- export declare function deleteUserAccount(userId: string): Promise<{
84
- success: boolean;
85
- }>;
86
- //# sourceMappingURL=userPrivacy.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"userPrivacy.d.ts","sourceRoot":"/","sources":["services/userPrivacy.ts"],"names":[],"mappings":"AA6DA,4EAA4E;AAC5E,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4ElD;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM;;GAkDrD"}