From 1bc9b4f7e21ad405c9529dabb0899ed64d17f145 Mon Sep 17 00:00:00 2001 From: FluxKit Date: Thu, 19 Feb 2026 14:40:44 +0000 Subject: [PATCH] feat: Add backup system for ZIP downloads --- src/main.ts | 3 ++ src/routes/backup.ts | 71 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 src/routes/backup.ts diff --git a/src/main.ts b/src/main.ts index 927eebe..143b677 100644 --- a/src/main.ts +++ b/src/main.ts @@ -22,6 +22,7 @@ import { logsRouter } from "./routes/logs.ts"; import { userSettingsRouter } from "./routes/usersettings.ts"; import gitlabRouter from "./routes/gitlab.ts"; import { giteaRouter } from "./routes/gitea.ts"; +import { backupRouter } from "./routes/backup.ts"; import { dockerRouter } from "./routes/docker.ts"; import { exportRouter } from "./routes/export.ts"; import { appUpdateRouter } from "./routes/appUpdate.ts"; @@ -78,6 +79,8 @@ app.use(userSettingsRouter.allowedMethods()); app.use(gitlabRouter.routes()); app.use(giteaRouter.routes()); app.use(giteaRouter.allowedMethods()); +app.use(backupRouter.routes()); +app.use(backupRouter.allowedMethods()); app.use(gitlabRouter.allowedMethods()); app.use(dockerRouter.routes()); app.use(dockerRouter.allowedMethods()); diff --git a/src/routes/backup.ts b/src/routes/backup.ts new file mode 100644 index 0000000..f7012c9 --- /dev/null +++ b/src/routes/backup.ts @@ -0,0 +1,71 @@ +import { Router } from "@oak/oak"; +import { authMiddleware } from "../middleware/auth.ts"; + +const router = new Router(); + +const GITEA_URL = Deno.env.get("GITEA_URL") || "https://git.kronos-soulution.de"; +const GITEA_TOKEN = Deno.env.get("GITEA_TOKEN") || ""; + +interface GiteaRepo { + id: number; + name: string; + full_name: string; + description: string | null; + default_branch: string; + html_url: string; + updated_at: string; + size: number; + owner: { login: string }; +} + +// Get all repos with download info +router.get("/api/backup/repos", authMiddleware, async (ctx) => { + try { + const response = await fetch(`${GITEA_URL}/api/v1/user/repos?limit=100`, { + headers: { "Authorization": `token ${GITEA_TOKEN}` } + }); + + if (!response.ok) throw new Error("Failed to fetch repos"); + + const repos: GiteaRepo[] = await response.json(); + + ctx.response.body = repos.map(r => ({ + id: r.id, + name: r.name, + fullName: r.full_name, + description: r.description, + branch: r.default_branch, + url: r.html_url, + updatedAt: r.updated_at, + size: r.size, + downloadUrl: `${GITEA_URL}/${r.full_name}/archive/${r.default_branch}.zip` + })); + } catch (err) { + ctx.response.status = 500; + ctx.response.body = { error: err instanceof Error ? err.message : "Failed to fetch repos" }; + } +}); + +// Proxy download (to avoid CORS issues) +router.get("/api/backup/download/:owner/:repo", authMiddleware, async (ctx) => { + const { owner, repo } = ctx.params; + const branch = ctx.request.url.searchParams.get("branch") || "main"; + + try { + const response = await fetch( + `${GITEA_URL}/${owner}/${repo}/archive/${branch}.zip`, + { headers: { "Authorization": `token ${GITEA_TOKEN}` } } + ); + + if (!response.ok) throw new Error(`Download failed: ${response.status}`); + + ctx.response.headers.set("Content-Type", "application/zip"); + ctx.response.headers.set("Content-Disposition", `attachment; filename="${repo}-${branch}.zip"`); + ctx.response.body = response.body; + } catch (err) { + ctx.response.status = 500; + ctx.response.body = { error: err instanceof Error ? err.message : "Download failed" }; + } +}); + +export const backupRouter = router;