blob: d251998e0f7d20593e69fe019bb63c6677245ddb [file] [log] [blame]
giod0026612025-05-08 13:00:36 +00001import { PrismaClient } from "@prisma/client";
2import express from "express";
3import { env } from "node:process";
4import axios from "axios";
5import { GithubClient } from "./github";
gio3ed59592025-05-14 16:51:09 +00006import { AppManager } from "./app_manager";
gio7d813702025-05-08 18:29:52 +00007import { z } from "zod";
giod0026612025-05-08 13:00:36 +00008
9const db = new PrismaClient();
gio3ed59592025-05-14 16:51:09 +000010const appManager = new AppManager();
giod0026612025-05-08 13:00:36 +000011
gio7d813702025-05-08 18:29:52 +000012const workers = new Map<number, string[]>();
gio3a921b82025-05-10 07:36:09 +000013const logs = new Map<number, Map<string, string>>();
gio7d813702025-05-08 18:29:52 +000014
giod0026612025-05-08 13:00:36 +000015const handleProjectCreate: express.Handler = async (req, resp) => {
16 try {
17 const { id } = await db.project.create({
18 data: {
gio09fcab52025-05-12 14:05:07 +000019 userId: resp.locals.userId,
giod0026612025-05-08 13:00:36 +000020 name: req.body.name,
21 },
22 });
23 resp.status(200);
24 resp.header("Content-Type", "application/json");
25 resp.write(
26 JSON.stringify({
gio74ab7852025-05-13 13:19:31 +000027 id: id.toString(),
giod0026612025-05-08 13:00:36 +000028 }),
29 );
30 } catch (e) {
31 console.log(e);
32 resp.status(500);
33 } finally {
34 resp.end();
35 }
36};
37
38const handleProjectAll: express.Handler = async (req, resp) => {
39 try {
40 const r = await db.project.findMany({
41 where: {
gio09fcab52025-05-12 14:05:07 +000042 userId: resp.locals.userId,
giod0026612025-05-08 13:00:36 +000043 },
44 });
45 resp.status(200);
46 resp.header("Content-Type", "application/json");
47 resp.write(
48 JSON.stringify(
49 r.map((p) => ({
50 id: p.id.toString(),
51 name: p.name,
52 })),
53 ),
54 );
55 } catch (e) {
56 console.log(e);
57 resp.status(500);
58 } finally {
59 resp.end();
60 }
61};
62
63const handleSave: express.Handler = async (req, resp) => {
64 try {
65 await db.project.update({
66 where: {
67 id: Number(req.params["projectId"]),
gio09fcab52025-05-12 14:05:07 +000068 userId: resp.locals.userId,
giod0026612025-05-08 13:00:36 +000069 },
70 data: {
giobd37a2b2025-05-15 04:28:42 +000071 draft: JSON.stringify(req.body),
giod0026612025-05-08 13:00:36 +000072 },
73 });
74 resp.status(200);
75 } catch (e) {
76 console.log(e);
77 resp.status(500);
78 } finally {
79 resp.end();
80 }
81};
82
gio818da4e2025-05-12 14:45:35 +000083function handleSavedGet(state: "deploy" | "draft"): express.Handler {
84 return async (req, resp) => {
85 try {
86 const r = await db.project.findUnique({
87 where: {
88 id: Number(req.params["projectId"]),
89 userId: resp.locals.userId,
90 },
91 select: {
92 state: true,
93 draft: true,
94 },
95 });
96 if (r == null) {
97 resp.status(404);
98 return;
99 }
giod0026612025-05-08 13:00:36 +0000100 resp.status(200);
101 resp.header("content-type", "application/json");
gio818da4e2025-05-12 14:45:35 +0000102 if (state === "deploy") {
giod0026612025-05-08 13:00:36 +0000103 if (r.state == null) {
104 resp.send({
105 nodes: [],
106 edges: [],
107 viewport: { x: 0, y: 0, zoom: 1 },
108 });
109 } else {
110 resp.send(JSON.parse(Buffer.from(r.state).toString("utf8")));
111 }
112 } else {
gio818da4e2025-05-12 14:45:35 +0000113 if (r.draft == null) {
114 if (r.state == null) {
115 resp.send({
116 nodes: [],
117 edges: [],
118 viewport: { x: 0, y: 0, zoom: 1 },
119 });
120 } else {
121 resp.send(JSON.parse(Buffer.from(r.state).toString("utf8")));
122 }
123 } else {
124 resp.send(JSON.parse(Buffer.from(r.draft).toString("utf8")));
125 }
giod0026612025-05-08 13:00:36 +0000126 }
gio818da4e2025-05-12 14:45:35 +0000127 } catch (e) {
128 console.log(e);
129 resp.status(500);
130 } finally {
131 resp.end();
giod0026612025-05-08 13:00:36 +0000132 }
gio818da4e2025-05-12 14:45:35 +0000133 };
134}
giod0026612025-05-08 13:00:36 +0000135
136const handleDelete: express.Handler = async (req, resp) => {
137 try {
138 const projectId = Number(req.params["projectId"]);
139 const p = await db.project.findUnique({
140 where: {
141 id: projectId,
gio09fcab52025-05-12 14:05:07 +0000142 userId: resp.locals.userId,
giod0026612025-05-08 13:00:36 +0000143 },
144 select: {
145 instanceId: true,
146 },
147 });
148 if (p === null) {
149 resp.status(404);
150 return;
151 }
gioe440db82025-05-13 12:21:44 +0000152 let ok = false;
153 if (p.instanceId === null) {
154 ok = true;
155 } else {
gio3ed59592025-05-14 16:51:09 +0000156 ok = await appManager.removeInstance(p.instanceId);
gioe440db82025-05-13 12:21:44 +0000157 }
158 if (ok) {
giod0026612025-05-08 13:00:36 +0000159 await db.project.delete({
160 where: {
161 id: projectId,
162 },
163 });
164 }
165 resp.status(200);
166 } catch (e) {
167 console.log(e);
168 resp.status(500);
169 } finally {
170 resp.end();
171 }
172};
173
giobd37a2b2025-05-15 04:28:42 +0000174function extractGithubRepos(serializedState: string | null): string[] {
gio3ed59592025-05-14 16:51:09 +0000175 if (!serializedState) {
176 return [];
177 }
178 try {
giobd37a2b2025-05-15 04:28:42 +0000179 const stateObj = JSON.parse(serializedState);
gio3ed59592025-05-14 16:51:09 +0000180 const githubNodes = stateObj.nodes.filter(
181 // eslint-disable-next-line @typescript-eslint/no-explicit-any
182 (n: any) => n.type === "github" && n.data?.repository?.id,
183 );
184 // eslint-disable-next-line @typescript-eslint/no-explicit-any
185 return githubNodes.map((n: any) => n.data.repository.sshURL);
186 } catch (error) {
187 console.error("Failed to parse state or extract GitHub repos:", error);
188 return [];
189 }
190}
191
192type RepoDiff = {
193 toAdd?: string[];
194 toDelete?: string[];
195};
196
197function calculateRepoDiff(oldRepos: string[], newRepos: string[]): RepoDiff {
198 const toAdd = newRepos.filter((repo) => !oldRepos.includes(repo));
199 const toDelete = oldRepos.filter((repo) => !newRepos.includes(repo));
200 return { toAdd, toDelete };
201}
202
203async function manageGithubKeys(github: GithubClient, deployKey: string, diff: RepoDiff): Promise<void> {
204 for (const repoUrl of diff.toDelete ?? []) {
205 try {
206 await github.removeDeployKey(repoUrl, deployKey);
207 console.log(`Removed deploy key from repository ${repoUrl}`);
208 } catch (error) {
209 console.error(`Failed to remove deploy key from repository ${repoUrl}:`, error);
210 }
211 }
212 for (const repoUrl of diff.toAdd ?? []) {
213 try {
214 await github.addDeployKey(repoUrl, deployKey);
215 console.log(`Added deploy key to repository ${repoUrl}`);
216 } catch (error) {
217 console.error(`Failed to add deploy key from repository ${repoUrl}:`, error);
218 }
219 }
220}
221
giod0026612025-05-08 13:00:36 +0000222const handleDeploy: express.Handler = async (req, resp) => {
223 try {
224 const projectId = Number(req.params["projectId"]);
giobd37a2b2025-05-15 04:28:42 +0000225 const state = JSON.stringify(req.body.state);
giod0026612025-05-08 13:00:36 +0000226 const p = await db.project.findUnique({
227 where: {
228 id: projectId,
gio09fcab52025-05-12 14:05:07 +0000229 userId: resp.locals.userId,
giod0026612025-05-08 13:00:36 +0000230 },
231 select: {
232 instanceId: true,
233 githubToken: true,
234 deployKey: true,
gio3ed59592025-05-14 16:51:09 +0000235 state: true,
giod0026612025-05-08 13:00:36 +0000236 },
237 });
238 if (p === null) {
239 resp.status(404);
240 return;
241 }
242 await db.project.update({
243 where: {
244 id: projectId,
245 },
246 data: {
247 draft: state,
248 },
249 });
gio3ed59592025-05-14 16:51:09 +0000250 let diff: RepoDiff | null = null;
251 let deployKey: string | null = null;
252 try {
253 if (p.instanceId == null) {
254 const deployResponse = await appManager.deploy(req.body.config);
giod0026612025-05-08 13:00:36 +0000255 await db.project.update({
256 where: {
257 id: projectId,
258 },
259 data: {
260 state,
261 draft: null,
gio3ed59592025-05-14 16:51:09 +0000262 instanceId: deployResponse.id,
263 deployKey: deployResponse.deployKey,
giob77cb932025-05-19 09:37:14 +0000264 access: JSON.stringify(deployResponse.access),
giod0026612025-05-08 13:00:36 +0000265 },
266 });
gio3ed59592025-05-14 16:51:09 +0000267 diff = { toAdd: extractGithubRepos(state) };
268 deployKey = deployResponse.deployKey;
269 } else {
giob77cb932025-05-19 09:37:14 +0000270 const deployResponse = await appManager.update(p.instanceId, req.body.config);
271 diff = calculateRepoDiff(extractGithubRepos(p.state), extractGithubRepos(state));
272 deployKey = p.deployKey;
273 await db.project.update({
274 where: {
275 id: projectId,
276 },
277 data: {
278 state,
279 draft: null,
280 access: JSON.stringify(deployResponse.access),
281 },
282 });
giod0026612025-05-08 13:00:36 +0000283 }
gio3ed59592025-05-14 16:51:09 +0000284 if (diff && p.githubToken && deployKey) {
285 const github = new GithubClient(p.githubToken);
286 await manageGithubKeys(github, deployKey, diff);
giod0026612025-05-08 13:00:36 +0000287 }
gio3ed59592025-05-14 16:51:09 +0000288 resp.status(200);
289 } catch (error) {
290 console.error("Deployment error:", error);
291 resp.status(500);
292 resp.write(JSON.stringify({ error: "Deployment failed" }));
giod0026612025-05-08 13:00:36 +0000293 }
294 } catch (e) {
295 console.log(e);
296 resp.status(500);
297 } finally {
298 resp.end();
299 }
300};
301
302const handleStatus: express.Handler = async (req, resp) => {
303 try {
304 const projectId = Number(req.params["projectId"]);
305 const p = await db.project.findUnique({
306 where: {
307 id: projectId,
gio09fcab52025-05-12 14:05:07 +0000308 userId: resp.locals.userId,
giod0026612025-05-08 13:00:36 +0000309 },
310 select: {
311 instanceId: true,
312 },
313 });
giod0026612025-05-08 13:00:36 +0000314 if (p === null) {
315 resp.status(404);
316 return;
317 }
318 if (p.instanceId == null) {
319 resp.status(404);
320 return;
321 }
gio3ed59592025-05-14 16:51:09 +0000322 try {
323 const status = await appManager.getStatus(p.instanceId);
324 resp.status(200);
325 resp.write(JSON.stringify(status));
326 } catch (error) {
327 console.error("Error getting status:", error);
328 resp.status(500);
giod0026612025-05-08 13:00:36 +0000329 }
330 } catch (e) {
331 console.log(e);
332 resp.status(500);
333 } finally {
334 resp.end();
335 }
336};
337
giobd37a2b2025-05-15 04:28:42 +0000338const handleRemoveDeployment: express.Handler = async (req, resp) => {
339 try {
340 const projectId = Number(req.params["projectId"]);
341 const p = await db.project.findUnique({
342 where: {
343 id: projectId,
344 userId: resp.locals.userId,
345 },
346 select: {
347 instanceId: true,
348 githubToken: true,
349 deployKey: true,
350 state: true,
351 draft: true,
352 },
353 });
354 if (p === null) {
355 resp.status(404);
356 resp.write(JSON.stringify({ error: "Project not found" }));
357 return;
358 }
359 if (p.instanceId == null) {
360 resp.status(400);
361 resp.write(JSON.stringify({ error: "Project not deployed" }));
362 return;
363 }
364 const removed = await appManager.removeInstance(p.instanceId);
365 if (!removed) {
366 resp.status(500);
367 resp.write(JSON.stringify({ error: "Failed to remove deployment from cluster" }));
368 return;
369 }
370 if (p.githubToken && p.deployKey && p.state) {
371 try {
372 const github = new GithubClient(p.githubToken);
373 const repos = extractGithubRepos(p.state);
374 const diff = { toDelete: repos, toAdd: [] };
375 await manageGithubKeys(github, p.deployKey, diff);
376 } catch (error) {
377 console.error("Error removing GitHub deploy keys:", error);
378 }
379 }
380 await db.project.update({
381 where: {
382 id: projectId,
383 },
384 data: {
385 instanceId: null,
386 deployKey: null,
giob77cb932025-05-19 09:37:14 +0000387 access: null,
giobd37a2b2025-05-15 04:28:42 +0000388 state: null,
389 draft: p.draft ?? p.state,
390 },
391 });
392 resp.status(200);
393 resp.write(JSON.stringify({ success: true }));
394 } catch (e) {
395 console.error("Error removing deployment:", e);
396 resp.status(500);
397 resp.write(JSON.stringify({ error: "Internal server error" }));
398 } finally {
399 resp.end();
400 }
401};
402
giod0026612025-05-08 13:00:36 +0000403const handleGithubRepos: express.Handler = async (req, resp) => {
404 try {
405 const projectId = Number(req.params["projectId"]);
406 const project = await db.project.findUnique({
gio09fcab52025-05-12 14:05:07 +0000407 where: {
408 id: projectId,
409 userId: resp.locals.userId,
410 },
411 select: {
412 githubToken: true,
413 },
giod0026612025-05-08 13:00:36 +0000414 });
giod0026612025-05-08 13:00:36 +0000415 if (!project?.githubToken) {
416 resp.status(400);
417 resp.write(JSON.stringify({ error: "GitHub token not configured" }));
418 return;
419 }
giod0026612025-05-08 13:00:36 +0000420 const github = new GithubClient(project.githubToken);
421 const repositories = await github.getRepositories();
giod0026612025-05-08 13:00:36 +0000422 resp.status(200);
423 resp.header("Content-Type", "application/json");
424 resp.write(JSON.stringify(repositories));
425 } catch (e) {
426 console.log(e);
427 resp.status(500);
428 resp.write(JSON.stringify({ error: "Failed to fetch repositories" }));
429 } finally {
430 resp.end();
431 }
432};
433
434const handleUpdateGithubToken: express.Handler = async (req, resp) => {
435 try {
436 const projectId = Number(req.params["projectId"]);
437 const { githubToken } = req.body;
giod0026612025-05-08 13:00:36 +0000438 await db.project.update({
gio09fcab52025-05-12 14:05:07 +0000439 where: {
440 id: projectId,
441 userId: resp.locals.userId,
442 },
giod0026612025-05-08 13:00:36 +0000443 data: { githubToken },
444 });
giod0026612025-05-08 13:00:36 +0000445 resp.status(200);
446 } catch (e) {
447 console.log(e);
448 resp.status(500);
449 } finally {
450 resp.end();
451 }
452};
453
454const handleEnv: express.Handler = async (req, resp) => {
455 const projectId = Number(req.params["projectId"]);
456 try {
457 const project = await db.project.findUnique({
gio09fcab52025-05-12 14:05:07 +0000458 where: {
459 id: projectId,
460 userId: resp.locals.userId,
461 },
giod0026612025-05-08 13:00:36 +0000462 select: {
463 deployKey: true,
464 githubToken: true,
giob77cb932025-05-19 09:37:14 +0000465 access: true,
giod0026612025-05-08 13:00:36 +0000466 },
467 });
giod0026612025-05-08 13:00:36 +0000468 if (!project) {
469 resp.status(404);
470 resp.write(JSON.stringify({ error: "Project not found" }));
471 return;
472 }
gio3a921b82025-05-10 07:36:09 +0000473 const projectLogs = logs.get(projectId) || new Map();
474 const services = Array.from(projectLogs.keys());
giod0026612025-05-08 13:00:36 +0000475 resp.status(200);
476 resp.write(
477 JSON.stringify({
gio7d813702025-05-08 18:29:52 +0000478 // TODO(gio): get from env or command line flags
gio33046722025-05-16 14:49:55 +0000479 managerAddr: "http://10.42.0.95:8081",
giod0026612025-05-08 13:00:36 +0000480 deployKey: project.deployKey,
giob77cb932025-05-19 09:37:14 +0000481 access: JSON.parse(project.access ?? "[]"),
giod0026612025-05-08 13:00:36 +0000482 integrations: {
483 github: !!project.githubToken,
484 },
485 networks: [
486 {
gio33046722025-05-16 14:49:55 +0000487 name: "Trial",
488 domain: "trial.dodoapp.xyz",
gio6d8b71c2025-05-19 12:57:35 +0000489 hasAuth: false,
giod0026612025-05-08 13:00:36 +0000490 },
491 ],
gio3a921b82025-05-10 07:36:09 +0000492 services,
gio3ed59592025-05-14 16:51:09 +0000493 user: {
494 id: resp.locals.userId,
495 username: resp.locals.username,
496 },
giod0026612025-05-08 13:00:36 +0000497 }),
498 );
499 } catch (error) {
500 console.error("Error checking integrations:", error);
501 resp.status(500);
502 resp.write(JSON.stringify({ error: "Internal server error" }));
503 } finally {
504 resp.end();
505 }
506};
507
gio3a921b82025-05-10 07:36:09 +0000508const handleServiceLogs: express.Handler = async (req, resp) => {
509 try {
510 const projectId = Number(req.params["projectId"]);
511 const service = req.params["service"];
gio09fcab52025-05-12 14:05:07 +0000512 const project = await db.project.findUnique({
513 where: {
514 id: projectId,
515 userId: resp.locals.userId,
516 },
517 });
518 if (project == null) {
519 resp.status(404);
520 resp.write(JSON.stringify({ error: "Project not found" }));
521 return;
522 }
gio3a921b82025-05-10 07:36:09 +0000523 const projectLogs = logs.get(projectId);
524 if (!projectLogs) {
525 resp.status(404);
526 resp.write(JSON.stringify({ error: "No logs found for this project" }));
527 return;
528 }
gio3a921b82025-05-10 07:36:09 +0000529 const serviceLog = projectLogs.get(service);
530 if (!serviceLog) {
531 resp.status(404);
532 resp.write(JSON.stringify({ error: "No logs found for this service" }));
533 return;
534 }
gio3a921b82025-05-10 07:36:09 +0000535 resp.status(200);
536 resp.write(JSON.stringify({ logs: serviceLog }));
537 } catch (e) {
538 console.log(e);
539 resp.status(500);
540 resp.write(JSON.stringify({ error: "Failed to get service logs" }));
541 } finally {
542 resp.end();
543 }
544};
545
gio7d813702025-05-08 18:29:52 +0000546const WorkerSchema = z.object({
gio3a921b82025-05-10 07:36:09 +0000547 service: z.string(),
gio7d813702025-05-08 18:29:52 +0000548 address: z.string().url(),
gio3a921b82025-05-10 07:36:09 +0000549 logs: z.optional(z.string()),
gio7d813702025-05-08 18:29:52 +0000550});
551
552const handleRegisterWorker: express.Handler = async (req, resp) => {
553 try {
554 const projectId = Number(req.params["projectId"]);
gio7d813702025-05-08 18:29:52 +0000555 const result = WorkerSchema.safeParse(req.body);
gio7d813702025-05-08 18:29:52 +0000556 if (!result.success) {
557 resp.status(400);
558 resp.write(
559 JSON.stringify({
560 error: "Invalid request data",
561 details: result.error.format(),
562 }),
563 );
564 return;
565 }
gio3a921b82025-05-10 07:36:09 +0000566 const { service, address, logs: log } = result.data;
gio7d813702025-05-08 18:29:52 +0000567 const projectWorkers = workers.get(projectId) || [];
gio7d813702025-05-08 18:29:52 +0000568 if (!projectWorkers.includes(address)) {
569 projectWorkers.push(address);
570 }
gio7d813702025-05-08 18:29:52 +0000571 workers.set(projectId, projectWorkers);
gio3a921b82025-05-10 07:36:09 +0000572 if (log) {
573 const svcLogs: Map<string, string> = logs.get(projectId) || new Map();
574 svcLogs.set(service, log);
575 logs.set(projectId, svcLogs);
576 }
gio7d813702025-05-08 18:29:52 +0000577 resp.status(200);
578 resp.write(
579 JSON.stringify({
580 success: true,
gio7d813702025-05-08 18:29:52 +0000581 }),
582 );
583 } catch (e) {
584 console.log(e);
585 resp.status(500);
586 resp.write(JSON.stringify({ error: "Failed to register worker" }));
587 } finally {
588 resp.end();
589 }
590};
591
592const handleReload: express.Handler = async (req, resp) => {
593 try {
594 const projectId = Number(req.params["projectId"]);
595 const projectWorkers = workers.get(projectId) || [];
gio09fcab52025-05-12 14:05:07 +0000596 const project = await db.project.findUnique({
597 where: {
598 id: projectId,
599 userId: resp.locals.userId,
600 },
601 });
602 if (project == null) {
603 resp.status(404);
604 resp.write(JSON.stringify({ error: "Project not found" }));
605 return;
606 }
gio7d813702025-05-08 18:29:52 +0000607 if (projectWorkers.length === 0) {
608 resp.status(404);
609 resp.write(JSON.stringify({ error: "No workers registered for this project" }));
610 return;
611 }
gio7d813702025-05-08 18:29:52 +0000612 await Promise.all(
613 projectWorkers.map(async (workerAddress) => {
614 try {
615 const updateEndpoint = `${workerAddress}/update`;
616 await axios.post(updateEndpoint);
gio0b4002c2025-05-11 15:48:51 +0000617 // eslint-disable-next-line @typescript-eslint/no-explicit-any
618 } catch (error: any) {
gio7d813702025-05-08 18:29:52 +0000619 console.log(`Failed to update worker ${workerAddress}: ${error.message || "Unknown error"}`);
620 }
621 }),
622 );
gio7d813702025-05-08 18:29:52 +0000623 resp.status(200);
624 resp.write(JSON.stringify({ success: true }));
625 } catch (e) {
626 console.log(e);
627 resp.status(500);
628 resp.write(JSON.stringify({ error: "Failed to reload workers" }));
629 } finally {
630 resp.end();
631 }
632};
633
gio09fcab52025-05-12 14:05:07 +0000634const auth = (req: express.Request, resp: express.Response, next: express.NextFunction) => {
635 const userId = req.get("x-forwarded-userid");
gio3ed59592025-05-14 16:51:09 +0000636 const username = req.get("x-forwarded-user");
637 if (userId == null || username == null) {
gio09fcab52025-05-12 14:05:07 +0000638 resp.status(401);
639 resp.write("Unauthorized");
640 resp.end();
641 return;
642 }
643 resp.locals.userId = userId;
gio3ed59592025-05-14 16:51:09 +0000644 resp.locals.username = username;
gio09fcab52025-05-12 14:05:07 +0000645 next();
646};
647
giod0026612025-05-08 13:00:36 +0000648async function start() {
649 await db.$connect();
650 const app = express();
651 app.use(express.json());
gio09fcab52025-05-12 14:05:07 +0000652 app.use(auth);
giod0026612025-05-08 13:00:36 +0000653 app.post("/api/project/:projectId/saved", handleSave);
gio818da4e2025-05-12 14:45:35 +0000654 app.get("/api/project/:projectId/saved/deploy", handleSavedGet("deploy"));
655 app.get("/api/project/:projectId/saved/draft", handleSavedGet("draft"));
giod0026612025-05-08 13:00:36 +0000656 app.post("/api/project/:projectId/deploy", handleDeploy);
657 app.get("/api/project/:projectId/status", handleStatus);
658 app.delete("/api/project/:projectId", handleDelete);
659 app.get("/api/project", handleProjectAll);
660 app.post("/api/project", handleProjectCreate);
661 app.get("/api/project/:projectId/repos/github", handleGithubRepos);
662 app.post("/api/project/:projectId/github-token", handleUpdateGithubToken);
663 app.get("/api/project/:projectId/env", handleEnv);
gio7d813702025-05-08 18:29:52 +0000664 app.post("/api/project/:projectId/reload", handleReload);
gio3a921b82025-05-10 07:36:09 +0000665 app.get("/api/project/:projectId/logs/:service", handleServiceLogs);
giobd37a2b2025-05-15 04:28:42 +0000666 app.post("/api/project/:projectId/remove-deployment", handleRemoveDeployment);
giod0026612025-05-08 13:00:36 +0000667 app.use("/", express.static("../front/dist"));
gio09fcab52025-05-12 14:05:07 +0000668
669 const api = express();
670 api.use(express.json());
671 api.post("/api/project/:projectId/workers", handleRegisterWorker);
672
giod0026612025-05-08 13:00:36 +0000673 app.listen(env.DODO_PORT_WEB, () => {
gio09fcab52025-05-12 14:05:07 +0000674 console.log("Web server started on port", env.DODO_PORT_WEB);
675 });
676
677 api.listen(env.DODO_PORT_API, () => {
678 console.log("Internal API server started on port", env.DODO_PORT_API);
giod0026612025-05-08 13:00:36 +0000679 });
680}
681
682start();