Skip to content

Conversation

Enejivk
Copy link
Contributor

@Enejivk Enejivk commented Oct 3, 2025

This pull request introduces a new feature for moving videos ("caps") to folders from the dashboard, including both backend server actions and a new client-side dialog component. It also refactors and improves folder-related queries and UI integration. The most significant changes are grouped below:

New Folder Move Feature:

  • Added a new FolderSelectionDialog React component for selecting a target folder when moving videos, with hierarchical folder display, loading states, and integration with toast notifications. (apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx)
  • Updated SelectedCapsBar to include a "Move to Folder" button and dialog, allowing users to move selected videos to a folder directly from the dashboard. (apps/web/app/(org)/dashboard/caps/components/SelectedCapsBar.tsx) [1] [2] [3] [4]

Server Actions and Backend Logic:

  • Added getAllFoldersAction and moveVideosToFolderAction server actions to fetch folders and move videos, with permission checks, error handling, and path revalidation for cache consistency. (apps/web/actions/folders/getAllFolders.ts, apps/web/actions/folders/moveVideosToFolder.ts) [1] [2]

Folder Query and Utility Improvements:

  • Improved folder and video database queries for better consistency, readability, and to support the new move functionality (e.g., using inArray, formatting, and error handling). (apps/web/lib/folder.ts) [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]

These changes together enable users to organize their videos into folders more efficiently, with a modern UI and robust backend support.

Summary by CodeRabbit

  • New Features
    • Move selected videos to a folder or to “No folder” directly from the dashboard.
    • New folder selection dialog with an expandable tree, root option, and loading/error states; supports organization and space contexts.
    • Integrated “Move” action into the Selected items bar with a folder icon button and confirmation flow.
    • Feedback on completion via success/error messages after moving videos.

- Add validation to ensure all requested videos exist in the space before updating
- Prevent silent partial updates when some videos are missing from space_videos
- Throw descriptive error when videos are not found in the specified space
- Improves data integrity and prevents misleading success responses
Copy link
Contributor

coderabbitai bot commented Oct 3, 2025

Walkthrough

Adds folder management: server actions to fetch all folders and move videos; a React dialog to select a destination folder; integration into the selected-caps toolbar; and supporting library APIs for folder queries and video moves across user/space/org roots with Effect-based execution and revalidation.

Changes

Cohort / File(s) Summary of changes
Server actions: folders
apps/web/actions/folders/getAllFolders.ts, apps/web/actions/folders/moveVideosToFolder.ts
Added getAllFoldersAction and moveVideosToFolderAction. Both authenticate current user, handle org/space root variants, run Effect pipelines with CurrentUser, and return success/error results. Move action triggers route revalidation and formats a result message.
UI: folder selection & integration
apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx, apps/web/app/(org)/dashboard/caps/components/SelectedCapsBar.tsx
New FolderSelectionDialog component to fetch and display hierarchical folders and trigger move. SelectedCapsBar now supports opening the dialog and passing selected IDs via a new moveSelectedCaps prop; adds a folder-open action button and related state/handlers.
Library: folder domain ops
apps/web/lib/folder.ts
Introduced getAllFolders to return hierarchical folders for user/space/org roots and moveVideosToFolder to move videos with validations and context-aware updates. Imported inArray for queries; applied minor formatting consistency.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant SB as SelectedCapsBar
  participant D as FolderSelectionDialog
  participant SA as Server Actions
  participant LIB as folder.ts

  U->>SB: Click "Move to folder"
  SB->>D: Open dialog
  D->>SA: getAllFoldersAction(root)
  SA->>LIB: getAllFolders(root) [Effect with CurrentUser]
  LIB-->>SA: folders tree
  SA-->>D: { success, folders }
  U->>D: Choose destination, Confirm
  D->>SA: moveVideosToFolderAction({ videoIds, targetFolderId, spaceId? })
  SA->>LIB: moveVideosToFolder(...) [Effect/Direct + CurrentUser]
  LIB-->>SA: { movedCount, originalFolderIds, targetFolderId }
  SA->>SA: Revalidate dashboard/folders/spaces paths
  SA-->>D: { success, message } / { error }
  D-->>SB: onConfirm callback
  SB-->>U: Show toast / update UI
Loading
sequenceDiagram
  autonumber
  participant SA as getAllFoldersAction
  participant Auth as getCurrentUser
  participant Eff as Effect runtime
  participant LIB as getAllFolders

  SA->>Auth: require user + activeOrganizationId
  alt authorized
    SA->>Eff: provide CurrentUser
    Eff->>LIB: getAllFolders(root)
    LIB-->>Eff: folders
    Eff-->>SA: success
  else unauthorized
    SA-->>SA: return { success: false, error }
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

