Skip to content

Conversation

Enejivk
Copy link
Contributor

@Enejivk Enejivk commented Oct 3, 2025


Video Folder Movement Functionality

Overview

This PR adds the ability for users to move multiple videos between folders using a simple dialog interface.

Changes

New file

  • moveVideosToFolder.ts – Server action for handling video moves with authentication, validation, and revalidation.

Updated files

  • folder.ts – Added moveVideosToFolder function using Effect.js for validation and batch database updates.
  • FolderSelectionDialog.tsx – Upgraded from UI-only to functional; now actually moves videos and shows user feedback.
  • SelectedCapsBar.tsx – Added missing videoIds prop to enable moving selected videos.

Benefits

  • Consistent with existing Effect.js patterns
  • Clean separation of business logic and UI
  • Strong type safety
  • Robust error handling
  • Efficient batch database operations

Next Steps

  • 🔄 Add folder color customization

Summary by CodeRabbit

  • New Features

    • Move selected videos to a folder or root via a new Folder Selection dialog with hierarchical folder tree, expand/collapse, and video counts.
    • Fetches folders for organization or space contexts and performs moves with clear success/error toasts.
  • Improvements

    • Added a “Move to Folder” action/button in the selection bar alongside delete.
    • Dashboards auto-refresh after moves; smoother selection and dialog open/close behavior.

Copy link
Contributor

coderabbitai bot commented Oct 3, 2025

Walkthrough

Adds server actions to list folders and move videos, a FolderSelectionDialog UI integrated into the selected-items toolbar, and library functions to build folder trees and perform video-folder moves with validation and cache revalidation.

Changes

Cohort / File(s) Summary
Server actions: folders
apps/web/actions/folders/getAllFolders.ts, apps/web/actions/folders/moveVideosToFolder.ts
New server actions: getAllFoldersAction and moveVideosToFolderAction. They validate user/org, provide CurrentUser to Effects, call lib functions, handle errors, and perform Next.js path revalidation after moves.
Dashboard UI: move-to-folder
apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx, apps/web/app/(org)/dashboard/caps/components/SelectedCapsBar.tsx
Adds FolderSelectionDialog component to display hierarchical folders and perform moves; integrates it into SelectedCapsBar via new optional moveSelectedCaps? prop and a folder action button.
Folder library
apps/web/lib/folder.ts
Adds getAllFolders and moveVideosToFolder exports, uses inArray for queries, assembles hierarchical folder trees, validates ownership/space/org context, and returns move results including original folder IDs.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant SelectedCapsBar as SelectedCapsBar
  participant Dialog as FolderSelectionDialog
  participant Actions as Server Actions
  participant Lib as Folder Lib
  participant Cache as Next.js Revalidate

  User->>SelectedCapsBar: Click "Move to folder"
  SelectedCapsBar->>Dialog: open()
  Dialog->>Actions: getAllFoldersAction(root)
  Actions->>Lib: getAllFolders(root) [with CurrentUser]
  Lib-->>Actions: folders
  Actions-->>Dialog: { success, folders }

  User->>Dialog: Choose target & confirm
  Dialog->>Actions: moveVideosToFolderAction({videoIds, targetFolderId, spaceId?})
  Actions->>Lib: moveVideosToFolder(..) [with CurrentUser]
  Lib-->>Actions: move result
  Actions->>Cache: revalidatePath(...) (caps, space/org folders)
  Actions-->>Dialog: { success, message }
  Dialog-->>SelectedCapsBar: onConfirm(folderId)
Loading
sequenceDiagram
  actor Client
  participant getAll as getAllFoldersAction
  participant lib as getAllFolders

  Client->>getAll: request(root)
  getAll->>getAll: validate user / org
  getAll->>lib: fetch folders
  lib-->>getAll: folders
  getAll-->>Client: { success, folders } / { error }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

A whisker-twitch, a folder hop,
I stash my caps from top to top.
I sniff the tree, then pick a nest—
Click confirm, I do the rest.
Videos scurry, tidy and neat,
A rabbit's warren, organized and sweet. 🐇📁

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly and accurately summarizes the main feature introduced in the PR—moving videos between folders—without extraneous details, making its purpose clear to reviewers.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Enejivk
Copy link
Contributor Author

Enejivk commented Oct 3, 2025

move_video.mp4

