Skip to content

Commit 2e56050

Browse files
authored
Refactor icon handling to properly use RPC and signed URLs (#1274)
* use proper rpc endpoints * fix staging access from preview branches * refactor all uploaded icon image handling * formatting * parse endpoint as url * apply ImageUrl to more places * fully complete migration i think * remove GetSignedUrl * formatting * fix endpoint path style detection * standardise s/z
1 parent 7b4c048 commit 2e56050

File tree

92 files changed

+1311
-1134
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

92 files changed

+1311
-1134
lines changed

apps/web/actions/organization/upload-space-icon.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@
33
import { db } from "@cap/database";
44
import { getCurrentUser } from "@cap/database/auth/session";
55
import { spaces } from "@cap/database/schema";
6-
import { serverEnv } from "@cap/env";
76
import { S3Buckets } from "@cap/web-backend";
8-
import type { Space } from "@cap/web-domain";
7+
import { ImageUpload, type Space } from "@cap/web-domain";
98
import { eq } from "drizzle-orm";
109
import { Effect, Option } from "effect";
1110
import { revalidatePath } from "next/cache";
@@ -54,9 +53,11 @@ export async function uploadSpaceIcon(
5453

5554
// Prepare new file key
5655
const fileExtension = file.name.split(".").pop();
57-
const fileKey = `organizations/${
58-
space.organizationId
59-
}/spaces/${spaceId}/icon-${Date.now()}.${fileExtension}`;
56+
const fileKey = ImageUpload.ImageKey.make(
57+
`organizations/${
58+
space.organizationId
59+
}/spaces/${spaceId}/icon-${Date.now()}.${fileExtension}`,
60+
);
6061

6162
const [bucket] = await S3Buckets.getBucketAccess(Option.none()).pipe(
6263
runPromise,
@@ -89,12 +90,13 @@ export async function uploadSpaceIcon(
8990
)
9091
.pipe(runPromise);
9192

92-
const iconUrl = fileKey;
93-
94-
await db().update(spaces).set({ iconUrl }).where(eq(spaces.id, spaceId));
93+
await db()
94+
.update(spaces)
95+
.set({ iconUrl: fileKey })
96+
.where(eq(spaces.id, spaceId));
9597

9698
revalidatePath("/dashboard");
97-
return { success: true, iconUrl };
99+
return { success: true, iconUrl: fileKey };
98100
} catch (error) {
99101
console.error("Error uploading space icon:", error);
100102
throw new Error(error instanceof Error ? error.message : "Upload failed");

apps/web/app/(org)/dashboard/Contexts.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
"use client";
22

3-
import type { users } from "@cap/database/schema";
43
import { buildEnv } from "@cap/env";
54
import Cookies from "js-cookie";
6-
import { usePathname } from "next/navigation";
5+
import { redirect, usePathname } from "next/navigation";
76
import { createContext, useContext, useEffect, useState } from "react";
7+
import { type CurrentUser, useCurrentUser } from "@/app/Layout/AuthContext";
88
import { UpgradeModal } from "@/components/UpgradeModal";
99
import type {
1010
Organization,
@@ -21,7 +21,7 @@ type SharedContext = {
2121
userSpaces: Spaces[] | null;
2222
sharedSpaces: Spaces[] | null;
2323
activeSpace: Spaces | null;
24-
user: typeof users.$inferSelect;
24+
user: CurrentUser;
2525
userCapsCount: number | null;
2626
isSubscribed: boolean;
2727
toggleSidebarCollapsed: () => void;
@@ -56,7 +56,6 @@ export function DashboardContexts({
5656
activeOrganization,
5757
spacesData,
5858
userCapsCount,
59-
user,
6059
isSubscribed,
6160
organizationSettings,
6261
userPreferences,
@@ -70,7 +69,6 @@ export function DashboardContexts({
7069
activeOrganization: SharedContext["activeOrganization"];
7170
spacesData: SharedContext["spacesData"];
7271
userCapsCount: SharedContext["userCapsCount"];
73-
user: SharedContext["user"];
7472
isSubscribed: SharedContext["isSubscribed"];
7573
organizationSettings: SharedContext["organizationSettings"];
7674
userPreferences: SharedContext["userPreferences"];
@@ -79,6 +77,9 @@ export function DashboardContexts({
7977
initialSidebarCollapsed: boolean;
8078
referClicked: boolean;
8179
}) {
80+
const user = useCurrentUser();
81+
if (!user) redirect("/login");
82+
8283
const [theme, setTheme] = useState<ITheme>(initialTheme);
8384
const [sidebarCollapsed, setSidebarCollapsed] = useState(
8485
initialSidebarCollapsed,

apps/web/app/(org)/dashboard/_components/MobileTab.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ const Orgs = ({
7878
<SignedImageUrl
7979
image={activeOrg?.organization.iconUrl}
8080
name={activeOrg?.organization.name ?? "No organization found"}
81-
type="organization"
8281
letterClass="text-xs"
8382
className="relative flex-shrink-0 mx-auto size-6"
8483
/>
@@ -138,7 +137,6 @@ const OrgsMenu = ({
138137
<SignedImageUrl
139138
image={organization.organization.iconUrl}
140139
name={organization.organization.name}
141-
type="organization"
142140
letterClass="text-xs"
143141
className="relative flex-shrink-0 size-5"
144142
/>

apps/web/app/(org)/dashboard/_components/Navbar/Items.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,6 @@ const AdminNavItems = ({ toggleMobileNav }: Props) => {
127127
name={
128128
activeOrg?.organization.name ?? "No organization found"
129129
}
130-
type="organization"
131130
letterClass={clsx(
132131
sidebarCollapsed ? "text-sm" : "text-[13px]",
133132
)}
@@ -216,7 +215,6 @@ const AdminNavItems = ({ toggleMobileNav }: Props) => {
216215
<SignedImageUrl
217216
image={organization.organization.iconUrl}
218217
name={organization.organization.name}
219-
type="organization"
220218
letterClass="text-xs"
221219
className="relative flex-shrink-0 size-5"
222220
/>

apps/web/app/(org)/dashboard/_components/Navbar/SpaceDialog.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
Input,
1515
Label,
1616
} from "@cap/ui";
17+
import type { ImageUpload } from "@cap/web-domain";
1718
import { faLayerGroup } from "@fortawesome/free-solid-svg-icons";
1819
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
1920
import { zodResolver } from "@hookform/resolvers/zod";
@@ -36,7 +37,7 @@ interface SpaceDialogProps {
3637
id: string;
3738
name: string;
3839
members: string[];
39-
iconUrl?: string;
40+
iconUrl?: ImageUpload.ImageUrl;
4041
} | null;
4142
onSpaceUpdated?: () => void;
4243
}
@@ -117,7 +118,7 @@ export interface NewSpaceFormProps {
117118
id: string;
118119
name: string;
119120
members: string[];
120-
iconUrl?: string;
121+
iconUrl?: ImageUpload.ImageUrl;
121122
} | null;
122123
}
123124

@@ -277,7 +278,6 @@ export const NewSpaceForm: React.FC<NewSpaceFormProps> = (props) => {
277278
<FileInput
278279
id="space-icon"
279280
name="icon"
280-
type="organization"
281281
initialPreviewUrl={space?.iconUrl || null}
282282
notDraggingClassName="hover:bg-gray-3"
283283
onChange={setSelectedFile}

apps/web/app/(org)/dashboard/_components/Navbar/SpacesList.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,6 @@ const SpacesList = ({ toggleMobileNav }: { toggleMobileNav?: () => void }) => {
282282
<SignedImageUrl
283283
image={space.iconUrl}
284284
name={space.name}
285-
type="organization"
286285
letterClass={clsx(
287286
sidebarCollapsed ? "text-sm" : "text-[11px]",
288287
)}

apps/web/app/(org)/dashboard/_components/Navbar/Top.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ const Top = () => {
104104
<SignedImageUrl
105105
image={activeSpace.iconUrl}
106106
name={activeSpace?.name}
107-
type="organization"
108107
letterClass="text-xs"
109108
className="relative flex-shrink-0 size-5"
110109
/>
@@ -262,9 +261,8 @@ const User = () => {
262261
>
263262
<div className="flex items-center">
264263
<SignedImageUrl
265-
image={user.image}
264+
image={user.imageUrl}
266265
name={user.name ?? "User"}
267-
type="user"
268266
letterClass="text-xs lg:text-md"
269267
className="flex-shrink-0 size-[24px] text-gray-12"
270268
/>

apps/web/app/(org)/dashboard/caps/Caps.tsx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,15 @@
22

33
import type { VideoMetadata } from "@cap/database/types";
44
import { Button } from "@cap/ui";
5-
import type { Video } from "@cap/web-domain";
5+
import type { ImageUpload, Video } from "@cap/web-domain";
66
import { faFolderPlus, faInfoCircle } from "@fortawesome/free-solid-svg-icons";
77
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
88
import { Effect, Exit } from "effect";
99
import { useRouter, useSearchParams } from "next/navigation";
1010
import { useEffect, useMemo, useRef, useState } from "react";
1111
import { toast } from "sonner";
12-
import { useEffectMutation } from "@/lib/EffectRuntime";
12+
import { useEffectMutation, useRpcClient } from "@/lib/EffectRuntime";
1313
import { useVideosAnalyticsQuery } from "@/lib/Queries/Analytics";
14-
import { AnalyticsRequest } from "@/lib/Requests/AnalyticsRequest";
15-
import { Rpc, withRpc } from "@/lib/Rpcs";
1614
import { useDashboardContext } from "../Contexts";
1715
import {
1816
NewFolderDialog,
@@ -36,11 +34,14 @@ export type VideoData = {
3634
totalComments: number;
3735
totalReactions: number;
3836
foldersData: FolderDataType[];
39-
sharedOrganizations: { id: string; name: string; iconUrl?: string }[];
37+
sharedOrganizations: {
38+
id: string;
39+
name: string;
40+
iconUrl?: ImageUpload.ImageUrl | null;
41+
}[];
4042
sharedSpaces?: {
4143
id: string;
4244
name: string;
43-
iconUrl: string;
4445
isOrg: boolean;
4546
organizationId: string;
4647
}[];
@@ -147,12 +148,12 @@ export const Caps = ({
147148
});
148149
};
149150

151+
const rpc = useRpcClient();
152+
150153
const { mutate: deleteCaps, isPending: isDeletingCaps } = useEffectMutation({
151154
mutationFn: Effect.fn(function* (ids: Video.VideoId[]) {
152155
if (ids.length === 0) return;
153156

154-
const rpc = yield* Rpc;
155-
156157
const fiber = yield* Effect.gen(function* () {
157158
const results = yield* Effect.all(
158159
ids.map((id) => rpc.VideoDelete(id).pipe(Effect.exit)),
@@ -203,7 +204,7 @@ export const Caps = ({
203204
});
204205

205206
const { mutate: deleteCap, isPending: isDeletingCap } = useEffectMutation({
206-
mutationFn: (id: Video.VideoId) => withRpc((r) => r.VideoDelete(id)),
207+
mutationFn: (id: Video.VideoId) => rpc.VideoDelete(id),
207208
onSuccess: () => {
208209
toast.success("Cap deleted successfully");
209210
router.refresh();

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
DropdownMenuItem,
99
DropdownMenuTrigger,
1010
} from "@cap/ui";
11-
import type { Video } from "@cap/web-domain";
11+
import type { ImageUpload, Video } from "@cap/web-domain";
1212
import { HttpClient } from "@effect/platform";
1313
import {
1414
faCheck,
@@ -41,7 +41,7 @@ import {
4141
type ImageLoadingStatus,
4242
VideoThumbnail,
4343
} from "@/components/VideoThumbnail";
44-
import { useEffectMutation } from "@/lib/EffectRuntime";
44+
import { useEffectMutation, useRpcClient } from "@/lib/EffectRuntime";
4545
import { withRpc } from "@/lib/Rpcs";
4646
import { usePublicEnv } from "@/utils/public-env";
4747
import { PasswordDialog } from "../PasswordDialog";
@@ -63,12 +63,12 @@ export interface CapCardProps extends PropsWithChildren {
6363
sharedOrganizations?: {
6464
id: string;
6565
name: string;
66-
iconUrl?: string | null;
66+
iconUrl?: ImageUpload.ImageUrl | null;
6767
}[];
6868
sharedSpaces?: {
6969
id: string;
7070
name: string;
71-
iconUrl?: string | null;
71+
iconUrl?: ImageUpload.ImageUrl | null;
7272
organizationId: string;
7373
}[];
7474
ownerName: string | null;
@@ -133,11 +133,12 @@ export const CapCard = ({
133133
const [confirmOpen, setConfirmOpen] = useState(false);
134134

135135
const router = useRouter();
136+
const rpc = useRpcClient();
136137

137138
const downloadMutation = useEffectMutation({
138139
mutationFn: () =>
139140
Effect.gen(function* () {
140-
const result = yield* withRpc((r) => r.VideoGetDownloadInfo(cap.id));
141+
const result = yield* rpc.VideoGetDownloadInfo(cap.id);
141142
const httpClient = yield* HttpClient.HttpClient;
142143
if (Option.isSome(result)) {
143144
const fetchResponse = yield* httpClient.get(result.value.downloadUrl);
@@ -175,7 +176,7 @@ export const CapCard = ({
175176
});
176177

177178
const duplicateMutation = useEffectMutation({
178-
mutationFn: () => withRpc((r) => r.VideoDuplicate(cap.id)),
179+
mutationFn: () => rpc.VideoDuplicate(cap.id),
179180
onSuccess: () => {
180181
router.refresh();
181182
},

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ import { useRouter } from "next/navigation";
99
import { useEffect, useRef, useState } from "react";
1010
import { toast } from "sonner";
1111
import { moveVideoToFolder } from "@/actions/folders/moveVideoToFolder";
12-
import { useEffectMutation } from "@/lib/EffectRuntime";
13-
import { withRpc } from "@/lib/Rpcs";
12+
import { useEffectMutation, useRpcClient } from "@/lib/EffectRuntime";
1413
import { ConfirmationDialog } from "../../_components/ConfirmationDialog";
1514
import { useDashboardContext, useTheme } from "../../Contexts";
1615
import { registerDropTarget } from "../../folder/[id]/components/ClientCapCard";
@@ -70,8 +69,10 @@ const FolderCard = ({
7069
}),
7170
});
7271

72+
const rpc = useRpcClient();
73+
7374
const deleteFolder = useEffectMutation({
74-
mutationFn: (id: Folder.FolderId) => withRpc((r) => r.FolderDelete(id)),
75+
mutationFn: (id: Folder.FolderId) => rpc.FolderDelete(id),
7576
onSuccess: () => {
7677
router.refresh();
7778
toast.success("Folder deleted successfully");
@@ -83,8 +84,7 @@ const FolderCard = ({
8384
});
8485

8586
const updateFolder = useEffectMutation({
86-
mutationFn: (data: Folder.FolderUpdate) =>
87-
withRpc((r) => r.FolderUpdate(data)),
87+
mutationFn: (data: Folder.FolderUpdate) => rpc.FolderUpdate(data),
8888
onSuccess: () => {
8989
toast.success("Folder name updated successfully");
9090
router.refresh();

0 commit comments

Comments
 (0)