Files
Wisecolt-CI/backend/src/routes/webhooks.ts
wisecolt e5fd3bd9d5 feat(deployments): docker tabanlı proje yönetim ve otomatik deploy sistemi ekle
Docker Compose projeleri için tam kapsamlı yönetim paneli ve otomatik deployment altyapısı eklendi.

Sistem özellikleri:
- Belirtilen root dizin altındaki docker-compose dosyası içeren projeleri tarama
- Git repo bağlantısı ile branch yönetimi ve klonlama/pull işlemleri
- Docker compose up/down komutları ile otomatik deploy
- Gitea webhook entegrasyonu ile commit bazlı tetikleme
- Deploy geçmişi, log kayıtları ve durum takibi (running/success/failed)
- Deploy metrikleri ve dashboard görselleştirmesi
- Webhook token ve secret yönetimi ile güvenlik
- Proje favicon servisi

Teknik değişiklikler:
- Backend: deploymentProject, deploymentRun ve settings modelleri eklendi
- Backend: deploymentService ile git ve docker işlemleri otomatize edildi
- Backend: webhook doğrulaması için signature kontrolü eklendi
- Docker: docker-cli ve docker-compose bağımlılıkları eklendi
- Frontend: deployments ve settings sayfaları eklendi
- Frontend: dashboard'a deploy metrikleri ve aktivite akışı eklendi
- API: /api/deployments ve /api/settings yolları eklendi
2026-01-18 16:24:11 +03:00

67 lines
2.5 KiB
TypeScript

import { Router, Request } from "express";
import crypto from "crypto";
import { deploymentService } from "../services/deploymentService.js";
const router = Router();
type RawBodyRequest = Request & { rawBody?: Buffer };
function getHeaderValue(value: string | string[] | undefined) {
if (!value) return "";
return Array.isArray(value) ? value[0] : value;
}
function verifySignature(rawBody: Buffer, secret: string, signature: string) {
const cleaned = signature.startsWith("sha256=") ? signature.slice(7) : signature;
const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
if (cleaned.length !== expected.length) return false;
return crypto.timingSafeEqual(Buffer.from(cleaned), Buffer.from(expected));
}
router.post("/api/deployments/webhook/:token", async (req, res) => {
const { token } = req.params;
const settings = await deploymentService.ensureSettings();
const authHeader = getHeaderValue(req.headers.authorization);
if (!authHeader) {
return res.status(401).json({ message: "Yetkisiz" });
}
const providedToken = authHeader.startsWith("Bearer ")
? authHeader.slice("Bearer ".length)
: authHeader;
if (providedToken !== settings.webhookToken) {
return res.status(401).json({ message: "Yetkisiz" });
}
const signatureHeader =
getHeaderValue(req.headers["x-gitea-signature"]) ||
getHeaderValue(req.headers["x-gitea-signature-256"]);
const rawBody = (req as RawBodyRequest).rawBody;
if (!rawBody || !signatureHeader) {
return res.status(401).json({ message: "Imza eksik" });
}
if (!verifySignature(rawBody, settings.webhookSecret, signatureHeader)) {
return res.status(401).json({ message: "Imza dogrulanamadi" });
}
const payload = req.body as { ref?: string; head_commit?: { message?: string }; commits?: Array<{ message?: string }> };
const ref = payload?.ref || "";
const branch = ref.startsWith("refs/heads/") ? ref.replace("refs/heads/", "") : ref;
const commitMessage =
payload?.head_commit?.message || payload?.commits?.[payload.commits.length - 1]?.message;
const project = await deploymentService.findByWebhookToken(token);
if (!project) return res.status(404).json({ message: "Deployment bulunamadi" });
if (branch && branch !== project.branch) {
return res.json({ ignored: true });
}
deploymentService
.runDeployment(project._id.toString(), commitMessage ? { message: commitMessage } : undefined)
.catch(() => undefined);
return res.json({ queued: true });
});
export default router;