Canvas: Update edges on port removal

Change-Id: I5f550f0511257207db4a2f0469957cc4449514bd
diff --git a/apps/canvas/back/index.js b/apps/canvas/back/index.js
index 425285c..6117e46 100644
--- a/apps/canvas/back/index.js
+++ b/apps/canvas/back/index.js
@@ -90,7 +90,7 @@
             select: {
                 state: true,
                 draft: true,
-            }
+            },
         });
         if (r == null) {
             resp.status(404);
@@ -133,7 +133,7 @@
             },
             select: {
                 instanceId: true,
-            }
+            },
         });
         if (p === null) {
             resp.status(404);
@@ -154,7 +154,7 @@
                 method: "post",
                 data: {
                     config: req.body.config,
-                }
+                },
             });
             if (r.status === 200) {
                 yield db.project.update({
@@ -176,7 +176,7 @@
                 method: "put",
                 data: {
                     config: req.body.config,
-                }
+                },
             });
             if (r.status === 200) {
                 yield db.project.update({
diff --git a/apps/canvas/back/index.ts b/apps/canvas/back/index.ts
index 6193a90..0f7a573 100644
--- a/apps/canvas/back/index.ts
+++ b/apps/canvas/back/index.ts
@@ -1,196 +1,202 @@
 import { PrismaClient } from "@prisma/client";
-import express, { response } from "express";
+import express from "express";
 import { env } from "node:process";
 import axios from "axios";
 
 const db = new PrismaClient();
 
 const handleProjectCreate: express.Handler = async (req, resp) => {
-    try {
-        const { id } = await db.project.create({
-            data: {
-                userId: "gio", // req.get("x-forwarded-userid")!,
-                name: req.body.name,
-            },
-        });
-        resp.status(200);
-        resp.header("Content-Type", "application/json");
-        resp.write(JSON.stringify({
-            id,
-        }))
-    } catch (e) {
-        console.log(e);
-        resp.status(500);
-    } finally {
-        resp.end();
-    }
+  try {
+    const { id } = await db.project.create({
+      data: {
+        userId: "gio", // req.get("x-forwarded-userid")!,
+        name: req.body.name,
+      },
+    });
+    resp.status(200);
+    resp.header("Content-Type", "application/json");
+    resp.write(
+      JSON.stringify({
+        id,
+      }),
+    );
+  } catch (e) {
+    console.log(e);
+    resp.status(500);
+  } finally {
+    resp.end();
+  }
 };
 
 const handleProjectAll: express.Handler = async (req, resp) => {
-    try {
-        const r = await db.project.findMany({
-            where: {
-                userId: "gio", // req.get("x-forwarded-userid")!,
-            },
-        });
-        resp.status(200);
-        resp.header("Content-Type", "application/json");
-        resp.write(JSON.stringify(r.map((p) => ({
-            id: p.id.toString(),
-            name: p.name,
-        }))))
-    } catch (e) {
-        console.log(e);
-        resp.status(500);
-    } finally {
-        resp.end();
-    }
+  try {
+    const r = await db.project.findMany({
+      where: {
+        userId: "gio", // req.get("x-forwarded-userid")!,
+      },
+    });
+    resp.status(200);
+    resp.header("Content-Type", "application/json");
+    resp.write(
+      JSON.stringify(
+        r.map((p) => ({
+          id: p.id.toString(),
+          name: p.name,
+        })),
+      ),
+    );
+  } catch (e) {
+    console.log(e);
+    resp.status(500);
+  } finally {
+    resp.end();
+  }
 };
 
 const handleSave: express.Handler = async (req, resp) => {
-    try {
-        await db.project.update({
-            where: {
-                id: Number(req.params["projectId"]),
-            },
-            data: {
-                draft: Buffer.from(JSON.stringify(req.body)),
-            },
-        });
-        resp.status(200);
-    } catch (e) {
-        console.log(e);
-        resp.status(500)
-    } finally {
-        resp.end();
-    }
+  try {
+    await db.project.update({
+      where: {
+        id: Number(req.params["projectId"]),
+      },
+      data: {
+        draft: Buffer.from(JSON.stringify(req.body)),
+      },
+    });
+    resp.status(200);
+  } catch (e) {
+    console.log(e);
+    resp.status(500);
+  } finally {
+    resp.end();
+  }
 };
 
 const handleSavedGet: express.Handler = async (req, resp) => {
-    try {
-        const r = await db.project.findUnique({
-            where: {
-                id: Number(req.params["projectId"]),
-            },
-            select: {
-                state: true,
-                draft: true,
-            }
-        });
-        if (r == null) {
-            resp.status(404);
+  try {
+    const r = await db.project.findUnique({
+      where: {
+        id: Number(req.params["projectId"]),
+      },
+      select: {
+        state: true,
+        draft: true,
+      },
+    });
+    if (r == null) {
+      resp.status(404);
+    } else {
+      resp.status(200);
+      resp.header("content-type", "application/json");
+      if (r.draft == null) {
+        if (r.state == null) {
+          resp.send({
+            nodes: [],
+            edges: [],
+            viewport: { x: 0, y: 0, zoom: 1 },
+          });
         } else {
-            resp.status(200);
-            resp.header("content-type", "application/json");
-            if (r.draft == null) {
-                if (r.state == null) {
-                    resp.send({
-                        nodes: [],
-                        edges: [],
-                        viewport: { x: 0, y: 0, zoom: 1 },
-                    });
-                } else {
-                    resp.send(JSON.parse(Buffer.from(r.state).toString("utf8")));
-                }
-            } else {
-                resp.send(JSON.parse(Buffer.from(r.draft).toString("utf8")));
-            }
+          resp.send(JSON.parse(Buffer.from(r.state).toString("utf8")));
         }
-    } catch (e) {
-        console.log(e);
-        resp.status(500);
-    } finally {
-        resp.end();
+      } else {
+        resp.send(JSON.parse(Buffer.from(r.draft).toString("utf8")));
+      }
     }
+  } catch (e) {
+    console.log(e);
+    resp.status(500);
+  } finally {
+    resp.end();
+  }
 };
 
 const handleDeploy: express.Handler = async (req, resp) => {
-    try {
-        const projectId = Number(req.params["projectId"]);
-        const state = Buffer.from(JSON.stringify(req.body.state));
-        const p = await db.project.findUnique({
-            where: {
-                id: projectId,
-            },
-            select: {
-                instanceId: true,
-            }
-        });
-        if (p === null) {
-            resp.status(404);
-            return;
-        }
-        await db.project.update({
-            where: {
-                id: projectId,
-            },
-            data: {
-                draft: state,
-            },
-        });
-        let r: { status: number, data: { id: string, deployKey: string } };
-        if (p.instanceId == null) {
-            r = await axios.request({
-                url: "http://appmanager.hgrz-appmanager.svc.cluster.local/api/dodo-app",
-                method: "post",
-                data: {
-                    config: req.body.config,
-                }
-            });
-            if (r.status === 200) {
-                await db.project.update({
-                    where: {
-                        id: projectId,
-                    },
-                    data: {
-                        state,
-                        draft: null,
-                        instanceId: r.data.id,
-                        deployKey: r.data.deployKey,
-                    },
-                });
-            }
-        } else {
-            r = await axios.request({
-                url: `http://appmanager.hgrz-appmanager.svc.cluster.local/api/dodo-app/${p.instanceId}`,
-                method: "put",
-                data: {
-                    config: req.body.config,
-                }
-            });
-            if (r.status === 200) {
-                await db.project.update({
-                    where: {
-                        id: projectId,
-                    },
-                    data: {
-                        state,
-                        draft: null,
-                    },
-                });
-            }
-        }
-    } catch (e) {
-        console.log(e);
-        resp.status(500);
-    } finally {
-        resp.end();
+  try {
+    const projectId = Number(req.params["projectId"]);
+    const state = Buffer.from(JSON.stringify(req.body.state));
+    const p = await db.project.findUnique({
+      where: {
+        id: projectId,
+      },
+      select: {
+        instanceId: true,
+      },
+    });
+    if (p === null) {
+      resp.status(404);
+      return;
     }
+    await db.project.update({
+      where: {
+        id: projectId,
+      },
+      data: {
+        draft: state,
+      },
+    });
+    let r: { status: number; data: { id: string; deployKey: string } };
+    if (p.instanceId == null) {
+      r = await axios.request({
+        url: "http://appmanager.hgrz-appmanager.svc.cluster.local/api/dodo-app",
+        method: "post",
+        data: {
+          config: req.body.config,
+        },
+      });
+      if (r.status === 200) {
+        await db.project.update({
+          where: {
+            id: projectId,
+          },
+          data: {
+            state,
+            draft: null,
+            instanceId: r.data.id,
+            deployKey: r.data.deployKey,
+          },
+        });
+      }
+    } else {
+      r = await axios.request({
+        url: `http://appmanager.hgrz-appmanager.svc.cluster.local/api/dodo-app/${p.instanceId}`,
+        method: "put",
+        data: {
+          config: req.body.config,
+        },
+      });
+      if (r.status === 200) {
+        await db.project.update({
+          where: {
+            id: projectId,
+          },
+          data: {
+            state,
+            draft: null,
+          },
+        });
+      }
+    }
+  } catch (e) {
+    console.log(e);
+    resp.status(500);
+  } finally {
+    resp.end();
+  }
 };
 
 async function start() {
-    await db.$connect();
-    const app = express();
-    app.use(express.json());
-    app.post("/api/project/:projectId/saved", handleSave);
-    app.get("/api/project/:projectId/saved", handleSavedGet);
-    app.post("/api/project/:projectId/deploy", handleDeploy);
-    app.get("/api/project", handleProjectAll);
-    app.post("/api/project", handleProjectCreate);
-    app.use("/", express.static("../front/dist"));
-    app.listen(env.DODO_PORT_WEB, () => {
-        console.log("started");
-    });
+  await db.$connect();
+  const app = express();
+  app.use(express.json());
+  app.post("/api/project/:projectId/saved", handleSave);
+  app.get("/api/project/:projectId/saved", handleSavedGet);
+  app.post("/api/project/:projectId/deploy", handleDeploy);
+  app.get("/api/project", handleProjectAll);
+  app.post("/api/project", handleProjectCreate);
+  app.use("/", express.static("../front/dist"));
+  app.listen(env.DODO_PORT_WEB, () => {
+    console.log("started");
+  });
 }
 
-start();
\ No newline at end of file
+start();
diff --git a/apps/canvas/back/prisma/dodo.db b/apps/canvas/back/prisma/dodo.db
index d382d23..21911b6 100644
--- a/apps/canvas/back/prisma/dodo.db
+++ b/apps/canvas/back/prisma/dodo.db
Binary files differ
diff --git a/apps/canvas/front/src/components/node-app.tsx b/apps/canvas/front/src/components/node-app.tsx
index 9bc5f92..a445c72 100644
--- a/apps/canvas/front/src/components/node-app.tsx
+++ b/apps/canvas/front/src/components/node-app.tsx
@@ -10,7 +10,7 @@
 import { Button } from './ui/button';
 import { Handle, Position } from "@xyflow/react";
 import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
-import { EditIcon } from "lucide-react";
+import { PencilIcon, XIcon } from "lucide-react";
 import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./ui/tooltip";
 
 export function NodeApp(node: ServiceNode) {
@@ -79,15 +79,17 @@
     }
   });
   const onSubmit = useCallback((values: z.infer<typeof portSchema>) => {
+    const portId = uuidv4();
     store.updateNodeData<"app">(id, {
       ports: (data.ports || []).concat({
-        id: uuidv4(),
+        id: portId,
         name: values.name,
         value: values.value,
       }),
       envVars: (data.envVars || []).concat({
         id: uuidv4(),
         source: null,
+        portId,
         name: `DODO_PORT_${values.name.toUpperCase()}`,
       }),
     });
@@ -201,6 +203,43 @@
       saveAlias(e, event.currentTarget.value, store);
     }
   }, [id, data, store]);
+  const removePort = useCallback((portId: string) => {
+    store.setEdges(store.edges.filter((e) => {
+      if (e.source !== id || e.sourceHandle !== "ports") {
+        return true;
+      }
+      if (e.targetHandle === "https") {
+        return false;
+      }
+      if (e.targetHandle === "env_var") {
+        const tn = store.nodes.find((n) => n.type === "app" && n.id == e.target);
+        console.log("111", tn!.data.envVars);
+        if (tn && (tn.data.envVars || []).find((ev) => ev.source === id && "portId" in ev && ev.portId === portId)) {
+          return false;
+        }
+      }
+      return true;
+    }));
+    store.nodes.filter((n) => n.type === "gateway-https" && n.data.https && n.data.https.serviceId === id && n.data.https.portId === portId).forEach((n) => {
+      store.updateNodeData<"gateway-https">(n.id, {
+        https: undefined,
+      });
+    });
+    store.nodes.filter((n) => n.type === "app" && n.data.envVars).forEach((n) => {
+      store.updateNodeData<"app">(n.id, {
+        envVars: n.data.envVars.filter((ev) => {
+          if (ev.source === id && "portId" in ev && ev.portId === portId) {
+            return false;
+          }
+          return true;
+        })
+      });
+    })
+    store.updateNodeData<"app">(id, {
+      ports: (data.ports || []).filter((p) => p.id !== portId),
+      envVars: (data.envVars || []).filter((ev) => !(ev.source === null && "portId" in ev && ev.portId === portId)),
+    });
+  }, [id, data, store]);
   return (
     <>
       <Form {...form}>
@@ -242,7 +281,7 @@
       </Form>
       Ports
       <ul>
-        {data && data.ports && data.ports.map((p) => (<li key={p.id}>{p.name} - {p.value}</li>))}
+        {data && data.ports && data.ports.map((p) => (<li key={p.id}><Button size={"icon"} variant={"ghost"} onClick={() => removePort(p.id)}><XIcon /></Button> {p.name} - {p.value}</li>))}
       </ul>
       <Form {...portForm}>
         <form className="flex flex-row space-x-1" onSubmit={portForm.handleSubmit(onSubmit)}>
@@ -286,7 +325,7 @@
                 <TooltipProvider>
                   <Tooltip>
                     <TooltipTrigger>
-                      <Button size={"icon"} variant={"ghost"}><EditIcon /></Button>
+                      <Button size={"icon"} variant={"ghost"}><PencilIcon /></Button>
                       {value}
                     </TooltipTrigger>
                     <TooltipContent>
diff --git a/apps/canvas/front/src/lib/state.ts b/apps/canvas/front/src/lib/state.ts
index 94bc77a..55fbc5d 100644
--- a/apps/canvas/front/src/lib/state.ts
+++ b/apps/canvas/front/src/lib/state.ts
@@ -195,6 +195,13 @@
   name: string;
   alias: string;
   isEditting: boolean;
+} | {
+  id: string;
+  source: string | null;
+  portId: string;
+  name: string;
+  alias: string;
+  isEditting: boolean;
 };
 
 export type EnvVar = {
@@ -202,11 +209,15 @@
   value: string;
 };
 
+export function nodeEnvVarNamePort(n: AppNode, portName: string): string {
+  return `DODO_SERVICE_${n.data.label.toUpperCase()}_ADDRESS_${portName.toUpperCase()}`;
+}
+
 export function nodeEnvVarNames(n: AppNode): string[] {
   switch (n.type) {
     case "app": return [
       `DODO_SERVICE_${n.data.label.toUpperCase()}_ADDRESS`, 
-      ...(n.data.ports || []).map((p) => `DODO_SERVICE_${n.data.label.toUpperCase()}_ADDRESS_${p.name.toUpperCase()}`),
+      ...(n.data.ports || []).map((p) => nodeEnvVarNamePort(n, p.name)),
     ];
     case "github": return [];
     case "gateway-https": return [];
@@ -421,6 +432,28 @@
         });
       }
     }
+    if (c.sourceHandle === "ports" && c.targetHandle === "env_var") {
+      const sourcePorts = sn.data.ports || [];
+      const id = uuidv4();
+      if (sourcePorts.length === 1) {
+        updateNode(c.target, {
+          ...tn,
+          data: {
+            ...tn.data,
+            envVars: [
+              ...(tn.data.envVars || []),
+              {
+                id: id,
+                source: c.source,
+                name: nodeEnvVarNamePort(sn, sourcePorts[0].name),
+                portId: sourcePorts[0].id,
+                isEditting: false,
+              },
+            ],
+          },
+        });
+      }
+    }
     if (c.sourceHandle === "volume") {
       updateNodeData<"volume">(c.source, {
         attachedTo: ((sn as VolumeNode).data.attachedTo || []).concat(c.source),