This is the result

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (9)
apps/web/actions/folders/getAllFolders.ts (1)

10-14: Trim unused variant or handle it.

The user variant isn’t used in getAllFolders (only org/space paths are honored). Remove it here or implement semantics to avoid confusion.

apps/web/lib/folder.ts (2)

19-21: Remove unused import.

revalidatePath isn’t used in this module. Drop the import.

-import { revalidatePath } from "next/cache";

88-93: Prefer inArray over manual SQL IN building.

Use Drizzle’s inArray to avoid manual SQL construction and to leverage parameter binding.

-      .where(
-        sql`${spaceVideos.videoId} IN (${sql.join(
-          videoIds.map((id) => sql`${id}`),
-          sql`, `
-        )})`
-      )
+      .where(inArray(spaceVideos.videoId, videoIds))
@@
-      .where(
-        sql`${sharedVideos.videoId} IN (${sql.join(
-          videoIds.map((id) => sql`${id}`),
-          sql`, `
-        )})`
-      )
+      .where(inArray(sharedVideos.videoId, videoIds))

Also applies to: 111-116

apps/web/actions/folders/moveVideosToFolder.ts (1)

45-69: Revalidation paths are fine; consider minimizing broad invalidations.

/dashboard/caps is always revalidated. Prefer targeted cache updates on the client (setQueryData/setQueriesData) per coding guidelines; keep server revalidation minimal.

As per coding guidelines

apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx (3)

58-76: Avoid useQuery here; adopt useEffectQuery and update client cache on move.

Replace this with the project’s standard query hook and wire the move mutation to update the relevant query cache rather than relying solely on route revalidation.

As per coding guidelines


168-187: Make the dialog UI-only to avoid duplicating business logic.

handleConfirm performs the server mutation. With onConfirm already provided, keep the mutation in the parent to maintain separation of concerns and enable cache updates there.

Apply this minimal change to make the dialog UI-only:

-  const handleConfirm = async () => {
-    try {
-      const result = await moveVideosToFolderAction({
-        videoIds,
-        targetFolderId: selectedFolderId,
-        spaceId: activeSpace?.id,
-      });
-
-      if (result.success) {
-        toast.success(result.message);
-        onConfirm(selectedFolderId);
-        setSelectedFolderId(null);
-      } else {
-        toast.error(result.error);
-      }
-    } catch (error) {
-      toast.error("Failed to move videos");
-      console.error("Error moving videos:", error);
-    }
-  };
+  const handleConfirm = () => {
+    onConfirm(selectedFolderId);
+    setSelectedFolderId(null);
+  };

Then implement the mutation in the parent with useEffectMutation.


115-147: Remove inline comments per repo policy.

JSX/inline comments are disallowed; the code is self-explanatory.

As per coding guidelines

Also applies to: 150-156, 158-166, 207-239, 240-246, 247-250, 252-263

apps/web/app/(org)/dashboard/caps/components/SelectedCapsBar.tsx (2)

15-21: Type consistency and prop usage.

selectedCaps is string[] while setSelectedCaps expects Video.VideoId[]. Align types to Video.VideoId[] for both to prevent widening.

