90 lines
3.5 KiB
JavaScript
90 lines
3.5 KiB
JavaScript
import express from "express";
|
|
import multer from "multer";
|
|
|
|
const app = express();
|
|
const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 25 * 1024 * 1024 } }); // 25 MB
|
|
|
|
const {
|
|
GITEA_API_BASE, // ex: http://gitea:3000 (ou https://forge.tld)
|
|
GITEA_TOKEN, // PAT du bot
|
|
GITEA_OWNER, // owner/org
|
|
GITEA_REPO // repo
|
|
} = process.env;
|
|
|
|
function mustEnv(name) {
|
|
if (!process.env[name]) throw new Error(`Missing env ${name}`);
|
|
}
|
|
["GITEA_API_BASE","GITEA_TOKEN","GITEA_OWNER","GITEA_REPO"].forEach(mustEnv);
|
|
|
|
function isEditor(req) {
|
|
// Adapte selon tes headers Authelia. Souvent Remote-Groups / Remote-User.
|
|
const groups = String(req.header("Remote-Groups") || req.header("X-Remote-Groups") || "");
|
|
return groups.split(/[,\s]+/).includes("editors");
|
|
}
|
|
|
|
async function giteaFetch(path, init = {}) {
|
|
const url = String(GITEA_API_BASE).replace(/\/+$/, "") + path;
|
|
const headers = new Headers(init.headers || {});
|
|
headers.set("Authorization", `token ${GITEA_TOKEN}`);
|
|
return fetch(url, { ...init, headers });
|
|
}
|
|
|
|
app.get("/health", (_req, res) => res.json({ ok: true }));
|
|
|
|
app.post("/media", upload.single("file"), async (req, res) => {
|
|
try {
|
|
if (!isEditor(req)) return res.status(403).json({ ok: false, error: "forbidden" });
|
|
|
|
const file = req.file;
|
|
const title = String(req.body.title || "").trim();
|
|
const body = String(req.body.body || "").trim();
|
|
const suggestedName = String(req.body.suggestedName || "").trim();
|
|
|
|
if (!file) return res.status(400).json({ ok: false, error: "missing_file" });
|
|
if (!title) return res.status(400).json({ ok: false, error: "missing_title" });
|
|
if (!body) return res.status(400).json({ ok: false, error: "missing_body" });
|
|
|
|
// 1) Create issue
|
|
const r1 = await giteaFetch(`/api/v1/repos/${encodeURIComponent(GITEA_OWNER)}/${encodeURIComponent(GITEA_REPO)}/issues`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ title, body })
|
|
});
|
|
|
|
if (!r1.ok) {
|
|
const t = await r1.text().catch(() => "");
|
|
return res.status(502).json({ ok: false, step: "create_issue", status: r1.status, detail: t.slice(0, 2000) });
|
|
}
|
|
|
|
const issue = await r1.json();
|
|
const index = issue?.number ?? issue?.index;
|
|
const issueUrl = issue?.html_url;
|
|
|
|
if (!index) return res.status(502).json({ ok: false, step: "create_issue", error: "missing_issue_index" });
|
|
|
|
// 2) Upload attachment (multipart field name = "attachment") :contentReference[oaicite:1]{index=1}
|
|
const fd = new FormData();
|
|
fd.append("attachment", new Blob([file.buffer], { type: file.mimetype || "application/octet-stream" }), file.originalname);
|
|
|
|
const q = suggestedName ? `?name=${encodeURIComponent(suggestedName)}` : "";
|
|
const r2 = await giteaFetch(`/api/v1/repos/${encodeURIComponent(GITEA_OWNER)}/${encodeURIComponent(GITEA_REPO)}/issues/${encodeURIComponent(String(index))}/assets${q}`, {
|
|
method: "POST",
|
|
body: fd
|
|
});
|
|
|
|
if (!r2.ok) {
|
|
const t = await r2.text().catch(() => "");
|
|
return res.status(502).json({ ok: false, step: "upload_asset", status: r2.status, detail: t.slice(0, 2000), issueUrl });
|
|
}
|
|
|
|
const asset = await r2.json().catch(() => ({}));
|
|
return res.json({ ok: true, issueUrl, issueIndex: index, asset });
|
|
} catch (e) {
|
|
return res.status(500).json({ ok: false, error: String(e?.message || e) });
|
|
}
|
|
});
|
|
|
|
app.listen(8787, "0.0.0.0", () => {
|
|
console.log("issue-bridge listening on :8787");
|
|
});
|