@goscribe/server 1.0.10 → 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
package/dist/trpc.js CHANGED
@@ -1,25 +1,74 @@
1
1
  import { initTRPC, TRPCError } from "@trpc/server";
2
2
  import superjson from "superjson";
3
+ import { logger } from "./lib/logger.js";
4
+ import { toTRPCError } from "./lib/errors.js";
3
5
  const t = initTRPC.context().create({
4
6
  transformer: superjson,
5
- errorFormatter({ shape }) {
6
- return shape;
7
+ errorFormatter({ shape, error }) {
8
+ // Log errors in development
9
+ if (process.env.NODE_ENV === 'development') {
10
+ logger.error('TRPC Error', 'TRPC', {
11
+ code: error.code,
12
+ message: error.message,
13
+ cause: error.cause,
14
+ });
15
+ }
16
+ return {
17
+ ...shape,
18
+ data: {
19
+ ...shape.data,
20
+ zodError: error.cause instanceof Error ? error.cause.message : null,
21
+ },
22
+ };
7
23
  },
8
24
  });
9
25
  export const router = t.router;
10
26
  export const middleware = t.middleware;
11
27
  export const publicProcedure = t.procedure;
12
- /** Middleware that enforces authentication */
28
+ /**
29
+ * Logging middleware
30
+ */
31
+ const loggingMiddleware = middleware(async ({ ctx, next, path, type }) => {
32
+ const start = Date.now();
33
+ const result = await next();
34
+ const duration = Date.now() - start;
35
+ logger.info(`TRPC ${type} ${path}`, 'TRPC', {
36
+ duration: `${duration}ms`,
37
+ userId: ctx.session?.user?.id,
38
+ });
39
+ return result;
40
+ });
41
+ /**
42
+ * Middleware that enforces authentication
43
+ */
13
44
  const isAuthed = middleware(({ ctx, next }) => {
14
45
  const hasUser = Boolean(ctx.session?.user?.id);
15
46
  if (!ctx.session || !hasUser) {
16
- throw new TRPCError({ code: "UNAUTHORIZED" });
47
+ throw new TRPCError({
48
+ code: "UNAUTHORIZED",
49
+ message: "You must be logged in to access this resource"
50
+ });
17
51
  }
18
52
  return next({
19
53
  ctx: {
20
54
  session: ctx.session,
55
+ userId: ctx.session.user.id,
21
56
  },
22
57
  });
23
58
  });
24
- /** Exported authed procedure */
25
- export const authedProcedure = publicProcedure.use(isAuthed);
59
+ /**
60
+ * Error handling middleware
61
+ */
62
+ const errorHandler = middleware(async ({ next }) => {
63
+ try {
64
+ return await next();
65
+ }
66
+ catch (error) {
67
+ throw toTRPCError(error);
68
+ }
69
+ });
70
+ /** Exported procedures with middleware */
71
+ export const authedProcedure = publicProcedure
72
+ .use(loggingMiddleware)
73
+ .use(errorHandler)
74
+ .use(isAuthed);
@@ -0,0 +1,126 @@
1
+ import type { Prisma } from '@prisma/client';
2
+ /**
3
+ * Common types for the application
4
+ */
5
+ export type PrismaTransaction = Omit<Prisma.TransactionClient, '$connect' | '$disconnect' | '$on' | '$transaction' | '$use'>;
6
+ /**
7
+ * User types
8
+ */
9
+ export interface UserSession {
10
+ id: string;
11
+ name?: string | null;
12
+ email?: string | null;
13
+ image?: string | null;
14
+ }
15
+ export interface AuthContext {
16
+ session: {
17
+ user: UserSession;
18
+ } | null;
19
+ userId?: string;
20
+ }
21
+ /**
22
+ * Artifact types
23
+ */
24
+ export type ArtifactTypeEnum = 'STUDY_GUIDE' | 'FLASHCARD_SET' | 'WORKSHEET' | 'MEETING_SUMMARY' | 'PODCAST_EPISODE';
25
+ export type DifficultyEnum = 'EASY' | 'MEDIUM' | 'HARD';
26
+ export type QuestionTypeEnum = 'MULTIPLE_CHOICE' | 'TEXT' | 'NUMERIC' | 'TRUE_FALSE' | 'MATCHING' | 'FILL_IN_THE_BLANK';
27
+ /**
28
+ * Worksheet types
29
+ */
30
+ export interface MarkSchemePoint {
31
+ point: number;
32
+ requirements: number;
33
+ }
34
+ export interface MarkScheme {
35
+ points: MarkSchemePoint[];
36
+ totalPoints: number;
37
+ }
38
+ export interface UserMarkSchemePoint extends MarkSchemePoint {
39
+ achievedPoints: number;
40
+ feedback: string;
41
+ }
42
+ export interface UserMarkScheme {
43
+ points: UserMarkSchemePoint[];
44
+ totalPoints: number;
45
+ }
46
+ export interface WorksheetQuestionMeta {
47
+ options?: string[];
48
+ completed?: boolean;
49
+ userAnswer?: string;
50
+ completedAt?: Date | null;
51
+ mark_scheme?: MarkScheme;
52
+ userMarkScheme?: UserMarkScheme;
53
+ }
54
+ export interface WorksheetQuestionProgressMeta {
55
+ userMarkScheme?: UserMarkScheme;
56
+ }
57
+ export interface CreateWorksheetQuestionInput {
58
+ prompt: string;
59
+ answer?: string;
60
+ type: QuestionTypeEnum;
61
+ difficulty?: DifficultyEnum;
62
+ order?: number;
63
+ meta?: WorksheetQuestionMeta;
64
+ }
65
+ export interface UpdateWorksheetQuestionInput extends Partial<CreateWorksheetQuestionInput> {
66
+ id: string;
67
+ }
68
+ /**
69
+ * Flashcard types
70
+ */
71
+ export interface CreateFlashcardInput {
72
+ front: string;
73
+ back: string;
74
+ tags?: string[];
75
+ order?: number;
76
+ }
77
+ export interface UpdateFlashcardInput extends Partial<CreateFlashcardInput> {
78
+ id: string;
79
+ }
80
+ /**
81
+ * File upload types
82
+ */
83
+ export interface FileUploadResult {
84
+ id: string;
85
+ name: string;
86
+ url: string;
87
+ mimeType: string;
88
+ size: number;
89
+ }
90
+ /**
91
+ * Pagination types
92
+ */
93
+ export interface PaginationInput {
94
+ page?: number;
95
+ limit?: number;
96
+ }
97
+ export interface PaginatedResult<T> {
98
+ data: T[];
99
+ pagination: {
100
+ page: number;
101
+ limit: number;
102
+ total: number;
103
+ totalPages: number;
104
+ };
105
+ }
106
+ /**
107
+ * Search types
108
+ */
109
+ export interface SearchInput extends PaginationInput {
110
+ query: string;
111
+ }
112
+ /**
113
+ * Response types
114
+ */
115
+ export type SuccessResponse<T = void> = {
116
+ success: true;
117
+ data: T;
118
+ message?: string;
119
+ };
120
+ export type ErrorResponse = {
121
+ success: false;
122
+ error: {
123
+ code: string;
124
+ message: string;
125
+ };
126
+ };
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goscribe/server",
3
- "version": "1.0.10",
3
+ "version": "1.1.0",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -22,9 +22,9 @@
22
22
  "dependencies": {
23
23
  "@auth/express": "^0.11.0",
24
24
  "@auth/prisma-adapter": "^2.10.0",
25
- "@google-cloud/storage": "^7.17.0",
26
25
  "@goscribe/server": "^1.0.8",
27
26
  "@prisma/client": "^6.14.0",
27
+ "@supabase/supabase-js": "^2.76.1",
28
28
  "@trpc/server": "^11.5.0",
29
29
  "@types/uuid": "^10.0.0",
30
30
  "@vingeray/editorjs-markdown-converter": "^0.1.2",
@@ -36,6 +36,7 @@
36
36
  "express": "^5.1.0",
37
37
  "helmet": "^8.1.0",
38
38
  "morgan": "^1.10.1",
39
+ "openai": "^6.3.0",
39
40
  "prisma": "^6.14.0",
40
41
  "pusher": "^5.2.0",
41
42
  "pusher-js": "^8.4.0",
@@ -50,11 +50,18 @@ model User {
50
50
  // Ownership
51
51
  folders Folder[] @relation("UserFolders")
52
52
  workspaces Workspace[] @relation("UserWorkspaces")
53
- invitedInWorkspaces Workspace[] @relation("WorkspaceSharedWith") // many-to-many
53
+ invitedInWorkspaces Workspace[] @relation("WorkspaceSharedWith") // many-to-many (deprecated)
54
+ workspaceMemberships WorkspaceMember[] // proper member management
54
55
  uploads FileAsset[] @relation("UserUploads")
55
56
  artifacts Artifact[] @relation("UserArtifacts")
56
57
  versions ArtifactVersion[] @relation("UserArtifactVersions")
58
+
59
+ // Progress tracking
60
+ flashcardProgress FlashcardProgress[] @relation("UserFlashcardProgress")
57
61
  worksheetQuestionProgress WorksheetQuestionProgress[]
62
+
63
+ // Invitations
64
+ sentInvitations WorkspaceInvitation[] @relation("UserInvitations")
58
65
 
59
66
  chats Chat[]
60
67
  createdAt DateTime @default(now())
@@ -109,6 +116,7 @@ model Workspace {
109
116
  ownerId String
110
117
  owner User @relation("UserWorkspaces", fields: [ownerId], references: [id], onDelete: Cascade)
111
118
  icon String @default("📄")
119
+ color String @default("#9D00FF")
112
120
 
113
121
  // A workspace (file) lives in a folder (nullable = root)
114
122
  folderId String?
@@ -116,15 +124,20 @@ model Workspace {
116
124
 
117
125
  channels Channel[]
118
126
 
119
- shareLink String? @unique // optional public share link
127
+ sharedWith User[] @relation("WorkspaceSharedWith") // many-to-many for sharing (deprecated)
128
+ members WorkspaceMember[] // proper member management with roles
129
+ fileBeingAnalyzed Boolean @default(false)
120
130
 
121
- sharedWith User[] @relation("WorkspaceSharedWith") // many-to-many for sharing
131
+ analysisProgress Json?
122
132
 
123
133
  // Raw uploads attached to this workspace
124
134
  uploads FileAsset[]
125
135
 
126
136
  // AI outputs for this workspace (study guides, flashcards, etc.)
127
137
  artifacts Artifact[]
138
+
139
+ // Invitations
140
+ invitations WorkspaceInvitation[]
128
141
 
129
142
  createdAt DateTime @default(now())
130
143
  updatedAt DateTime @updatedAt
@@ -201,10 +214,14 @@ model Artifact {
201
214
  title String
202
215
  isArchived Boolean @default(false)
203
216
 
217
+ generating Boolean @default(false)
218
+ generatingMetadata Json?
219
+
204
220
  // Worksheet-specific fields
205
221
  difficulty Difficulty? // only meaningful for WORKSHEET
206
222
  estimatedTime String? // only meaningful for WORKSHEET
207
223
 
224
+ imageObjectKey String?
208
225
  description String?
209
226
 
210
227
  createdById String?
@@ -213,6 +230,7 @@ model Artifact {
213
230
  versions ArtifactVersion[] // text/transcript versions etc.
214
231
  flashcards Flashcard[] // only meaningful for FLASHCARD_SET
215
232
  questions WorksheetQuestion[] // only meaningful for WORKSHEET
233
+ podcastSegments PodcastSegment[] // only meaningful for PODCAST_EPISODE
216
234
 
217
235
  createdAt DateTime @default(now())
218
236
  updatedAt DateTime @updatedAt
@@ -256,11 +274,51 @@ model Flashcard {
256
274
  tags String[] // optional keywords
257
275
  order Int @default(0)
258
276
 
277
+ // User progress tracking
278
+ progress FlashcardProgress[]
279
+
259
280
  createdAt DateTime @default(now())
260
281
 
261
282
  @@index([artifactId])
262
283
  }
263
284
 
285
+ //
286
+ // User Progress on Flashcards (spaced repetition, mastery tracking)
287
+ //
288
+ model FlashcardProgress {
289
+ id String @id @default(cuid())
290
+ userId String
291
+ user User @relation("UserFlashcardProgress", fields: [userId], references: [id], onDelete: Cascade)
292
+
293
+ flashcardId String
294
+ flashcard Flashcard @relation(fields: [flashcardId], references: [id], onDelete: Cascade)
295
+
296
+ // Study statistics
297
+ timesStudied Int @default(0)
298
+ timesCorrect Int @default(0)
299
+ timesIncorrect Int @default(0)
300
+ timesIncorrectConsecutive Int @default(0) // Track consecutive failures
301
+
302
+ // Spaced repetition data
303
+ easeFactor Float @default(2.5) // SM-2 algorithm ease factor
304
+ interval Int @default(0) // Days until next review
305
+ repetitions Int @default(0) // Consecutive correct answers
306
+
307
+ // Mastery level (0-100)
308
+ masteryLevel Int @default(0)
309
+
310
+ // Timestamps
311
+ lastStudiedAt DateTime?
312
+ nextReviewAt DateTime?
313
+
314
+ createdAt DateTime @default(now())
315
+ updatedAt DateTime @updatedAt
316
+
317
+ @@unique([userId, flashcardId])
318
+ @@index([userId, nextReviewAt])
319
+ @@index([flashcardId])
320
+ }
321
+
264
322
  //
265
323
  // Worksheet Questions (child items of a WORKSHEET Artifact)
266
324
  //
@@ -294,8 +352,9 @@ model WorksheetQuestionProgress {
294
352
  userId String
295
353
  user User @relation(fields: [userId], references: [id], onDelete: Cascade)
296
354
 
297
- completed Boolean @default(false)
355
+ modified Boolean @default(false)
298
356
  userAnswer String?
357
+ correct Boolean? @default(false)
299
358
  completedAt DateTime?
300
359
  attempts Int @default(0)
301
360
  timeSpentSec Int?
@@ -306,4 +365,83 @@ model WorksheetQuestionProgress {
306
365
 
307
366
  @@unique([worksheetQuestionId, userId])
308
367
  @@index([userId])
368
+ }
369
+
370
+ //
371
+ // Workspace Members (with roles)
372
+ //
373
+ model WorkspaceMember {
374
+ id String @id @default(cuid())
375
+ workspaceId String
376
+ workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
377
+
378
+ userId String
379
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
380
+
381
+ role String @default("member") // "owner", "admin", "member"
382
+
383
+ joinedAt DateTime @default(now())
384
+ updatedAt DateTime @updatedAt
385
+
386
+ @@unique([workspaceId, userId]) // One membership per user per workspace
387
+ @@index([workspaceId])
388
+ @@index([userId])
389
+ }
390
+
391
+ //
392
+ // Workspace Invitations
393
+ //
394
+ model WorkspaceInvitation {
395
+ id String @id @default(cuid())
396
+ workspaceId String
397
+ workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
398
+
399
+ email String
400
+ role String @default("member") // "owner", "admin", "member"
401
+ token String @unique @default(cuid()) // UUID for invitation link
402
+
403
+ invitedById String
404
+ invitedBy User @relation("UserInvitations", fields: [invitedById], references: [id], onDelete: Cascade)
405
+
406
+ acceptedAt DateTime?
407
+ expiresAt DateTime @default(dbgenerated("NOW() + INTERVAL '7 days'"))
408
+
409
+ createdAt DateTime @default(now())
410
+ updatedAt DateTime @updatedAt
411
+
412
+ @@unique([workspaceId, email]) // One invitation per email per workspace
413
+ @@index([token])
414
+ @@index([workspaceId])
415
+ }
416
+
417
+ //
418
+ // Podcast Segments (child items of a PODCAST_EPISODE Artifact)
419
+ //
420
+ model PodcastSegment {
421
+ id String @id @default(cuid())
422
+ artifactId String
423
+ artifact Artifact @relation(fields: [artifactId], references: [id], onDelete: Cascade)
424
+
425
+ title String
426
+ content String // Full text content of the segment
427
+ startTime Int // Start time in seconds
428
+ duration Int // Duration in seconds
429
+ order Int // Display order within the episode
430
+
431
+ // Audio file reference
432
+ objectKey String? // Google Cloud Storage object key
433
+ audioUrl String? // Cached signed URL (temporary)
434
+
435
+ // Metadata
436
+ keyPoints String[] // Array of key points
437
+ meta Json? // Additional metadata (voice settings, etc.)
438
+
439
+ generating Boolean @default(false)
440
+ generatingMetadata Json? // Additional metadata (voice settings, etc.)
441
+
442
+ createdAt DateTime @default(now())
443
+ updatedAt DateTime @updatedAt
444
+
445
+ @@index([artifactId, order]) // For efficient ordering
446
+ @@index([artifactId, startTime]) // For time-based queries
309
447
  }