From 5ba1c0303085775a50196b71cc027faa01ad8eee Mon Sep 17 00:00:00 2001 From: Ishaan Dey Date: Wed, 1 May 2024 01:53:49 -0400 Subject: [PATCH] sharing logic --- backend/database/src/index.ts | 14 +++++++- backend/server/src/types.ts | 8 +++++ frontend/app/(app)/code/[id]/page.tsx | 21 +++++++++-- frontend/components/editor/index.tsx | 7 ++-- frontend/components/editor/navbar/index.tsx | 6 ++++ frontend/components/editor/navbar/share.tsx | 40 +++++++++++---------- frontend/lib/actions.ts | 25 ++++++------- frontend/lib/types.ts | 7 ++++ 8 files changed, 90 insertions(+), 38 deletions(-) diff --git a/backend/database/src/index.ts b/backend/database/src/index.ts index 4cf8d51..4e1610f 100644 --- a/backend/database/src/index.ts +++ b/backend/database/src/index.ts @@ -36,6 +36,9 @@ export default { const id = params.get("id") as string; const res = await db.query.sandbox.findFirst({ where: (sandbox, { eq }) => eq(sandbox.id, id), + with: { + usersToSandboxes: true, + }, }); return json(res ?? {}); } else { @@ -99,9 +102,18 @@ export default { const user = await db.query.user.findFirst({ where: (user, { eq }) => eq(user.email, email), + with: { + usersToSandboxes: true, + }, }); - if (!user) return invalidRequest; + if (!user) { + return new Response("No user associated with email.", { status: 400 }); + } + + if (user.usersToSandboxes.find((uts) => uts.sandboxId === sandboxId)) { + return new Response("User already has access.", { status: 400 }); + } await db.insert(usersToSandboxes).values({ userId: user.id, sandboxId }).get(); diff --git a/backend/server/src/types.ts b/backend/server/src/types.ts index be0d21f..650f529 100644 --- a/backend/server/src/types.ts +++ b/backend/server/src/types.ts @@ -5,6 +5,10 @@ export type User = { name: string email: string sandbox: Sandbox[] + usersToSandboxes: { + userId: string + sandboxId: string + }[] } export type Sandbox = { @@ -13,6 +17,10 @@ export type Sandbox = { type: "react" | "node" visibility: "public" | "private" userId: string + usersToSandboxes: { + userId: string + sandboxId: string + }[] } export type TFolder = { diff --git a/frontend/app/(app)/code/[id]/page.tsx b/frontend/app/(app)/code/[id]/page.tsx index b0d646d..765c4b8 100644 --- a/frontend/app/(app)/code/[id]/page.tsx +++ b/frontend/app/(app)/code/[id]/page.tsx @@ -1,5 +1,5 @@ import Navbar from "@/components/editor/navbar" -import { Sandbox, User } from "@/lib/types" +import { Sandbox, User, UsersToSandboxes } from "@/lib/types" import { currentUser } from "@clerk/nextjs" import dynamic from "next/dynamic" import { redirect } from "next/navigation" @@ -20,6 +20,20 @@ const getSandboxData = async (id: string) => { return sandboxData } +const getSharedUsers = async (usersToSandboxes: UsersToSandboxes[]) => { + const shared = await Promise.all( + usersToSandboxes.map(async (user) => { + const userRes = await fetch( + `http://localhost:8787/api/user?id=${user.userId}` + ) + const userData: User = await userRes.json() + return { id: userData.id, name: userData.name } + }) + ) + + return shared +} + export default async function CodePage({ params }: { params: { id: string } }) { const user = await currentUser() const sandboxId = params.id @@ -30,12 +44,13 @@ export default async function CodePage({ params }: { params: { id: string } }) { const userData = await getUserData(user.id) const sandboxData = await getSandboxData(sandboxId) + const shared = await getSharedUsers(sandboxData.usersToSandboxes) return (
- +
- +
) diff --git a/frontend/components/editor/index.tsx b/frontend/components/editor/index.tsx index 2365b84..089488a 100644 --- a/frontend/components/editor/index.tsx +++ b/frontend/components/editor/index.tsx @@ -30,12 +30,13 @@ import { processFileType, validateName } from "@/lib/utils" import { toast } from "sonner" import EditorTerminal from "./terminal" import { Button } from "../ui/button" +import { User } from "@/lib/types" export default function CodeEditor({ - userId, + userData, sandboxId, }: { - userId: string + userData: User sandboxId: string }) { const clerk = useClerk() @@ -54,7 +55,7 @@ export default function CodeEditor({ const [terminals, setTerminals] = useState([]) const socket = io( - `http://localhost:4000?userId=${userId}&sandboxId=${sandboxId}` + `http://localhost:4000?userId=${userData.id}&sandboxId=${sandboxId}` ) useEffect(() => { diff --git a/frontend/components/editor/navbar/index.tsx b/frontend/components/editor/navbar/index.tsx index 7dff4f2..ae45487 100644 --- a/frontend/components/editor/navbar/index.tsx +++ b/frontend/components/editor/navbar/index.tsx @@ -14,9 +14,14 @@ import ShareSandboxModal from "./share" export default function Navbar({ userData, sandboxData, + shared, }: { userData: User sandboxData: Sandbox + shared: { + id: string + name: string + }[] }) { const [isEditOpen, setIsEditOpen] = useState(false) const [isShareOpen, setIsShareOpen] = useState(false) @@ -32,6 +37,7 @@ export default function Navbar({ open={isShareOpen} setOpen={setIsShareOpen} data={sandboxData} + shared={shared} />
diff --git a/frontend/components/editor/navbar/share.tsx b/frontend/components/editor/navbar/share.tsx index f941c3e..67ebf3d 100644 --- a/frontend/components/editor/navbar/share.tsx +++ b/frontend/components/editor/navbar/share.tsx @@ -30,8 +30,8 @@ import { SelectValue, } from "@/components/ui/select" import { Loader2, UserPlus, X } from "lucide-react" -import { useState, useTransition } from "react" -import { Sandbox } from "@/lib/types" +import { useEffect, useState, useTransition } from "react" +import { Sandbox, User } from "@/lib/types" import { Button } from "@/components/ui/button" import Avatar from "@/components/ui/avatar" import { shareSandbox } from "@/lib/actions" @@ -45,10 +45,15 @@ export default function ShareSandboxModal({ open, setOpen, data, + shared, }: { open: boolean setOpen: (open: boolean) => void data: Sandbox + shared: { + id: string + name: string + }[] }) { const [loading, setLoading] = useState(false) @@ -60,17 +65,12 @@ export default function ShareSandboxModal({ }) async function onSubmit(values: z.infer) { - // if (!user.isSignedIn) return - // const sandboxData = { type: selected, userId: user.user.id, ...values } - // setLoading(true) - // const id = await createSandbox(sandboxData) - - console.log(values) - setLoading(true) const res = await shareSandbox(data.id, values.email) - if (!res) { - toast.error("Failed to share.") + if (!res.success) { + toast.error(res.message) + } else { + toast.success("Shared successfully.") } setLoading(false) @@ -121,15 +121,17 @@ export default function ShareSandboxModal({ Manage Access
-
-
- - Ishaan Dey + {shared.map((user) => ( +
+
+ + {user.name} +
+
- -
+ ))}
diff --git a/frontend/lib/actions.ts b/frontend/lib/actions.ts index 0e59d5f..c87fd8a 100644 --- a/frontend/lib/actions.ts +++ b/frontend/lib/actions.ts @@ -44,18 +44,19 @@ export async function deleteSandbox(id: string) { } export async function shareSandbox(sandboxId: string, email: string) { - try { - const res = await fetch("http://localhost:8787/api/sandbox/share", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ sandboxId, email }), - }) + const res = await fetch("http://localhost:8787/api/sandbox/share", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ sandboxId, email }), + }) + const text = await res.text() - revalidatePath(`/code/${sandboxId}`) - return true - } catch (err) { - return false + if (res.status !== 200) { + return { success: false, message: text } } + + revalidatePath(`/code/${sandboxId}`) + return { success: true, message: "Shared successfully." } } diff --git a/frontend/lib/types.ts b/frontend/lib/types.ts index 7b729ee..01739ca 100644 --- a/frontend/lib/types.ts +++ b/frontend/lib/types.ts @@ -5,6 +5,7 @@ export type User = { name: string email: string sandbox: Sandbox[] + usersToSandboxes: UsersToSandboxes[] } export type Sandbox = { @@ -13,6 +14,12 @@ export type Sandbox = { type: "react" | "node" visibility: "public" | "private" userId: string + usersToSandboxes: UsersToSandboxes[] +} + +export type UsersToSandboxes = { + userId: string + sandboxId: string } export type R2Files = {