Skip to content

Commit 10a7f5a

Browse files
authored
web: Add videos button in folders & more (#1161)
* add videos button + show folder the video is in * some fixes * fixes * add folderId column to shared videos and fix folder * adjust height * fixes * Update _journal.json * ts * ts * cleanup * more cleanups * Update remove-videos.ts
1 parent db748d0 commit 10a7f5a

29 files changed

+821
-253
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
"use server";
2+
3+
import { db } from "@cap/database";
4+
import { getCurrentUser } from "@cap/database/auth/session";
5+
import {
6+
folders,
7+
sharedVideos,
8+
spaceVideos,
9+
videos,
10+
} from "@cap/database/schema";
11+
import type { Folder, Space, Video } from "@cap/web-domain";
12+
import { and, eq, inArray } from "drizzle-orm";
13+
import { revalidatePath } from "next/cache";
14+
15+
export async function addVideosToFolder(
16+
folderId: Folder.FolderId,
17+
videoIds: Video.VideoId[],
18+
spaceId: Space.SpaceIdOrOrganisationId,
19+
) {
20+
try {
21+
const user = await getCurrentUser();
22+
23+
if (!user || !user.id) {
24+
throw new Error("Unauthorized");
25+
}
26+
27+
if (!folderId || !videoIds || videoIds.length === 0) {
28+
throw new Error("Missing required data");
29+
}
30+
31+
const [folder] = await db()
32+
.select({ id: folders.id, spaceId: folders.spaceId })
33+
.from(folders)
34+
.where(eq(folders.id, folderId));
35+
36+
if (!folder) {
37+
throw new Error("Folder not found");
38+
}
39+
40+
const userVideos = await db()
41+
.select({ id: videos.id })
42+
.from(videos)
43+
.where(and(eq(videos.ownerId, user.id), inArray(videos.id, videoIds)));
44+
45+
const validVideoIds = userVideos.map((v) => v.id);
46+
47+
if (validVideoIds.length === 0) {
48+
throw new Error("No valid videos found");
49+
}
50+
51+
const isAllSpacesEntry = spaceId === user.activeOrganizationId;
52+
53+
//if video already exists in the space, then move it
54+
if (isAllSpacesEntry) {
55+
await db()
56+
.update(sharedVideos)
57+
.set({ folderId })
58+
.where(
59+
and(
60+
eq(sharedVideos.organizationId, user.activeOrganizationId),
61+
inArray(sharedVideos.videoId, validVideoIds),
62+
),
63+
);
64+
} else {
65+
await db()
66+
.update(spaceVideos)
67+
.set({ folderId })
68+
.where(
69+
and(
70+
eq(spaceVideos.spaceId, spaceId),
71+
inArray(spaceVideos.videoId, validVideoIds),
72+
),
73+
);
74+
}
75+
76+
revalidatePath(`/dashboard/caps`);
77+
revalidatePath(`/dashboard/folder/${folderId}`);
78+
if (spaceId) {
79+
revalidatePath(`/dashboard/spaces/${spaceId}/folder/${folderId}`);
80+
}
81+
82+
return {
83+
success: true,
84+
message: `${validVideoIds.length} video${validVideoIds.length === 1 ? "" : "s"} added to folder`,
85+
addedCount: validVideoIds.length,
86+
};
87+
} catch (error) {
88+
console.error("Error adding videos to folder:", error);
89+
return {
90+
success: false,
91+
error:
92+
error instanceof Error
93+
? error.message
94+
: "Failed to add videos to folder",
95+
};
96+
}
97+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"use server";
2+
3+
import { db } from "@cap/database";
4+
import { getCurrentUser } from "@cap/database/auth/session";
5+
import { sharedVideos, spaceVideos } from "@cap/database/schema";
6+
import type { Folder, Space, Video } from "@cap/web-domain";
7+
import { eq } from "drizzle-orm";
8+
9+
export async function getFolderVideoIds(
10+
folderId: Folder.FolderId,
11+
spaceId: Space.SpaceIdOrOrganisationId,
12+
) {
13+
try {
14+
const user = await getCurrentUser();
15+
16+
if (!user || !user.id) {
17+
throw new Error("Unauthorized");
18+
}
19+
20+
if (!folderId) {
21+
throw new Error("Folder ID is required");
22+
}
23+
24+
const isAllSpacesEntry = user.activeOrganizationId === spaceId;
25+
26+
const rows = isAllSpacesEntry
27+
? await db()
28+
.select({ id: sharedVideos.videoId })
29+
.from(sharedVideos)
30+
.where(eq(sharedVideos.folderId, folderId))
31+
: await db()
32+
.select({ id: spaceVideos.videoId })
33+
.from(spaceVideos)
34+
.where(eq(spaceVideos.folderId, folderId));
35+
36+
return {
37+
success: true,
38+
data: rows.map((r) => r.id as Video.VideoId),
39+
};
40+
} catch (error) {
41+
console.error("Error fetching folder video IDs:", error);
42+
return {
43+
success: false,
44+
error:
45+
error instanceof Error
46+
? error.message
47+
: "Failed to fetch folder videos",
48+
};
49+
}
50+
}

apps/web/actions/folders/moveVideoToFolder.ts

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,23 @@
22

33
import { db } from "@cap/database";
44
import { getCurrentUser } from "@cap/database/auth/session";
5-
import { folders, spaceVideos, videos } from "@cap/database/schema";
6-
import type { Folder, Video } from "@cap/web-domain";
5+
import {
6+
folders,
7+
sharedVideos,
8+
spaceVideos,
9+
videos,
10+
} from "@cap/database/schema";
11+
import type { Folder, Space, Video } from "@cap/web-domain";
712
import { and, eq } from "drizzle-orm";
813
import { revalidatePath } from "next/cache";
9-
1014
export async function moveVideoToFolder({
1115
videoId,
1216
folderId,
1317
spaceId,
1418
}: {
1519
videoId: Video.VideoId;
1620
folderId: Folder.FolderId | null;
17-
spaceId?: string | null;
21+
spaceId?: Space.SpaceIdOrOrganisationId | null;
1822
}) {
1923
const user = await getCurrentUser();
2024
if (!user || !user.activeOrganizationId)
@@ -30,6 +34,8 @@ export async function moveVideoToFolder({
3034

3135
const originalFolderId = currentVideo?.folderId;
3236

37+
const isAllSpacesEntry = spaceId === user.activeOrganizationId;
38+
3339
// If folderId is provided, verify it exists and belongs to the same organization
3440
if (folderId) {
3541
const [folder] = await db()
@@ -47,24 +53,36 @@ export async function moveVideoToFolder({
4753
}
4854
}
4955

50-
if (spaceId) {
56+
if (spaceId && !isAllSpacesEntry) {
5157
await db()
5258
.update(spaceVideos)
5359
.set({
5460
folderId: folderId === null ? null : folderId,
5561
})
56-
.where(eq(spaceVideos.videoId, videoId));
62+
.where(
63+
and(eq(spaceVideos.videoId, videoId), eq(spaceVideos.spaceId, spaceId)),
64+
);
65+
} else if (spaceId && isAllSpacesEntry) {
66+
await db()
67+
.update(sharedVideos)
68+
.set({
69+
folderId: folderId === null ? null : folderId,
70+
})
71+
.where(
72+
and(
73+
eq(sharedVideos.videoId, videoId),
74+
eq(sharedVideos.organizationId, user.activeOrganizationId),
75+
),
76+
);
77+
} else {
78+
await db()
79+
.update(videos)
80+
.set({
81+
folderId: folderId === null ? null : folderId,
82+
})
83+
.where(eq(videos.id, videoId));
5784
}
5885

59-
// Update the video's folderId
60-
await db()
61-
.update(videos)
62-
.set({
63-
folderId,
64-
updatedAt: new Date(),
65-
})
66-
.where(eq(videos.id, videoId));
67-
6886
// Always revalidate the main caps page
6987
revalidatePath(`/dashboard/caps`);
7088

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
"use server";
2+
3+
import { db } from "@cap/database";
4+
import { getCurrentUser } from "@cap/database/auth/session";
5+
import {
6+
folders,
7+
sharedVideos,
8+
spaceVideos,
9+
videos,
10+
} from "@cap/database/schema";
11+
import type { Folder, Space, Video } from "@cap/web-domain";
12+
import { and, eq, inArray, sql } from "drizzle-orm";
13+
import { revalidatePath } from "next/cache";
14+
15+
export async function removeVideosFromFolder(
16+
folderId: Folder.FolderId,
17+
videoIds: Video.VideoId[],
18+
spaceId: Space.SpaceIdOrOrganisationId,
19+
) {
20+
try {
21+
const user = await getCurrentUser();
22+
23+
if (!user || !user.id) {
24+
throw new Error("Unauthorized");
25+
}
26+
27+
const isAllSpacesEntry = user.activeOrganizationId === spaceId;
28+
29+
if (!folderId || !videoIds || videoIds.length === 0) {
30+
throw new Error("Missing required data");
31+
}
32+
33+
// Verify folder exists and is accessible
34+
const [folder] = await db()
35+
.select({ id: folders.id, spaceId: folders.spaceId })
36+
.from(folders)
37+
.where(eq(folders.id, folderId));
38+
39+
if (!folder) {
40+
throw new Error("Folder not found");
41+
}
42+
43+
// Only allow updating videos the user owns
44+
const userVideos = await db()
45+
.select({ id: videos.id })
46+
.from(videos)
47+
.where(and(eq(videos.ownerId, user.id), inArray(videos.id, videoIds)));
48+
49+
const validVideoIds = userVideos.map((v) => v.id);
50+
51+
if (validVideoIds.length === 0) {
52+
throw new Error("No valid videos found");
53+
}
54+
55+
// Clear the folderId on the videos
56+
await db()
57+
.update(videos)
58+
.set({ folderId: null, updatedAt: new Date() })
59+
.where(
60+
and(inArray(videos.id, validVideoIds), eq(videos.folderId, folderId)),
61+
);
62+
63+
// Clear the folderId in the appropriate table based on context
64+
if (isAllSpacesEntry || !folder.spaceId) {
65+
// Organization-level folder - clear folderId in sharedVideos
66+
await db()
67+
.update(sharedVideos)
68+
.set({ folderId: null })
69+
.where(
70+
and(
71+
eq(sharedVideos.organizationId, user.activeOrganizationId),
72+
inArray(sharedVideos.videoId, validVideoIds),
73+
eq(sharedVideos.folderId, folderId),
74+
),
75+
);
76+
} else if (folder.spaceId) {
77+
// Space-level folder - clear folderId in spaceVideos
78+
await db()
79+
.update(spaceVideos)
80+
.set({ folderId: null })
81+
.where(
82+
and(
83+
sql`${spaceVideos.spaceId} = ${folder.spaceId}`,
84+
inArray(spaceVideos.videoId, validVideoIds),
85+
eq(spaceVideos.folderId, folderId),
86+
),
87+
);
88+
}
89+
90+
// Revalidate relevant paths
91+
revalidatePath(`/dashboard/caps`);
92+
revalidatePath(`/dashboard/folder/${folderId}`);
93+
if (folder.spaceId) {
94+
revalidatePath(`/dashboard/spaces/${folder.spaceId}/folder/${folderId}`);
95+
}
96+
97+
return {
98+
success: true,
99+
message: `${validVideoIds.length} video${validVideoIds.length === 1 ? "" : "s"} removed from folder`,
100+
removedCount: validVideoIds.length,
101+
};
102+
} catch (error) {
103+
console.error("Error removing videos from folder:", error);
104+
return {
105+
success: false,
106+
error:
107+
error instanceof Error
108+
? error.message
109+
: "Failed to remove videos from folder",
110+
};
111+
}
112+
}

0 commit comments

Comments
 (0)