dynamic worker routes based on env

This commit is contained in:
Ishaan Dey 2024-05-26 18:37:36 -07:00
parent 6285a68102
commit 6376493ae7
17 changed files with 427 additions and 260 deletions

View File

@ -1,16 +1,18 @@
// import type { DrizzleD1Database } from "drizzle-orm/d1"; // import type { DrizzleD1Database } from "drizzle-orm/d1";
import { drizzle } from "drizzle-orm/d1"; import { drizzle } from "drizzle-orm/d1"
import { json } from "itty-router-extras"; import { json } from "itty-router-extras"
import { ZodError, z } from "zod"; import { ZodError, z } from "zod"
import { user, sandbox, usersToSandboxes } from "./schema"; import { user, sandbox, usersToSandboxes } from "./schema"
import * as schema from "./schema"; import * as schema from "./schema"
import { and, eq, sql } from "drizzle-orm"; import { and, eq, sql } from "drizzle-orm"
export interface Env { export interface Env {
DB: D1Database; DB: D1Database
STORAGE: any; STORAGE: any
KV: KVNamespace; KV: KVNamespace
KEY: string
STORAGE_WORKER_URL: string
} }
// https://github.com/drizzle-team/drizzle-orm/tree/main/examples/cloudflare-d1 // https://github.com/drizzle-team/drizzle-orm/tree/main/examples/cloudflare-d1
@ -19,130 +21,163 @@ export interface Env {
// npx wrangler d1 execute d1-sandbox --local --file=./drizzle/<FILE> // npx wrangler d1 execute d1-sandbox --local --file=./drizzle/<FILE>
export default { export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> { async fetch(
const success = new Response("Success", { status: 200 }); request: Request,
const invalidRequest = new Response("Invalid Request", { status: 400 }); env: Env,
const notFound = new Response("Not Found", { status: 404 }); ctx: ExecutionContext
const methodNotAllowed = new Response("Method Not Allowed", { status: 405 }); ): Promise<Response> {
const success = new Response("Success", { status: 200 })
const invalidRequest = new Response("Invalid Request", { status: 400 })
const notFound = new Response("Not Found", { status: 404 })
const methodNotAllowed = new Response("Method Not Allowed", { status: 405 })
const url = new URL(request.url); const url = new URL(request.url)
const path = url.pathname; const path = url.pathname
const method = request.method; const method = request.method
const db = drizzle(env.DB, { schema }); if (request.headers.get("Authorization") !== env.KEY) {
return new Response("Unauthorized", { status: 401 })
}
const db = drizzle(env.DB, { schema })
if (path === "/api/session") { if (path === "/api/session") {
if (method === "PUT") { if (method === "PUT") {
const body = await request.json(); const body = await request.json()
const schema = z.object({ const schema = z.object({
userId: z.string(), userId: z.string(),
sandboxId: z.string(), sandboxId: z.string(),
}); })
const { userId, sandboxId } = schema.parse(body); const { userId, sandboxId } = schema.parse(body)
await env.KV.put(userId, sandboxId); await env.KV.put(userId, sandboxId)
return success; return success
} else if (method === "GET") { } else if (method === "GET") {
const params = url.searchParams; const params = url.searchParams
if (params.has("id")) { if (params.has("id")) {
const id = params.get("id") as string; const id = params.get("id") as string
const sandboxId = await env.KV.get(id); const sandboxId = await env.KV.get(id)
return json({ sandboxId }); return json({ sandboxId })
} else return invalidRequest; } else return invalidRequest
} else if (method === "DELETE") { } else if (method === "DELETE") {
const params = url.searchParams; const params = url.searchParams
if (params.has("id")) { if (params.has("id")) {
const id = params.get("id") as string; const id = params.get("id") as string
await env.KV.delete(id); await env.KV.delete(id)
return success; return success
} else return invalidRequest; } else return invalidRequest
} else return methodNotAllowed; } else return methodNotAllowed
} else if (path === "/api/sandbox") { } else if (path === "/api/sandbox") {
if (method === "GET") { if (method === "GET") {
const params = url.searchParams; const params = url.searchParams
if (params.has("id")) { if (params.has("id")) {
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: { with: {
usersToSandboxes: true, usersToSandboxes: true,
}, },
}); })
return json(res ?? {}); return json(res ?? {})
} else { } else {
const res = await db.select().from(sandbox).all(); const res = await db.select().from(sandbox).all()
return json(res ?? {}); return json(res ?? {})
} }
} else if (method === "DELETE") { } else if (method === "DELETE") {
const params = url.searchParams; const params = url.searchParams
if (params.has("id")) { if (params.has("id")) {
const id = params.get("id") as string; const id = params.get("id") as string
await db.delete(usersToSandboxes).where(eq(usersToSandboxes.sandboxId, id)); await db
await db.delete(sandbox).where(eq(sandbox.id, id)); .delete(usersToSandboxes)
.where(eq(usersToSandboxes.sandboxId, id))
await db.delete(sandbox).where(eq(sandbox.id, id))
const deleteStorageRequest = new Request("https://storage.ishaan1013.workers.dev/api/project", { const deleteStorageRequest = new Request(
method: "DELETE", `${env.STORAGE_WORKER_URL}/api/project`,
body: JSON.stringify({ sandboxId: id }), {
headers: { "Content-Type": "application/json" }, method: "DELETE",
}); body: JSON.stringify({ sandboxId: id }),
const deleteStorageRes = await env.STORAGE.fetch(deleteStorageRequest); headers: {
"Content-Type": "application/json",
Authorization: `${env.KEY}`,
},
}
)
await env.STORAGE.fetch(deleteStorageRequest)
return success; return success
} else { } else {
return invalidRequest; return invalidRequest
} }
} else if (method === "POST") { } else if (method === "POST") {
const postSchema = z.object({ const postSchema = z.object({
id: z.string(), id: z.string(),
name: z.string().optional(), name: z.string().optional(),
visibility: z.enum(["public", "private"]).optional(), visibility: z.enum(["public", "private"]).optional(),
}); })
const body = await request.json(); const body = await request.json()
const { id, name, visibility } = postSchema.parse(body); const { id, name, visibility } = postSchema.parse(body)
const sb = await db.update(sandbox).set({ name, visibility }).where(eq(sandbox.id, id)).returning().get(); const sb = await db
.update(sandbox)
.set({ name, visibility })
.where(eq(sandbox.id, id))
.returning()
.get()
return success; return success
} else if (method === "PUT") { } else if (method === "PUT") {
const initSchema = z.object({ const initSchema = z.object({
type: z.enum(["react", "node"]), type: z.enum(["react", "node"]),
name: z.string(), name: z.string(),
userId: z.string(), userId: z.string(),
visibility: z.enum(["public", "private"]), visibility: z.enum(["public", "private"]),
}); })
const body = await request.json(); const body = await request.json()
const { type, name, userId, visibility } = initSchema.parse(body); const { type, name, userId, visibility } = initSchema.parse(body)
const allSandboxes = await db.select().from(sandbox).all(); const allSandboxes = await db.select().from(sandbox).all()
if (allSandboxes.length >= 8) { if (allSandboxes.length >= 8) {
return new Response("You reached the maximum # of sandboxes.", { status: 400 }); return new Response("You reached the maximum # of sandboxes.", {
status: 400,
})
} }
const sb = await db.insert(sandbox).values({ type, name, userId, visibility, createdAt: new Date() }).returning().get(); const sb = await db
.insert(sandbox)
.values({ type, name, userId, visibility, createdAt: new Date() })
.returning()
.get()
const initStorageRequest = new Request("https://storage.ishaan1013.workers.dev/api/init", { const initStorageRequest = new Request(
method: "POST", `${env.STORAGE_WORKER_URL}/api/init`,
body: JSON.stringify({ sandboxId: sb.id, type }), {
headers: { "Content-Type": "application/json" }, method: "POST",
}); body: JSON.stringify({ sandboxId: sb.id, type }),
headers: {
"Content-Type": "application/json",
Authorization: `${env.KEY}`,
},
}
)
await env.STORAGE.fetch(initStorageRequest); await env.STORAGE.fetch(initStorageRequest)
return new Response(sb.id, { status: 200 }); return new Response(sb.id, { status: 200 })
} else { } else {
return methodNotAllowed; return methodNotAllowed
} }
} else if (path === "/api/sandbox/share") { } else if (path === "/api/sandbox/share") {
if (method === "GET") { if (method === "GET") {
const params = url.searchParams; const params = url.searchParams
if (params.has("id")) { if (params.has("id")) {
const id = params.get("id") as string; const id = params.get("id") as string
const res = await db.query.usersToSandboxes.findMany({ const res = await db.query.usersToSandboxes.findMany({
where: (uts, { eq }) => eq(uts.userId, id), where: (uts, { eq }) => eq(uts.userId, id),
}); })
const owners = await Promise.all( const owners = await Promise.all(
res.map(async (r) => { res.map(async (r) => {
@ -151,22 +186,28 @@ export default {
with: { with: {
author: true, author: true,
}, },
}); })
if (!sb) return; if (!sb) return
return { id: sb.id, name: sb.name, type: sb.type, author: sb.author.name, sharedOn: r.sharedOn }; return {
id: sb.id,
name: sb.name,
type: sb.type,
author: sb.author.name,
sharedOn: r.sharedOn,
}
}) })
); )
return json(owners ?? {}); return json(owners ?? {})
} else return invalidRequest; } else return invalidRequest
} else if (method === "POST") { } else if (method === "POST") {
const shareSchema = z.object({ const shareSchema = z.object({
sandboxId: z.string(), sandboxId: z.string(),
email: z.string(), email: z.string(),
}); })
const body = await request.json(); const body = await request.json()
const { sandboxId, email } = shareSchema.parse(body); const { sandboxId, email } = shareSchema.parse(body)
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),
@ -174,66 +215,78 @@ export default {
sandbox: true, sandbox: true,
usersToSandboxes: true, usersToSandboxes: true,
}, },
}); })
if (!user) { if (!user) {
return new Response("No user associated with email.", { status: 400 }); return new Response("No user associated with email.", { status: 400 })
} }
if (user.sandbox.find((sb) => sb.id === sandboxId)) { if (user.sandbox.find((sb) => sb.id === sandboxId)) {
return new Response("Cannot share with yourself!", { status: 400 }); return new Response("Cannot share with yourself!", { status: 400 })
} }
if (user.usersToSandboxes.find((uts) => uts.sandboxId === sandboxId)) { if (user.usersToSandboxes.find((uts) => uts.sandboxId === sandboxId)) {
return new Response("User already has access.", { status: 400 }); return new Response("User already has access.", { status: 400 })
} }
await db.insert(usersToSandboxes).values({ userId: user.id, sandboxId, sharedOn: new Date() }).get(); await db
.insert(usersToSandboxes)
.values({ userId: user.id, sandboxId, sharedOn: new Date() })
.get()
return success; return success
} else if (method === "DELETE") { } else if (method === "DELETE") {
const deleteShareSchema = z.object({ const deleteShareSchema = z.object({
sandboxId: z.string(), sandboxId: z.string(),
userId: z.string(), userId: z.string(),
}); })
const body = await request.json(); const body = await request.json()
const { sandboxId, userId } = deleteShareSchema.parse(body); const { sandboxId, userId } = deleteShareSchema.parse(body)
await db.delete(usersToSandboxes).where(and(eq(usersToSandboxes.userId, userId), eq(usersToSandboxes.sandboxId, sandboxId))); await db
.delete(usersToSandboxes)
.where(
and(
eq(usersToSandboxes.userId, userId),
eq(usersToSandboxes.sandboxId, sandboxId)
)
)
return success; return success
} else return methodNotAllowed; } else return methodNotAllowed
} else if (path === "/api/sandbox/generate" && method === "POST") { } else if (path === "/api/sandbox/generate" && method === "POST") {
const generateSchema = z.object({ const generateSchema = z.object({
userId: z.string(), userId: z.string(),
}); })
const body = await request.json(); const body = await request.json()
const { userId } = generateSchema.parse(body); const { userId } = generateSchema.parse(body)
const dbUser = await db.query.user.findFirst({ const dbUser = await db.query.user.findFirst({
where: (user, { eq }) => eq(user.id, userId), where: (user, { eq }) => eq(user.id, userId),
}); })
if (!dbUser) { if (!dbUser) {
return new Response("User not found.", { status: 400 }); return new Response("User not found.", { status: 400 })
} }
if (dbUser.generations !== null && dbUser.generations >= 10) { if (dbUser.generations !== null && dbUser.generations >= 10) {
return new Response("You reached the maximum # of generations.", { status: 400 }); return new Response("You reached the maximum # of generations.", {
status: 400,
})
} }
await db await db
.update(user) .update(user)
.set({ generations: sql`${user.generations} + 1` }) .set({ generations: sql`${user.generations} + 1` })
.where(eq(user.id, userId)) .where(eq(user.id, userId))
.get(); .get()
return success; return success
} else if (path === "/api/user") { } else if (path === "/api/user") {
if (method === "GET") { if (method === "GET") {
const params = url.searchParams; const params = url.searchParams
if (params.has("id")) { if (params.has("id")) {
const id = params.get("id") as string; const id = params.get("id") as string
const res = await db.query.user.findFirst({ const res = await db.query.user.findFirst({
where: (user, { eq }) => eq(user.id, id), where: (user, { eq }) => eq(user.id, id),
with: { with: {
@ -242,34 +295,38 @@ export default {
}, },
usersToSandboxes: true, usersToSandboxes: true,
}, },
}); })
return json(res ?? {}); return json(res ?? {})
} else { } else {
const res = await db.select().from(user).all(); const res = await db.select().from(user).all()
return json(res ?? {}); return json(res ?? {})
} }
} else if (method === "POST") { } else if (method === "POST") {
const userSchema = z.object({ const userSchema = z.object({
id: z.string(), id: z.string(),
name: z.string(), name: z.string(),
email: z.string().email(), email: z.string().email(),
}); })
const body = await request.json(); const body = await request.json()
const { id, name, email } = userSchema.parse(body); const { id, name, email } = userSchema.parse(body)
const res = await db.insert(user).values({ id, name, email }).returning().get(); const res = await db
return json({ res }); .insert(user)
.values({ id, name, email })
.returning()
.get()
return json({ res })
} else if (method === "DELETE") { } else if (method === "DELETE") {
const params = url.searchParams; const params = url.searchParams
if (params.has("id")) { if (params.has("id")) {
const id = params.get("id") as string; const id = params.get("id") as string
await db.delete(user).where(eq(user.id, id)); await db.delete(user).where(eq(user.id, id))
return success; return success
} else return invalidRequest; } else return invalidRequest
} else { } else {
return methodNotAllowed; return methodNotAllowed
} }
} else return notFound; } else return notFound
}, },
}; }

