builds.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. "use strict";
  2. var __importDefault = (this && this.__importDefault) || function (mod) {
  3. return (mod && mod.__esModule) ? mod : { "default": mod };
  4. };
  5. Object.defineProperty(exports, "__esModule", { value: true });
  6. exports.buildsRouter = buildsRouter;
  7. const express_1 = require("express");
  8. const uuid_1 = require("uuid");
  9. const path_1 = __importDefault(require("path"));
  10. const fs_1 = __importDefault(require("fs"));
  11. const buildService_1 = require("../services/buildService");
  12. function buildsRouter(db, storageDir, onThemeSaved) {
  13. const router = (0, express_1.Router)();
  14. const buildService = new buildService_1.BuildService(db, storageDir);
  15. // POST /api/v1/creatives/:id/builds — 触发构建
  16. router.post("/creatives/:id/builds", async (req, res) => {
  17. try {
  18. const { id } = req.params;
  19. const { platforms, theme } = req.body;
  20. if (!platforms || !Array.isArray(platforms) || platforms.length === 0) {
  21. res.status(400).json({
  22. error: { message: "platforms must be a non-empty array" },
  23. });
  24. return;
  25. }
  26. const creative = db
  27. .prepare("SELECT * FROM creatives WHERE id = ?")
  28. .get(id);
  29. if (!creative) {
  30. res.status(404).json({ error: { message: "Creative not found" } });
  31. return;
  32. }
  33. if (creative.status === "draft") {
  34. res.status(400).json({
  35. error: { message: "Please upload assets before building" },
  36. });
  37. return;
  38. }
  39. // 保存 theme
  40. if (theme) {
  41. db.prepare("UPDATE creatives SET theme = ?, updated_at = datetime('now') WHERE id = ?").run(JSON.stringify(theme), id);
  42. onThemeSaved?.(id, theme, storageDir);
  43. }
  44. const buildId = (0, uuid_1.v4)();
  45. const themeSnapshot = theme || JSON.parse(creative.theme || "{}");
  46. db.prepare(`INSERT INTO builds (id, creative_id, platforms, theme_snapshot, status)
  47. VALUES (?, ?, ?, ?, 'pending')`).run(buildId, id, JSON.stringify(platforms), JSON.stringify(themeSnapshot));
  48. // 更新创意状态
  49. db.prepare("UPDATE creatives SET status = 'building', updated_at = datetime('now') WHERE id = ?").run(id);
  50. // 异步构建(不阻塞响应)
  51. buildService.enqueue(buildId, id, platforms, themeSnapshot);
  52. res.status(201).json({
  53. data: { id: buildId, status: "pending", platforms },
  54. });
  55. }
  56. catch (err) {
  57. res.status(500).json({ error: { message: err.message } });
  58. }
  59. });
  60. // GET /api/v1/creatives/:id/builds — 构建历史
  61. router.get("/creatives/:id/builds", (req, res) => {
  62. const builds = db
  63. .prepare("SELECT * FROM builds WHERE creative_id = ? ORDER BY created_at DESC LIMIT 20")
  64. .all(req.params.id);
  65. res.json({
  66. data: builds.map((b) => ({
  67. id: b.id,
  68. creativeId: b.creative_id,
  69. status: b.status,
  70. platforms: JSON.parse(b.platforms),
  71. results: b.results ? JSON.parse(b.results) : null,
  72. errorLog: b.error_log,
  73. startedAt: b.started_at,
  74. finishedAt: b.finished_at,
  75. createdAt: b.created_at,
  76. })),
  77. });
  78. });
  79. // GET /api/v1/builds/:id/status — 构建状态轮询
  80. router.get("/builds/:id/status", (req, res) => {
  81. const build = db
  82. .prepare("SELECT * FROM builds WHERE id = ?")
  83. .get(req.params.id);
  84. if (!build) {
  85. res.status(404).json({ error: { message: "Build not found" } });
  86. return;
  87. }
  88. res.json({
  89. data: {
  90. id: build.id,
  91. status: build.status,
  92. results: build.results ? JSON.parse(build.results) : null,
  93. errorLog: build.error_log,
  94. startedAt: build.started_at,
  95. finishedAt: build.finished_at,
  96. createdAt: build.created_at,
  97. },
  98. });
  99. });
  100. // GET /api/v1/builds/:id/download/all — 下载全部产物 ZIP(必须在 :platform 之前注册)
  101. router.get("/builds/:id/download/all", (req, res) => {
  102. const build = db
  103. .prepare("SELECT * FROM builds WHERE id = ?")
  104. .get(req.params.id);
  105. if (!build || build.status !== "completed") {
  106. res.status(404).json({ error: { message: "Build not found or not completed" } });
  107. return;
  108. }
  109. const zipPath = path_1.default.join(storageDir, "creatives", build.creative_id, "builds", build.id, "all.zip");
  110. if (!fs_1.default.existsSync(zipPath)) {
  111. res.status(404).json({ error: { message: "ZIP file not found" } });
  112. return;
  113. }
  114. res.download(zipPath, `playable-ad-${build.id.slice(0, 8)}.zip`);
  115. });
  116. // GET /api/v1/builds/:id/download/:platform — 下载单个平台产物
  117. router.get("/builds/:id/download/:platform", (req, res) => {
  118. const build = db
  119. .prepare("SELECT * FROM builds WHERE id = ?")
  120. .get(req.params.id);
  121. if (!build || build.status !== "completed") {
  122. res.status(404).json({ error: { message: "Build not found or not completed" } });
  123. return;
  124. }
  125. const results = JSON.parse(build.results || "[]");
  126. const result = results.find((r) => r.platform === req.params.platform);
  127. if (!result) {
  128. res.status(404).json({ error: { message: "Platform not found in build results" } });
  129. return;
  130. }
  131. const filePath = path_1.default.join(storageDir, "creatives", build.creative_id, "builds", build.id, req.params.platform, "index.html");
  132. if (!fs_1.default.existsSync(filePath)) {
  133. res.status(404).json({ error: { message: "File not found on disk" } });
  134. return;
  135. }
  136. res.download(filePath, "index.html");
  137. });
  138. return router;
  139. }
  140. //# sourceMappingURL=builds.js.map