@goscribe/server 1.0.11 → 1.1.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.
- package/ANALYSIS_PROGRESS_SPEC.md +463 -0
- package/PROGRESS_QUICK_REFERENCE.md +239 -0
- package/dist/lib/ai-session.d.ts +20 -9
- package/dist/lib/ai-session.js +316 -80
- package/dist/lib/auth.d.ts +35 -2
- package/dist/lib/auth.js +88 -15
- package/dist/lib/env.d.ts +32 -0
- package/dist/lib/env.js +46 -0
- package/dist/lib/errors.d.ts +33 -0
- package/dist/lib/errors.js +78 -0
- package/dist/lib/inference.d.ts +4 -1
- package/dist/lib/inference.js +9 -11
- package/dist/lib/logger.d.ts +62 -0
- package/dist/lib/logger.js +342 -0
- package/dist/lib/podcast-prompts.d.ts +43 -0
- package/dist/lib/podcast-prompts.js +135 -0
- package/dist/lib/pusher.d.ts +1 -0
- package/dist/lib/pusher.js +14 -2
- package/dist/lib/storage.d.ts +3 -3
- package/dist/lib/storage.js +51 -47
- package/dist/lib/validation.d.ts +51 -0
- package/dist/lib/validation.js +64 -0
- package/dist/routers/_app.d.ts +697 -111
- package/dist/routers/_app.js +5 -0
- package/dist/routers/auth.d.ts +11 -1
- package/dist/routers/chat.d.ts +11 -1
- package/dist/routers/flashcards.d.ts +205 -6
- package/dist/routers/flashcards.js +144 -66
- package/dist/routers/members.d.ts +165 -0
- package/dist/routers/members.js +531 -0
- package/dist/routers/podcast.d.ts +78 -63
- package/dist/routers/podcast.js +330 -393
- package/dist/routers/studyguide.d.ts +11 -1
- package/dist/routers/worksheets.d.ts +124 -13
- package/dist/routers/worksheets.js +123 -50
- package/dist/routers/workspace.d.ts +213 -26
- package/dist/routers/workspace.js +303 -181
- package/dist/server.js +12 -4
- package/dist/services/flashcard-progress.service.d.ts +183 -0
- package/dist/services/flashcard-progress.service.js +383 -0
- package/dist/services/flashcard.service.d.ts +183 -0
- package/dist/services/flashcard.service.js +224 -0
- package/dist/services/podcast-segment-reorder.d.ts +0 -0
- package/dist/services/podcast-segment-reorder.js +107 -0
- package/dist/services/podcast.service.d.ts +0 -0
- package/dist/services/podcast.service.js +326 -0
- package/dist/services/worksheet.service.d.ts +0 -0
- package/dist/services/worksheet.service.js +295 -0
- package/dist/trpc.d.ts +13 -2
- package/dist/trpc.js +55 -6
- package/dist/types/index.d.ts +126 -0
- package/dist/types/index.js +1 -0
- package/package.json +3 -2
- package/prisma/schema.prisma +142 -4
- package/src/lib/ai-session.ts +356 -85
- package/src/lib/auth.ts +113 -19
- package/src/lib/env.ts +59 -0
- package/src/lib/errors.ts +92 -0
- package/src/lib/inference.ts +11 -11
- package/src/lib/logger.ts +405 -0
- package/src/lib/pusher.ts +15 -3
- package/src/lib/storage.ts +56 -51
- package/src/lib/validation.ts +75 -0
- package/src/routers/_app.ts +5 -0
- package/src/routers/chat.ts +2 -23
- package/src/routers/flashcards.ts +108 -24
- package/src/routers/members.ts +586 -0
- package/src/routers/podcast.ts +385 -420
- package/src/routers/worksheets.ts +118 -36
- package/src/routers/workspace.ts +356 -195
- package/src/server.ts +13 -4
- package/src/services/flashcard-progress.service.ts +541 -0
- package/src/trpc.ts +59 -6
- package/src/types/index.ts +165 -0
- package/AUTH_FRONTEND_SPEC.md +0 -21
- package/CHAT_FRONTEND_SPEC.md +0 -474
- package/DATABASE_SETUP.md +0 -165
- package/MEETINGSUMMARY_FRONTEND_SPEC.md +0 -28
- package/PODCAST_FRONTEND_SPEC.md +0 -595
- package/STUDYGUIDE_FRONTEND_SPEC.md +0 -18
- package/WORKSHEETS_FRONTEND_SPEC.md +0 -26
- package/WORKSPACE_FRONTEND_SPEC.md +0 -47
- package/test-ai-integration.js +0 -134
package/dist/lib/storage.js
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
// src/server/lib/
|
|
2
|
-
import {
|
|
1
|
+
// src/server/lib/storage.ts
|
|
2
|
+
import { createClient } from '@supabase/supabase-js';
|
|
3
3
|
import { v4 as uuidv4 } from 'uuid';
|
|
4
|
-
// Initialize
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const bucketName = process.env.GCP_BUCKET || process.env.GOOGLE_CLOUD_BUCKET_NAME || 'your-bucket-name';
|
|
14
|
-
export async function uploadToGCS(fileBuffer, fileName, contentType, makePublic = false) {
|
|
15
|
-
const bucket = storage.bucket(bucketName);
|
|
4
|
+
// Initialize Supabase Storage
|
|
5
|
+
const supabaseUrl = process.env.SUPABASE_URL;
|
|
6
|
+
const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
|
|
7
|
+
if (!supabaseUrl || !supabaseServiceKey) {
|
|
8
|
+
throw new Error('Missing required Supabase environment variables: SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY');
|
|
9
|
+
}
|
|
10
|
+
const supabase = createClient(supabaseUrl, supabaseServiceKey);
|
|
11
|
+
const bucketName = process.env.SUPABASE_BUCKET || 'media';
|
|
12
|
+
export async function uploadToSupabase(fileBuffer, fileName, contentType, makePublic = false) {
|
|
16
13
|
const objectKey = `podcasts/${uuidv4()}_${fileName}`;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
public: makePublic,
|
|
14
|
+
// Upload the file to Supabase Storage
|
|
15
|
+
const { data, error } = await supabase.storage
|
|
16
|
+
.from(bucketName)
|
|
17
|
+
.upload(objectKey, fileBuffer, {
|
|
18
|
+
contentType,
|
|
19
|
+
upsert: false,
|
|
24
20
|
});
|
|
25
|
-
|
|
21
|
+
if (error) {
|
|
22
|
+
throw new Error(`Failed to upload file to Supabase: ${error.message}`);
|
|
23
|
+
}
|
|
24
|
+
const url = `${supabaseUrl}/storage/v1/object/public/${bucketName}/${objectKey}`;
|
|
26
25
|
// Generate signed URL for private files
|
|
27
26
|
let signedUrl;
|
|
28
27
|
if (!makePublic) {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
28
|
+
const { data: signedUrlData, error: signedUrlError } = await supabase.storage
|
|
29
|
+
.from(bucketName)
|
|
30
|
+
.createSignedUrl(objectKey, 24 * 60 * 60); // 24 hours
|
|
31
|
+
if (signedUrlError) {
|
|
32
|
+
throw new Error(`Failed to generate signed URL: ${signedUrlError.message}`);
|
|
33
|
+
}
|
|
34
|
+
signedUrl = signedUrlData.signedUrl;
|
|
35
35
|
}
|
|
36
36
|
return {
|
|
37
37
|
url,
|
|
@@ -40,28 +40,32 @@ export async function uploadToGCS(fileBuffer, fileName, contentType, makePublic
|
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
42
|
export async function generateSignedUrl(objectKey, expiresInHours = 24) {
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return signedUrl;
|
|
43
|
+
const { data, error } = await supabase.storage
|
|
44
|
+
.from(bucketName)
|
|
45
|
+
.createSignedUrl(objectKey, expiresInHours * 60 * 60);
|
|
46
|
+
if (error) {
|
|
47
|
+
throw new Error(`Failed to generate signed URL: ${error.message}`);
|
|
48
|
+
}
|
|
49
|
+
return data.signedUrl;
|
|
51
50
|
}
|
|
52
|
-
export async function
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
export async function deleteFromSupabase(objectKey) {
|
|
52
|
+
const { error } = await supabase.storage
|
|
53
|
+
.from(bucketName)
|
|
54
|
+
.remove([objectKey]);
|
|
55
|
+
if (error) {
|
|
56
|
+
throw new Error(`Failed to delete file from Supabase: ${error.message}`);
|
|
57
|
+
}
|
|
56
58
|
}
|
|
57
59
|
export async function makeFilePublic(objectKey) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
// In Supabase, files are public by default when uploaded to public buckets
|
|
61
|
+
// For private buckets, you would need to update the bucket policy
|
|
62
|
+
// This function is kept for compatibility but may not be needed
|
|
63
|
+
console.log(`File ${objectKey} is already public in Supabase Storage`);
|
|
61
64
|
}
|
|
62
65
|
export async function makeFilePrivate(objectKey) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
// In Supabase, you would need to update the bucket policy to make files private
|
|
67
|
+
// This function is kept for compatibility but may not be needed
|
|
68
|
+
console.log(`File ${objectKey} privacy is controlled by bucket policy in Supabase Storage`);
|
|
66
69
|
}
|
|
67
|
-
|
|
70
|
+
// Export supabase client for direct access if needed
|
|
71
|
+
export const supabaseClient = supabase;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Common validation schemas
|
|
4
|
+
*/
|
|
5
|
+
export declare const commonSchemas: {
|
|
6
|
+
id: z.ZodString;
|
|
7
|
+
email: z.ZodString;
|
|
8
|
+
url: z.ZodString;
|
|
9
|
+
pagination: z.ZodObject<{
|
|
10
|
+
page: z.ZodDefault<z.ZodNumber>;
|
|
11
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
12
|
+
}, z.core.$strip>;
|
|
13
|
+
search: z.ZodObject<{
|
|
14
|
+
query: z.ZodString;
|
|
15
|
+
}, z.core.$strip>;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Enums for type safety
|
|
19
|
+
*/
|
|
20
|
+
export declare const ArtifactType: z.ZodEnum<{
|
|
21
|
+
STUDY_GUIDE: "STUDY_GUIDE";
|
|
22
|
+
FLASHCARD_SET: "FLASHCARD_SET";
|
|
23
|
+
WORKSHEET: "WORKSHEET";
|
|
24
|
+
MEETING_SUMMARY: "MEETING_SUMMARY";
|
|
25
|
+
PODCAST_EPISODE: "PODCAST_EPISODE";
|
|
26
|
+
}>;
|
|
27
|
+
export declare const Difficulty: z.ZodEnum<{
|
|
28
|
+
EASY: "EASY";
|
|
29
|
+
MEDIUM: "MEDIUM";
|
|
30
|
+
HARD: "HARD";
|
|
31
|
+
}>;
|
|
32
|
+
export declare const QuestionType: z.ZodEnum<{
|
|
33
|
+
MULTIPLE_CHOICE: "MULTIPLE_CHOICE";
|
|
34
|
+
TEXT: "TEXT";
|
|
35
|
+
NUMERIC: "NUMERIC";
|
|
36
|
+
TRUE_FALSE: "TRUE_FALSE";
|
|
37
|
+
MATCHING: "MATCHING";
|
|
38
|
+
FILL_IN_THE_BLANK: "FILL_IN_THE_BLANK";
|
|
39
|
+
}>;
|
|
40
|
+
/**
|
|
41
|
+
* Validation helper that throws ValidationError
|
|
42
|
+
*/
|
|
43
|
+
export declare function validateSchema<T extends z.ZodType>(schema: T, data: unknown): z.infer<T>;
|
|
44
|
+
/**
|
|
45
|
+
* Sanitize string inputs
|
|
46
|
+
*/
|
|
47
|
+
export declare function sanitizeString(input: string, maxLength?: number): string;
|
|
48
|
+
/**
|
|
49
|
+
* Validate ownership
|
|
50
|
+
*/
|
|
51
|
+
export declare function validateOwnership(ownerId: string, userId: string): void;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ValidationError } from './errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* Common validation schemas
|
|
5
|
+
*/
|
|
6
|
+
export const commonSchemas = {
|
|
7
|
+
id: z.string().cuid(),
|
|
8
|
+
email: z.string().email(),
|
|
9
|
+
url: z.string().url(),
|
|
10
|
+
pagination: z.object({
|
|
11
|
+
page: z.number().int().positive().default(1),
|
|
12
|
+
limit: z.number().int().positive().max(100).default(20),
|
|
13
|
+
}),
|
|
14
|
+
search: z.object({
|
|
15
|
+
query: z.string().min(1).max(200),
|
|
16
|
+
}),
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Enums for type safety
|
|
20
|
+
*/
|
|
21
|
+
export const ArtifactType = z.enum([
|
|
22
|
+
'STUDY_GUIDE',
|
|
23
|
+
'FLASHCARD_SET',
|
|
24
|
+
'WORKSHEET',
|
|
25
|
+
'MEETING_SUMMARY',
|
|
26
|
+
'PODCAST_EPISODE',
|
|
27
|
+
]);
|
|
28
|
+
export const Difficulty = z.enum(['EASY', 'MEDIUM', 'HARD']);
|
|
29
|
+
export const QuestionType = z.enum([
|
|
30
|
+
'MULTIPLE_CHOICE',
|
|
31
|
+
'TEXT',
|
|
32
|
+
'NUMERIC',
|
|
33
|
+
'TRUE_FALSE',
|
|
34
|
+
'MATCHING',
|
|
35
|
+
'FILL_IN_THE_BLANK',
|
|
36
|
+
]);
|
|
37
|
+
/**
|
|
38
|
+
* Validation helper that throws ValidationError
|
|
39
|
+
*/
|
|
40
|
+
export function validateSchema(schema, data) {
|
|
41
|
+
const result = schema.safeParse(data);
|
|
42
|
+
if (!result.success) {
|
|
43
|
+
const errors = result.error.message;
|
|
44
|
+
throw new ValidationError(`Validation failed: ${errors}`);
|
|
45
|
+
}
|
|
46
|
+
return result.data;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Sanitize string inputs
|
|
50
|
+
*/
|
|
51
|
+
export function sanitizeString(input, maxLength = 10000) {
|
|
52
|
+
return input
|
|
53
|
+
.trim()
|
|
54
|
+
.slice(0, maxLength)
|
|
55
|
+
.replace(/[<>]/g, ''); // Basic XSS prevention
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Validate ownership
|
|
59
|
+
*/
|
|
60
|
+
export function validateOwnership(ownerId, userId) {
|
|
61
|
+
if (ownerId !== userId) {
|
|
62
|
+
throw new ValidationError('You do not have permission to access this resource');
|
|
63
|
+
}
|
|
64
|
+
}
|