Canvas: Rework Deployment/Gateways tab

Change-Id: I938262b9a6ba2af060531e7dcdf91ddd66721385
diff --git a/apps/canvas/back/prisma/migrations/20250517144829_access/migration.sql b/apps/canvas/back/prisma/migrations/20250517144829_access/migration.sql
new file mode 100644
index 0000000..0127521
--- /dev/null
+++ b/apps/canvas/back/prisma/migrations/20250517144829_access/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "Project" ADD COLUMN "access" TEXT;
diff --git a/apps/canvas/back/prisma/schema.prisma b/apps/canvas/back/prisma/schema.prisma
index 09ddc69..68ff022 100644
--- a/apps/canvas/back/prisma/schema.prisma
+++ b/apps/canvas/back/prisma/schema.prisma
@@ -22,4 +22,5 @@
   instanceId String?
   deployKey String?
   githubToken String?
+  access String?
 }
\ No newline at end of file
diff --git a/apps/canvas/back/src/app_manager.ts b/apps/canvas/back/src/app_manager.ts
index 2f62ddd..803192c 100644
--- a/apps/canvas/back/src/app_manager.ts
+++ b/apps/canvas/back/src/app_manager.ts
@@ -1,9 +1,54 @@
 import axios from "axios";
 import { z } from "zod";
 
+const accessSchema = z.discriminatedUnion("type", [
+	z.object({
+		type: z.literal("https"),
+		name: z.string(),
+		address: z.string(),
+	}),
+	z.object({
+		type: z.literal("ssh"),
+		name: z.string(),
+		host: z.string(),
+		port: z.number(),
+	}),
+	z.object({
+		type: z.literal("tcp"),
+		name: z.string(),
+		host: z.string(),
+		port: z.number(),
+	}),
+	z.object({
+		type: z.literal("udp"),
+		name: z.string(),
+		host: z.string(),
+		port: z.number(),
+	}),
+	z.object({
+		type: z.literal("postgresql"),
+		name: z.string(),
+		host: z.string(),
+		port: z.number(),
+		database: z.string(),
+		username: z.string(),
+		password: z.string(),
+	}),
+	z.object({
+		type: z.literal("mongodb"),
+		name: z.string(),
+		host: z.string(),
+		port: z.number(),
+		database: z.string(),
+		username: z.string(),
+		password: z.string(),
+	}),
+]);
+
 export const DeployResponseSchema = z.object({
 	id: z.string(),
 	deployKey: z.string(),
+	access: z.array(accessSchema),
 });
 
 export type DeployResponse = z.infer<typeof DeployResponseSchema>;
@@ -24,6 +69,7 @@
 		if (response.status !== 200) {
 			throw new Error(`Failed to deploy application: ${response.statusText}`);
 		}
+		console.log(response.data);
 		const result = DeployResponseSchema.safeParse(response.data);
 		if (!result.success) {
 			throw new Error(`Invalid deploy response format: ${result.error.message}`);
@@ -31,13 +77,20 @@
 		return result.data;
 	}
 
-	async update(instanceId: string, config: unknown): Promise<boolean> {
+	async update(instanceId: string, config: unknown): Promise<DeployResponse> {
 		const response = await axios.request({
 			url: `${this.baseUrl}/api/dodo-app/${instanceId}`,
 			method: "put",
 			data: { config },
 		});
-		return response.status === 200;
+		if (response.status !== 200) {
+			throw new Error(`Failed to update application: ${response.statusText}`);
+		}
+		const result = DeployResponseSchema.safeParse(response.data);
+		if (!result.success) {
+			throw new Error(`Invalid update response format: ${result.error.message}`);
+		}
+		return result.data;
 	}
 
 	async getStatus(instanceId: string): Promise<unknown> {
diff --git a/apps/canvas/back/src/index.ts b/apps/canvas/back/src/index.ts
index 6dede14..4dc942f 100644
--- a/apps/canvas/back/src/index.ts
+++ b/apps/canvas/back/src/index.ts
@@ -261,29 +261,25 @@
 						draft: null,
 						instanceId: deployResponse.id,
 						deployKey: deployResponse.deployKey,
+						access: JSON.stringify(deployResponse.access),
 					},
 				});
 				diff = { toAdd: extractGithubRepos(state) };
 				deployKey = deployResponse.deployKey;
 			} else {
-				const success = await appManager.update(p.instanceId, req.body.config);
-				if (success) {
-					diff = calculateRepoDiff(extractGithubRepos(p.state), extractGithubRepos(state));
-					deployKey = p.deployKey;
-					await db.project.update({
-						where: {
-							id: projectId,
-						},
-						data: {
-							state,
-							draft: null,
-						},
-					});
-				} else {
-					resp.status(500);
-					resp.write(JSON.stringify({ error: "Failed to update deployment" }));
-					return;
-				}
+				const deployResponse = await appManager.update(p.instanceId, req.body.config);
+				diff = calculateRepoDiff(extractGithubRepos(p.state), extractGithubRepos(state));
+				deployKey = p.deployKey;
+				await db.project.update({
+					where: {
+						id: projectId,
+					},
+					data: {
+						state,
+						draft: null,
+						access: JSON.stringify(deployResponse.access),
+					},
+				});
 			}
 			if (diff && p.githubToken && deployKey) {
 				const github = new GithubClient(p.githubToken);
@@ -388,6 +384,7 @@
 			data: {
 				instanceId: null,
 				deployKey: null,
+				access: null,
 				state: null,
 				draft: p.draft ?? p.state,
 			},
@@ -465,6 +462,7 @@
 			select: {
 				deployKey: true,
 				githubToken: true,
+				access: true,
 			},
 		});
 		if (!project) {
@@ -480,6 +478,7 @@
 				// TODO(gio): get from env or command line flags
 				managerAddr: "http://10.42.0.95:8081",
 				deployKey: project.deployKey,
+				access: JSON.parse(project.access ?? "[]"),
 				integrations: {
 					github: !!project.githubToken,
 				},
diff --git a/apps/canvas/back/start.sh b/apps/canvas/back/start.sh
new file mode 100755
index 0000000..23bc786
--- /dev/null
+++ b/apps/canvas/back/start.sh
@@ -0,0 +1 @@
+DODO_PORT_WEB=8080 DODO_PORT_API=8081 npm run start