blob: cd8249d1cc32bc9ce3d0c5cbe42a1cc10e6fb9a0 [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,
giod0026612025-05-08 13:00:36 +0000264 },
265 });
gio3ed59592025-05-14 16:51:09 +0000266 diff = { toAdd: extractGithubRepos(state) };
267 deployKey = deployResponse.deployKey;
268 } else {
269 const success = await appManager.update(p.instanceId, req.body.config);
270 if (success) {
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 },
281 });
282 } else {
283 resp.status(500);
284 resp.write(JSON.stringify({ error: "Failed to update deployment" }));
285 return;
giod0026612025-05-08 13:00:36 +0000286 }
287 }
gio3ed59592025-05-14 16:51:09 +0000288 if (diff && p.githubToken && deployKey) {
289 const github = new GithubClient(p.githubToken);
290 await manageGithubKeys(github, deployKey, diff);
giod0026612025-05-08 13:00:36 +0000291 }
gio3ed59592025-05-14 16:51:09 +0000292 resp.status(200);
293 } catch (error) {
294 console.error("Deployment error:", error);
295 resp.status(500);
296 resp.write(JSON.stringify({ error: "Deployment failed" }));
giod0026612025-05-08 13:00:36 +0000297 }
298 } catch (e) {
299 console.log(e);
300 resp.status(500);
301 } finally {
302 resp.end();
303 }
304};
305
306const handleStatus: express.Handler = async (req, resp) => {
307 try {
308 const projectId = Number(req.params["projectId"]);
309 const p = await db.project.findUnique({
310 where: {
311 id: projectId,
gio09fcab52025-05-12 14:05:07 +0000312 userId: resp.locals.userId,
giod0026612025-05-08 13:00:36 +0000313 },
314 select: {
315 instanceId: true,
316 },
317 });
giod0026612025-05-08 13:00:36 +0000318 if (p === null) {
319 resp.status(404);
320 return;
321 }
322 if (p.instanceId == null) {
323 resp.status(404);
324 return;
325 }
gio3ed59592025-05-14 16:51:09 +0000326 try {
327 const status = await appManager.getStatus(p.instanceId);
328 resp.status(200);
329 resp.write(JSON.stringify(status));
330 } catch (error) {
331 console.error("Error getting status:", error);
332 resp.status(500);
giod0026612025-05-08 13:00:36 +0000333 }
334 } catch (e) {
335 console.log(e);
336 resp.status(500);
337 } finally {
338 resp.end();
339 }
340};
341
giobd37a2b2025-05-15 04:28:42 +0000342const handleRemoveDeployment: express.Handler = async (req, resp) => {
343 try {
344 const projectId = Number(req.params["projectId"]);
345 const p = await db.project.findUnique({
346 where: {
347 id: projectId,
348 userId: resp.locals.userId,
349 },
350 select: {
351 instanceId: true,
352 githubToken: true,
353 deployKey: true,
354 state: true,
355 draft: true,
356 },
357 });
358 if (p === null) {
359 resp.status(404);
360 resp.write(JSON.stringify({ error: "Project not found" }));
361 return;
362 }
363 if (p.instanceId == null) {
364 resp.status(400);
365 resp.write(JSON.stringify({ error: "Project not deployed" }));
366 return;
367 }
368 const removed = await appManager.removeInstance(p.instanceId);
369 if (!removed) {
370 resp.status(500);
371 resp.write(JSON.stringify({ error: "Failed to remove deployment from cluster" }));
372 return;
373 }
374 if (p.githubToken && p.deployKey && p.state) {
375 try {
376 const github = new GithubClient(p.githubToken);
377 const repos = extractGithubRepos(p.state);
378 const diff = { toDelete: repos, toAdd: [] };
379 await manageGithubKeys(github, p.deployKey, diff);
380 } catch (error) {
381 console.error("Error removing GitHub deploy keys:", error);
382 }
383 }
384 await db.project.update({
385 where: {
386 id: projectId,
387 },
388 data: {
389 instanceId: null,
390 deployKey: null,
391 state: null,
392 draft: p.draft ?? p.state,
393 },
394 });
395 resp.status(200);
396 resp.write(JSON.stringify({ success: true }));
397 } catch (e) {
398 console.error("Error removing deployment:", e);
399 resp.status(500);
400 resp.write(JSON.stringify({ error: "Internal server error" }));
401 } finally {
402 resp.end();
403 }
404};
405
giod0026612025-05-08 13:00:36 +0000406const handleGithubRepos: express.Handler = async (req, resp) => {
407 try {
408 const projectId = Number(req.params["projectId"]);
409 const project = await db.project.findUnique({
gio09fcab52025-05-12 14:05:07 +0000410 where: {
411 id: projectId,
412 userId: resp.locals.userId,
413 },
414 select: {
415 githubToken: true,
416 },
giod0026612025-05-08 13:00:36 +0000417 });
giod0026612025-05-08 13:00:36 +0000418 if (!project?.githubToken) {
419 resp.status(400);
420 resp.write(JSON.stringify({ error: "GitHub token not configured" }));
421 return;
422 }
giod0026612025-05-08 13:00:36 +0000423 const github = new GithubClient(project.githubToken);
424 const repositories = await github.getRepositories();
giod0026612025-05-08 13:00:36 +0000425 resp.status(200);
426 resp.header("Content-Type", "application/json");
427 resp.write(JSON.stringify(repositories));
428 } catch (e) {
429 console.log(e);
430 resp.status(500);
431 resp.write(JSON.stringify({ error: "Failed to fetch repositories" }));
432 } finally {
433 resp.end();
434 }
435};
436
437const handleUpdateGithubToken: express.Handler = async (req, resp) => {
438 try {
439 const projectId = Number(req.params["projectId"]);
440 const { githubToken } = req.body;
giod0026612025-05-08 13:00:36 +0000441 await db.project.update({
gio09fcab52025-05-12 14:05:07 +0000442 where: {
443 id: projectId,
444 userId: resp.locals.userId,
445 },
giod0026612025-05-08 13:00:36 +0000446 data: { githubToken },
447 });
giod0026612025-05-08 13:00:36 +0000448 resp.status(200);
449 } catch (e) {
450 console.log(e);
451 resp.status(500);
452 } finally {
453 resp.end();
454 }
455};
456
457const handleEnv: express.Handler = async (req, resp) => {
458 const projectId = Number(req.params["projectId"]);
459 try {
460 const project = await db.project.findUnique({
gio09fcab52025-05-12 14:05:07 +0000461 where: {
462 id: projectId,
463 userId: resp.locals.userId,
464 },
giod0026612025-05-08 13:00:36 +0000465 select: {
466 deployKey: true,
467 githubToken: true,
468 },
469 });
giod0026612025-05-08 13:00:36 +0000470 if (!project) {
471 resp.status(404);
472 resp.write(JSON.stringify({ error: "Project not found" }));
473 return;
474 }
gio3a921b82025-05-10 07:36:09 +0000475 const projectLogs = logs.get(projectId) || new Map();
476 const services = Array.from(projectLogs.keys());
giod0026612025-05-08 13:00:36 +0000477 resp.status(200);
478 resp.write(
479 JSON.stringify({
gio7d813702025-05-08 18:29:52 +0000480 // TODO(gio): get from env or command line flags
gio09fcab52025-05-12 14:05:07 +0000481 managerAddr: "http://10.42.0.211:8081",
giod0026612025-05-08 13:00:36 +0000482 deployKey: project.deployKey,
483 integrations: {
484 github: !!project.githubToken,
485 },
486 networks: [
487 {
488 name: "Public",
489 domain: "v1.dodo.cloud",
490 },
491 {
492 name: "Private",
493 domain: "p.v1.dodo.cloud",
494 },
495 ],
gio3a921b82025-05-10 07:36:09 +0000496 services,
gio3ed59592025-05-14 16:51:09 +0000497 user: {
498 id: resp.locals.userId,
499 username: resp.locals.username,
500 },
giod0026612025-05-08 13:00:36 +0000501 }),
502 );
503 } catch (error) {
504 console.error("Error checking integrations:", error);
505 resp.status(500);
506 resp.write(JSON.stringify({ error: "Internal server error" }));
507 } finally {
508 resp.end();
509 }
510};
511
gio3a921b82025-05-10 07:36:09 +0000512const handleServiceLogs: express.Handler = async (req, resp) => {
513 try {
514 const projectId = Number(req.params["projectId"]);
515 const service = req.params["service"];
gio09fcab52025-05-12 14:05:07 +0000516 const project = await db.project.findUnique({
517 where: {
518 id: projectId,
519 userId: resp.locals.userId,
520 },
521 });
522 if (project == null) {
523 resp.status(404);
524 resp.write(JSON.stringify({ error: "Project not found" }));
525 return;
526 }
gio3a921b82025-05-10 07:36:09 +0000527 const projectLogs = logs.get(projectId);
528 if (!projectLogs) {
529 resp.status(404);
530 resp.write(JSON.stringify({ error: "No logs found for this project" }));
531 return;
532 }
gio3a921b82025-05-10 07:36:09 +0000533 const serviceLog = projectLogs.get(service);
534 if (!serviceLog) {
535 resp.status(404);
536 resp.write(JSON.stringify({ error: "No logs found for this service" }));
537 return;
538 }
gio3a921b82025-05-10 07:36:09 +0000539 resp.status(200);
540 resp.write(JSON.stringify({ logs: serviceLog }));
541 } catch (e) {
542 console.log(e);
543 resp.status(500);
544 resp.write(JSON.stringify({ error: "Failed to get service logs" }));
545 } finally {
546 resp.end();
547 }
548};
549
gio7d813702025-05-08 18:29:52 +0000550const WorkerSchema = z.object({
gio3a921b82025-05-10 07:36:09 +0000551 service: z.string(),
gio7d813702025-05-08 18:29:52 +0000552 address: z.string().url(),
gio3a921b82025-05-10 07:36:09 +0000553 logs: z.optional(z.string()),
gio7d813702025-05-08 18:29:52 +0000554});
555
556const handleRegisterWorker: express.Handler = async (req, resp) => {
557 try {
558 const projectId = Number(req.params["projectId"]);
gio7d813702025-05-08 18:29:52 +0000559 const result = WorkerSchema.safeParse(req.body);
gio7d813702025-05-08 18:29:52 +0000560 if (!result.success) {
561 resp.status(400);
562 resp.write(
563 JSON.stringify({
564 error: "Invalid request data",
565 details: result.error.format(),
566 }),
567 );
568 return;
569 }
gio3a921b82025-05-10 07:36:09 +0000570 const { service, address, logs: log } = result.data;
gio7d813702025-05-08 18:29:52 +0000571 const projectWorkers = workers.get(projectId) || [];
gio7d813702025-05-08 18:29:52 +0000572 if (!projectWorkers.includes(address)) {
573 projectWorkers.push(address);
574 }
gio7d813702025-05-08 18:29:52 +0000575 workers.set(projectId, projectWorkers);
gio3a921b82025-05-10 07:36:09 +0000576 if (log) {
577 const svcLogs: Map<string, string> = logs.get(projectId) || new Map();
578 svcLogs.set(service, log);
579 logs.set(projectId, svcLogs);
580 }
gio7d813702025-05-08 18:29:52 +0000581 resp.status(200);
582 resp.write(
583 JSON.stringify({
584 success: true,
gio7d813702025-05-08 18:29:52 +0000585 }),
586 );
587 } catch (e) {
588 console.log(e);
589 resp.status(500);
590 resp.write(JSON.stringify({ error: "Failed to register worker" }));
591 } finally {
592 resp.end();
593 }
594};
595
596const handleReload: express.Handler = async (req, resp) => {
597 try {
598 const projectId = Number(req.params["projectId"]);
599 const projectWorkers = workers.get(projectId) || [];
gio09fcab52025-05-12 14:05:07 +0000600 const project = await db.project.findUnique({
601 where: {
602 id: projectId,
603 userId: resp.locals.userId,
604 },
605 });
606 if (project == null) {
607 resp.status(404);
608 resp.write(JSON.stringify({ error: "Project not found" }));
609 return;
610 }
gio7d813702025-05-08 18:29:52 +0000611 if (projectWorkers.length === 0) {
612 resp.status(404);
613 resp.write(JSON.stringify({ error: "No workers registered for this project" }));
614 return;
615 }
gio7d813702025-05-08 18:29:52 +0000616 await Promise.all(
617 projectWorkers.map(async (workerAddress) => {
618 try {
619 const updateEndpoint = `${workerAddress}/update`;
620 await axios.post(updateEndpoint);
gio0b4002c2025-05-11 15:48:51 +0000621 // eslint-disable-next-line @typescript-eslint/no-explicit-any
622 } catch (error: any) {
gio7d813702025-05-08 18:29:52 +0000623 console.log(`Failed to update worker ${workerAddress}: ${error.message || "Unknown error"}`);
624 }
625 }),
626 );
gio7d813702025-05-08 18:29:52 +0000627 resp.status(200);
628 resp.write(JSON.stringify({ success: true }));
629 } catch (e) {
630 console.log(e);
631 resp.status(500);
632 resp.write(JSON.stringify({ error: "Failed to reload workers" }));
633 } finally {
634 resp.end();
635 }
636};
637
gio09fcab52025-05-12 14:05:07 +0000638const auth = (req: express.Request, resp: express.Response, next: express.NextFunction) => {
639 const userId = req.get("x-forwarded-userid");
gio3ed59592025-05-14 16:51:09 +0000640 const username = req.get("x-forwarded-user");
641 if (userId == null || username == null) {
gio09fcab52025-05-12 14:05:07 +0000642 resp.status(401);
643 resp.write("Unauthorized");
644 resp.end();
645 return;
646 }
647 resp.locals.userId = userId;
gio3ed59592025-05-14 16:51:09 +0000648 resp.locals.username = username;
gio09fcab52025-05-12 14:05:07 +0000649 next();
650};
651
giod0026612025-05-08 13:00:36 +0000652async function start() {
653 await db.$connect();
654 const app = express();
655 app.use(express.json());
gio09fcab52025-05-12 14:05:07 +0000656 app.use(auth);
giod0026612025-05-08 13:00:36 +0000657 app.post("/api/project/:projectId/saved", handleSave);
gio818da4e2025-05-12 14:45:35 +0000658 app.get("/api/project/:projectId/saved/deploy", handleSavedGet("deploy"));
659 app.get("/api/project/:projectId/saved/draft", handleSavedGet("draft"));
giod0026612025-05-08 13:00:36 +0000660 app.post("/api/project/:projectId/deploy", handleDeploy);
661 app.get("/api/project/:projectId/status", handleStatus);
662 app.delete("/api/project/:projectId", handleDelete);
663 app.get("/api/project", handleProjectAll);
664 app.post("/api/project", handleProjectCreate);
665 app.get("/api/project/:projectId/repos/github", handleGithubRepos);
666 app.post("/api/project/:projectId/github-token", handleUpdateGithubToken);
667 app.get("/api/project/:projectId/env", handleEnv);
gio7d813702025-05-08 18:29:52 +0000668 app.post("/api/project/:projectId/reload", handleReload);
gio3a921b82025-05-10 07:36:09 +0000669 app.get("/api/project/:projectId/logs/:service", handleServiceLogs);
giobd37a2b2025-05-15 04:28:42 +0000670 app.post("/api/project/:projectId/remove-deployment", handleRemoveDeployment);
giod0026612025-05-08 13:00:36 +0000671 app.use("/", express.static("../front/dist"));
gio09fcab52025-05-12 14:05:07 +0000672
673 const api = express();
674 api.use(express.json());
675 api.post("/api/project/:projectId/workers", handleRegisterWorker);
676
giod0026612025-05-08 13:00:36 +0000677 app.listen(env.DODO_PORT_WEB, () => {
gio09fcab52025-05-12 14:05:07 +0000678 console.log("Web server started on port", env.DODO_PORT_WEB);
679 });
680
681 api.listen(env.DODO_PORT_API, () => {
682 console.log("Internal API server started on port", env.DODO_PORT_API);
giod0026612025-05-08 13:00:36 +0000683 });
684}
685
686start();