2024-05-25 01:16:25 -07:00
|
|
|
// import type { DrizzleD1Database } from "drizzle-orm/d1";
|
2024-05-26 18:37:36 -07:00
|
|
|
import { drizzle } from "drizzle-orm/d1"
|
|
|
|
import { json } from "itty-router-extras"
|
2024-10-24 12:44:38 -06:00
|
|
|
import { z } from "zod"
|
2024-04-16 19:06:23 -04:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
import { and, eq, sql } from "drizzle-orm"
|
2024-10-24 12:44:38 -06:00
|
|
|
import * as schema from "./schema"
|
|
|
|
import { sandbox, user, usersToSandboxes } from "./schema"
|
2024-04-22 00:30:50 -04:00
|
|
|
|
2024-04-16 18:19:34 -04:00
|
|
|
export interface Env {
|
2024-05-26 18:37:36 -07:00
|
|
|
DB: D1Database
|
|
|
|
STORAGE: any
|
|
|
|
KEY: string
|
|
|
|
STORAGE_WORKER_URL: string
|
2024-04-16 18:19:34 -04:00
|
|
|
}
|
|
|
|
|
2024-04-21 22:55:49 -04:00
|
|
|
// https://github.com/drizzle-team/drizzle-orm/tree/main/examples/cloudflare-d1
|
|
|
|
|
2024-04-27 16:22:35 -04:00
|
|
|
// npm run generate
|
|
|
|
// npx wrangler d1 execute d1-sandbox --local --file=./drizzle/<FILE>
|
|
|
|
|
2024-04-16 18:19:34 -04:00
|
|
|
export default {
|
2024-05-26 18:37:36 -07:00
|
|
|
async fetch(
|
|
|
|
request: Request,
|
|
|
|
env: Env,
|
|
|
|
ctx: ExecutionContext
|
|
|
|
): 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 path = url.pathname
|
|
|
|
const method = request.method
|
|
|
|
|
|
|
|
if (request.headers.get("Authorization") !== env.KEY) {
|
|
|
|
return new Response("Unauthorized", { status: 401 })
|
|
|
|
}
|
|
|
|
|
|
|
|
const db = drizzle(env.DB, { schema })
|
2024-04-17 21:24:57 -04:00
|
|
|
|
2024-05-26 18:45:28 -07:00
|
|
|
if (path === "/api/sandbox") {
|
2024-04-27 16:22:35 -04:00
|
|
|
if (method === "GET") {
|
2024-05-26 18:37:36 -07:00
|
|
|
const params = url.searchParams
|
2024-04-27 16:22:35 -04:00
|
|
|
if (params.has("id")) {
|
2024-05-26 18:37:36 -07:00
|
|
|
const id = params.get("id") as string
|
2024-04-27 16:22:35 -04:00
|
|
|
const res = await db.query.sandbox.findFirst({
|
|
|
|
where: (sandbox, { eq }) => eq(sandbox.id, id),
|
2024-05-01 01:53:49 -04:00
|
|
|
with: {
|
|
|
|
usersToSandboxes: true,
|
|
|
|
},
|
2024-05-26 18:37:36 -07:00
|
|
|
})
|
|
|
|
return json(res ?? {})
|
2024-04-27 16:22:35 -04:00
|
|
|
} else {
|
2024-05-26 18:37:36 -07:00
|
|
|
const res = await db.select().from(sandbox).all()
|
|
|
|
return json(res ?? {})
|
2024-04-27 16:22:35 -04:00
|
|
|
}
|
2024-04-27 21:24:20 -04:00
|
|
|
} else if (method === "DELETE") {
|
2024-05-26 18:37:36 -07:00
|
|
|
const params = url.searchParams
|
2024-04-27 21:24:20 -04:00
|
|
|
if (params.has("id")) {
|
2024-05-26 18:37:36 -07:00
|
|
|
const id = params.get("id") as string
|
|
|
|
await db
|
|
|
|
.delete(usersToSandboxes)
|
|
|
|
.where(eq(usersToSandboxes.sandboxId, id))
|
|
|
|
await db.delete(sandbox).where(eq(sandbox.id, id))
|
|
|
|
|
|
|
|
const deleteStorageRequest = new Request(
|
|
|
|
`${env.STORAGE_WORKER_URL}/api/project`,
|
|
|
|
{
|
|
|
|
method: "DELETE",
|
|
|
|
body: JSON.stringify({ sandboxId: id }),
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
Authorization: `${env.KEY}`,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
await env.STORAGE.fetch(deleteStorageRequest)
|
|
|
|
|
|
|
|
return success
|
2024-04-27 21:24:20 -04:00
|
|
|
} else {
|
2024-05-26 18:37:36 -07:00
|
|
|
return invalidRequest
|
2024-04-27 21:24:20 -04:00
|
|
|
}
|
|
|
|
} else if (method === "POST") {
|
2024-05-25 01:16:25 -07:00
|
|
|
const postSchema = z.object({
|
2024-04-27 21:24:20 -04:00
|
|
|
id: z.string(),
|
|
|
|
name: z.string().optional(),
|
|
|
|
visibility: z.enum(["public", "private"]).optional(),
|
2024-05-26 18:37:36 -07:00
|
|
|
})
|
|
|
|
|
|
|
|
const body = await request.json()
|
|
|
|
const { id, name, visibility } = postSchema.parse(body)
|
|
|
|
const sb = await db
|
|
|
|
.update(sandbox)
|
|
|
|
.set({ name, visibility })
|
|
|
|
.where(eq(sandbox.id, id))
|
|
|
|
.returning()
|
|
|
|
.get()
|
|
|
|
|
|
|
|
return success
|
2024-04-27 16:22:35 -04:00
|
|
|
} else if (method === "PUT") {
|
|
|
|
const initSchema = z.object({
|
2024-08-18 06:52:41 -07:00
|
|
|
type: z.string(),
|
2024-04-27 16:22:35 -04:00
|
|
|
name: z.string(),
|
|
|
|
userId: z.string(),
|
|
|
|
visibility: z.enum(["public", "private"]),
|
2024-05-26 18:37:36 -07:00
|
|
|
})
|
2024-04-22 00:30:50 -04:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
const body = await request.json()
|
|
|
|
const { type, name, userId, visibility } = initSchema.parse(body)
|
2024-04-24 02:22:06 -04:00
|
|
|
|
2024-06-28 02:39:03 -04:00
|
|
|
const userSandboxes = await db
|
|
|
|
.select()
|
|
|
|
.from(sandbox)
|
|
|
|
.where(eq(sandbox.userId, userId))
|
|
|
|
.all()
|
|
|
|
|
|
|
|
if (userSandboxes.length >= 8) {
|
2024-05-26 18:37:36 -07:00
|
|
|
return new Response("You reached the maximum # of sandboxes.", {
|
|
|
|
status: 400,
|
|
|
|
})
|
2024-05-05 12:55:34 -07:00
|
|
|
}
|
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
const sb = await db
|
|
|
|
.insert(sandbox)
|
|
|
|
.values({ type, name, userId, visibility, createdAt: new Date() })
|
|
|
|
.returning()
|
|
|
|
.get()
|
|
|
|
|
|
|
|
const initStorageRequest = new Request(
|
|
|
|
`${env.STORAGE_WORKER_URL}/api/init`,
|
|
|
|
{
|
|
|
|
method: "POST",
|
|
|
|
body: JSON.stringify({ sandboxId: sb.id, type }),
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
Authorization: `${env.KEY}`,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
)
|
2024-05-06 21:29:25 -07:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
await env.STORAGE.fetch(initStorageRequest)
|
2024-04-27 16:22:35 -04:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
return new Response(sb.id, { status: 200 })
|
2024-04-27 00:28:00 -04:00
|
|
|
} else {
|
2024-05-26 18:37:36 -07:00
|
|
|
return methodNotAllowed
|
2024-04-27 00:28:00 -04:00
|
|
|
}
|
2024-05-01 02:22:02 -04:00
|
|
|
} else if (path === "/api/sandbox/share") {
|
2024-05-01 02:49:25 -04:00
|
|
|
if (method === "GET") {
|
2024-05-26 18:37:36 -07:00
|
|
|
const params = url.searchParams
|
2024-05-01 02:49:25 -04:00
|
|
|
if (params.has("id")) {
|
2024-05-26 18:37:36 -07:00
|
|
|
const id = params.get("id") as string
|
2024-05-01 02:49:25 -04:00
|
|
|
const res = await db.query.usersToSandboxes.findMany({
|
|
|
|
where: (uts, { eq }) => eq(uts.userId, id),
|
2024-05-26 18:37:36 -07:00
|
|
|
})
|
2024-05-01 02:49:25 -04:00
|
|
|
|
|
|
|
const owners = await Promise.all(
|
|
|
|
res.map(async (r) => {
|
|
|
|
const sb = await db.query.sandbox.findFirst({
|
|
|
|
where: (sandbox, { eq }) => eq(sandbox.id, r.sandboxId),
|
|
|
|
with: {
|
|
|
|
author: true,
|
|
|
|
},
|
2024-05-26 18:37:36 -07:00
|
|
|
})
|
|
|
|
if (!sb) return
|
|
|
|
return {
|
|
|
|
id: sb.id,
|
|
|
|
name: sb.name,
|
|
|
|
type: sb.type,
|
|
|
|
author: sb.author.name,
|
|
|
|
sharedOn: r.sharedOn,
|
|
|
|
}
|
2024-05-01 02:49:25 -04:00
|
|
|
})
|
2024-05-26 18:37:36 -07:00
|
|
|
)
|
2024-05-01 02:49:25 -04:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
return json(owners ?? {})
|
|
|
|
} else return invalidRequest
|
2024-05-01 02:49:25 -04:00
|
|
|
} else if (method === "POST") {
|
2024-05-01 02:22:02 -04:00
|
|
|
const shareSchema = z.object({
|
|
|
|
sandboxId: z.string(),
|
|
|
|
email: z.string(),
|
2024-05-26 18:37:36 -07:00
|
|
|
})
|
2024-05-01 01:53:49 -04:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
const body = await request.json()
|
|
|
|
const { sandboxId, email } = shareSchema.parse(body)
|
2024-05-01 02:22:02 -04:00
|
|
|
|
|
|
|
const user = await db.query.user.findFirst({
|
|
|
|
where: (user, { eq }) => eq(user.email, email),
|
|
|
|
with: {
|
2024-05-01 08:20:08 -04:00
|
|
|
sandbox: true,
|
2024-05-01 02:22:02 -04:00
|
|
|
usersToSandboxes: true,
|
|
|
|
},
|
2024-05-26 18:37:36 -07:00
|
|
|
})
|
2024-05-01 02:22:02 -04:00
|
|
|
|
|
|
|
if (!user) {
|
2024-05-26 18:37:36 -07:00
|
|
|
return new Response("No user associated with email.", { status: 400 })
|
2024-05-01 02:22:02 -04:00
|
|
|
}
|
|
|
|
|
2024-05-01 08:20:08 -04:00
|
|
|
if (user.sandbox.find((sb) => sb.id === sandboxId)) {
|
2024-05-26 18:37:36 -07:00
|
|
|
return new Response("Cannot share with yourself!", { status: 400 })
|
2024-05-01 08:20:08 -04:00
|
|
|
}
|
|
|
|
|
2024-05-01 02:22:02 -04:00
|
|
|
if (user.usersToSandboxes.find((uts) => uts.sandboxId === sandboxId)) {
|
2024-05-26 18:37:36 -07:00
|
|
|
return new Response("User already has access.", { status: 400 })
|
2024-05-01 02:22:02 -04:00
|
|
|
}
|
2024-05-01 01:29:16 -04:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
await db
|
|
|
|
.insert(usersToSandboxes)
|
|
|
|
.values({ userId: user.id, sandboxId, sharedOn: new Date() })
|
|
|
|
.get()
|
2024-05-01 01:29:16 -04:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
return success
|
2024-05-01 02:22:02 -04:00
|
|
|
} else if (method === "DELETE") {
|
|
|
|
const deleteShareSchema = z.object({
|
|
|
|
sandboxId: z.string(),
|
|
|
|
userId: z.string(),
|
2024-05-26 18:37:36 -07:00
|
|
|
})
|
|
|
|
|
|
|
|
const body = await request.json()
|
|
|
|
const { sandboxId, userId } = deleteShareSchema.parse(body)
|
|
|
|
|
|
|
|
await db
|
|
|
|
.delete(usersToSandboxes)
|
|
|
|
.where(
|
|
|
|
and(
|
|
|
|
eq(usersToSandboxes.userId, userId),
|
|
|
|
eq(usersToSandboxes.sandboxId, sandboxId)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
return success
|
|
|
|
} else return methodNotAllowed
|
2024-05-05 16:51:30 -07:00
|
|
|
} else if (path === "/api/sandbox/generate" && method === "POST") {
|
|
|
|
const generateSchema = z.object({
|
|
|
|
userId: z.string(),
|
2024-05-26 18:37:36 -07:00
|
|
|
})
|
|
|
|
const body = await request.json()
|
|
|
|
const { userId } = generateSchema.parse(body)
|
2024-05-05 16:51:30 -07:00
|
|
|
|
|
|
|
const dbUser = await db.query.user.findFirst({
|
|
|
|
where: (user, { eq }) => eq(user.id, userId),
|
2024-05-26 18:37:36 -07:00
|
|
|
})
|
2024-05-05 16:51:30 -07:00
|
|
|
if (!dbUser) {
|
2024-05-26 18:37:36 -07:00
|
|
|
return new Response("User not found.", { status: 400 })
|
2024-05-05 16:51:30 -07:00
|
|
|
}
|
2024-05-17 22:25:55 -07:00
|
|
|
if (dbUser.generations !== null && dbUser.generations >= 10) {
|
2024-05-26 18:37:36 -07:00
|
|
|
return new Response("You reached the maximum # of generations.", {
|
|
|
|
status: 400,
|
|
|
|
})
|
2024-05-05 16:51:30 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
await db
|
|
|
|
.update(user)
|
|
|
|
.set({ generations: sql`${user.generations} + 1` })
|
|
|
|
.where(eq(user.id, userId))
|
2024-05-26 18:37:36 -07:00
|
|
|
.get()
|
2024-05-05 16:51:30 -07:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
return success
|
2024-04-24 02:22:06 -04:00
|
|
|
} else if (path === "/api/user") {
|
2024-04-18 15:25:20 -04:00
|
|
|
if (method === "GET") {
|
2024-05-26 18:37:36 -07:00
|
|
|
const params = url.searchParams
|
2024-04-18 15:25:20 -04:00
|
|
|
|
|
|
|
if (params.has("id")) {
|
2024-05-26 18:37:36 -07:00
|
|
|
const id = params.get("id") as string
|
2024-04-18 00:13:40 -04:00
|
|
|
const res = await db.query.user.findFirst({
|
|
|
|
where: (user, { eq }) => eq(user.id, id),
|
|
|
|
with: {
|
2024-05-25 20:13:31 -07:00
|
|
|
sandbox: {
|
|
|
|
orderBy: (sandbox, { desc }) => [desc(sandbox.createdAt)],
|
|
|
|
},
|
2024-05-01 01:29:16 -04:00
|
|
|
usersToSandboxes: true,
|
2024-04-18 00:13:40 -04:00
|
|
|
},
|
2024-05-26 18:37:36 -07:00
|
|
|
})
|
|
|
|
return json(res ?? {})
|
2024-04-17 22:55:02 -04:00
|
|
|
} else {
|
2024-05-26 18:37:36 -07:00
|
|
|
const res = await db.select().from(user).all()
|
|
|
|
return json(res ?? {})
|
2024-04-17 22:55:02 -04:00
|
|
|
}
|
2024-04-18 15:25:20 -04:00
|
|
|
} else if (method === "POST") {
|
|
|
|
const userSchema = z.object({
|
|
|
|
id: z.string(),
|
|
|
|
name: z.string(),
|
|
|
|
email: z.string().email(),
|
2024-11-10 21:52:52 -05:00
|
|
|
username: z.string(),
|
|
|
|
avatarUrl: z.string().optional(),
|
|
|
|
createdAt: z.string().optional(),
|
|
|
|
generations: z.number().optional(),
|
2024-05-26 18:37:36 -07:00
|
|
|
})
|
2024-04-18 15:25:20 -04:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
const body = await request.json()
|
2024-11-10 21:52:52 -05:00
|
|
|
const { id, name, email, username, avatarUrl, createdAt, generations } = userSchema.parse(body)
|
2024-04-18 15:25:20 -04:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
const res = await db
|
|
|
|
.insert(user)
|
2024-11-10 21:52:52 -05:00
|
|
|
.values({
|
|
|
|
id,
|
|
|
|
name,
|
|
|
|
email,
|
|
|
|
username,
|
|
|
|
avatarUrl,
|
|
|
|
createdAt: createdAt ? new Date(createdAt) : new Date(),
|
|
|
|
generations,
|
|
|
|
})
|
2024-05-26 18:37:36 -07:00
|
|
|
.returning()
|
|
|
|
.get()
|
|
|
|
return json({ res })
|
2024-05-22 19:35:19 -07:00
|
|
|
} else if (method === "DELETE") {
|
2024-05-26 18:37:36 -07:00
|
|
|
const params = url.searchParams
|
2024-05-22 19:35:19 -07:00
|
|
|
if (params.has("id")) {
|
2024-05-26 18:37:36 -07:00
|
|
|
const id = params.get("id") as string
|
|
|
|
await db.delete(user).where(eq(user.id, id))
|
|
|
|
return success
|
|
|
|
} else return invalidRequest
|
2024-04-17 21:24:57 -04:00
|
|
|
} else {
|
2024-05-26 18:37:36 -07:00
|
|
|
return methodNotAllowed
|
2024-04-17 21:24:57 -04:00
|
|
|
}
|
2024-11-10 21:52:52 -05:00
|
|
|
} else if (path === "/api/user/check-username") {
|
|
|
|
if (method === "GET") {
|
|
|
|
const params = url.searchParams
|
|
|
|
const username = params.get("username")
|
|
|
|
|
|
|
|
if (!username) return invalidRequest
|
|
|
|
|
|
|
|
const exists = await db.query.user.findFirst({
|
|
|
|
where: (user, { eq }) => eq(user.username, username)
|
|
|
|
})
|
|
|
|
|
|
|
|
return json({ exists: !!exists })
|
|
|
|
}
|
|
|
|
return methodNotAllowed
|
2024-05-26 18:37:36 -07:00
|
|
|
} else return notFound
|
2024-04-17 21:24:57 -04:00
|
|
|
},
|
2024-05-26 18:37:36 -07:00
|
|
|
}
|