View File

@ -10,3 +10,7 @@ services = [{ binding = "STORAGE", service = "storage" }]
binding = "DB" binding = "DB"
database_name = "" database_name = ""
database_id = "" database_id = ""
[vars]
KEY = ""
STORAGE_WORKER_URL = ""

View File

@ -1 +1,2 @@
PORT=4000 PORT=4000
WORKERS_KEY=

View File

@ -70,7 +70,12 @@ io.use(async (socket, next) => {
const { sandboxId, userId } = parseQuery.data; const { sandboxId, userId } = parseQuery.data;
const dbUser = await fetch( const dbUser = await fetch(
`https://database.ishaan1013.workers.dev/api/user?id=${userId}` `${process.env.DATABASE_WORKER_URL}/api/user?id=${userId}`,
{
headers: {
Authorization: `${process.env.WORKERS_KEY}`,
},
}
); );
const dbUserJSON = (await dbUser.json()) as User; const dbUserJSON = (await dbUser.json()) as User;
@ -400,11 +405,12 @@ io.on("connection", async (socket) => {
) => { ) => {
// Log code generation credit in DB // Log code generation credit in DB
const fetchPromise = fetch( const fetchPromise = fetch(
`https://database.ishaan1013.workers.dev/api/sandbox/generate`, `${process.env.DATABASE_WORKER_URL}/api/sandbox/generate`,
{ {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `${process.env.WORKERS_KEY}`,
}, },
body: JSON.stringify({ body: JSON.stringify({
userId: data.userId, userId: data.userId,
@ -414,7 +420,7 @@ io.on("connection", async (socket) => {
// Generate code from cloudflare workers AI // Generate code from cloudflare workers AI
const generateCodePromise = fetch( const generateCodePromise = fetch(
`https://ai.ishaan1013.workers.dev/api?fileName=${fileName}&code=${code}&line=${line}&instructions=${instructions}`, `${process.env.AI_WORKER_URL}/api?fileName=${fileName}&code=${code}&line=${line}&instructions=${instructions}`,
{ {
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",

View File

@ -12,7 +12,12 @@ dotenv.config();
export const getSandboxFiles = async (id: string) => { export const getSandboxFiles = async (id: string) => {
const res = await fetch( const res = await fetch(
`https://storage.ishaan1013.workers.dev/api?sandboxId=${id}` `${process.env.STORAGE_WORKER_URL}/api?sandboxId=${id}`,
{
headers: {
Authorization: `${process.env.WORKERS_KEY}`,
},
}
); );
const data: R2Files = await res.json(); const data: R2Files = await res.json();
@ -23,7 +28,12 @@ export const getSandboxFiles = async (id: string) => {
export const getFolder = async (folderId: string) => { export const getFolder = async (folderId: string) => {
const res = await fetch( const res = await fetch(
`https://storage.ishaan1013.workers.dev/api?folderId=${folderId}` `${process.env.STORAGE_WORKER_URL}/api?folderId=${folderId}`,
{
headers: {
Authorization: `${process.env.WORKERS_KEY}`,
},
}
); );
const data: R2Files = await res.json(); const data: R2Files = await res.json();
@ -88,7 +98,12 @@ const processFiles = async (paths: string[], id: string) => {
const fetchFileContent = async (fileId: string): Promise<string> => { const fetchFileContent = async (fileId: string): Promise<string> => {
try { try {
const fileRes = await fetch( const fileRes = await fetch(
`https://storage.ishaan1013.workers.dev/api?fileId=${fileId}` `${process.env.STORAGE_WORKER_URL}/api?fileId=${fileId}`,
{
headers: {
Authorization: `${process.env.WORKERS_KEY}`,
},
}
); );
return await fileRes.text(); return await fileRes.text();
} catch (error) { } catch (error) {
@ -98,10 +113,11 @@ const fetchFileContent = async (fileId: string): Promise<string> => {
}; };
export const createFile = async (fileId: string) => { export const createFile = async (fileId: string) => {
const res = await fetch(`https://storage.ishaan1013.workers.dev/api`, { const res = await fetch(`${process.env.STORAGE_WORKER_URL}/api`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `${process.env.WORKERS_KEY}`,
}, },
body: JSON.stringify({ fileId }), body: JSON.stringify({ fileId }),
}); });
@ -113,10 +129,11 @@ export const renameFile = async (
newFileId: string, newFileId: string,
data: string data: string
) => { ) => {
const res = await fetch(`https://storage.ishaan1013.workers.dev/api/rename`, { const res = await fetch(`${process.env.STORAGE_WORKER_URL}/api/rename`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `${process.env.WORKERS_KEY}`,
}, },
body: JSON.stringify({ fileId, newFileId, data }), body: JSON.stringify({ fileId, newFileId, data }),
}); });
@ -124,10 +141,11 @@ export const renameFile = async (
}; };
export const saveFile = async (fileId: string, data: string) => { export const saveFile = async (fileId: string, data: string) => {
const res = await fetch(`https://storage.ishaan1013.workers.dev/api/save`, { const res = await fetch(`${process.env.STORAGE_WORKER_URL}/api/save`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `${process.env.WORKERS_KEY}`,
}, },
body: JSON.stringify({ fileId, data }), body: JSON.stringify({ fileId, data }),
}); });
@ -135,10 +153,11 @@ export const saveFile = async (fileId: string, data: string) => {
}; };
export const deleteFile = async (fileId: string) => { export const deleteFile = async (fileId: string) => {
const res = await fetch(`https://storage.ishaan1013.workers.dev/api`, { const res = await fetch(`${process.env.STORAGE_WORKER_URL}/api`, {
method: "DELETE", method: "DELETE",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `${process.env.WORKERS_KEY}`,
}, },
body: JSON.stringify({ fileId }), body: JSON.stringify({ fileId }),
}); });
@ -147,7 +166,12 @@ export const deleteFile = async (fileId: string) => {
export const getProjectSize = async (id: string) => { export const getProjectSize = async (id: string) => {
const res = await fetch( const res = await fetch(
`https://storage.ishaan1013.workers.dev/api/size?sandboxId=${id}` `${process.env.STORAGE_WORKER_URL}/api/size?sandboxId=${id}`,
{
headers: {
Authorization: `${process.env.WORKERS_KEY}`,
},
}
); );
return (await res.json()).size; return (await res.json()).size;
}; };

View File

@ -1,150 +1,159 @@
import { z } from 'zod'; import { z } from "zod"
import startercode from './startercode'; import startercode from "./startercode"
export interface Env { export interface Env {
R2: R2Bucket; R2: R2Bucket
KEY: string
} }
export default { export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> { async fetch(
const success = new Response('Success', { status: 200 }); request: Request,
const invalidRequest = new Response('Invalid Request', { status: 400 }); env: Env,
const notFound = new Response('Not Found', { status: 404 }); ctx: ExecutionContext
const methodNotAllowed = new Response('Method Not Allowed', { status: 405 }); ): Promise<Response> {
const success = new Response("Success", { status: 200 })
const invalidRequest = new Response("Invalid Request", { status: 400 })
const notFound = new Response("Not Found", { status: 404 })
const methodNotAllowed = new Response("Method Not Allowed", { status: 405 })
const url = new URL(request.url); if (request.headers.get("Authorization") !== env.KEY) {
const path = url.pathname; return new Response("Unauthorized", { status: 401 })
const method = request.method; }
if (path === '/api/project' && method === 'DELETE') { const url = new URL(request.url)
const path = url.pathname
const method = request.method
if (path === "/api/project" && method === "DELETE") {
const deleteSchema = z.object({ const deleteSchema = z.object({
sandboxId: z.string(), sandboxId: z.string(),
}); })
const body = await request.json(); const body = await request.json()
const { sandboxId } = deleteSchema.parse(body); const { sandboxId } = deleteSchema.parse(body)
const res = await env.R2.list({ prefix: 'projects/' + sandboxId }); const res = await env.R2.list({ prefix: "projects/" + sandboxId })
// delete all files // delete all files
await Promise.all( await Promise.all(
res.objects.map(async (file) => { res.objects.map(async (file) => {
await env.R2.delete(file.key); await env.R2.delete(file.key)
}) })
); )
return success; return success
} else if (path === '/api/size' && method === 'GET') { } else if (path === "/api/size" && method === "GET") {
const params = url.searchParams; const params = url.searchParams
const sandboxId = params.get('sandboxId'); const sandboxId = params.get("sandboxId")
if (sandboxId) { if (sandboxId) {
const res = await env.R2.list({ prefix: `projects/${sandboxId}` }); const res = await env.R2.list({ prefix: `projects/${sandboxId}` })
// sum up the size of all files // sum up the size of all files
let size = 0; let size = 0
for (const file of res.objects) { for (const file of res.objects) {
size += file.size; size += file.size
} }
return new Response(JSON.stringify({ size }), { status: 200 }); return new Response(JSON.stringify({ size }), { status: 200 })
} else return invalidRequest; } else return invalidRequest
} else if (path === '/api') { } else if (path === "/api") {
if (method === 'GET') { if (method === "GET") {
const params = url.searchParams; const params = url.searchParams
const sandboxId = params.get('sandboxId'); const sandboxId = params.get("sandboxId")
const folderId = params.get('folderId'); const folderId = params.get("folderId")
const fileId = params.get('fileId'); const fileId = params.get("fileId")
if (sandboxId) { if (sandboxId) {
const res = await env.R2.list({ prefix: `projects/${sandboxId}` }); const res = await env.R2.list({ prefix: `projects/${sandboxId}` })
return new Response(JSON.stringify(res), { status: 200 }); return new Response(JSON.stringify(res), { status: 200 })
} else if (folderId) { } else if (folderId) {
const res = await env.R2.list({ prefix: folderId }); const res = await env.R2.list({ prefix: folderId })
return new Response(JSON.stringify(res), { status: 200 }); return new Response(JSON.stringify(res), { status: 200 })
} else if (fileId) { } else if (fileId) {
const obj = await env.R2.get(fileId); const obj = await env.R2.get(fileId)
if (obj === null) { if (obj === null) {
return new Response(`${fileId} not found`, { status: 404 }); return new Response(`${fileId} not found`, { status: 404 })
} }
const headers = new Headers(); const headers = new Headers()
headers.set('etag', obj.httpEtag); headers.set("etag", obj.httpEtag)
obj.writeHttpMetadata(headers); obj.writeHttpMetadata(headers)
const text = await obj.text(); const text = await obj.text()
return new Response(text, { return new Response(text, {
headers, headers,
}); })
} else return invalidRequest; } else return invalidRequest
} else if (method === 'POST') { } else if (method === "POST") {
const createSchema = z.object({ const createSchema = z.object({
fileId: z.string(), fileId: z.string(),
}); })
const body = await request.json(); const body = await request.json()
const { fileId } = createSchema.parse(body); const { fileId } = createSchema.parse(body)
await env.R2.put(fileId, ''); await env.R2.put(fileId, "")
return success; return success
} else if (method === 'DELETE') { } else if (method === "DELETE") {
const deleteSchema = z.object({ const deleteSchema = z.object({
fileId: z.string(), fileId: z.string(),
}); })
const body = await request.json(); const body = await request.json()
const { fileId } = deleteSchema.parse(body); const { fileId } = deleteSchema.parse(body)
await env.R2.delete(fileId); await env.R2.delete(fileId)
return success; return success
} else return methodNotAllowed; } else return methodNotAllowed
} else if (path === '/api/rename' && method === 'POST') { } else if (path === "/api/rename" && method === "POST") {
const renameSchema = z.object({ const renameSchema = z.object({
fileId: z.string(), fileId: z.string(),
newFileId: z.string(), newFileId: z.string(),
data: z.string(), data: z.string(),
}); })
const body = await request.json(); const body = await request.json()
const { fileId, newFileId, data } = renameSchema.parse(body); const { fileId, newFileId, data } = renameSchema.parse(body)
await env.R2.delete(fileId); await env.R2.delete(fileId)
await env.R2.put(newFileId, data); await env.R2.put(newFileId, data)
return success; return success
} else if (path === '/api/save' && method === 'POST') { } else if (path === "/api/save" && method === "POST") {
const renameSchema = z.object({ const renameSchema = z.object({
fileId: z.string(), fileId: z.string(),
data: z.string(), data: z.string(),
}); })
const body = await request.json(); const body = await request.json()
const { fileId, data } = renameSchema.parse(body); const { fileId, data } = renameSchema.parse(body)
await env.R2.put(fileId, data); await env.R2.put(fileId, data)
return success; return success
} else if (path === '/api/init' && method === 'POST') { } else if (path === "/api/init" && method === "POST") {
const initSchema = z.object({ const initSchema = z.object({
sandboxId: z.string(), sandboxId: z.string(),
type: z.enum(['react', 'node']), type: z.enum(["react", "node"]),
}); })
const body = await request.json(); const body = await request.json()
const { sandboxId, type } = initSchema.parse(body); const { sandboxId, type } = initSchema.parse(body)
console.log(startercode[type]); console.log(startercode[type])
await Promise.all( await Promise.all(
startercode[type].map(async (file) => { startercode[type].map(async (file) => {
await env.R2.put(`projects/${sandboxId}/${file.name}`, file.body); await env.R2.put(`projects/${sandboxId}/${file.name}`, file.body)
}) })
); )
return success; return success
} else { } else {
return notFound; return notFound
} }
}, },
}; }

View File

@ -11,3 +11,6 @@ workers_dev = true
binding = 'R2' binding = 'R2'
bucket_name = '' bucket_name = ''
preview_bucket_name = '' preview_bucket_name = ''
[vars]
KEY = ''

View File

@ -4,6 +4,10 @@ NEXT_PUBLIC_LIVEBLOCKS_PUBLIC_KEY=
LIVEBLOCKS_SECRET_KEY= LIVEBLOCKS_SECRET_KEY=
NEXT_PUBLIC_SERVER_PORT=4000 NEXT_PUBLIC_SERVER_PORT=4000
NEXT_PUBLIC_APP_URL=http://localhost:3000
NEXT_PUBLIC_DATABASE_WORKER_URL=
NEXT_PUBLIC_STORAGE_WORKER_URL=
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up

View File

@ -11,7 +11,12 @@ export const revalidate = 0
const getUserData = async (id: string) => { const getUserData = async (id: string) => {
const userRes = await fetch( const userRes = await fetch(
`https://database.ishaan1013.workers.dev/api/user?id=${id}` `${process.env.NEXT_PUBLIC_DATABASE_WORKER_URL}/api/user?id=${id}`,
{
headers: {
Authorization: `${process.env.NEXT_PUBLIC_WORKERS_KEY}`,
},
}
) )
const userData: User = await userRes.json() const userData: User = await userRes.json()
return userData return userData
@ -19,7 +24,12 @@ const getUserData = async (id: string) => {
const getSandboxData = async (id: string) => { const getSandboxData = async (id: string) => {
const sandboxRes = await fetch( const sandboxRes = await fetch(
`https://database.ishaan1013.workers.dev/api/sandbox?id=${id}` `${process.env.NEXT_PUBLIC_DATABASE_WORKER_URL}/api/sandbox?id=${id}`,
{
headers: {
Authorization: `${process.env.NEXT_PUBLIC_WORKERS_KEY}`,
},
}
) )
const sandboxData: Sandbox = await sandboxRes.json() const sandboxData: Sandbox = await sandboxRes.json()
return sandboxData return sandboxData
@ -33,7 +43,12 @@ const getSharedUsers = async (usersToSandboxes: UsersToSandboxes[]) => {
const shared = await Promise.all( const shared = await Promise.all(
usersToSandboxes.map(async (user) => { usersToSandboxes.map(async (user) => {
const userRes = await fetch( const userRes = await fetch(
`https://database.ishaan1013.workers.dev/api/user?id=${user.userId}` `${process.env.NEXT_PUBLIC_DATABASE_WORKER_URL}/api/user?id=${user.userId}`,
{
headers: {
Authorization: `${process.env.NEXT_PUBLIC_WORKERS_KEY}`,
},
}
) )
const userData: User = await userRes.json() const userData: User = await userRes.json()
return { id: userData.id, name: userData.name } return { id: userData.id, name: userData.name }

View File

@ -12,12 +12,22 @@ export default async function DashboardPage() {
} }
const userRes = await fetch( const userRes = await fetch(
`https://database.ishaan1013.workers.dev/api/user?id=${user.id}` `${process.env.NEXT_PUBLIC_DATABASE_WORKER_URL}/api/user?id=${user.id}`,
{
headers: {
Authorization: `${process.env.NEXT_PUBLIC_WORKERS_KEY}`,
},
}
) )
const userData = (await userRes.json()) as User const userData = (await userRes.json()) as User
const sharedRes = await fetch( const sharedRes = await fetch(
`https://database.ishaan1013.workers.dev/api/sandbox/share?id=${user.id}` `${process.env.NEXT_PUBLIC_DATABASE_WORKER_URL}/api/sandbox/share?id=${user.id}`,
{
headers: {
Authorization: `${process.env.NEXT_PUBLIC_WORKERS_KEY}`,
},
}
) )
const shared = (await sharedRes.json()) as { const shared = (await sharedRes.json()) as {
id: string id: string

View File

@ -1,30 +1,36 @@
import { User } from "@/lib/types"; import { User } from "@/lib/types"
import { currentUser } from "@clerk/nextjs"; import { currentUser } from "@clerk/nextjs"
import { redirect } from "next/navigation"; import { redirect } from "next/navigation"
export default async function AppAuthLayout({ export default async function AppAuthLayout({
children, children,
}: { }: {
children: React.ReactNode; children: React.ReactNode
}) { }) {
const user = await currentUser(); const user = await currentUser()
if (!user) { if (!user) {
redirect("/"); redirect("/")
} }
const dbUser = await fetch( const dbUser = await fetch(
`https://database.ishaan1013.workers.dev/api/user?id=${user.id}` `${process.env.NEXT_PUBLIC_DATABASE_WORKER_URL}/api/user?id=${user.id}`,
); {
const dbUserJSON = (await dbUser.json()) as User; headers: {
Authorization: `${process.env.NEXT_PUBLIC_WORKERS_KEY}`,
},
}
)
const dbUserJSON = (await dbUser.json()) as User
if (!dbUserJSON.id) { if (!dbUserJSON.id) {
const res = await fetch( const res = await fetch(
"https://database.ishaan1013.workers.dev/api/user", `${process.env.NEXT_PUBLIC_DATABASE_WORKER_URL}/api/user`,
{ {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `${process.env.NEXT_PUBLIC_WORKERS_KEY}`,
}, },
body: JSON.stringify({ body: JSON.stringify({
id: user.id, id: user.id,
@ -32,8 +38,8 @@ export default async function AppAuthLayout({
email: user.emailAddresses[0].emailAddress, email: user.emailAddresses[0].emailAddress,
}), }),
} }
); )
} }
return <>{children}</>; return <>{children}</>
} }

View File

@ -18,7 +18,12 @@ export async function POST(request: NextRequest) {
} }
const res = await fetch( const res = await fetch(
`https://database.ishaan1013.workers.dev/api/user?id=${clerkUser.id}` `${process.env.NEXT_PUBLIC_DATABASE_WORKER_URL}/api/user?id=${clerkUser.id}`,
{
headers: {
Authorization: `${process.env.NEXT_PUBLIC_WORKERS_KEY}`,
},
}
) )
const user = (await res.json()) as User const user = (await res.json()) as User

View File

@ -60,9 +60,7 @@ export default function GenerateInput({
regenerate?: boolean regenerate?: boolean
}) => { }) => {
if (user.generations >= 10) { if (user.generations >= 10) {
toast.error( toast.error("You reached the maximum # of generations.")
"You reached the maximum # of generations. Contact @ishaandey_ on X/Twitter to reset :)"
)
return return
} }

View File

@ -51,8 +51,7 @@ export default function Loading({
</DialogTitle> </DialogTitle>
{didFail ? ( {didFail ? (
<DialogDescription className="pt-2"> <DialogDescription className="pt-2">
Try again in a minute, or contact @ishaandey_ on Twitter/X if it Try again soon.
still doesn't work.
</DialogDescription> </DialogDescription>
) : description ? ( ) : description ? (
<DialogDescription className="pt-2"> <DialogDescription className="pt-2">

View File

@ -75,13 +75,17 @@ export default function ShareSandboxModal({
{data.visibility === "private" ? ( {data.visibility === "private" ? (
<DialogDescription className="text-sm text-muted-foreground"> <DialogDescription className="text-sm text-muted-foreground">
This sandbox is private. Making it public will allow shared This sandbox is private. Making it public will allow shared
users to view and collaborate. You can still share & manage access below. users to view and collaborate. You can still share & manage
access below.
</DialogDescription> </DialogDescription>
) : null} ) : null}
</DialogHeader> </DialogHeader>
<div className="flex space-x-4 w-full"> <div className="flex space-x-4 w-full">
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="flex w-full"> <form
onSubmit={form.handleSubmit(onSubmit)}
className="flex w-full"
>
<FormField <FormField
control={form.control} control={form.control}
name="email" name="email"
@ -101,7 +105,8 @@ export default function ShareSandboxModal({
<Button disabled={loading} type="submit" className=""> <Button disabled={loading} type="submit" className="">
{loading ? ( {loading ? (
<> <>
<Loader2 className="animate-spin mr-2 h-4 w-4" /> Loading... <Loader2 className="animate-spin mr-2 h-4 w-4" />{" "}
Loading...
</> </>
) : ( ) : (
<> <>
@ -111,11 +116,19 @@ export default function ShareSandboxModal({
</Button> </Button>
</form> </form>
</Form> </Form>
<Button onClick={() => { <Button
navigator.clipboard.writeText(`https://s.ishaand.com/code/${data.id}`) onClick={() => {
toast.success("Link copied to clipboard.") navigator.clipboard.writeText(
}} size="icon" disabled={loading} variant="outline" className="shrink-0"> `${process.env.NEXT_PUBLIC_APP_URL}/code/${data.id}`
<Link className="h-4 w-4" /> )
toast.success("Link copied to clipboard.")
}}
size="icon"
disabled={loading}
variant="outline"
className="shrink-0"
>
<Link className="h-4 w-4" />
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -9,11 +9,12 @@ export async function createSandbox(body: {
visibility: string visibility: string
}) { }) {
const res = await fetch( const res = await fetch(
"https://database.ishaan1013.workers.dev/api/sandbox", `${process.env.NEXT_PUBLIC_DATABASE_WORKER_URL}/api/sandbox`,
{ {
method: "PUT", method: "PUT",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `${process.env.NEXT_PUBLIC_WORKERS_KEY}`,
}, },
body: JSON.stringify(body), body: JSON.stringify(body),
} }
@ -27,10 +28,11 @@ export async function updateSandbox(body: {
name?: string name?: string
visibility?: "public" | "private" visibility?: "public" | "private"
}) { }) {
await fetch("https://database.ishaan1013.workers.dev/api/sandbox", { await fetch(`${process.env.NEXT_PUBLIC_DATABASE_WORKER_URL}/api/sandbox`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `${process.env.NEXT_PUBLIC_WORKERS_KEY}`,
}, },
body: JSON.stringify(body), body: JSON.stringify(body),
}) })
@ -39,20 +41,27 @@ export async function updateSandbox(body: {
} }
export async function deleteSandbox(id: string) { export async function deleteSandbox(id: string) {
await fetch(`https://database.ishaan1013.workers.dev/api/sandbox?id=${id}`, { await fetch(
method: "DELETE", `${process.env.NEXT_PUBLIC_DATABASE_WORKER_URL}/api/sandbox?id=${id}`,
}) {
method: "DELETE",
headers: {
Authorization: `${process.env.NEXT_PUBLIC_WORKERS_KEY}`,
},
}
)
revalidatePath("/dashboard") revalidatePath("/dashboard")
} }
export async function shareSandbox(sandboxId: string, email: string) { export async function shareSandbox(sandboxId: string, email: string) {
const res = await fetch( const res = await fetch(
"https://database.ishaan1013.workers.dev/api/sandbox/share", `${process.env.NEXT_PUBLIC_DATABASE_WORKER_URL}/api/sandbox/share`,
{ {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `${process.env.NEXT_PUBLIC_WORKERS_KEY}`,
}, },
body: JSON.stringify({ sandboxId, email }), body: JSON.stringify({ sandboxId, email }),
} }
@ -68,13 +77,17 @@ export async function shareSandbox(sandboxId: string, email: string) {
} }
export async function unshareSandbox(sandboxId: string, userId: string) { export async function unshareSandbox(sandboxId: string, userId: string) {
await fetch("https://database.ishaan1013.workers.dev/api/sandbox/share", { await fetch(
method: "DELETE", `${process.env.NEXT_PUBLIC_DATABASE_WORKER_URL}/api/sandbox/share`,
headers: { {
"Content-Type": "application/json", method: "DELETE",
}, headers: {
body: JSON.stringify({ sandboxId, userId }), "Content-Type": "application/json",
}) Authorization: `${process.env.NEXT_PUBLIC_WORKERS_KEY}`,
},
body: JSON.stringify({ sandboxId, userId }),
}
)
revalidatePath(`/code/${sandboxId}`) revalidatePath(`/code/${sandboxId}`)
} }