chore: add missing diagrams/scripts + archicrat-ia routes
This commit is contained in:
7
bridge/Dockerfile
Normal file
7
bridge/Dockerfile
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
FROM node:22-bookworm-slim
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package.json package-lock.json* ./
|
||||||
|
RUN npm install --omit=dev
|
||||||
|
COPY server.mjs ./
|
||||||
|
EXPOSE 8787
|
||||||
|
CMD ["node","server.mjs"]
|
||||||
15
bridge/docker-compose-bridge.yml
Normal file
15
bridge/docker-compose-bridge.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
services:
|
||||||
|
issue_bridge:
|
||||||
|
build: ./bridge
|
||||||
|
environment:
|
||||||
|
GITEA_API_BASE: "http://gitea:3000"
|
||||||
|
GITEA_TOKEN: "${GITEA_TOKEN}"
|
||||||
|
GITEA_OWNER: "Archicratia"
|
||||||
|
GITEA_REPO: "archicratie-edition"
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
|
||||||
|
networks:
|
||||||
|
internal:
|
||||||
|
external: true
|
||||||
10
bridge/package.json
Normal file
10
bridge/package.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"name": "issue-bridge",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"express": "^4.19.2",
|
||||||
|
"multer": "^1.4.5-lts.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
89
bridge/server.mjs
Normal file
89
bridge/server.mjs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
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");
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user