sharing logic

This commit is contained in:
Ishaan Dey 2024-05-01 01:53:49 -04:00
parent 66a76eb0f9
commit 5ba1c03030
8 changed files with 90 additions and 38 deletions

View File

@ -36,6 +36,9 @@ export default {
const id = params.get("id") as string; const id = params.get("id") as string;
const res = await db.query.sandbox.findFirst({ const res = await db.query.sandbox.findFirst({
where: (sandbox, { eq }) => eq(sandbox.id, id), where: (sandbox, { eq }) => eq(sandbox.id, id),
with: {
usersToSandboxes: true,
},
}); });
return json(res ?? {}); return json(res ?? {});
} else { } else {
@ -99,9 +102,18 @@ export default {
const user = await db.query.user.findFirst({ const user = await db.query.user.findFirst({
where: (user, { eq }) => eq(user.email, email), 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(); await db.insert(usersToSandboxes).values({ userId: user.id, sandboxId }).get();

View File

@ -5,6 +5,10 @@ export type User = {
name: string name: string
email: string email: string
sandbox: Sandbox[] sandbox: Sandbox[]
usersToSandboxes: {
userId: string
sandboxId: string
}[]
} }
export type Sandbox = { export type Sandbox = {
@ -13,6 +17,10 @@ export type Sandbox = {
type: "react" | "node" type: "react" | "node"
visibility: "public" | "private" visibility: "public" | "private"
userId: string userId: string
usersToSandboxes: {
userId: string
sandboxId: string
}[]
} }
export type TFolder = { export type TFolder = {

View File

@ -1,5 +1,5 @@
import Navbar from "@/components/editor/navbar" 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 { currentUser } from "@clerk/nextjs"
import dynamic from "next/dynamic" import dynamic from "next/dynamic"
import { redirect } from "next/navigation" import { redirect } from "next/navigation"
@ -20,6 +20,20 @@ const getSandboxData = async (id: string) => {
return sandboxData 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 } }) { export default async function CodePage({ params }: { params: { id: string } }) {
const user = await currentUser() const user = await currentUser()
const sandboxId = params.id const sandboxId = params.id
@ -30,12 +44,13 @@ export default async function CodePage({ params }: { params: { id: string } }) {
const userData = await getUserData(user.id) const userData = await getUserData(user.id)
const sandboxData = await getSandboxData(sandboxId) const sandboxData = await getSandboxData(sandboxId)
const shared = await getSharedUsers(sandboxData.usersToSandboxes)
return ( return (
<div className="overflow-hidden overscroll-none w-screen flex flex-col h-screen bg-background"> <div className="overflow-hidden overscroll-none w-screen flex flex-col h-screen bg-background">
<Navbar userData={userData} sandboxData={sandboxData} /> <Navbar userData={userData} sandboxData={sandboxData} shared={shared} />
<div className="w-screen flex grow"> <div className="w-screen flex grow">
<CodeEditor userId={user.id} sandboxId={sandboxId} /> <CodeEditor userData={userData} sandboxId={sandboxId} />
</div> </div>
</div> </div>
) )

View File

@ -30,12 +30,13 @@ import { processFileType, validateName } from "@/lib/utils"
import { toast } from "sonner" import { toast } from "sonner"
import EditorTerminal from "./terminal" import EditorTerminal from "./terminal"
import { Button } from "../ui/button" import { Button } from "../ui/button"
import { User } from "@/lib/types"
export default function CodeEditor({ export default function CodeEditor({
userId, userData,
sandboxId, sandboxId,
}: { }: {
userId: string userData: User
sandboxId: string sandboxId: string
}) { }) {
const clerk = useClerk() const clerk = useClerk()
@ -54,7 +55,7 @@ export default function CodeEditor({
const [terminals, setTerminals] = useState<string[]>([]) const [terminals, setTerminals] = useState<string[]>([])
const socket = io( const socket = io(
`http://localhost:4000?userId=${userId}&sandboxId=${sandboxId}` `http://localhost:4000?userId=${userData.id}&sandboxId=${sandboxId}`
) )
useEffect(() => { useEffect(() => {

View File

@ -14,9 +14,14 @@ import ShareSandboxModal from "./share"
export default function Navbar({ export default function Navbar({
userData, userData,
sandboxData, sandboxData,
shared,
}: { }: {
userData: User userData: User
sandboxData: Sandbox sandboxData: Sandbox
shared: {
id: string
name: string
}[]
}) { }) {
const [isEditOpen, setIsEditOpen] = useState(false) const [isEditOpen, setIsEditOpen] = useState(false)
const [isShareOpen, setIsShareOpen] = useState(false) const [isShareOpen, setIsShareOpen] = useState(false)
@ -32,6 +37,7 @@ export default function Navbar({
open={isShareOpen} open={isShareOpen}
setOpen={setIsShareOpen} setOpen={setIsShareOpen}
data={sandboxData} data={sandboxData}
shared={shared}
/> />
<div className="h-14 px-2 w-full flex items-center justify-between border-b border-border"> <div className="h-14 px-2 w-full flex items-center justify-between border-b border-border">
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">

View File

@ -30,8 +30,8 @@ import {
SelectValue, SelectValue,
} from "@/components/ui/select" } from "@/components/ui/select"
import { Loader2, UserPlus, X } from "lucide-react" import { Loader2, UserPlus, X } from "lucide-react"
import { useState, useTransition } from "react" import { useEffect, useState, useTransition } from "react"
import { Sandbox } from "@/lib/types" import { Sandbox, User } from "@/lib/types"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import Avatar from "@/components/ui/avatar" import Avatar from "@/components/ui/avatar"
import { shareSandbox } from "@/lib/actions" import { shareSandbox } from "@/lib/actions"
@ -45,10 +45,15 @@ export default function ShareSandboxModal({
open, open,
setOpen, setOpen,
data, data,
shared,
}: { }: {
open: boolean open: boolean
setOpen: (open: boolean) => void setOpen: (open: boolean) => void
data: Sandbox data: Sandbox
shared: {
id: string
name: string
}[]
}) { }) {
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
@ -60,17 +65,12 @@ export default function ShareSandboxModal({
}) })
async function onSubmit(values: z.infer<typeof formSchema>) { async function onSubmit(values: z.infer<typeof formSchema>) {
// 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) setLoading(true)
const res = await shareSandbox(data.id, values.email) const res = await shareSandbox(data.id, values.email)
if (!res) { if (!res.success) {
toast.error("Failed to share.") toast.error(res.message)
} else {
toast.success("Shared successfully.")
} }
setLoading(false) setLoading(false)
@ -121,15 +121,17 @@ export default function ShareSandboxModal({
<DialogTitle>Manage Access</DialogTitle> <DialogTitle>Manage Access</DialogTitle>
</DialogHeader> </DialogHeader>
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center justify-between"> {shared.map((user) => (
<div className="flex items-center"> <div key={user.id} className="flex items-center justify-between">
<Avatar name="Ishaan Dey" className="mr-2" /> <div className="flex items-center">
Ishaan Dey <Avatar name={user.name} className="mr-2" />
{user.name}
</div>
<Button variant="ghost" size="smIcon">
<X className="w-4 h-4" />
</Button>
</div> </div>
<Button variant="ghost" size="smIcon"> ))}
<X className="w-4 h-4" />
</Button>
</div>
</div> </div>
</div> </div>
</DialogContent> </DialogContent>

View File

@ -44,18 +44,19 @@ export async function deleteSandbox(id: string) {
} }
export async function shareSandbox(sandboxId: string, email: string) { export async function shareSandbox(sandboxId: string, email: string) {
try { const res = await fetch("http://localhost:8787/api/sandbox/share", {
const res = await fetch("http://localhost:8787/api/sandbox/share", { method: "POST",
method: "POST", headers: {
headers: { "Content-Type": "application/json",
"Content-Type": "application/json", },
}, body: JSON.stringify({ sandboxId, email }),
body: JSON.stringify({ sandboxId, email }), })
}) const text = await res.text()
revalidatePath(`/code/${sandboxId}`) if (res.status !== 200) {
return true return { success: false, message: text }
} catch (err) {
return false
} }
revalidatePath(`/code/${sandboxId}`)
return { success: true, message: "Shared successfully." }
} }

View File

@ -5,6 +5,7 @@ export type User = {
name: string name: string
email: string email: string
sandbox: Sandbox[] sandbox: Sandbox[]
usersToSandboxes: UsersToSandboxes[]
} }
export type Sandbox = { export type Sandbox = {
@ -13,6 +14,12 @@ export type Sandbox = {
type: "react" | "node" type: "react" | "node"
visibility: "public" | "private" visibility: "public" | "private"
userId: string userId: string
usersToSandboxes: UsersToSandboxes[]
}
export type UsersToSandboxes = {
userId: string
sandboxId: string
} }
export type R2Files = { export type R2Files = {