@goscribe/server 1.1.7 → 1.3.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 (56) hide show
  1. package/.env.example +43 -0
  2. package/check-difficulty.cjs +14 -0
  3. package/check-questions.cjs +14 -0
  4. package/db-summary.cjs +22 -0
  5. package/dist/routers/auth.js +1 -1
  6. package/mcq-test.cjs +36 -0
  7. package/package.json +10 -2
  8. package/prisma/migrations/20260413143206_init/migration.sql +873 -0
  9. package/prisma/schema.prisma +485 -292
  10. package/src/context.ts +4 -1
  11. package/src/lib/activity_human_description.test.ts +28 -0
  12. package/src/lib/activity_human_description.ts +239 -0
  13. package/src/lib/activity_log_service.test.ts +37 -0
  14. package/src/lib/activity_log_service.ts +353 -0
  15. package/src/lib/ai-session.ts +194 -112
  16. package/src/lib/constants.ts +14 -0
  17. package/src/lib/email.ts +230 -0
  18. package/src/lib/env.ts +23 -6
  19. package/src/lib/inference.ts +3 -3
  20. package/src/lib/logger.ts +26 -9
  21. package/src/lib/notification-service.test.ts +106 -0
  22. package/src/lib/notification-service.ts +677 -0
  23. package/src/lib/prisma.ts +6 -1
  24. package/src/lib/pusher.ts +90 -6
  25. package/src/lib/retry.ts +61 -0
  26. package/src/lib/storage.ts +2 -2
  27. package/src/lib/stripe.ts +39 -0
  28. package/src/lib/subscription_service.ts +722 -0
  29. package/src/lib/usage_service.ts +74 -0
  30. package/src/lib/worksheet-generation.test.ts +31 -0
  31. package/src/lib/worksheet-generation.ts +139 -0
  32. package/src/lib/workspace-access.ts +13 -0
  33. package/src/routers/_app.ts +11 -0
  34. package/src/routers/admin.ts +710 -0
  35. package/src/routers/annotations.ts +227 -0
  36. package/src/routers/auth.ts +432 -33
  37. package/src/routers/copilot.ts +719 -0
  38. package/src/routers/flashcards.ts +207 -80
  39. package/src/routers/members.ts +280 -80
  40. package/src/routers/notifications.ts +142 -0
  41. package/src/routers/payment.ts +448 -0
  42. package/src/routers/podcast.ts +133 -108
  43. package/src/routers/studyguide.ts +80 -74
  44. package/src/routers/worksheets.ts +300 -80
  45. package/src/routers/workspace.ts +538 -328
  46. package/src/scripts/purge-deleted-users.ts +167 -0
  47. package/src/server.ts +140 -12
  48. package/src/services/flashcard-progress.service.ts +52 -43
  49. package/src/trpc.ts +184 -5
  50. package/test-generate.js +30 -0
  51. package/test-ratio.cjs +9 -0
  52. package/zod-test.cjs +22 -0
  53. package/prisma/migrations/20250826124819_add_worksheet_difficulty_and_estimated_time/migration.sql +0 -213
  54. package/prisma/migrations/20250826133236_add_worksheet_question_progress/migration.sql +0 -31
  55. package/prisma/seed.mjs +0 -135
  56. package/src/routers/meetingsummary.ts +0 -416
@@ -1,18 +1,15 @@
1
1
  import { z } from 'zod';
2
2
  import { TRPCError } from '@trpc/server';
3
- import { router, authedProcedure } from '../trpc.js';
3
+ import { router, authedProcedure, verifiedProcedure,limitedProcedure } from '../trpc.js';
4
4
  import { v4 as uuidv4 } from 'uuid';
5
5
  import inference from '../lib/inference.js';
6
6
  import { uploadToSupabase, generateSignedUrl, deleteFromSupabase } from '../lib/storage.js';
7
7
  import PusherService from '../lib/pusher.js';
8
8
  import { aiSessionService } from '../lib/ai-session.js';
