@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.
- package/.github/workflows/deploy.yml +1 -1
- package/dist/instrument.js +3 -3
- package/dist/instrument.js.map +1 -1
- package/dist/models/announcement.d.ts +0 -2
- package/dist/models/announcement.d.ts.map +1 -1
- package/dist/models/auth.d.ts +0 -8
- package/dist/models/auth.d.ts.map +1 -1
- package/dist/models/auth.js +2 -4
- package/dist/models/auth.js.map +1 -1
- package/dist/models/event.d.ts +0 -2
- package/dist/models/event.d.ts.map +1 -1
- package/dist/models/file.d.ts +0 -24
- package/dist/models/file.d.ts.map +1 -1
- package/dist/models/marketing.d.ts +15 -2
- package/dist/models/marketing.d.ts.map +1 -1
- package/dist/models/marketing.js +4 -12
- package/dist/models/marketing.js.map +1 -1
- package/dist/pipelines/aiLabChat.d.ts.map +1 -1
- package/dist/pipelines/aiLabChat.js +26 -34
- package/dist/pipelines/aiLabChat.js.map +1 -1
- package/dist/pipelines/gradeWorksheet.d.ts.map +1 -1
- package/dist/pipelines/gradeWorksheet.js +75 -36
- package/dist/pipelines/gradeWorksheet.js.map +1 -1
- package/dist/pipelines/labChatPrompt.d.ts.map +1 -1
- package/dist/pipelines/labChatPrompt.js +3 -6
- package/dist/pipelines/labChatPrompt.js.map +1 -1
- package/dist/routers/_app.d.ts +26 -222
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/assignment.d.ts +0 -10
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/auth.d.ts +0 -2
- package/dist/routers/auth.d.ts.map +1 -1
- package/dist/routers/auth.js +2 -15
- package/dist/routers/auth.js.map +1 -1
- package/dist/routers/class.d.ts +0 -4
- package/dist/routers/class.d.ts.map +1 -1
- package/dist/routers/event.d.ts +0 -2
- package/dist/routers/event.d.ts.map +1 -1
- package/dist/routers/marketing.d.ts +13 -0
- package/dist/routers/marketing.d.ts.map +1 -1
- package/dist/routers/school.d.ts +0 -4
- package/dist/routers/school.d.ts.map +1 -1
- package/dist/routers/user.d.ts +0 -89
- package/dist/routers/user.d.ts.map +1 -1
- package/dist/routers/user.js +2 -19
- package/dist/routers/user.js.map +1 -1
- package/dist/seedDatabase.d.ts +0 -2
- package/dist/seedDatabase.d.ts.map +1 -1
- package/dist/services/aiChatStream.d.ts +0 -2
- package/dist/services/aiChatStream.d.ts.map +1 -1
- package/dist/services/aiChatStream.js +4 -31
- package/dist/services/aiChatStream.js.map +1 -1
- package/dist/services/aiProposalTools.d.ts.map +1 -1
- package/dist/services/aiProposalTools.js +7 -7
- package/dist/services/aiProposalTools.js.map +1 -1
- package/dist/services/aiTools.js +3 -3
- package/dist/services/aiTools.js.map +1 -1
- package/dist/services/assignment.d.ts +0 -10
- package/dist/services/assignment.d.ts.map +1 -1
- package/dist/services/auth.d.ts +0 -2
- package/dist/services/auth.d.ts.map +1 -1
- package/dist/services/auth.js +4 -13
- package/dist/services/auth.js.map +1 -1
- package/dist/services/class.d.ts +0 -4
- package/dist/services/class.d.ts.map +1 -1
- package/dist/services/event.d.ts +0 -2
- package/dist/services/event.d.ts.map +1 -1
- package/dist/services/marketing.d.ts +14 -1
- package/dist/services/marketing.d.ts.map +1 -1
- package/dist/services/marketing.js +5 -5
- package/dist/services/marketing.js.map +1 -1
- package/dist/services/openaiDocumentGen.d.ts +0 -12
- package/dist/services/openaiDocumentGen.d.ts.map +1 -1
- package/dist/services/openaiDocumentGen.js +63 -130
- package/dist/services/openaiDocumentGen.js.map +1 -1
- package/dist/services/school.d.ts +0 -4
- package/dist/services/school.d.ts.map +1 -1
- package/dist/utils/email.d.ts.map +1 -1
- package/dist/utils/email.js +3 -7
- package/dist/utils/email.js.map +1 -1
- package/package.json +1 -1
- package/prisma/schema.prisma +0 -3
- package/src/instrument.ts +1 -1
- package/src/models/auth.ts +0 -4
- package/src/models/marketing.ts +2 -10
- package/src/pipelines/aiLabChat.ts +28 -41
- package/src/pipelines/gradeWorksheet.ts +95 -38
- package/src/pipelines/labChatPrompt.ts +1 -4
- package/src/routers/auth.ts +0 -13
- package/src/routers/user.ts +0 -26
- package/src/services/aiChatStream.ts +3 -30
- package/src/services/aiProposalTools.ts +5 -6
- package/src/services/aiTools.ts +1 -1
- package/src/services/auth.ts +2 -14
- package/src/services/marketing.ts +3 -3
- package/src/services/openaiDocumentGen.ts +64 -146
- package/src/utils/email.ts +1 -5
- package/tests/integration/globalSetup.ts +18 -10
- package/tests/integration/routers/auth.test.ts +18 -13
- package/dist/lib/legal.d.ts +0 -3
- package/dist/lib/legal.d.ts.map +0 -1
- package/dist/lib/legal.js +0 -6
- package/dist/lib/legal.js.map +0 -1
- package/dist/services/userPrivacy.d.ts +0 -86
- package/dist/services/userPrivacy.d.ts.map +0 -1
- package/dist/services/userPrivacy.js +0 -184
- package/dist/services/userPrivacy.js.map +0 -1
- package/prisma/migrations/20260611120000_add_user_terms_acceptance/migration.sql +0 -3
- package/src/lib/legal.ts +0 -2
- package/src/services/userPrivacy.ts +0 -196
- package/tests/helpers/registerInput.ts +0 -20
- package/tests/unit/services/aiChatStream.test.ts +0 -92
- 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
|
-
'
|
|
18
|
-
'
|
|
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
|
|
71
|
-
docx: 'python-docx
|
|
72
|
-
doc: 'python-docx
|
|
73
|
-
xlsx: 'openpyxl or pandas
|
|
74
|
-
pptx: 'python-pptx
|
|
75
|
-
csv: 'pandas or csv module
|
|
76
|
-
md: 'write
|
|
77
|
-
html: 'write a complete
|
|
78
|
-
txt: 'write plain text
|
|
79
|
-
json: 'json.dumps with
|
|
80
|
-
xml: 'well-formed XML
|
|
81
|
-
tex: 'LaTeX source
|
|
82
|
-
png: 'matplotlib or Pillow
|
|
83
|
-
jpeg: 'matplotlib or Pillow
|
|
84
|
-
jpg: 'matplotlib or Pillow
|
|
85
|
-
gif: 'matplotlib or Pillow
|
|
86
|
-
zip: 'zipfile
|
|
87
|
-
tar: 'tarfile
|
|
88
|
-
py: '
|
|
89
|
-
js: '
|
|
90
|
-
ts: '
|
|
91
|
-
css: 'stylesheet
|
|
92
|
-
sh: 'shell script
|
|
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
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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 =
|
|
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:
|
|
251
|
-
|
|
252
|
-
{
|
|
253
|
-
|
|
254
|
-
|
|
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
|
|
260
|
-
`
|
|
261
|
-
`
|
|
262
|
-
|
|
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
|
|
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
|
|
291
|
-
|
|
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');
|
package/src/utils/email.ts
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
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
|
|
package/dist/lib/legal.d.ts
DELETED
package/dist/lib/legal.d.ts.map
DELETED
|
@@ -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
|
package/dist/lib/legal.js.map
DELETED
|
@@ -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"}
|