@goscribe/server 1.0.11 → 1.1.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 (83) hide show
  1. package/ANALYSIS_PROGRESS_SPEC.md +463 -0
  2. package/PROGRESS_QUICK_REFERENCE.md +239 -0
  3. package/dist/lib/ai-session.d.ts +20 -9
  4. package/dist/lib/ai-session.js +316 -80
  5. package/dist/lib/auth.d.ts +35 -2
  6. package/dist/lib/auth.js +88 -15
  7. package/dist/lib/env.d.ts +32 -0
  8. package/dist/lib/env.js +46 -0
  9. package/dist/lib/errors.d.ts +33 -0
  10. package/dist/lib/errors.js +78 -0
  11. package/dist/lib/inference.d.ts +4 -1
  12. package/dist/lib/inference.js +9 -11
  13. package/dist/lib/logger.d.ts +62 -0
  14. package/dist/lib/logger.js +342 -0
  15. package/dist/lib/podcast-prompts.d.ts +43 -0
  16. package/dist/lib/podcast-prompts.js +135 -0
  17. package/dist/lib/pusher.d.ts +1 -0
  18. package/dist/lib/pusher.js +14 -2
  19. package/dist/lib/storage.d.ts +3 -3
  20. package/dist/lib/storage.js +51 -47
  21. package/dist/lib/validation.d.ts +51 -0
  22. package/dist/lib/validation.js +64 -0
  23. package/dist/routers/_app.d.ts +697 -111
  24. package/dist/routers/_app.js +5 -0
  25. package/dist/routers/auth.d.ts +11 -1
  26. package/dist/routers/chat.d.ts +11 -1
  27. package/dist/routers/flashcards.d.ts +205 -6
  28. package/dist/routers/flashcards.js +144 -66
  29. package/dist/routers/members.d.ts +165 -0
  30. package/dist/routers/members.js +531 -0
  31. package/dist/routers/podcast.d.ts +78 -63
  32. package/dist/routers/podcast.js +330 -393
  33. package/dist/routers/studyguide.d.ts +11 -1
  34. package/dist/routers/worksheets.d.ts +124 -13
  35. package/dist/routers/worksheets.js +123 -50
  36. package/dist/routers/workspace.d.ts +213 -26
  37. package/dist/routers/workspace.js +303 -181
  38. package/dist/server.js +12 -4
  39. package/dist/services/flashcard-progress.service.d.ts +183 -0
  40. package/dist/services/flashcard-progress.service.js +383 -0
  41. package/dist/services/flashcard.service.d.ts +183 -0
  42. package/dist/services/flashcard.service.js +224 -0
  43. package/dist/services/podcast-segment-reorder.d.ts +0 -0
  44. package/dist/services/podcast-segment-reorder.js +107 -0
  45. package/dist/services/podcast.service.d.ts +0 -0
  46. package/dist/services/podcast.service.js +326 -0
  47. package/dist/services/worksheet.service.d.ts +0 -0
  48. package/dist/services/worksheet.service.js +295 -0
  49. package/dist/trpc.d.ts +13 -2
  50. package/dist/trpc.js +55 -6
  51. package/dist/types/index.d.ts +126 -0
  52. package/dist/types/index.js +1 -0
  53. package/package.json +3 -2
  54. package/prisma/schema.prisma +142 -4
  55. package/src/lib/ai-session.ts +356 -85
  56. package/src/lib/auth.ts +113 -19
  57. package/src/lib/env.ts +59 -0
  58. package/src/lib/errors.ts +92 -0
  59. package/src/lib/inference.ts +11 -11
  60. package/src/lib/logger.ts +405 -0
  61. package/src/lib/pusher.ts +15 -3
  62. package/src/lib/storage.ts +56 -51
  63. package/src/lib/validation.ts +75 -0
  64. package/src/routers/_app.ts +5 -0
  65. package/src/routers/chat.ts +2 -23
  66. package/src/routers/flashcards.ts +108 -24
  67. package/src/routers/members.ts +586 -0
  68. package/src/routers/podcast.ts +385 -420
  69. package/src/routers/worksheets.ts +117 -35
  70. package/src/routers/workspace.ts +328 -195
  71. package/src/server.ts +13 -4
  72. package/src/services/flashcard-progress.service.ts +541 -0
  73. package/src/trpc.ts +59 -6
  74. package/src/types/index.ts +165 -0
  75. package/AUTH_FRONTEND_SPEC.md +0 -21
  76. package/CHAT_FRONTEND_SPEC.md +0 -474
  77. package/DATABASE_SETUP.md +0 -165
  78. package/MEETINGSUMMARY_FRONTEND_SPEC.md +0 -28
  79. package/PODCAST_FRONTEND_SPEC.md +0 -595
  80. package/STUDYGUIDE_FRONTEND_SPEC.md +0 -18
  81. package/WORKSHEETS_FRONTEND_SPEC.md +0 -26
  82. package/WORKSPACE_FRONTEND_SPEC.md +0 -47
  83. package/test-ai-integration.js +0 -134
@@ -1,37 +1,37 @@
1
- // src/server/lib/gcs.ts
2
- import { Storage } from '@google-cloud/storage';
1
+ // src/server/lib/storage.ts
2
+ import { createClient } from '@supabase/supabase-js';
3
3
  import { v4 as uuidv4 } from 'uuid';
4
- // Initialize Google Cloud Storage
5
- const storage = new Storage({
6
- projectId: process.env.GCP_PROJECT_ID || process.env.GOOGLE_CLOUD_PROJECT_ID,
7
- credentials: process.env.GCP_CLIENT_EMAIL && process.env.GCP_PRIVATE_KEY ? {
8
- client_email: process.env.GCP_CLIENT_EMAIL,
9
- private_key: process.env.GCP_PRIVATE_KEY?.replace(/\\n/g, "\n"),
10
- } : undefined,
11
- keyFilename: process.env.GOOGLE_CLOUD_KEY_FILE || process.env.GCP_KEY_FILE,
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
- const file = bucket.file(objectKey);
18
- // Upload the file
19
- await file.save(fileBuffer, {
20
- metadata: {
21
- contentType,
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
- const url = `gs://${bucketName}/${objectKey}`;
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 [signedUrlResult] = await file.getSignedUrl({
30
- version: 'v4',
31
- action: 'read',
32
- expires: Date.now() + 24 * 60 * 60 * 1000, // 24 hours
33
- });
34
- signedUrl = signedUrlResult;
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 bucket = storage.bucket(bucketName);
44
- const file = bucket.file(objectKey);
45
- const [signedUrl] = await file.getSignedUrl({
46
- version: 'v4',
47
- action: 'read',
48
- expires: Date.now() + expiresInHours * 60 * 60 * 1000,
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 deleteFromGCS(objectKey) {
53
- const bucket = storage.bucket(bucketName);
54
- const file = bucket.file(objectKey);
55
- await file.delete();
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
- const bucket = storage.bucket(bucketName);
59
- const file = bucket.file(objectKey);
60
- await file.makePublic();
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
- const bucket = storage.bucket(bucketName);
64
- const file = bucket.file(objectKey);
65
- await file.makePrivate();
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
- export const bucket = storage.bucket(bucketName);
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
+ }