In burrows of code I hop and explore,
Stacking neat folders, then moving some more.
A click, a confirm—videos glide,
From root to a nook where tags can reside.
I twitch my nose—success toast shines,
Carrots for queries, and revalidated lines. 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title “Move video to folder” succinctly and accurately describes the primary feature introduced by this pull request, namely adding the ability to move videos into folders from the dashboard. It is concise, imperative, and clearly related to the main change without extraneous detail.
✨ 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.

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: 3

🧹 Nitpick comments (1)
apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.tsx (1)

207-263: Remove inline JSX comments

Project guidelines forbid inline/block comments in TSX/JSX. The JSX {/* … */} blocks at Lines 209, 240, 248, etc. need to go—replace them with self-describing markup or helper components instead.

📜 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 5d8d23c.

📒 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/caps/components/SelectedCapsBar.tsx
  • apps/web/lib/folder.ts
  • apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.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/caps/components/SelectedCapsBar.tsx
  • apps/web/lib/folder.ts
  • apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.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/caps/components/SelectedCapsBar.tsx
  • apps/web/lib/folder.ts
  • apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.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/caps/components/SelectedCapsBar.tsx
  • apps/web/lib/folder.ts
  • apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.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/caps/components/SelectedCapsBar.tsx
  • apps/web/lib/folder.ts
  • apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.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/caps/components/SelectedCapsBar.tsx
  • apps/web/app/(org)/dashboard/_components/FolderSelectionDialog.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 (309-381)
packages/web-domain/src/Authentication.ts (1)
  • CurrentUser (7-10)
apps/web/actions/folders/moveVideosToFolder.ts (4)
apps/web/lib/folder.ts (1)
  • moveVideosToFolder (383-493)
packages/web-domain/src/Policy.ts (1)
  • Policy (7-10)
packages/web-domain/src/Authentication.ts (1)
  • CurrentUser (7-10)
apps/web/lib/server.ts (1)
  • runPromise (59-71)
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)
apps/web/lib/folder.ts (4)
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/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 (17-97)
⏰ 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 +57 to +188
// Fetch folders using the server action
const { data: foldersData, isLoading } = useQuery({
queryKey: ["folders", activeOrganization?.organization.id, activeSpace?.id],
queryFn: async () => {
const root = activeSpace?.id
? { variant: "space" as const, spaceId: activeSpace.id }
: {
variant: "org" as const,
organizationId: activeOrganization!.organization.id,
};

const result = await getAllFoldersAction(root);

if (!result.success) {
throw new Error(result.error || "Failed to fetch folders");
}

return result.folders;
},
enabled: open && !!activeOrganization?.organization.id,
});

const folders = foldersData || [];

// Toggle folder expansion
const toggleFolderExpansion = (folderId: string) => {
setExpandedFolders((prev) => {
const newSet = new Set(prev);
if (newSet.has(folderId)) {
newSet.delete(folderId);
} else {
newSet.add(folderId);
}
return newSet;
});
};

// Render folder with improved design
const renderFolder = (folder: FolderWithChildren, depth = 0) => {
const hasChildren = folder.children && folder.children.length > 0;
const isExpanded = expandedFolders.has(folder.id);
const isSelected = selectedFolderId === folder.id;

return (
<div key={folder.id} className="relative">
<div
onClick={() => setSelectedFolderId(folder.id)}
className={`
group flex items-center gap-3 px-3 py-2 rounded-lg cursor-pointer
transition-colors border-l-4
${
isSelected
? "bg-blue-3 border-blue-9"
: "border-transparent hover:bg-gray-3"
}
`}
style={{ marginLeft: `${depth * 16}px` }}
>
{/* Expand/Collapse button */}
{hasChildren ? (
<button
onClick={(e) => {
e.stopPropagation();
toggleFolderExpansion(folder.id);
}}
className="flex-shrink-0 w-5 h-5 flex items-center justify-center rounded hover:bg-gray-4 transition"
>
<FontAwesomeIcon
className="w-3.5 h-3.5 text-gray-10"
icon={isExpanded ? faChevronDown : faChevronRight}
/>
</button>
) : (
<div className="w-5 h-5 flex-shrink-0" />
)}

{/* Folder icon */}
<div className="flex items-center gap-2 flex-1">
<div className="flex-shrink-0 w-7 h-7 rounded-md bg-gray-2 border border-gray-6 shadow-sm flex items-center justify-center">
<FontAwesomeIcon
className="w-4 h-4 text-gray-11"
icon={faFolderOpen}
/>
</div>

<div className="flex-1">
<p className="text-sm font-medium text-gray-12">{folder.name}</p>
<p className="text-xs text-gray-9">
{folder.videoCount} video{folder.videoCount !== 1 ? "s" : ""}
</p>
</div>
</div>

{/* Selection indicator */}
{isSelected && (
<div className="ml-auto w-5 h-5 rounded-full bg-blue-9 flex items-center justify-center">
<div className="w-2.5 h-2.5 bg-white rounded-full" />
</div>
)}
</div>

{/* Render children if expanded */}
{hasChildren && isExpanded && (
<div className="mt-1">
{folder.children.map((child) => renderFolder(child, depth + 1))}
</div>
)}
</div>
);
};

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);
}
};

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Use EffectRuntime helpers for data fetching and mutations

