deepsite / app /api /projects /route.ts
enzostvs's picture
enzostvs HF Staff
fix pst route
6caebc0
Raw
History Blame Contribute Delete
4.23 kB
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 });
}
}