Spaces:
Running
Running
| import { NextResponse } from "next/server"; | |
| import { RepoDesignation, createRepo, uploadFiles } from "@huggingface/hub"; | |
| import { auth } from "@/lib/auth"; | |
| import { | |
| COLORS, | |
| EMOJIS_FOR_SPACE, | |
| injectDeepSiteBadge, | |
| isIndexPage, | |
| } from "@/lib/utils"; | |
| // todo: catch error while publishing project, and return the error to the user | |
| // if space has been created, but can't push, try again or catch well the error and return the error to the user | |
| export async function POST(request: Request) { | |
| const session = await auth(); | |
| if (!session) { | |
| return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); | |
| } | |
| const token = session.accessToken; | |
| const body = await request.json(); | |
| const { projectTitle, files, prompt } = body; | |
| if (!files) { | |
| return NextResponse.json( | |
| { error: "Project title and files are required" }, | |
| { status: 400 } | |
| ); | |
| } | |
| const title = | |
| projectTitle || projectTitle !== "" ? projectTitle : "DeepSite Project"; | |
| let formattedTitle = title | |
| .toLowerCase() | |
| .replace(/[^a-z0-9]+/g, "-") | |
| .split("-") | |
| .filter(Boolean) | |
| .join("-") | |
| .slice(0, 75); | |
| formattedTitle = | |
| formattedTitle + "-" + Math.random().toString(36).substring(2, 7); | |
| const repo: RepoDesignation = { | |
| type: "space", | |
| name: session.user?.username + "/" + formattedTitle, | |
| }; | |
| // Escape YAML values to prevent injection attacks | |
| const escapeYamlValue = (value: string): string => { | |
| if (/[:|>]|^[-*#]|^\s|['"]/.test(value) || value.includes("\n")) { | |
| return `"${value.replace(/"/g, '\\"')}"`; | |
| } | |
| return value; | |
| }; | |
| // Escape markdown headers to prevent injection | |
| const escapeMarkdownHeader = (value: string): string => { | |
| return value.replace(/^#+\s*/g, "").replace(/\n/g, " "); | |
| }; | |
| const colorFrom = COLORS[Math.floor(Math.random() * COLORS.length)]; | |
| const colorTo = COLORS[Math.floor(Math.random() * COLORS.length)]; | |
| const emoji = | |
| EMOJIS_FOR_SPACE[Math.floor(Math.random() * EMOJIS_FOR_SPACE.length)]; | |
| const README = `--- | |
| title: ${escapeYamlValue(projectTitle)} | |
| colorFrom: ${colorFrom} | |
| colorTo: ${colorTo} | |
| sdk: static | |
| emoji: ${emoji} | |
| tags: | |
| - deepsite-v4 | |
| --- | |
| # ${escapeMarkdownHeader(title)} | |
| This project has been created with [DeepSite](https://deepsite.hf.co) AI Vibe Coding. | |
| `; | |
| const filesToUpload: File[] = [ | |
| new File([README], "README.md", { type: "text/markdown" }), | |
| ]; | |
| for (const file of files) { | |
| let mimeType = "text/html"; | |
| if (file.path.endsWith(".css")) { | |
| mimeType = "text/css"; | |
| } else if (file.path.endsWith(".js")) { | |
| mimeType = "text/javascript"; | |
| } | |
| const content = | |
| mimeType === "text/html" && isIndexPage(file.path) | |
| ? injectDeepSiteBadge(file.content) | |
| : file.content; | |
| filesToUpload.push(new File([content], file.path, { type: mimeType })); | |
| } | |
| let repoUrl: string | undefined; | |
| try { | |
| // Create the space first | |
| const createResult = await createRepo({ | |
| accessToken: token as string, | |
| repo: repo, | |
| sdk: "static", | |
| }); | |
| repoUrl = createResult.repoUrl; | |
| // Escape commit message to prevent injection | |
| const escapeCommitMessage = (message: string): string => { | |
| return message.replace(/[\r\n]/g, " ").slice(0, 200); | |
| }; | |
| const commitMessage = escapeCommitMessage(prompt ?? "Initial DeepSite commit"); | |
| // Upload files to the created space | |
| await uploadFiles({ | |
| repo, | |
| files: filesToUpload, | |
| accessToken: token as string, | |
| commitTitle: commitMessage, | |
| }); | |
| const path = repoUrl.split("/").slice(-2).join("/"); | |
| return NextResponse.json({ repoUrl: path }, { status: 200 }); | |
| } catch (error) { | |
| const errMsg = | |
| error instanceof Error ? error.message : "Failed to create or upload to space"; | |
| // If space was created but upload failed, include the repo URL in the error | |
| if (repoUrl) { | |
| const path = repoUrl.split("/").slice(-2).join("/"); | |
| return NextResponse.json({ | |
| error: `${errMsg}. Space was created at ${path} but files could not be uploaded.`, | |
| repoUrl: path, | |
| partialSuccess: true | |
| }, { status: 500 }); | |
| } | |
| return NextResponse.json({ error: errMsg }, { status: 500 }); | |
| } | |
| } | |