-interface SelectedCapsBarProps {
-  selectedCaps: string[];
-  setSelectedCaps: (caps: Video.VideoId[]) => void;
+interface SelectedCapsBarProps {
+  selectedCaps: Video.VideoId[];
+  setSelectedCaps: (caps: Video.VideoId[]) => void;

118-125: Own the mutation in the parent using EffectRuntime.

With the dialog made UI-only, handle the move via useEffectMutation here (and update local/query caches with setQueryData).

As per coding guidelines

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5c83606 and 568e885.

📒 Files selected for processing (5)
  • apps/web/actions/folders/getAllFolders.ts (1 hunks)
  • apps/web/actions/folders/moveVideosToFolder.ts (1 hunks)
  • apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx (1 hunks)
  • apps/web/app/(org)/dashboard/caps/components/SelectedCapsBar.tsx (1 hunks)
  • apps/web/lib/folder.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
apps/web/actions/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

All Groq/OpenAI calls must be implemented in Next.js Server Actions under apps/web/actions; do not place AI calls elsewhere

Files:

  • apps/web/actions/folders/getAllFolders.ts
  • apps/web/actions/folders/moveVideosToFolder.ts
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for all client-side server state and data fetching in the web app
Web mutations should call Server Actions directly and perform targeted cache updates with setQueryData/setQueriesData rather than broad invalidations
Client code should use useEffectQuery/useEffectMutation and useRpcClient from apps/web/lib/EffectRuntime.ts; do not create ManagedRuntime inside components

Files:

  • apps/web/actions/folders/getAllFolders.ts
  • apps/web/actions/folders/moveVideosToFolder.ts
  • apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx
  • apps/web/lib/folder.ts
  • apps/web/app/(org)/dashboard/caps/components/SelectedCapsBar.tsx
**/*.{ts,tsx,js,jsx,rs}

📄 CodeRabbit inference engine (CLAUDE.md)

Do not add inline, block, or docstring comments in any language; code must be self-explanatory

Files:

  • apps/web/actions/folders/getAllFolders.ts
  • apps/web/actions/folders/moveVideosToFolder.ts
  • apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx
  • apps/web/lib/folder.ts
  • apps/web/app/(org)/dashboard/caps/components/SelectedCapsBar.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use strict TypeScript and avoid any; leverage shared types from packages

**/*.{ts,tsx}: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by running pnpm format.

Files:

  • apps/web/actions/folders/getAllFolders.ts
  • apps/web/actions/folders/moveVideosToFolder.ts
  • apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx
  • apps/web/lib/folder.ts
  • apps/web/app/(org)/dashboard/caps/components/SelectedCapsBar.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g., user-menu.tsx).
Use PascalCase for React/Solid components.

Files:

  • apps/web/actions/folders/getAllFolders.ts
  • apps/web/actions/folders/moveVideosToFolder.ts
  • apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx
  • apps/web/lib/folder.ts
  • apps/web/app/(org)/dashboard/caps/components/SelectedCapsBar.tsx
apps/web/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

On the client, always use useEffectQuery or useEffectMutation from @/lib/EffectRuntime; never call EffectRuntime.run* directly in components.

Files:

  • apps/web/actions/folders/getAllFolders.ts
  • apps/web/actions/folders/moveVideosToFolder.ts
  • apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx
  • apps/web/lib/folder.ts
  • apps/web/app/(org)/dashboard/caps/components/SelectedCapsBar.tsx
apps/web/app/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Prefer Server Components for initial data in the Next.js App Router and pass initialData to client components

Files:

  • apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx
  • apps/web/app/(org)/dashboard/caps/components/SelectedCapsBar.tsx
🧬 Code graph analysis (5)
apps/web/actions/folders/getAllFolders.ts (3)
apps/web/lib/server.ts (1)
  • runPromise (59-71)
apps/web/lib/folder.ts (1)
  • getAllFolders (308-380)
packages/web-domain/src/Authentication.ts (1)
  • CurrentUser (7-10)
apps/web/actions/folders/moveVideosToFolder.ts (4)
packages/web-domain/src/Folder.ts (1)
  • Folder (19-27)
apps/web/lib/server.ts (1)
  • runPromise (59-71)
apps/web/lib/folder.ts (1)
  • moveVideosToFolder (382-449)
packages/web-domain/src/Authentication.ts (1)
  • CurrentUser (7-10)
apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx (3)
apps/web/app/(org)/dashboard/Contexts.tsx (1)
  • useDashboardContext (44-44)
apps/web/actions/folders/getAllFolders.ts (1)
  • getAllFoldersAction (9-35)
apps/web/actions/folders/moveVideosToFolder.ts (1)
  • moveVideosToFolderAction (16-84)
apps/web/lib/folder.ts (6)
packages/database/index.ts (1)
  • db (29-34)
packages/web-backend/src/Database.ts (1)
  • Database (9-19)
packages/database/schema.ts (9)
  • folders (219-243)
  • spaceVideos (577-597)
  • spaces (532-552)
  • organizations (149-169)
  • sharedVideos (295-315)
  • videos (245-293)
  • comments (317-337)
  • users (47-96)
  • videoUploads (656-662)
packages/web-domain/src/Folder.ts (3)
  • Folder (19-27)
  • FolderId (8-8)
  • FolderId (9-9)
packages/web-domain/src/Video.ts (3)
  • Video (14-59)
  • VideoId (10-10)
  • VideoId (11-11)
packages/web-domain/src/Authentication.ts (1)
  • CurrentUser (7-10)
apps/web/app/(org)/dashboard/caps/components/SelectedCapsBar.tsx (3)
packages/web-domain/src/Video.ts (3)
  • Video (14-59)
  • VideoId (10-10)
  • VideoId (11-11)
apps/web/app/(org)/dashboard/_components/ConfirmationDialog.tsx (1)
  • ConfirmationDialog (25-72)
apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx (1)
  • FolderSelectionDialog (43-287)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
🔇 Additional comments (1)
apps/web/actions/folders/getAllFolders.ts (1)

24-26: LGTM: Effect context + runtime wiring

Providing CurrentUser via Effect and running with runPromise is consistent with the existing pattern.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 568e885 and 91415b3.

📒 Files selected for processing (1)
  • apps/web/lib/folder.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for all client-side server state and data fetching in the web app
Web mutations should call Server Actions directly and perform targeted cache updates with setQueryData/setQueriesData rather than broad invalidations
Client code should use useEffectQuery/useEffectMutation and useRpcClient from apps/web/lib/EffectRuntime.ts; do not create ManagedRuntime inside components

Files:

  • apps/web/lib/folder.ts
**/*.{ts,tsx,js,jsx,rs}

📄 CodeRabbit inference engine (CLAUDE.md)

Do not add inline, block, or docstring comments in any language; code must be self-explanatory

Files:

  • apps/web/lib/folder.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use strict TypeScript and avoid any; leverage shared types from packages

**/*.{ts,tsx}: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by running pnpm format.

Files:

  • apps/web/lib/folder.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g., user-menu.tsx).