9
-
10
- // Prisma enum values mapped manually to avoid type import issues in ESM
11
- const ArtifactType = {
12
- PODCAST_EPISODE: 'PODCAST_EPISODE',
13
- STUDY_GUIDE: 'STUDY_GUIDE',
14
- FLASHCARD_SET: 'FLASHCARD_SET',
15
- } as const;
9
+ import { ArtifactType } from '../lib/constants.js';
10
+ import { workspaceAccessFilter } from '../lib/workspace-access.js';
11
+ import { logger } from '../lib/logger.js';
12
+ import { notifyArtifactFailed, notifyArtifactReady } from '../lib/notification-service.js';
16
13
 
17
14
  // Podcast segment schema
18
15
  const podcastSegmentSchema = z.object({
@@ -76,11 +73,11 @@ export const podcast = router({
76
73
  // Check if workspace exists
77
74
 
78
75
  if (!workspace) throw new TRPCError({ code: 'NOT_FOUND' });
79
-
76
+
80
77
  const artifacts = await ctx.db.artifact.findMany({
81
- where: {
82
- workspaceId: input.workspaceId,
83
- type: ArtifactType.PODCAST_EPISODE
78
+ where: {
79
+ workspaceId: input.workspaceId,
80
+ type: ArtifactType.PODCAST_EPISODE
84
81
  },
85
82
  include: {
86
83
  versions: {
@@ -93,10 +90,10 @@ export const podcast = router({
93
90
  },
94
91
  orderBy: { updatedAt: 'desc' },
95
92
  });
96
-
97
- console.log(`📻 Found ${artifacts.length} podcast artifacts`);
93
+
94
+ logger.debug(`Found ${artifacts.length} podcast artifacts`);
98
95
  artifacts.forEach((artifact, i) => {
99
- console.log(` Podcast ${i + 1}: "${artifact.title}" - ${artifact.podcastSegments.length} segments`);
96
+ logger.debug(` Podcast ${i + 1}: "${artifact.title}" - ${artifact.podcastSegments.length} segments`);
100
97
  });
101
98
 
102
99
  // Transform to include segments with fresh signed URLs
@@ -109,7 +106,7 @@ export const podcast = router({
109
106
  if (artifact.imageObjectKey) {
110
107
  objectUrl = await generateSignedUrl(artifact.imageObjectKey, 24);
111
108
  }
112
-
109
+
113
110
  // Generate fresh signed URLs for all segments
114
111
  const segmentsWithUrls = await Promise.all(
115
112
  artifact.podcastSegments.map(async (segment) => {
@@ -126,7 +123,7 @@ export const podcast = router({
126
123
  order: segment.order,
127
124
  };
128
125
  } catch (error) {
129
- console.error(`Failed to generate signed URL for segment ${segment.id}:`, error);
126
+ logger.error(`Failed to generate signed URL for segment ${segment.id}:`, error);
130
127
  return {
131
128
  id: segment.id,
132
129
  title: segment.title,
@@ -154,10 +151,10 @@ export const podcast = router({
154
151
  let metadata = null;
155
152
  if (latestVersion) {
156
153
  try {
157
- console.log(latestVersion.data)
154
+ logger.debug(JSON.stringify(latestVersion.data))
158
155
  metadata = podcastMetadataSchema.parse(latestVersion.data);
159
156
  } catch (error) {
160
- console.error('Failed to parse podcast metadata:', error);
157
+ logger.error('Failed to parse podcast metadata:', error);
161
158
  }
162
159
  }
163
160
 
@@ -188,10 +185,10 @@ export const podcast = router({
188
185
  .input(z.object({ episodeId: z.string() }))
189
186
  .query(async ({ ctx, input }) => {
190
187
  const episode = await ctx.db.artifact.findFirst({
191
- where: {
188
+ where: {
192
189
  id: input.episodeId,
193
190
  type: ArtifactType.PODCAST_EPISODE,
194
- workspace: { ownerId: ctx.session.user.id }
191
+ workspace: workspaceAccessFilter(ctx.session.user.id)
195
192
  },
196
193
  include: {
197
194
  versions: {
@@ -204,23 +201,23 @@ export const podcast = router({
204
201
  },
205
202
  });
206
203
 
207
- console.log(episode)
204
+ logger.debug(JSON.stringify(episode))
208
205
 
209
206
  if (!episode) throw new TRPCError({ code: 'NOT_FOUND' });
210
-
207
+
211
208
  const latestVersion = episode.versions[0];
212
209
  if (!latestVersion) throw new TRPCError({ code: 'NOT_FOUND', message: 'No version found' });
213
-
214
- console.log(latestVersion)
210
+
211
+ logger.debug(JSON.stringify(latestVersion))
215
212
  try {
216
213
  const metadata = podcastMetadataSchema.parse(latestVersion.data);
217
214
  } catch (error) {
218
- console.error('Failed to parse podcast metadata:', error);
215
+ logger.error('Failed to parse podcast metadata:', error);
219
216
  }
220
217
  const metadata = podcastMetadataSchema.parse(latestVersion.data);
221
218
 
222
219
  const imageUrl = episode.imageObjectKey ? await generateSignedUrl(episode.imageObjectKey, 24) : null;
223
-
220
+
224
221
 
225
222
  // Generate fresh signed URLs for all segments
226
223
  const segmentsWithUrls = await Promise.all(
@@ -240,7 +237,7 @@ export const podcast = router({
240
237
  order: segment.order,
241
238
  };
242
239
  } catch (error) {
243
- console.error(`Failed to generate signed URL for segment ${segment.id}:`, error);
240
+ logger.error(`Failed to generate signed URL for segment ${segment.id}:`, error);
244
241
  return {
245
242
  id: segment.id,
246
243
  title: segment.title,
@@ -267,7 +264,7 @@ export const podcast = router({
267
264
  };
268
265
  })
269
266
  );
270
-
267
+
271
268
  return {
272
269
  id: episode.id,
273
270
  title: metadata.title, // Use title from version metadata
@@ -282,7 +279,7 @@ export const podcast = router({
282
279
  }),
283
280
 
284
281
  // Generate podcast episode from text input
285
- generateEpisode: authedProcedure
282
+ generateEpisode: limitedProcedure
286
283
  .input(z.object({
287
284
  workspaceId: z.string(),
288
285
  podcastData: podcastInputSchema,
@@ -292,33 +289,33 @@ export const podcast = router({
292
289
  where: { id: input.workspaceId, ownerId: ctx.session.user.id },
293
290
  });
294
291
  if (!workspace) throw new TRPCError({ code: 'NOT_FOUND' });
295
-
296
- // Emit podcast generation start notification
297
- await PusherService.emitTaskComplete(input.workspaceId, 'podcast_generation_start', {
298
- title: input.podcastData.title
299
- });
300
292
 
301
- const BEGIN_PODCAST_GENERATION_MESSAGE = 'Structuring podcast contents...';
293
+ // Emit podcast generation start notification
294
+ await PusherService.emitTaskComplete(input.workspaceId, 'podcast_generation_start', {
295
+ title: input.podcastData.title
296
+ });
302
297
 
303
- const newArtifact = await ctx.db.artifact.create({
304
- data: {
305
- title: '----',
306
- type: ArtifactType.PODCAST_EPISODE,
307
- generating: true,
308
- generatingMetadata: {
309
- message: BEGIN_PODCAST_GENERATION_MESSAGE,
310
- },
311
- workspace: {
312
- connect: {
313
- id: input.workspaceId,
314
- }
298
+ const BEGIN_PODCAST_GENERATION_MESSAGE = 'Structuring podcast contents...';
299
+
300
+ const newArtifact = await ctx.db.artifact.create({
301
+ data: {
302
+ title: '----',
303
+ type: ArtifactType.PODCAST_EPISODE,
304
+ generating: true,
305
+ generatingMetadata: {
306
+ message: BEGIN_PODCAST_GENERATION_MESSAGE,
307
+ },
308
+ workspace: {
309
+ connect: {
310
+ id: input.workspaceId,
315
311
  }
316
312
  }
317
- });
313
+ }
314
+ });
318
315
 
319
- await PusherService.emitTaskComplete(input.workspaceId, 'podcast_info', {
320
- message: BEGIN_PODCAST_GENERATION_MESSAGE,
321
- });
316
+ await PusherService.emitTaskComplete(input.workspaceId, 'podcast_info', {
317
+ message: BEGIN_PODCAST_GENERATION_MESSAGE,
318
+ });
322
319
 
323
320
  try {
324
321
 
@@ -332,9 +329,9 @@ export const podcast = router({
332
329
  );
333
330
 
334
331
  if (!structureResult.success || !structureResult.structure) {
335
- throw new TRPCError({
336
- code: 'INTERNAL_SERVER_ERROR',
337
- message: 'Failed to generate podcast structure'
332
+ throw new TRPCError({
333
+ code: 'INTERNAL_SERVER_ERROR',
334
+ message: 'Failed to generate podcast structure'
338
335
  });
339
336
  }
340
337
 
@@ -366,7 +363,7 @@ export const podcast = router({
366
363
  }
367
364
  });
368
365
 
369
- await PusherService.emitTaskComplete(input.workspaceId, 'podcast_info', {
366
+ await PusherService.emitTaskComplete(input.workspaceId, 'podcast_info', {
370
367
  message: `Generating podcast image...`,
371
368
  });
372
369
 
@@ -409,7 +406,7 @@ export const podcast = router({
409
406
  }
410
407
  });
411
408
 
412
- await PusherService.emitTaskComplete(input.workspaceId, 'podcast_info', {
409
+ await PusherService.emitTaskComplete(input.workspaceId, 'podcast_info', {
413
410
  message: `Generating audio for segment ${i + 1} of ${structure.segments.length}...`,
414
411
  });
415
412
 
@@ -444,47 +441,56 @@ export const podcast = router({
444
441
 
445
442
  } catch (audioError) {
446
443
  const errorMessage = audioError instanceof Error ? audioError.message : 'Unknown error';
447
- console.error(`❌ Error generating audio for segment ${i + 1}:`, {
444
+ logger.error(`❌ Error generating audio for segment ${i + 1}:`, {
448
445
  title: segment.title,
449
446
  error: errorMessage,
450
447
  stack: audioError instanceof Error ? audioError.stack : undefined,
451
448
  });
452
-
449
+
453
450
  // Track failed segment
454
451
  failedSegments.push({
455
452
  index: i + 1,
456
453
  title: segment.title || `Segment ${i + 1}`,
457
454
  error: errorMessage,
458
455
  });
459
-
460
- await PusherService.emitTaskComplete(input.workspaceId, 'podcast_segment_error', {
456
+
457
+ await PusherService.emitTaskComplete(input.workspaceId, 'podcast_segment_error', {
461
458
  segmentIndex: i + 1,
462
459
  segmentTitle: segment.title || `Segment ${i + 1}`,
463
460
  error: errorMessage,
464
461
  successfulSegments: segments.length,
465
462
  failedSegments: failedSegments.length,
466
463
  });
467
-
464
+
468
465
  // Continue with other segments even if one fails
469
466
  }
470
467
  }
471
-
468
+
472
469
  // Check if any segments were successfully generated
473
470
  if (segments.length === 0) {
474
- console.error('No segments were successfully generated');
475
- await PusherService.emitError(input.workspaceId,
476
- `Failed to generate any segments. ${failedSegments.length} segment(s) failed.`,
471
+ logger.error('No segments were successfully generated');
472
+ await PusherService.emitError(input.workspaceId,
473
+ `Failed to generate any segments. ${failedSegments.length} segment(s) failed.`,
477
474
  'podcast'
478
475
  );
479
-
476
+
480
477
  // Cleanup the artifact
478
+ await notifyArtifactFailed(ctx.db, {
479
+ userId: ctx.session.user.id,
480
+ workspaceId: input.workspaceId,
481
+ artifactType: ArtifactType.PODCAST_EPISODE,
482
+ artifactId: newArtifact.id,
483
+ title: input.podcastData.title,
484
+ message: `Failed to generate any audio segments. All ${failedSegments.length} attempts failed.`,
485
+ }).catch(() => {});
486
+
481
487
  await ctx.db.artifact.delete({
482
488
  where: { id: newArtifact.id },
483
489
  });
484
-
485
- throw new TRPCError({
486
- code: 'INTERNAL_SERVER_ERROR',
487
- message: `Failed to generate any audio segments. All ${failedSegments.length} attempts failed.`
490
+
491
+ throw new TRPCError({
492
+ code: 'INTERNAL_SERVER_ERROR',
493
+ message: `Failed to generate any audio segments. All ${failedSegments.length} attempts failed.`
488
494
  });
489
495
  }
490
496
 
@@ -500,7 +506,7 @@ export const podcast = router({
500
506
  }
501
507
  });
502
508
 
503
- await PusherService.emitTaskComplete(input.workspaceId, 'podcast_info', {
509
+ await PusherService.emitTaskComplete(input.workspaceId, 'podcast_info', {
504
510
  message: `Preparing podcast summary...`,
505
511
  });
506
512
 
@@ -527,7 +533,7 @@ export const podcast = router({
527
533
  Podcast Title: ${structure.episodeTitle}
528
534
  Segments: ${JSON.stringify(segments.map(s => ({ title: s.title, keyPoints: s.keyPoints })))}`;
529
535
 
530
- const summaryResponse = await inference(summaryPrompt);
536
+ const summaryResponse = await inference([{ role: "user", content: summaryPrompt }]);
531
537
  const summaryContent: string = summaryResponse.choices[0].message.content || '';
532
538
 
533
539
  let episodeSummary;
@@ -539,8 +545,8 @@ export const podcast = router({
539
545
  }
540
546
  episodeSummary = JSON.parse(jsonMatch[0]);
541
547
  } catch (parseError) {
542
- console.error('Failed to parse summary response:', summaryContent);
543
- await PusherService.emitTaskComplete(input.workspaceId, 'podcast_summary_error', {
548
+ logger.error('Failed to parse summary response:', summaryContent);
549
+ await PusherService.emitTaskComplete(input.workspaceId, 'podcast_summary_error', {
544
550
  error: 'Failed to parse summary response'
545
551
  });
546
552
  episodeSummary = {
@@ -555,13 +561,13 @@ export const podcast = router({
555
561
  }
556
562
 
557
563
  // Emit summary generation completion notification
558
- await PusherService.emitTaskComplete(input.workspaceId, 'podcast_info', {
564
+ await PusherService.emitTaskComplete(input.workspaceId, 'podcast_info', {
559
565
  message: `Podcast summary generated.`,
560
566
  });
561
567
 
562
568
  // Step 4: Create artifact and initial version
563
569
  const episodeTitle = structure.episodeTitle || input.podcastData.title;
564
-
570
+
565
571
  await ctx.db.artifact.update({
566
572
  where: {
567
573
  id: newArtifact.id,
@@ -574,7 +580,7 @@ export const podcast = router({
574
580
  createdById: ctx.session.user.id,
575
581
  },
576
582
  });
577
-
583
+
578
584
  const createdSegments = await ctx.db.podcastSegment.createMany({
579
585
  data: segments.map(segment => ({
580
586
  artifactId: newArtifact.id,
@@ -591,7 +597,7 @@ export const podcast = router({
591
597
  },
592
598
  })),
593
599
  });
594
-
600
+
595
601
  const metadata = {
596
602
  title: episodeTitle,
597
603
  description: input.podcastData.description,
@@ -621,7 +627,14 @@ export const podcast = router({
621
627
  });
622
628
 
623
629
  // Emit podcast generation completion notification
624
- await PusherService.emitPodcastComplete(input.workspaceId, {});
630
+ await PusherService.emitPodcastComplete(input.workspaceId, newArtifact);
631
+ await notifyArtifactReady(ctx.db, {
632
+ userId: ctx.session.user.id,
633
+ workspaceId: input.workspaceId,
634
+ artifactId: newArtifact.id,
635
+ artifactType: ArtifactType.PODCAST_EPISODE,
636
+ title: metadata.title,
637
+ }).catch(() => {});
625
638
 
626
639
  return {
627
640
  id: newArtifact.id,
@@ -633,7 +646,19 @@ export const podcast = router({
633
646
 
634
647
  } catch (error) {
635
648
 
636
- console.error('Error generating podcast episode:', error);
649
+ logger.error('Error generating podcast episode:', error);
650
+
651
+ await notifyArtifactFailed(ctx.db, {
652
+ userId: ctx.session.user.id,
653
+ workspaceId: input.workspaceId,
654
+ artifactType: ArtifactType.PODCAST_EPISODE,
655
+ artifactId: newArtifact.id,
656
+ title: input.podcastData.title,
657
+ message:
658
+ error instanceof Error
659
+ ? error.message
660
+ : 'Podcast generation failed.',
661
+ }).catch(() => {});
637
662
 
638
663
  await ctx.db.artifact.delete({
639
664
  where: {
@@ -641,9 +666,9 @@ export const podcast = router({
641
666
  },
642
667
  });
643
668
  await PusherService.emitError(input.workspaceId, `Failed to generate podcast episode: ${error instanceof Error ? error.message : 'Unknown error'}`, 'podcast');
644
- throw new TRPCError({
645
- code: 'INTERNAL_SERVER_ERROR',
646
- message: `Failed to generate podcast episode: ${error instanceof Error ? error.message : 'Unknown error'}`
669
+ throw new TRPCError({
670
+ code: 'INTERNAL_SERVER_ERROR',
671
+ message: `Failed to generate podcast episode: ${error instanceof Error ? error.message : 'Unknown error'}`
647
672
  });
648
673
  }
649
674
  }),
@@ -660,10 +685,10 @@ export const podcast = router({
660
685
  .input(z.object({ episodeId: z.string() }))
661
686
  .query(async ({ ctx, input }) => {
662
687
  const episode = await ctx.db.artifact.findFirst({
663
- where: {
688
+ where: {
664
689
  id: input.episodeId,
665
690
  type: ArtifactType.PODCAST_EPISODE,
666
- workspace: { ownerId: ctx.session.user.id }
691
+ workspace: workspaceAccessFilter(ctx.session.user.id)
667
692
  },
668
693
  include: {
669
694
  versions: {
@@ -675,14 +700,14 @@ export const podcast = router({
675
700
  },
676
701
  },
677
702
  });
678
-
703
+
679
704
  if (!episode) throw new TRPCError({ code: 'NOT_FOUND' });
680
-
705
+
681
706
  const latestVersion = episode.versions[0];
682
707
  if (!latestVersion) throw new TRPCError({ code: 'NOT_FOUND', message: 'No version found' });
683
708
 
684
709
  const metadata = podcastMetadataSchema.parse(latestVersion.data);
685
-
710
+
686
711
  return {
687
712
  segments: episode.podcastSegments.map(s => ({
688
713
  id: s.id,
@@ -711,10 +736,10 @@ export const podcast = router({
711
736
  }))
712
737
  .mutation(async ({ ctx, input }) => {
713
738
  const episode = await ctx.db.artifact.findFirst({
714
- where: {
739
+ where: {
715
740
  id: input.episodeId,
716
741
  type: ArtifactType.PODCAST_EPISODE,
717
- workspace: { ownerId: ctx.session.user.id }
742
+ workspace: workspaceAccessFilter(ctx.session.user.id)
718
743
  },
719
744
  include: {
720
745
  versions: {
@@ -723,14 +748,14 @@ export const podcast = router({
723
748
  },
724
749
  },
725
750
  });
726
-
751
+
727
752
  if (!episode) throw new TRPCError({ code: 'NOT_FOUND' });
728
-
753
+
729
754
  const latestVersion = episode.versions[0];
730
755
  if (!latestVersion) throw new TRPCError({ code: 'NOT_FOUND', message: 'No version found' });
731
756
 
732
757
  const metadata = podcastMetadataSchema.parse(latestVersion.data);
733
-
758
+
734
759
  // Update metadata
735
760
  if (input.title) metadata.title = input.title;
736
761
  if (input.description) metadata.description = input.description;
@@ -746,7 +771,7 @@ export const podcast = router({
746
771
  createdById: ctx.session.user.id,
747
772
  },
748
773
  });
749
-
774
+
750
775
  // Update the artifact with basic info for listing/searching
751
776
  return ctx.db.artifact.update({
752
777
  where: { id: input.episodeId },
@@ -763,10 +788,10 @@ export const podcast = router({
763
788
  .input(z.object({ episodeId: z.string() }))
764
789
  .mutation(async ({ ctx, input }) => {
765
790
  const episode = await ctx.db.artifact.findFirst({
766
- where: {
791
+ where: {
767
792
  id: input.episodeId,
768
793
  type: ArtifactType.PODCAST_EPISODE,
769
- workspace: { ownerId: ctx.session.user.id }
794
+ workspace: workspaceAccessFilter(ctx.session.user.id)
770
795
  },
771
796
  include: {
772
797
  versions: {
@@ -775,12 +800,12 @@ export const podcast = router({
775
800
  },
776
801
  },
777
802
  });
778
-
803
+
779
804
  if (!episode) throw new TRPCError({ code: 'NOT_FOUND' });
780
805
 
781
806
  try {
782
807
  // Emit episode deletion start notification
783
- await PusherService.emitTaskComplete(episode.workspaceId, 'podcast_deletion_start', {
808
+ await PusherService.emitTaskComplete(episode.workspaceId, 'podcast_deletion_start', {
784
809
  episodeId: input.episodeId,
785
810
  episodeTitle: episode.title || 'Untitled Episode'
786
811
  });
@@ -796,7 +821,7 @@ export const podcast = router({
796
821
  try {
797
822
  await deleteFromSupabase(segment.objectKey);
798
823
  } catch (error) {
799
- console.error(`Failed to delete audio file ${segment.objectKey}:`, error);
824
+ logger.error(`Failed to delete audio file ${segment.objectKey}:`, error);
800
825
  }
801
826
  }
802
827
  }
@@ -817,7 +842,7 @@ export const podcast = router({
817
842
  });
818
843
 
819
844
  // Emit episode deletion completion notification
820
- await PusherService.emitTaskComplete(episode.workspaceId, 'podcast_deletion_complete', {
845
+ await PusherService.emitTaskComplete(episode.workspaceId, 'podcast_deletion_complete', {
821
846
  episodeId: input.episodeId,
822
847
  episodeTitle: episode.title || 'Untitled Episode'
823
848
  });
@@ -825,11 +850,11 @@ export const podcast = router({
825
850
  return true;
826
851
 
827
852
  } catch (error) {
828
- console.error('Error deleting episode:', error);
853
+ logger.error('Error deleting episode:', error);
829
854
  await PusherService.emitError(episode.workspaceId, `Failed to delete episode: ${error instanceof Error ? error.message : 'Unknown error'}`, 'podcast');
830
- throw new TRPCError({
831
- code: 'INTERNAL_SERVER_ERROR',
832
- message: 'Failed to delete episode'
855
+ throw new TRPCError({
856
+ code: 'INTERNAL_SERVER_ERROR',
857
+ message: 'Failed to delete episode'
833
858
  });
834
859
  }
835
860
  }),
@@ -839,10 +864,10 @@ export const podcast = router({
839
864
  .input(z.object({ segmentId: z.string() }))
840
865
  .query(async ({ ctx, input }) => {
841
866
  const segment = await ctx.db.podcastSegment.findFirst({
842
- where: {
867
+ where: {
843
868
  id: input.segmentId,
844
869
  artifact: {
845
- workspace: { ownerId: ctx.session.user.id }
870
+ workspace: workspaceAccessFilter(ctx.session.user.id)
846
871
  }
847
872
  },
848
873
  include: {
@@ -858,7 +883,7 @@ export const podcast = router({
858
883
  try {
859
884
  audioUrl = await generateSignedUrl(segment.objectKey, 24); // 24 hours
860
885
  } catch (error) {
861
- console.error(`Failed to generate signed URL for segment ${segment.id}:`, error);
886
+ logger.error(`Failed to generate signed URL for segment ${segment.id}:`, error);
862
887
  }
863
888
  }
864
889