mirror of
https://gitbruv.vercel.app/api/git/bruv/gitbruv.git
synced 2025-12-20 23:24:09 +01:00
update
This commit is contained in:
parent
468781e311
commit
1055bd7f07
8 changed files with 416 additions and 85 deletions
135
app/api/file/[...path]/route.ts
Normal file
135
app/api/file/[...path]/route.ts
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { db } from "@/db";
|
||||
import { users, repositories } from "@/db/schema";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import { getSession } from "@/lib/session";
|
||||
import git from "isomorphic-git";
|
||||
import { createR2Fs, getRepoPrefix } from "@/lib/r2-fs";
|
||||
|
||||
const CHUNK_SIZE = 64 * 1024;
|
||||
|
||||
export async function GET(request: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
|
||||
const { path } = await params;
|
||||
|
||||
if (path.length < 4) {
|
||||
return NextResponse.json({ error: "Invalid path" }, { status: 400 });
|
||||
}
|
||||
|
||||
const [username, repoName, branch, ...fileParts] = path;
|
||||
const filePath = fileParts.join("/");
|
||||
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(users.username, username),
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: "User not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
const repo = await db.query.repositories.findFirst({
|
||||
where: and(eq(repositories.ownerId, user.id), eq(repositories.name, repoName)),
|
||||
});
|
||||
|
||||
if (!repo) {
|
||||
return NextResponse.json({ error: "Repository not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
if (repo.visibility === "private") {
|
||||
const session = await getSession();
|
||||
if (!session?.user || session.user.id !== repo.ownerId) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 403 });
|
||||
}
|
||||
}
|
||||
|
||||
const repoPrefix = getRepoPrefix(user.id, `${repoName}.git`);
|
||||
const fs = createR2Fs(repoPrefix);
|
||||
|
||||
try {
|
||||
const commits = await git.log({
|
||||
fs,
|
||||
gitdir: "/",
|
||||
ref: branch,
|
||||
depth: 1,
|
||||
});
|
||||
|
||||
if (commits.length === 0) {
|
||||
return NextResponse.json({ error: "Branch not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
const commitOid = commits[0].oid;
|
||||
const parts = filePath.split("/").filter(Boolean);
|
||||
const fileName = parts.pop()!;
|
||||
|
||||
let currentTree = (await git.readTree({ fs, gitdir: "/", oid: commitOid })).tree;
|
||||
|
||||
for (const part of parts) {
|
||||
const entry = currentTree.find((e) => e.path === part && e.type === "tree");
|
||||
if (!entry) {
|
||||
return NextResponse.json({ error: "Path not found" }, { status: 404 });
|
||||
}
|
||||
currentTree = (await git.readTree({ fs, gitdir: "/", oid: entry.oid })).tree;
|
||||
}
|
||||
|
||||
const fileEntry = currentTree.find((e) => e.path === fileName && e.type === "blob");
|
||||
if (!fileEntry) {
|
||||
return NextResponse.json({ error: "File not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
const { blob } = await git.readBlob({
|
||||
fs,
|
||||
gitdir: "/",
|
||||
oid: fileEntry.oid,
|
||||
});
|
||||
|
||||
const rangeHeader = request.headers.get("range");
|
||||
|
||||
if (rangeHeader) {
|
||||
const match = rangeHeader.match(/bytes=(\d+)-(\d*)/);
|
||||
if (match) {
|
||||
const start = parseInt(match[1], 10);
|
||||
const end = match[2] ? parseInt(match[2], 10) : Math.min(start + CHUNK_SIZE - 1, blob.length - 1);
|
||||
const chunk = blob.slice(start, end + 1);
|
||||
|
||||
return new NextResponse(chunk, {
|
||||
status: 206,
|
||||
headers: {
|
||||
"Content-Range": `bytes ${start}-${end}/${blob.length}`,
|
||||
"Accept-Ranges": "bytes",
|
||||
"Content-Length": chunk.length.toString(),
|
||||
"Content-Type": "application/octet-stream",
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const stream = new ReadableStream({
|
||||
start(controller) {
|
||||
let offset = 0;
|
||||
const push = () => {
|
||||
if (offset >= blob.length) {
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
const chunk = blob.slice(offset, offset + CHUNK_SIZE);
|
||||
controller.enqueue(chunk);
|
||||
offset += CHUNK_SIZE;
|
||||
setTimeout(push, 0);
|
||||
};
|
||||
push();
|
||||
},
|
||||
});
|
||||
|
||||
return new NextResponse(stream, {
|
||||
headers: {
|
||||
"Content-Type": "application/octet-stream",
|
||||
"Content-Length": blob.length.toString(),
|
||||
"Accept-Ranges": "bytes",
|
||||
"X-Total-Size": blob.length.toString(),
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("File streaming error:", err);
|
||||
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue