chore: refactor into RemoteFileStorage
This commit is contained in:
parent
fe0adb8e84
commit
ae38a77759
@ -1,14 +1,6 @@
|
|||||||
import { FilesystemEvent, Sandbox, WatchHandle } from "e2b"
|
import { FilesystemEvent, Sandbox, WatchHandle } from "e2b"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import {
|
import RemoteFileStorage from "./RemoteFileStorage"
|
||||||
createFile,
|
|
||||||
deleteFile,
|
|
||||||
getFolder,
|
|
||||||
getProjectSize,
|
|
||||||
getSandboxFiles,
|
|
||||||
renameFile,
|
|
||||||
saveFile,
|
|
||||||
} from "./fileoperations"
|
|
||||||
import { MAX_BODY_SIZE } from "./ratelimit"
|
import { MAX_BODY_SIZE } from "./ratelimit"
|
||||||
import { TFile, TFileData, TFolder } from "./types"
|
import { TFile, TFileData, TFolder } from "./types"
|
||||||
|
|
||||||
@ -18,6 +10,65 @@ export type SandboxFiles = {
|
|||||||
fileData: TFileData[]
|
fileData: TFileData[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const processFiles = async (paths: string[], id: string) => {
|
||||||
|
const root: TFolder = { id: "/", type: "folder", name: "/", children: [] }
|
||||||
|
const fileData: TFileData[] = []
|
||||||
|
|
||||||
|
paths.forEach((path) => {
|
||||||
|
const allParts = path.split("/")
|
||||||
|
if (allParts[1] !== id) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = allParts.slice(2)
|
||||||
|
let current: TFolder = root
|
||||||
|
|
||||||
|
for (let i = 0; i < parts.length; i++) {
|
||||||
|
const part = parts[i]
|
||||||
|
const isFile = i === parts.length - 1 && part.length
|
||||||
|
const existing = current.children.find((child) => child.name === part)
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
if (!isFile) {
|
||||||
|
current = existing as TFolder
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isFile) {
|
||||||
|
const file: TFile = { id: path, type: "file", name: part }
|
||||||
|
current.children.push(file)
|
||||||
|
fileData.push({ id: path, data: "" })
|
||||||
|
} else {
|
||||||
|
const folder: TFolder = {
|
||||||
|
// id: path, // todo: wrong id. for example, folder "src" ID is: projects/a7vgttfqbgy403ratp7du3ln/src/App.css
|
||||||
|
id: `projects/${id}/${parts.slice(0, i + 1).join("/")}`,
|
||||||
|
type: "folder",
|
||||||
|
name: part,
|
||||||
|
children: [],
|
||||||
|
}
|
||||||
|
current.children.push(folder)
|
||||||
|
current = folder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
fileData.map(async (file) => {
|
||||||
|
const data = await RemoteFileStorage.fetchFileContent(file.id)
|
||||||
|
file.data = data
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
files: root.children,
|
||||||
|
fileData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSandboxFiles = async (id: string) => {
|
||||||
|
return await processFiles(await RemoteFileStorage.getSandboxPaths(id), id)
|
||||||
|
}
|
||||||
|
|
||||||
// FileManager class to handle file operations in a sandbox
|
// FileManager class to handle file operations in a sandbox
|
||||||
export class FileManager {
|
export class FileManager {
|
||||||
private sandboxId: string
|
private sandboxId: string
|
||||||
@ -285,7 +336,7 @@ export class FileManager {
|
|||||||
|
|
||||||
// Get folder content
|
// Get folder content
|
||||||
async getFolder(folderId: string): Promise<string[]> {
|
async getFolder(folderId: string): Promise<string[]> {
|
||||||
return getFolder(folderId)
|
return RemoteFileStorage.getFolder(folderId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save file content
|
// Save file content
|
||||||
@ -295,7 +346,7 @@ export class FileManager {
|
|||||||
if (Buffer.byteLength(body, "utf-8") > MAX_BODY_SIZE) {
|
if (Buffer.byteLength(body, "utf-8") > MAX_BODY_SIZE) {
|
||||||
throw new Error("File size too large. Please reduce the file size.")
|
throw new Error("File size too large. Please reduce the file size.")
|
||||||
}
|
}
|
||||||
await saveFile(fileId, body)
|
await RemoteFileStorage.saveFile(fileId, body)
|
||||||
const file = this.sandboxFiles.fileData.find((f) => f.id === fileId)
|
const file = this.sandboxFiles.fileData.find((f) => f.id === fileId)
|
||||||
if (!file) return
|
if (!file) return
|
||||||
file.data = body
|
file.data = body
|
||||||
@ -323,7 +374,7 @@ export class FileManager {
|
|||||||
fileData.id = newFileId
|
fileData.id = newFileId
|
||||||
file.id = newFileId
|
file.id = newFileId
|
||||||
|
|
||||||
await renameFile(fileId, newFileId, fileData.data)
|
await RemoteFileStorage.renameFile(fileId, newFileId, fileData.data)
|
||||||
const newFiles = await getSandboxFiles(this.sandboxId)
|
const newFiles = await getSandboxFiles(this.sandboxId)
|
||||||
return newFiles.files
|
return newFiles.files
|
||||||
}
|
}
|
||||||
@ -346,7 +397,7 @@ export class FileManager {
|
|||||||
|
|
||||||
// Create a new file
|
// Create a new file
|
||||||
async createFile(name: string): Promise<boolean> {
|
async createFile(name: string): Promise<boolean> {
|
||||||
const size: number = await getProjectSize(this.sandboxId)
|
const size: number = await RemoteFileStorage.getProjectSize(this.sandboxId)
|
||||||
if (size > 200 * 1024 * 1024) {
|
if (size > 200 * 1024 * 1024) {
|
||||||
throw new Error("Project size exceeded. Please delete some files.")
|
throw new Error("Project size exceeded. Please delete some files.")
|
||||||
}
|
}
|
||||||
@ -367,7 +418,7 @@ export class FileManager {
|
|||||||
data: "",
|
data: "",
|
||||||
})
|
})
|
||||||
|
|
||||||
await createFile(id)
|
await RemoteFileStorage.createFile(id)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -389,7 +440,7 @@ export class FileManager {
|
|||||||
|
|
||||||
await this.moveFileInContainer(fileId, newFileId)
|
await this.moveFileInContainer(fileId, newFileId)
|
||||||
await this.fixPermissions()
|
await this.fixPermissions()
|
||||||
await renameFile(fileId, newFileId, fileData.data)
|
await RemoteFileStorage.renameFile(fileId, newFileId, fileData.data)
|
||||||
|
|
||||||
fileData.id = newFileId
|
fileData.id = newFileId
|
||||||
file.id = newFileId
|
file.id = newFileId
|
||||||
@ -405,7 +456,7 @@ export class FileManager {
|
|||||||
(f) => f.id !== fileId
|
(f) => f.id !== fileId
|
||||||
)
|
)
|
||||||
|
|
||||||
await deleteFile(fileId)
|
await RemoteFileStorage.deleteFile(fileId)
|
||||||
|
|
||||||
const newFiles = await getSandboxFiles(this.sandboxId)
|
const newFiles = await getSandboxFiles(this.sandboxId)
|
||||||
return newFiles.files
|
return newFiles.files
|
||||||
@ -413,7 +464,7 @@ export class FileManager {
|
|||||||
|
|
||||||
// Delete a folder
|
// Delete a folder
|
||||||
async deleteFolder(folderId: string): Promise<(TFolder | TFile)[]> {
|
async deleteFolder(folderId: string): Promise<(TFolder | TFile)[]> {
|
||||||
const files = await getFolder(folderId)
|
const files = await RemoteFileStorage.getFolder(folderId)
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
files.map(async (file) => {
|
files.map(async (file) => {
|
||||||
@ -421,7 +472,7 @@ export class FileManager {
|
|||||||
this.sandboxFiles.fileData = this.sandboxFiles.fileData.filter(
|
this.sandboxFiles.fileData = this.sandboxFiles.fileData.filter(
|
||||||
(f) => f.id !== file
|
(f) => f.id !== file
|
||||||
)
|
)
|
||||||
await deleteFile(file)
|
await RemoteFileStorage.deleteFile(file)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -437,4 +488,4 @@ export class FileManager {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
117
backend/server/src/RemoteFileStorage.ts
Normal file
117
backend/server/src/RemoteFileStorage.ts
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import * as dotenv from "dotenv"
|
||||||
|
import { R2Files } from "./types"
|
||||||
|
|
||||||
|
dotenv.config()
|
||||||
|
|
||||||
|
export const RemoteFileStorage = {
|
||||||
|
getSandboxPaths: async (id: string) => {
|
||||||
|
const res = await fetch(
|
||||||
|
`${process.env.STORAGE_WORKER_URL}/api?sandboxId=${id}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `${process.env.WORKERS_KEY}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const data: R2Files = await res.json()
|
||||||
|
|
||||||
|
return data.objects.map((obj) => obj.key)
|
||||||
|
},
|
||||||
|
|
||||||
|
getFolder: async (folderId: string) => {
|
||||||
|
const res = await fetch(
|
||||||
|
`${process.env.STORAGE_WORKER_URL}/api?folderId=${folderId}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `${process.env.WORKERS_KEY}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const data: R2Files = await res.json()
|
||||||
|
|
||||||
|
return data.objects.map((obj) => obj.key)
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchFileContent: async (fileId: string): Promise<string> => {
|
||||||
|
try {
|
||||||
|
const fileRes = await fetch(
|
||||||
|
`${process.env.STORAGE_WORKER_URL}/api?fileId=${fileId}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `${process.env.WORKERS_KEY}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return await fileRes.text()
|
||||||
|
} catch (error) {
|
||||||
|
console.error("ERROR fetching file:", error)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
createFile: async (fileId: string) => {
|
||||||
|
const res = await fetch(`${process.env.STORAGE_WORKER_URL}/api`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `${process.env.WORKERS_KEY}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ fileId }),
|
||||||
|
})
|
||||||
|
return res.ok
|
||||||
|
},
|
||||||
|
|
||||||
|
renameFile: async (
|
||||||
|
fileId: string,
|
||||||
|
newFileId: string,
|
||||||
|
data: string
|
||||||
|
) => {
|
||||||
|
const res = await fetch(`${process.env.STORAGE_WORKER_URL}/api/rename`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `${process.env.WORKERS_KEY}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ fileId, newFileId, data }),
|
||||||
|
})
|
||||||
|
return res.ok
|
||||||
|
},
|
||||||
|
|
||||||
|
saveFile: async (fileId: string, data: string) => {
|
||||||
|
const res = await fetch(`${process.env.STORAGE_WORKER_URL}/api/save`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `${process.env.WORKERS_KEY}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ fileId, data }),
|
||||||
|
})
|
||||||
|
return res.ok
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteFile: async (fileId: string) => {
|
||||||
|
const res = await fetch(`${process.env.STORAGE_WORKER_URL}/api`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `${process.env.WORKERS_KEY}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ fileId }),
|
||||||
|
})
|
||||||
|
return res.ok
|
||||||
|
},
|
||||||
|
|
||||||
|
getProjectSize: async (id: string) => {
|
||||||
|
const res = await fetch(
|
||||||
|
`${process.env.STORAGE_WORKER_URL}/api/size?sandboxId=${id}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `${process.env.WORKERS_KEY}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return (await res.json()).size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RemoteFileStorage
|
@ -1,170 +0,0 @@
|
|||||||
import * as dotenv from "dotenv"
|
|
||||||
import { R2Files, TFile, TFileData, TFolder } from "./types"
|
|
||||||
|
|
||||||
dotenv.config()
|
|
||||||
|
|
||||||
export const getSandboxFiles = async (id: string) => {
|
|
||||||
const res = await fetch(
|
|
||||||
`${process.env.STORAGE_WORKER_URL}/api?sandboxId=${id}`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `${process.env.WORKERS_KEY}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
const data: R2Files = await res.json()
|
|
||||||
|
|
||||||
const paths = data.objects.map((obj) => obj.key)
|
|
||||||
const processedFiles = await processFiles(paths, id)
|
|
||||||
return processedFiles
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getFolder = async (folderId: string) => {
|
|
||||||
const res = await fetch(
|
|
||||||
`${process.env.STORAGE_WORKER_URL}/api?folderId=${folderId}`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `${process.env.WORKERS_KEY}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
const data: R2Files = await res.json()
|
|
||||||
|
|
||||||
return data.objects.map((obj) => obj.key)
|
|
||||||
}
|
|
||||||
|
|
||||||
const processFiles = async (paths: string[], id: string) => {
|
|
||||||
const root: TFolder = { id: "/", type: "folder", name: "/", children: [] }
|
|
||||||
const fileData: TFileData[] = []
|
|
||||||
|
|
||||||
paths.forEach((path) => {
|
|
||||||
const allParts = path.split("/")
|
|
||||||
if (allParts[1] !== id) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const parts = allParts.slice(2)
|
|
||||||
let current: TFolder = root
|
|
||||||
|
|
||||||
for (let i = 0; i < parts.length; i++) {
|
|
||||||
const part = parts[i]
|
|
||||||
const isFile = i === parts.length - 1 && part.length
|
|
||||||
const existing = current.children.find((child) => child.name === part)
|
|
||||||
|
|
||||||
if (existing) {
|
|
||||||
if (!isFile) {
|
|
||||||
current = existing as TFolder
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isFile) {
|
|
||||||
const file: TFile = { id: path, type: "file", name: part }
|
|
||||||
current.children.push(file)
|
|
||||||
fileData.push({ id: path, data: "" })
|
|
||||||
} else {
|
|
||||||
const folder: TFolder = {
|
|
||||||
// id: path, // todo: wrong id. for example, folder "src" ID is: projects/a7vgttfqbgy403ratp7du3ln/src/App.css
|
|
||||||
id: `projects/${id}/${parts.slice(0, i + 1).join("/")}`,
|
|
||||||
type: "folder",
|
|
||||||
name: part,
|
|
||||||
children: [],
|
|
||||||
}
|
|
||||||
current.children.push(folder)
|
|
||||||
current = folder
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
fileData.map(async (file) => {
|
|
||||||
const data = await fetchFileContent(file.id)
|
|
||||||
file.data = data
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
files: root.children,
|
|
||||||
fileData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchFileContent = async (fileId: string): Promise<string> => {
|
|
||||||
try {
|
|
||||||
const fileRes = await fetch(
|
|
||||||
`${process.env.STORAGE_WORKER_URL}/api?fileId=${fileId}`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `${process.env.WORKERS_KEY}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return await fileRes.text()
|
|
||||||
} catch (error) {
|
|
||||||
console.error("ERROR fetching file:", error)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createFile = async (fileId: string) => {
|
|
||||||
const res = await fetch(`${process.env.STORAGE_WORKER_URL}/api`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `${process.env.WORKERS_KEY}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ fileId }),
|
|
||||||
})
|
|
||||||
return res.ok
|
|
||||||
}
|
|
||||||
|
|
||||||
export const renameFile = async (
|
|
||||||
fileId: string,
|
|
||||||
newFileId: string,
|
|
||||||
data: string
|
|
||||||
) => {
|
|
||||||
const res = await fetch(`${process.env.STORAGE_WORKER_URL}/api/rename`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `${process.env.WORKERS_KEY}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ fileId, newFileId, data }),
|
|
||||||
})
|
|
||||||
return res.ok
|
|
||||||
}
|
|
||||||
|
|
||||||
export const saveFile = async (fileId: string, data: string) => {
|
|
||||||
const res = await fetch(`${process.env.STORAGE_WORKER_URL}/api/save`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `${process.env.WORKERS_KEY}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ fileId, data }),
|
|
||||||
})
|
|
||||||
return res.ok
|
|
||||||
}
|
|
||||||
|
|
||||||
export const deleteFile = async (fileId: string) => {
|
|
||||||
const res = await fetch(`${process.env.STORAGE_WORKER_URL}/api`, {
|
|
||||||
method: "DELETE",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `${process.env.WORKERS_KEY}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ fileId }),
|
|
||||||
})
|
|
||||||
return res.ok
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getProjectSize = async (id: string) => {
|
|
||||||
const res = await fetch(
|
|
||||||
`${process.env.STORAGE_WORKER_URL}/api/size?sandboxId=${id}`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `${process.env.WORKERS_KEY}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return (await res.json()).size
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user