Files
Wisecolt-CI/backend/src/routes/deployments.ts
wisecolt e7a5690d98 feat(deployments): anlık durum ve log izleme özelliği ekle
- Socket.IO tabanlı gerçek zamanlı deployment log ve durum bildirimleri ekle
- deployment:subscribe ve deployment:unsubscribe soket olaylarını destekle
- DeploymentService'e anlık durum ve log yayınlama özelliği ekle
- Deployment silinirken docker kaynaklarını temizle
- Ortam değişkenlerini tek bir .env.example dosyasında birleştir
- Docker compose yapılandırmasını güncelle (PWD ve DEPLOYMENTS_ROOT kullan)
- Repo URL'sinden proje adını otomatik öner
- Güvensiz bağlamlar için clipboard kopya fallback mekanizması ekle
- Socket.IO path'ini /api/socket.io olarak ayarla
2026-01-19 15:11:45 +03:00

207 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;