2024-05-26 18:37:36 -07:00
|
|
|
import { z } from "zod"
|
2024-09-15 10:29:23 -07:00
|
|
|
import pLimit from 'p-limit';
|
2024-04-23 01:53:37 -04:00
|
|
|
|
|
|
|
export interface Env {
|
2024-05-26 18:37:36 -07:00
|
|
|
R2: R2Bucket
|
2024-09-15 10:29:23 -07:00
|
|
|
Templates: R2Bucket
|
2024-05-26 18:37:36 -07:00
|
|
|
KEY: string
|
2024-04-23 01:53:37 -04:00
|
|
|
}
|
2024-04-22 00:30:50 -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 })
|
|
|
|
|
|
|
|
if (request.headers.get("Authorization") !== env.KEY) {
|
|
|
|
return new Response("Unauthorized", { status: 401 })
|
|
|
|
}
|
2024-04-23 17:46:51 -04:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
const url = new URL(request.url)
|
|
|
|
const path = url.pathname
|
|
|
|
const method = request.method
|
2024-04-23 17:46:51 -04:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
if (path === "/api/project" && method === "DELETE") {
|
2024-05-23 23:05:01 -07:00
|
|
|
const deleteSchema = z.object({
|
|
|
|
sandboxId: z.string(),
|
2024-05-26 18:37:36 -07:00
|
|
|
})
|
2024-05-23 23:05:01 -07:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
const body = await request.json()
|
|
|
|
const { sandboxId } = deleteSchema.parse(body)
|
2024-05-23 23:05:01 -07:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
const res = await env.R2.list({ prefix: "projects/" + sandboxId })
|
2024-05-23 23:05:01 -07:00
|
|
|
// delete all files
|
|
|
|
await Promise.all(
|
|
|
|
res.objects.map(async (file) => {
|
2024-05-26 18:37:36 -07:00
|
|
|
await env.R2.delete(file.key)
|
2024-05-23 23:05:01 -07:00
|
|
|
})
|
2024-05-26 18:37:36 -07:00
|
|
|
)
|
2024-05-23 23:05:01 -07:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
return success
|
|
|
|
} else if (path === "/api/size" && method === "GET") {
|
|
|
|
const params = url.searchParams
|
|
|
|
const sandboxId = params.get("sandboxId")
|
2024-05-13 22:43:56 -07:00
|
|
|
|
|
|
|
if (sandboxId) {
|
2024-05-26 18:37:36 -07:00
|
|
|
const res = await env.R2.list({ prefix: `projects/${sandboxId}` })
|
2024-05-13 22:43:56 -07:00
|
|
|
|
|
|
|
// sum up the size of all files
|
2024-05-26 18:37:36 -07:00
|
|
|
let size = 0
|
2024-05-13 22:43:56 -07:00
|
|
|
for (const file of res.objects) {
|
2024-05-26 18:37:36 -07:00
|
|
|
size += file.size
|
2024-05-13 22:43:56 -07:00
|
|
|
}
|
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
return new Response(JSON.stringify({ size }), { status: 200 })
|
|
|
|
} else return invalidRequest
|
|
|
|
} else if (path === "/api") {
|
|
|
|
if (method === "GET") {
|
|
|
|
const params = url.searchParams
|
|
|
|
const sandboxId = params.get("sandboxId")
|
|
|
|
const folderId = params.get("folderId")
|
|
|
|
const fileId = params.get("fileId")
|
2024-04-26 21:57:30 -04:00
|
|
|
|
|
|
|
if (sandboxId) {
|
2024-05-26 18:37:36 -07:00
|
|
|
const res = await env.R2.list({ prefix: `projects/${sandboxId}` })
|
|
|
|
return new Response(JSON.stringify(res), { status: 200 })
|
2024-05-13 22:43:56 -07:00
|
|
|
} else if (folderId) {
|
2024-05-26 18:37:36 -07:00
|
|
|
const res = await env.R2.list({ prefix: folderId })
|
|
|
|
return new Response(JSON.stringify(res), { status: 200 })
|
2024-04-26 21:57:30 -04:00
|
|
|
} else if (fileId) {
|
2024-05-26 18:37:36 -07:00
|
|
|
const obj = await env.R2.get(fileId)
|
2024-04-26 21:57:30 -04:00
|
|
|
if (obj === null) {
|
2024-05-26 18:37:36 -07:00
|
|
|
return new Response(`${fileId} not found`, { status: 404 })
|
2024-04-26 21:57:30 -04:00
|
|
|
}
|
2024-05-26 18:37:36 -07:00
|
|
|
const headers = new Headers()
|
|
|
|
headers.set("etag", obj.httpEtag)
|
|
|
|
obj.writeHttpMetadata(headers)
|
2024-04-26 21:57:30 -04:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
const text = await obj.text()
|
2024-04-26 21:57:30 -04:00
|
|
|
|
|
|
|
return new Response(text, {
|
|
|
|
headers,
|
2024-05-26 18:37:36 -07:00
|
|
|
})
|
|
|
|
} else return invalidRequest
|
|
|
|
} else if (method === "POST") {
|
2024-04-29 00:50:25 -04:00
|
|
|
const createSchema = z.object({
|
|
|
|
fileId: z.string(),
|
2024-05-26 18:37:36 -07:00
|
|
|
})
|
2024-04-29 00:50:25 -04:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
const body = await request.json()
|
|
|
|
const { fileId } = createSchema.parse(body)
|
2024-04-29 00:50:25 -04:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
await env.R2.put(fileId, "")
|
2024-04-29 00:50:25 -04:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
return success
|
|
|
|
} else if (method === "DELETE") {
|
2024-04-30 01:56:43 -04:00
|
|
|
const deleteSchema = z.object({
|
|
|
|
fileId: z.string(),
|
2024-05-26 18:37:36 -07:00
|
|
|
})
|
2024-04-30 01:56:43 -04:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
const body = await request.json()
|
|
|
|
const { fileId } = deleteSchema.parse(body)
|
2024-04-30 01:56:43 -04:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
await env.R2.delete(fileId)
|
2024-04-30 01:56:43 -04:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
return success
|
|
|
|
} else return methodNotAllowed
|
|
|
|
} else if (path === "/api/rename" && method === "POST") {
|
2024-04-27 14:23:09 -04:00
|
|
|
const renameSchema = z.object({
|
|
|
|
fileId: z.string(),
|
|
|
|
newFileId: z.string(),
|
|
|
|
data: z.string(),
|
2024-05-26 18:37:36 -07:00
|
|
|
})
|
2024-04-27 14:23:09 -04:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
const body = await request.json()
|
|
|
|
const { fileId, newFileId, data } = renameSchema.parse(body)
|
2024-04-27 14:23:09 -04:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
await env.R2.delete(fileId)
|
|
|
|
await env.R2.put(newFileId, data)
|
2024-04-27 14:23:09 -04:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
return success
|
|
|
|
} else if (path === "/api/save" && method === "POST") {
|
2024-04-27 19:12:25 -04:00
|
|
|
const renameSchema = z.object({
|
|
|
|
fileId: z.string(),
|
|
|
|
data: z.string(),
|
2024-05-26 18:37:36 -07:00
|
|
|
})
|
2024-04-27 19:12:25 -04:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
const body = await request.json()
|
|
|
|
const { fileId, data } = renameSchema.parse(body)
|
2024-04-27 19:12:25 -04:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
await env.R2.put(fileId, data)
|
2024-04-27 19:12:25 -04:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
return success
|
|
|
|
} else if (path === "/api/init" && method === "POST") {
|
2024-04-23 17:46:51 -04:00
|
|
|
const initSchema = z.object({
|
|
|
|
sandboxId: z.string(),
|
2024-08-18 06:52:41 -07:00
|
|
|
type: z.string(),
|
2024-05-26 18:37:36 -07:00
|
|
|
})
|
2024-04-23 17:46:51 -04:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
const body = await request.json()
|
|
|
|
const { sandboxId, type } = initSchema.parse(body)
|
2024-04-23 17:46:51 -04:00
|
|
|
|
2024-08-18 06:56:22 -07:00
|
|
|
console.log(`Copying template: ${type}`);
|
2024-04-23 17:46:51 -04:00
|
|
|
|
2024-08-18 06:56:22 -07:00
|
|
|
// List all objects under the directory
|
2024-09-15 10:29:23 -07:00
|
|
|
const { objects } = await env.Templates.list({ prefix: type });
|
|
|
|
|
|
|
|
// Copy each object to the new directory with a 5 concurrency limit
|
|
|
|
const limit = pLimit(5);
|
|
|
|
await Promise.all(objects.map(({ key }) =>
|
|
|
|
limit(async () => {
|
|
|
|
const destinationKey = key.replace(type, `projects/${sandboxId}`);
|
|
|
|
const fileBody = await env.Templates.get(key).then(res => res?.body ?? "");
|
2024-08-18 06:56:22 -07:00
|
|
|
await env.R2.put(destinationKey, fileBody);
|
2024-09-15 10:29:23 -07:00
|
|
|
})
|
|
|
|
));
|
2024-04-23 17:46:51 -04:00
|
|
|
|
2024-05-26 18:37:36 -07:00
|
|
|
return success
|
2024-04-23 17:46:51 -04:00
|
|
|
} else {
|
2024-05-26 18:37:36 -07:00
|
|
|
return notFound
|
2024-04-23 01:53:37 -04:00
|
|
|
}
|
2024-04-22 00:30:50 -04:00
|
|
|
},
|
2024-05-26 18:37:36 -07:00
|
|
|
}
|