Per the web coding guidelines, client components under apps/web should rely on useEffectQuery / useEffectMutation together with useRpcClient rather than calling server actions through useQuery and manual async handlers. Please refactor the folder fetch to useEffectQuery and wrap moveVideosToFolderAction in a useEffectMutation, wiring the returned mutation state into your UI (spinner/disabled, success/error handling) and updating any relevant caches with setQueryData instead of ad-hoc toasts. This keeps all client/server coordination on the standardized Effect runtime path.

Comment on lines +296 to +341
and(
eq(folders.parentId, folderId),
eq(folders.organizationId, user.activeOrganizationId),
root.variant === "space"
? eq(folders.spaceId, root.spaceId)
: undefined
)
)
);

return childFolders;
});

export const getAllFolders = Effect.fn(function* (
root:
| { variant: "user" }
| { variant: "space"; spaceId: string }
| { variant: "org"; organizationId: string }
) {
const db = yield* Database;
const user = yield* CurrentUser;

if (!user.activeOrganizationId) throw new Error("No active organization");

// Get all folders in one query
const allFolders = yield* db.execute((db) =>
db
.select({
id: folders.id,
name: folders.name,
color: folders.color,
parentId: folders.parentId,
organizationId: folders.organizationId,
videoCount: sql<number>`(
SELECT COUNT(*) FROM videos WHERE videos.folderId = folders.id
)`,
})
.from(folders)
.where(
and(
eq(folders.organizationId, user.activeOrganizationId),
root.variant === "space"
? eq(folders.spaceId, root.spaceId)
: undefined
)
)
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

Do not surface space-scoped folders outside their space context

When root.variant is "org" or "user" the query still returns folders whose spaceId is set, so the org-level picker shows space-only folders. Selecting one will later force videos into an incompatible space context. Please add an explicit isNull(folders.spaceId) filter for non-space roots (and the analogous guard in getChildFolders) so that only folders that actually belong to the requested scope are returned.

-import { and, desc, eq, inArray } from "drizzle-orm";
+import { and, desc, eq, inArray, isNull } from "drizzle-orm";
...
       .where(
         and(
           eq(folders.organizationId, user.activeOrganizationId),
-          root.variant === "space"
-            ? eq(folders.spaceId, root.spaceId)
-            : undefined
+          root.variant === "space"
+            ? eq(folders.spaceId, root.spaceId)
+            : isNull(folders.spaceId)
         )
       )

Apply the same isNull(folders.spaceId) constraint inside getChildFolders when root.variant !== "space" so the hierarchy stays consistent across both helpers.

📝 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
and(
eq(folders.parentId, folderId),
eq(folders.organizationId, user.activeOrganizationId),
root.variant === "space"
? eq(folders.spaceId, root.spaceId)
: undefined
)
)
);
return childFolders;
});
export const getAllFolders = Effect.fn(function* (
root:
| { variant: "user" }
| { variant: "space"; spaceId: string }
| { variant: "org"; organizationId: string }
) {
const db = yield* Database;
const user = yield* CurrentUser;
if (!user.activeOrganizationId) throw new Error("No active organization");
// Get all folders in one query
const allFolders = yield* db.execute((db) =>
db
.select({
id: folders.id,
name: folders.name,
color: folders.color,
parentId: folders.parentId,
organizationId: folders.organizationId,
videoCount: sql<number>`(
SELECT COUNT(*) FROM videos WHERE videos.folderId = folders.id
)`,
})
.from(folders)
.where(
and(
eq(folders.organizationId, user.activeOrganizationId),
root.variant === "space"
? eq(folders.spaceId, root.spaceId)
: undefined
)
)
// at the top of apps/web/lib/folder.ts
import { and, desc, eq, inArray, isNull } from "drizzle-orm";
// ... inside getChildFolders (apply the same change here) ...
export const getAllFolders = Effect.fn(function* (
root:
| { variant: "user" }
| { variant: "space"; spaceId: string }
| { variant: "org"; organizationId: string }
) {
const db = yield* Database;
const user = yield* CurrentUser;
if (!user.activeOrganizationId) throw new Error("No active organization");
// Get all folders in one query
const allFolders = yield* db.execute((db) =>
db
.select({
id: folders.id,
name: folders.name,
color: folders.color,
parentId: folders.parentId,
organizationId: folders.organizationId,
videoCount: sql<number>`(
SELECT COUNT(*) FROM videos WHERE videos.folderId = folders.id
)`,
})
.from(folders)
- .where(
- and(
- eq(folders.organizationId, user.activeOrganizationId),
- root.variant === "space"
- ? eq(folders.spaceId, root.spaceId)
- : undefined
- )
.where(
and(
eq(folders.organizationId, user.activeOrganizationId),
root.variant === "space"
? eq(folders.spaceId, root.spaceId)
: isNull(folders.spaceId)
)
)
);
return allFolders;
});
🤖 Prompt for AI Agents
In apps/web/lib/folder.ts around lines 296 to 341, org/user-root queries
currently still return space-scoped folders because there is no explicit check
for folders.spaceId being null; update the where clauses in getAllFolders
(shown) and add the analogous isNull(folders.spaceId) condition inside
getChildFolders when root.variant !== "space" so that non-space roots only
return folders with no spaceId. Concretely, when root.variant === "space" keep
the eq(folders.spaceId, root.spaceId) check, otherwise add
isNull(folders.spaceId) to the and(...) filter; apply the same conditional logic
in getChildFolders so child-folder queries also exclude space-scoped folders for
org/user roots.

Comment on lines +416 to +427
if (targetFolderId) {
const targetFolder = yield* getFolderById(targetFolderId);

if (targetFolder.organizationId !== user.activeOrganizationId) {
throw new Error("Target folder not found or you don't have access to it");
}

// Validate space context if provided
if (root?.variant === "space" && targetFolder.spaceId !== root.spaceId) {
throw new Error("Target folder does not belong to the specified space");
}
}
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

Block moves into space folders when we are not operating in that space

Even after filtering, a crafted request can still target a folder whose spaceId is set while root is "org"/"user". That updates videos.folderId with a space-only folder and breaks invariants. Defensive guard:

   if (targetFolderId) {
     const targetFolder = yield* getFolderById(targetFolderId);

     if (targetFolder.organizationId !== user.activeOrganizationId) {
       throw new Error("Target folder not found or you don't have access to it");
     }

     // Validate space context if provided
     if (root?.variant === "space" && targetFolder.spaceId !== root.spaceId) {
       throw new Error("Target folder does not belong to the specified space");
     }
+
+    if (root?.variant !== "space" && targetFolder.spaceId !== null) {
+      throw new Error("Target folder is scoped to a space and cannot be used here");
+    }
   }

This keeps org-level moves from leaking into space-only folders.

📝 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 (targetFolderId) {
const targetFolder = yield* getFolderById(targetFolderId);
if (targetFolder.organizationId !== user.activeOrganizationId) {
throw new Error("Target folder not found or you don't have access to it");
}
// Validate space context if provided
if (root?.variant === "space" && targetFolder.spaceId !== root.spaceId) {
throw new Error("Target folder does not belong to the specified space");
}
}
if (targetFolderId) {
const targetFolder = yield* getFolderById(targetFolderId);
if (targetFolder.organizationId !== user.activeOrganizationId) {
throw new Error("Target folder not found or you don't have access to it");
}
// Validate space context if provided
if (root?.variant === "space" && targetFolder.spaceId !== root.spaceId) {
throw new Error("Target folder does not belong to the specified space");
}
if (root?.variant !== "space" && targetFolder.spaceId !== null) {
throw new Error("Target folder is scoped to a space and cannot be used here");
}
}
🤖 Prompt for AI Agents
In apps/web/lib/folder.ts around lines 416 to 427, the code currently only
checks that targetFolder.spaceId matches root.spaceId when root.variant ===
"space", but it doesn't prevent moves into space-only folders when root is "org"
or "user"; add a defensive guard so that if root?.variant !== "space" and
targetFolder.spaceId is set (non-null/undefined), throw an error (e.g., "Target
folder does not belong to the specified space" or "Target folder not accessible
in current context"). Keep the existing equality check for the space case, and
ensure the thrown error message and access check occur before allowing updates
to videos.folderId.

@Enejivk
Copy link
Contributor Author

Enejivk commented Oct 3, 2025

r.mp4

@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