From 601731b8315917c1a2ad5279a0a683399caf0e43 Mon Sep 17 00:00:00 2001 From: Minit Date: Wed, 22 Apr 2026 13:24:52 +0530 Subject: [PATCH 1/5] fix(api): enforce ownership check on AI generation endpoint Co-Authored-By: Claude Sonnet 4.6 --- apps/web/app/api/video/ai/route.ts | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/apps/web/app/api/video/ai/route.ts b/apps/web/app/api/video/ai/route.ts index f3dc57b35d9..6bcf0b22f8c 100644 --- a/apps/web/app/api/video/ai/route.ts +++ b/apps/web/app/api/video/ai/route.ts @@ -2,10 +2,8 @@ import { db } from "@cap/database"; import { getCurrentUser } from "@cap/database/auth/session"; import { users, videos } from "@cap/database/schema"; import type { VideoMetadata } from "@cap/database/types"; -import { provideOptionalAuth, VideosPolicy } from "@cap/web-backend"; -import { Policy, type Video } from "@cap/web-domain"; -import { eq } from "drizzle-orm"; -import { Effect, Exit } from "effect"; +import type { Video } from "@cap/web-domain"; +import { and, eq } from "drizzle-orm"; import type { NextRequest } from "next/server"; import { startAiGeneration } from "@/lib/generate-ai"; import * as EffectRuntime from "@/lib/server"; @@ -30,22 +28,10 @@ export async function GET(request: NextRequest) { ); } - const exit = await Effect.gen(function* () { - const videosPolicy = yield* VideosPolicy; - - return yield* Effect.promise(() => - db().select().from(videos).where(eq(videos.id, videoId)), - ).pipe(Policy.withPublicPolicy(videosPolicy.canView(videoId))); - }).pipe(provideOptionalAuth, EffectRuntime.runPromiseExit); - - if (Exit.isFailure(exit)) { - return Response.json( - { error: true, message: "Video not found" }, - { status: 404 }, - ); - } - - const result = exit.value; + const result = await db() + .select() + .from(videos) + .where(and(eq(videos.id, videoId), eq(videos.ownerId, user.id))); if (result.length === 0 || !result[0]) { return Response.json( { error: true, message: "Video not found" }, From f52965d55862bc979a75ae42a2e62995915c7eaf Mon Sep 17 00:00:00 2001 From: MinitJain Date: Fri, 19 Jun 2026 22:10:46 +0530 Subject: [PATCH 2/5] fix(api): drop unused EffectRuntime import, simplify video query with limit(1) --- apps/web/app/api/video/ai/route.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/apps/web/app/api/video/ai/route.ts b/apps/web/app/api/video/ai/route.ts index 6bcf0b22f8c..33fbe09eb7f 100644 --- a/apps/web/app/api/video/ai/route.ts +++ b/apps/web/app/api/video/ai/route.ts @@ -6,7 +6,6 @@ import type { Video } from "@cap/web-domain"; import { and, eq } from "drizzle-orm"; import type { NextRequest } from "next/server"; import { startAiGeneration } from "@/lib/generate-ai"; -import * as EffectRuntime from "@/lib/server"; import { isAiGenerationEnabled } from "@/utils/flags"; export const dynamic = "force-dynamic"; @@ -28,18 +27,17 @@ export async function GET(request: NextRequest) { ); } - const result = await db() + const [video] = await db() .select() .from(videos) - .where(and(eq(videos.id, videoId), eq(videos.ownerId, user.id))); - if (result.length === 0 || !result[0]) { + .where(and(eq(videos.id, videoId), eq(videos.ownerId, user.id))) + .limit(1); + if (!video) { return Response.json( { error: true, message: "Video not found" }, { status: 404 }, ); } - - const video = result[0]; const metadata: VideoMetadata = (video.metadata as VideoMetadata) || {}; if (metadata.summary || metadata.chapters) { From 85292e3ed1f29bf43cdcca922ef0168eb925a479 Mon Sep 17 00:00:00 2001 From: MinitJain Date: Fri, 19 Jun 2026 22:38:29 +0530 Subject: [PATCH 3/5] fix(typecheck): add missing fontawesome-svg-core dep and fix dispatch type - Add @fortawesome/fontawesome-svg-core to package.json (was missing, causing "Cannot find module" typecheck error in Footer.tsx) - Fix createVideo return type in caption-tracks.test.ts to include FakeVideo intersection so video.dispatch() typechecks correctly --- apps/web/__tests__/unit/caption-tracks.test.ts | 4 ++-- apps/web/package.json | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/web/__tests__/unit/caption-tracks.test.ts b/apps/web/__tests__/unit/caption-tracks.test.ts index 74b208b8d07..5a3f1a15e37 100644 --- a/apps/web/__tests__/unit/caption-tracks.test.ts +++ b/apps/web/__tests__/unit/caption-tracks.test.ts @@ -76,11 +76,11 @@ function createCueList(text: string): TextTrackCueList { function createVideo( textTracks: FakeTextTrackList, trackElements: FakeTrackElement[], -): HTMLVideoElement { +): HTMLVideoElement & FakeVideo { return new FakeVideo( textTracks, trackElements, - ) as unknown as HTMLVideoElement; + ) as unknown as HTMLVideoElement & FakeVideo; } describe("bindCaptionTrackCueText", () => { diff --git a/apps/web/package.json b/apps/web/package.json index 3cfcc639540..76736884601 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -41,6 +41,7 @@ "@effect/rpc": "^0.71.0", "@effect/sql-mysql2": "^0.47.0", "@effect/workflow": "^0.11.3", + "@fortawesome/fontawesome-svg-core": "^6.7.2", "@fortawesome/free-brands-svg-icons": "^6.7.2", "@fortawesome/free-solid-svg-icons": "^6.7.2", "@fortawesome/react-fontawesome": "^0.2.2", From 99fc492cdb1c0738aa3705f7f087a8bb3cff0fe8 Mon Sep 17 00:00:00 2001 From: MinitJain Date: Fri, 19 Jun 2026 22:43:50 +0530 Subject: [PATCH 4/5] fix(typecheck): fix IconDefinition import without modifying lockfile Replace `import type { IconDefinition } from "@fortawesome/fontawesome-svg-core"` with a local type alias `type IconDefinition = typeof faDiscord`. fontawesome-svg-core is a transitive dep not listed in package.json; importing it directly breaks CI's frozen-lockfile install. --- apps/web/app/(site)/Footer.tsx | 3 ++- apps/web/package.json | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/app/(site)/Footer.tsx b/apps/web/app/(site)/Footer.tsx index 035f5b986ba..cbbe06e5027 100644 --- a/apps/web/app/(site)/Footer.tsx +++ b/apps/web/app/(site)/Footer.tsx @@ -1,12 +1,13 @@ "use client"; import { Logo } from "@cap/ui"; -import type { IconDefinition } from "@fortawesome/fontawesome-svg-core"; import { faDiscord, faLinkedinIn, faXTwitter, } from "@fortawesome/free-brands-svg-icons"; + +type IconDefinition = typeof faDiscord; import { faChevronDown } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import Link from "next/link"; diff --git a/apps/web/package.json b/apps/web/package.json index 76736884601..3cfcc639540 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -41,7 +41,6 @@ "@effect/rpc": "^0.71.0", "@effect/sql-mysql2": "^0.47.0", "@effect/workflow": "^0.11.3", - "@fortawesome/fontawesome-svg-core": "^6.7.2", "@fortawesome/free-brands-svg-icons": "^6.7.2", "@fortawesome/free-solid-svg-icons": "^6.7.2", "@fortawesome/react-fontawesome": "^0.2.2", From 016829cbeb0abea8c890fb146ee4fe46bff87d55 Mon Sep 17 00:00:00 2001 From: MinitJain Date: Fri, 19 Jun 2026 22:48:57 +0530 Subject: [PATCH 5/5] fix(format): move IconDefinition type alias after imports; select only needed video columns - Move `type IconDefinition` below all import statements to satisfy Biome organizeImports ordering rule - Select only ownerId/metadata/transcriptionStatus from videos table instead of SELECT * (tembo perf suggestion) --- apps/web/app/(site)/Footer.tsx | 4 ++-- apps/web/app/api/video/ai/route.ts | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/web/app/(site)/Footer.tsx b/apps/web/app/(site)/Footer.tsx index cbbe06e5027..fa85b0d54ca 100644 --- a/apps/web/app/(site)/Footer.tsx +++ b/apps/web/app/(site)/Footer.tsx @@ -6,14 +6,14 @@ import { faLinkedinIn, faXTwitter, } from "@fortawesome/free-brands-svg-icons"; - -type IconDefinition = typeof faDiscord; import { faChevronDown } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import Link from "next/link"; import type { ReactNode } from "react"; import { useState } from "react"; +type IconDefinition = typeof faDiscord; + type FooterLink = { label: string; href: string; diff --git a/apps/web/app/api/video/ai/route.ts b/apps/web/app/api/video/ai/route.ts index 33fbe09eb7f..cab430729f5 100644 --- a/apps/web/app/api/video/ai/route.ts +++ b/apps/web/app/api/video/ai/route.ts @@ -28,7 +28,11 @@ export async function GET(request: NextRequest) { } const [video] = await db() - .select() + .select({ + ownerId: videos.ownerId, + metadata: videos.metadata, + transcriptionStatus: videos.transcriptionStatus, + }) .from(videos) .where(and(eq(videos.id, videoId), eq(videos.ownerId, user.id))) .limit(1);