-
Notifications
You must be signed in to change notification settings - Fork 916
web: Add videos button in folders & more #1161
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
91cadee
a3b82f4
fc3b392
37155d5
4fb54eb
d0dad2f
1d2f118
9c0ca54
2e4203d
031e0ea
96d18f2
95e8bd1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
"use server"; | ||
|
||
import { db } from "@cap/database"; | ||
import { getCurrentUser } from "@cap/database/auth/session"; | ||
import { | ||
folders, | ||
sharedVideos, | ||
spaceVideos, | ||
videos, | ||
} from "@cap/database/schema"; | ||
import type { Folder, Space, Video } from "@cap/web-domain"; | ||
import { and, eq, inArray } from "drizzle-orm"; | ||
import { revalidatePath } from "next/cache"; | ||
|
||
export async function addVideosToFolder( | ||
folderId: Folder.FolderId, | ||
videoIds: Video.VideoId[], | ||
spaceId: Space.SpaceIdOrOrganisationId, | ||
) { | ||
try { | ||
const user = await getCurrentUser(); | ||
|
||
if (!user || !user.id) { | ||
throw new Error("Unauthorized"); | ||
} | ||
|
||
if (!folderId || !videoIds || videoIds.length === 0) { | ||
throw new Error("Missing required data"); | ||
} | ||
|
||
const [folder] = await db() | ||
.select({ id: folders.id, spaceId: folders.spaceId }) | ||
.from(folders) | ||
.where(eq(folders.id, folderId)); | ||
|
||
if (!folder) { | ||
throw new Error("Folder not found"); | ||
} | ||
|
||
const userVideos = await db() | ||
.select({ id: videos.id }) | ||
.from(videos) | ||
.where(and(eq(videos.ownerId, user.id), inArray(videos.id, videoIds))); | ||
|
||
const validVideoIds = userVideos.map((v) => v.id); | ||
|
||
if (validVideoIds.length === 0) { | ||
throw new Error("No valid videos found"); | ||
} | ||
|
||
const isAllSpacesEntry = spaceId === user.activeOrganizationId; | ||
|
||
//if video already exists in the space, then move it | ||
if (isAllSpacesEntry) { | ||
await db() | ||
.update(sharedVideos) | ||
.set({ folderId }) | ||
.where( | ||
and( | ||
eq(sharedVideos.organizationId, user.activeOrganizationId), | ||
inArray(sharedVideos.videoId, validVideoIds), | ||
), | ||
); | ||
} else { | ||
await db() | ||
.update(spaceVideos) | ||
.set({ folderId }) | ||
.where( | ||
and( | ||
eq(spaceVideos.spaceId, spaceId), | ||
inArray(spaceVideos.videoId, validVideoIds), | ||
), | ||
); | ||
} | ||
|
||
revalidatePath(`/dashboard/caps`); | ||
revalidatePath(`/dashboard/folder/${folderId}`); | ||
if (spaceId) { | ||
revalidatePath(`/dashboard/spaces/${spaceId}/folder/${folderId}`); | ||
} | ||
|
||
return { | ||
success: true, | ||
message: `${validVideoIds.length} video${validVideoIds.length === 1 ? "" : "s"} added to folder`, | ||
addedCount: validVideoIds.length, | ||
}; | ||
} catch (error) { | ||
console.error("Error adding videos to folder:", error); | ||
return { | ||
success: false, | ||
error: | ||
error instanceof Error | ||
? error.message | ||
: "Failed to add videos to folder", | ||
}; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
"use server"; | ||
|
||
import { db } from "@cap/database"; | ||
import { getCurrentUser } from "@cap/database/auth/session"; | ||
import { sharedVideos, spaceVideos } from "@cap/database/schema"; | ||
import type { Folder, Space, Video } from "@cap/web-domain"; | ||
import { eq } from "drizzle-orm"; | ||
|
||
export async function getFolderVideoIds( | ||
folderId: Folder.FolderId, | ||
spaceId: Space.SpaceIdOrOrganisationId, | ||
) { | ||
try { | ||
const user = await getCurrentUser(); | ||
|
||
if (!user || !user.id) { | ||
throw new Error("Unauthorized"); | ||
} | ||
|
||
if (!folderId) { | ||
throw new Error("Folder ID is required"); | ||
} | ||
|
||
const isAllSpacesEntry = user.activeOrganizationId === spaceId; | ||
|
||
const rows = isAllSpacesEntry | ||
? await db() | ||
.select({ id: sharedVideos.videoId }) | ||
.from(sharedVideos) | ||
.where(eq(sharedVideos.folderId, folderId)) | ||
: await db() | ||
.select({ id: spaceVideos.videoId }) | ||
.from(spaceVideos) | ||
.where(eq(spaceVideos.folderId, folderId)); | ||
|
||
return { | ||
success: true, | ||
data: rows.map((r) => r.id as Video.VideoId), | ||
}; | ||
} catch (error) { | ||
console.error("Error fetching folder video IDs:", error); | ||
return { | ||
success: false, | ||
error: | ||
error instanceof Error | ||
? error.message | ||
: "Failed to fetch folder videos", | ||
}; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,19 +2,23 @@ | |
|
||
import { db } from "@cap/database"; | ||
import { getCurrentUser } from "@cap/database/auth/session"; | ||
import { folders, spaceVideos, videos } from "@cap/database/schema"; | ||
import type { Folder, Video } from "@cap/web-domain"; | ||
import { | ||
folders, | ||
sharedVideos, | ||
spaceVideos, | ||
videos, | ||
} from "@cap/database/schema"; | ||
import type { Folder, Space, Video } from "@cap/web-domain"; | ||
import { and, eq } from "drizzle-orm"; | ||
import { revalidatePath } from "next/cache"; | ||
|
||
export async function moveVideoToFolder({ | ||
videoId, | ||
folderId, | ||
spaceId, | ||
}: { | ||
videoId: Video.VideoId; | ||
folderId: Folder.FolderId | null; | ||
spaceId?: string | null; | ||
spaceId?: Space.SpaceIdOrOrganisationId | null; | ||
}) { | ||
const user = await getCurrentUser(); | ||
if (!user || !user.activeOrganizationId) | ||
|
@@ -30,6 +34,8 @@ export async function moveVideoToFolder({ | |
|
||
const originalFolderId = currentVideo?.folderId; | ||
|
||
const isAllSpacesEntry = spaceId === user.activeOrganizationId; | ||
|
||
// If folderId is provided, verify it exists and belongs to the same organization | ||
if (folderId) { | ||
const [folder] = await db() | ||
|
@@ -47,24 +53,36 @@ export async function moveVideoToFolder({ | |
} | ||
} | ||
|
||
if (spaceId) { | ||
if (spaceId && !isAllSpacesEntry) { | ||
await db() | ||
.update(spaceVideos) | ||
.set({ | ||
folderId: folderId === null ? null : folderId, | ||
}) | ||
.where(eq(spaceVideos.videoId, videoId)); | ||
.where( | ||
and(eq(spaceVideos.videoId, videoId), eq(spaceVideos.spaceId, spaceId)), | ||
); | ||
} else if (spaceId && isAllSpacesEntry) { | ||
await db() | ||
.update(sharedVideos) | ||
.set({ | ||
folderId: folderId === null ? null : folderId, | ||
}) | ||
.where( | ||
and( | ||
eq(sharedVideos.videoId, videoId), | ||
eq(sharedVideos.organizationId, user.activeOrganizationId), | ||
), | ||
); | ||
} else { | ||
await db() | ||
.update(videos) | ||
.set({ | ||
folderId: folderId === null ? null : folderId, | ||
}) | ||
.where(eq(videos.id, videoId)); | ||
} | ||
Comment on lines
+56
to
84
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chainVerify The function branches between updating Add a verification step after line 25 to ensure the 🏁 Script executed: #!/bin/bash
# Check if there's existing validation for spaceId in related actions
rg -nP --type=ts -C3 'spaceId.*activeOrganizationId' apps/web/actions/ Length of output: 1055 Add validation for spaceId ownership 🤖 Prompt for AI Agents
|
||
|
||
// Update the video's folderId | ||
await db() | ||
.update(videos) | ||
.set({ | ||
folderId, | ||
updatedAt: new Date(), | ||
}) | ||
.where(eq(videos.id, videoId)); | ||
|
||
// Always revalidate the main caps page | ||
revalidatePath(`/dashboard/caps`); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
"use server"; | ||
|
||
import { db } from "@cap/database"; | ||
import { getCurrentUser } from "@cap/database/auth/session"; | ||
import { | ||
folders, | ||
sharedVideos, | ||
spaceVideos, | ||
videos, | ||
} from "@cap/database/schema"; | ||
import type { Folder, Space, Video } from "@cap/web-domain"; | ||
import { and, eq, inArray, sql } from "drizzle-orm"; | ||
import { revalidatePath } from "next/cache"; | ||
|
||
export async function removeVideosFromFolder( | ||
folderId: Folder.FolderId, | ||
videoIds: Video.VideoId[], | ||
spaceId: Space.SpaceIdOrOrganisationId, | ||
) { | ||
try { | ||
const user = await getCurrentUser(); | ||
|
||
if (!user || !user.id) { | ||
throw new Error("Unauthorized"); | ||
} | ||
|
||
const isAllSpacesEntry = user.activeOrganizationId === spaceId; | ||
|
||
if (!folderId || !videoIds || videoIds.length === 0) { | ||
throw new Error("Missing required data"); | ||
} | ||
|
||
// Verify folder exists and is accessible | ||
const [folder] = await db() | ||
.select({ id: folders.id, spaceId: folders.spaceId }) | ||
.from(folders) | ||
.where(eq(folders.id, folderId)); | ||
|
||
if (!folder) { | ||
throw new Error("Folder not found"); | ||
} | ||
|
||
// Only allow updating videos the user owns | ||
const userVideos = await db() | ||
.select({ id: videos.id }) | ||
.from(videos) | ||
.where(and(eq(videos.ownerId, user.id), inArray(videos.id, videoIds))); | ||
|
||
const validVideoIds = userVideos.map((v) => v.id); | ||
|
||
if (validVideoIds.length === 0) { | ||
throw new Error("No valid videos found"); | ||
} | ||
|
||
// Clear the folderId on the videos | ||
await db() | ||
.update(videos) | ||
.set({ folderId: null, updatedAt: new Date() }) | ||
.where( | ||
and(inArray(videos.id, validVideoIds), eq(videos.folderId, folderId)), | ||
); | ||
|
||
// Clear the folderId in the appropriate table based on context | ||
if (isAllSpacesEntry || !folder.spaceId) { | ||
// Organization-level folder - clear folderId in sharedVideos | ||
await db() | ||
.update(sharedVideos) | ||
.set({ folderId: null }) | ||
.where( | ||
and( | ||
eq(sharedVideos.organizationId, user.activeOrganizationId), | ||
inArray(sharedVideos.videoId, validVideoIds), | ||
eq(sharedVideos.folderId, folderId), | ||
), | ||
); | ||
} else if (folder.spaceId) { | ||
// Space-level folder - clear folderId in spaceVideos | ||
await db() | ||
.update(spaceVideos) | ||
.set({ folderId: null }) | ||
.where( | ||
and( | ||
sql`${spaceVideos.spaceId} = ${folder.spaceId}`, | ||
inArray(spaceVideos.videoId, validVideoIds), | ||
eq(spaceVideos.folderId, folderId), | ||
), | ||
); | ||
} | ||
|
||
// Revalidate relevant paths | ||
revalidatePath(`/dashboard/caps`); | ||
revalidatePath(`/dashboard/folder/${folderId}`); | ||
if (folder.spaceId) { | ||
revalidatePath(`/dashboard/spaces/${folder.spaceId}/folder/${folderId}`); | ||
} | ||
|
||
return { | ||
success: true, | ||
message: `${validVideoIds.length} video${validVideoIds.length === 1 ? "" : "s"} removed from folder`, | ||
removedCount: validVideoIds.length, | ||
}; | ||
} catch (error) { | ||
console.error("Error removing videos from folder:", error); | ||
return { | ||
success: false, | ||
error: | ||
error instanceof Error | ||
? error.message | ||
: "Failed to remove videos from folder", | ||
}; | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.