Use PascalCase for React/Solid components.

Files:

  • apps/web/lib/folder.ts
apps/web/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

On the client, always use useEffectQuery or useEffectMutation from @/lib/EffectRuntime; never call EffectRuntime.run* directly in components.

Files:

  • apps/web/lib/folder.ts
🧬 Code graph analysis (1)
apps/web/lib/folder.ts (6)
packages/database/index.ts (1)
  • db (29-34)
packages/web-backend/src/Database.ts (1)
  • Database (9-19)
packages/database/schema.ts (7)
  • folders (219-243)
  • spaceVideos (577-597)
  • sharedVideos (295-315)
  • videos (245-293)
  • comments (317-337)
  • users (47-96)
  • videoUploads (656-662)
packages/web-domain/src/Folder.ts (3)
  • Folder (19-27)
  • FolderId (8-8)
  • FolderId (9-9)
packages/web-domain/src/Video.ts (3)
  • Video (14-59)
  • VideoId (10-10)
  • VideoId (11-11)
packages/web-domain/src/Authentication.ts (1)
  • CurrentUser (7-10)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)

Comment on lines +398 to +407
const existingVideos = yield* db.execute((db) =>
db
.select({
id: videos.id,
folderId: videos.folderId,
ownerId: videos.ownerId,
})
.from(videos)
.where(and(inArray(videos.id, videoIds), eq(videos.ownerId, user.id)))
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Enforce org scoping when selecting videos to move.

The validation query only checks ownerId, so a user active in org A can move their videos from another org into A’s folders. That breaks tenant isolation and poisons folder listings. Add the org constraint to the WHERE clause before proceeding.

Apply this diff:

       .from(videos)
-      .where(and(inArray(videos.id, videoIds), eq(videos.ownerId, user.id)))
+      .where(
+        and(
+          inArray(videos.id, videoIds),
+          eq(videos.ownerId, user.id),
+          eq(videos.orgId, user.activeOrganizationId)
+        )
+      )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const existingVideos = yield* db.execute((db) =>
db
.select({
id: videos.id,
folderId: videos.folderId,
ownerId: videos.ownerId,
})
.from(videos)
.where(and(inArray(videos.id, videoIds), eq(videos.ownerId, user.id)))
);
const existingVideos = yield* db.execute((db) =>
db
.select({
id: videos.id,
folderId: videos.folderId,
ownerId: videos.ownerId,
})
.from(videos)
.where(
and(
inArray(videos.id, videoIds),
eq(videos.ownerId, user.id),
eq(videos.orgId, user.activeOrganizationId)
)
)
);
🤖 Prompt for AI Agents
In apps/web/lib/folder.ts around lines 398 to 407, the query that selects
existingVideos only filters by ownerId which allows cross-org moves; add the org
constraint to the WHERE clause (e.g., include eq(videos.orgId, user.orgId)) so
the .where(...) becomes an and(...) that combines inArray(videos.id, videoIds),
eq(videos.ownerId, user.id), and eq(videos.orgId, user.orgId); ensure the new
condition uses the correct videos org column name and is applied before
proceeding.

Comment on lines +432 to +464
if (root?.variant === "space") {
// Collect originals from space_videos
const spaceRows = yield* db.execute((db) =>
db
.select({
folderId: spaceVideos.folderId,
videoId: spaceVideos.videoId,
})
.from(spaceVideos)
.where(
and(
eq(spaceVideos.spaceId, root.spaceId),
inArray(spaceVideos.videoId, videoIds)
)
)
);
originalFolderIds = [
...new Set(spaceRows.map((r) => r.folderId).filter(Boolean)),
];

// Update per-space folder placement
yield* db.execute((db) =>
db
.update(spaceVideos)
.set({ folderId: targetFolderId })
.where(
and(
eq(spaceVideos.spaceId, root.spaceId),
inArray(spaceVideos.videoId, videoIds)
)
)
);
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Abort when a video isn’t linked to the target space.

If any videoId isn’t present in space_videos for the given space, spaceRows misses it and the update silently skips that video while movedCount still reports success. Fail fast so callers get a consistent result.

Apply this diff:

     const spaceRows = yield* db.execute((db) =>
       db
         .select({
           folderId: spaceVideos.folderId,
           videoId: spaceVideos.videoId,
         })
         .from(spaceVideos)
         .where(
           and(
             eq(spaceVideos.spaceId, root.spaceId),
             inArray(spaceVideos.videoId, videoIds)
           )
         )
     );
+    if (spaceRows.length !== videoIds.length) {
+      throw new Error(
+        "Some videos are not part of the specified space or you lack access"
+      );
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (root?.variant === "space") {
// Collect originals from space_videos
const spaceRows = yield* db.execute((db) =>
db
.select({
folderId: spaceVideos.folderId,
videoId: spaceVideos.videoId,
})
.from(spaceVideos)
.where(
and(
eq(spaceVideos.spaceId, root.spaceId),
inArray(spaceVideos.videoId, videoIds)
)
)
);
originalFolderIds = [
...new Set(spaceRows.map((r) => r.folderId).filter(Boolean)),
];
// Update per-space folder placement
yield* db.execute((db) =>
db
.update(spaceVideos)
.set({ folderId: targetFolderId })
.where(
and(
eq(spaceVideos.spaceId, root.spaceId),
inArray(spaceVideos.videoId, videoIds)
)
)
);
} else {
if (root?.variant === "space") {
// Collect originals from space_videos
const spaceRows = yield* db.execute((db) =>
db
.select({
folderId: spaceVideos.folderId,
videoId: spaceVideos.videoId,
})
.from(spaceVideos)
.where(
and(
eq(spaceVideos.spaceId, root.spaceId),
inArray(spaceVideos.videoId, videoIds)
)
)
);
if (spaceRows.length !== videoIds.length) {
throw new Error(
"Some videos are not part of the specified space or you lack access"
);
}
originalFolderIds = [
...new Set(spaceRows.map((r) => r.folderId).filter(Boolean)),
];
// Update per-space folder placement
yield* db.execute((db) =>
db
.update(spaceVideos)
.set({ folderId: targetFolderId })
.where(
and(
eq(spaceVideos.spaceId, root.spaceId),
inArray(spaceVideos.videoId, videoIds)
)
)
);
} else {
🤖 Prompt for AI Agents
In apps/web/lib/folder.ts around lines 432 to 464, the code queries space_videos
for the given space and videoIds then performs an update but doesn’t detect if
some videoIds were missing from space_videos; this causes the update to silently
skip those videos while movedCount later reports success. After fetching
spaceRows, compute which requested videoIds are absent (compare input videoIds
to spaceRows.videoId); if any are missing, abort immediately by throwing or
returning an error that lists the missing videoIds (do not perform the update).
Only proceed to set originalFolderIds and run the update when all requested
videoIds are present to ensure movedCount remains consistent.

@Enejivk Enejivk closed this Oct 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant