import { Router } from "express"; import fs from "fs"; import path from "path"; import { authMiddleware } from "../middleware/authMiddleware.js"; import { deploymentService } from "../services/deploymentService.js"; import { DeploymentProject } from "../models/deploymentProject.js"; import { DeploymentRun } from "../models/deploymentRun.js"; import fs from "fs"; const router = Router(); const faviconCandidates = [ "favicon.ico", "public/favicon.ico", "public/favicon.png", "public/favicon.svg", "assets/favicon.ico" ]; function getContentType(filePath: string) { if (filePath.endsWith(".svg")) return "image/svg+xml"; if (filePath.endsWith(".png")) return "image/png"; return "image/x-icon"; } router.get("/:id/favicon", async (req, res) => { const { id } = req.params; const project = await DeploymentProject.findById(id).lean(); if (!project) return res.status(404).end(); const rootPath = path.resolve(project.rootPath); for (const candidate of faviconCandidates) { const filePath = path.join(rootPath, candidate); if (!fs.existsSync(filePath)) continue; res.setHeader("Content-Type", getContentType(filePath)); res.setHeader("Cache-Control", "public, max-age=300"); return fs.createReadStream(filePath).pipe(res); } return res.status(404).end(); }); router.get("/branches", async (req, res) => { authMiddleware(req, res, async () => { const repoUrl = req.query.repoUrl as string | undefined; if (!repoUrl) { return res.status(400).json({ message: "repoUrl gerekli" }); } try { const branches = await deploymentService.listRemoteBranches(repoUrl); return res.json({ branches }); } catch (err) { return res.status(400).json({ message: "Branch listesi alınamadı", error: (err as Error).message }); } }); }); router.get("/compose-files", async (req, res) => { authMiddleware(req, res, async () => { const repoUrl = req.query.repoUrl as string | undefined; const branch = req.query.branch as string | undefined; if (!repoUrl || !branch) { return res.status(400).json({ message: "repoUrl ve branch gerekli" }); } try { const files = await deploymentService.listRemoteComposeFiles(repoUrl, branch); return res.json({ files }); } catch (err) { return res.status(400).json({ message: "Compose listesi alınamadı", error: (err as Error).message }); } }); }); router.get("/metrics/summary", async (req, res) => { authMiddleware(req, res, async () => { const since = new Date(); since.setDate(since.getDate() - 7); const dailyStats = await DeploymentRun.aggregate([ { $match: { startedAt: { $gte: since } } }, { $group: { _id: { $dateToString: { format: "%Y-%m-%d", date: "$startedAt" } }, total: { $sum: 1 }, success: { $sum: { $cond: [{ $eq: ["$status", "success"] }, 1, 0] } }, failed: { $sum: { $cond: [{ $eq: ["$status", "failed"] }, 1, 0] } }, avgDurationMs: { $avg: "$durationMs" } } }, { $sort: { _id: 1 } } ]); const recentRuns = await DeploymentRun.find() .sort({ startedAt: -1 }) .limit(10) .populate("project", "name repoUrl rootPath") .lean(); return res.json({ recentRuns, dailyStats }); }); }); router.get("/", async (_req, res) => { authMiddleware(_req, res, async () => { const projects = await DeploymentProject.find().sort({ createdAt: -1 }).lean(); return res.json(projects); }); }); router.get("/:id", async (req, res) => { authMiddleware(req, res, async () => { const { id } = req.params; const project = await DeploymentProject.findById(id).lean(); if (!project) return res.status(404).json({ message: "Deployment bulunamadı" }); const runs = await DeploymentRun.find({ project: id }) .sort({ startedAt: -1 }) .limit(20) .lean(); return res.json({ project, runs }); }); }); router.post("/", async (req, res) => { authMiddleware(req, res, async () => { const { name, repoUrl, branch, composeFile, port } = req.body; if (!name || !repoUrl || !branch || !composeFile) { return res.status(400).json({ message: "Tüm alanlar gerekli" }); } try { const created = await deploymentService.createProject({ name, repoUrl, branch, composeFile, port }); deploymentService .runDeployment(created._id.toString(), { message: "First deployment" }) .catch(() => undefined); return res.status(201).json(created); } catch (err) { return res.status(400).json({ message: "Deployment oluşturulamadı", error: (err as Error).message }); } }); }); router.put("/:id", async (req, res) => { authMiddleware(req, res, async () => { const { id } = req.params; const { name, repoUrl, branch, composeFile, port } = req.body; if (!name || !repoUrl || !branch || !composeFile) { return res.status(400).json({ message: "Tüm alanlar gerekli" }); } try { const updated = await deploymentService.updateProject(id, { name, repoUrl, branch, composeFile, port }); if (!updated) return res.status(404).json({ message: "Deployment bulunamadı" }); return res.json(updated); } catch (err) { return res.status(400).json({ message: "Deployment güncellenemedi", error: (err as Error).message }); } }); }); router.delete("/:id", async (req, res) => { authMiddleware(req, res, async () => { const { id } = req.params; try { const project = await DeploymentProject.findById(id); if (!project) return res.status(404).json({ message: "Deployment bulunamadı" }); await deploymentService.cleanupProjectResources(project); await DeploymentProject.findByIdAndDelete(id); await DeploymentRun.deleteMany({ project: id }); await fs.promises.rm(project.rootPath, { recursive: true, force: true }); return res.json({ success: true }); } catch (err) { return res.status(400).json({ message: "Deployment silinemedi", error: (err as Error).message }); } }); }); router.post("/:id/run", async (req, res) => { authMiddleware(req, res, async () => { const { id } = req.params; const project = await DeploymentProject.findById(id); if (!project) return res.status(404).json({ message: "Deployment bulunamadı" }); deploymentService .runDeployment(id, { message: "Elle deploy tetikleme" }) .catch(() => undefined); return res.json({ queued: true }); }); }); export default router;