Canvas: Generate graph state out of dodo-app config

Restructure code, create shared config lib.

Change-Id: I2cf06d35c486d4557484daf8618a2c215316fa7e
diff --git a/apps/canvas/config/src/graph.ts b/apps/canvas/config/src/graph.ts
new file mode 100644
index 0000000..e8741f9
--- /dev/null
+++ b/apps/canvas/config/src/graph.ts
@@ -0,0 +1,320 @@
+import { z } from "zod";
+import { Node } from "@xyflow/react";
+import { Domain, ServiceType, VolumeType } from "./types.js";
+
+export const serviceAnalyzisSchema = z.object({
+	name: z.string(),
+	location: z.string(),
+	configVars: z.array(
+		z.object({
+			name: z.string(),
+			category: z.enum(["CommandLineFlag", "EnvironmentVariable"]),
+			type: z.optional(z.enum(["String", "Number", "Boolean"])),
+			semanticType: z.optional(
+				z.enum([
+					"EXPANDED_ENV_VAR",
+					"PORT",
+					"FILESYSTEM_PATH",
+					"DATABASE_URL",
+					"SQLITE_PATH",
+					"POSTGRES_URL",
+					"POSTGRES_PASSWORD",
+					"POSTGRES_USER",
+					"POSTGRES_DB",
+					"POSTGRES_PORT",
+					"POSTGRES_HOST",
+					"POSTGRES_SSL",
+					"MONGO_URL",
+					"MONGO_PASSWORD",
+					"MONGO_USER",
+					"MONGO_DB",
+					"MONGO_PORT",
+					"MONGO_HOST",
+					"MONGO_SSL",
+				]),
+			),
+		}),
+	),
+});
+
+export type BoundEnvVar =
+	| {
+			id: string;
+			source: string | null;
+	  }
+	| {
+			id: string;
+			source: string | null;
+			name: string;
+			isEditting: boolean;
+	  }
+	| {
+			id: string;
+			source: string | null;
+			name: string;
+			alias: string;
+			isEditting: boolean;
+	  }
+	| {
+			id: string;
+			source: string | null;
+			portId: string;
+			name: string;
+			alias: string;
+			isEditting: boolean;
+	  };
+
+export type EnvVar = {
+	name: string;
+	value: string;
+};
+
+export type InitData = {
+	label: string;
+	envVars: BoundEnvVar[];
+	ports: Port[];
+};
+
+export type NodeData = InitData & {
+	activeField?: string | undefined;
+	state?: string | null;
+};
+
+export type PortConnectedTo = {
+	serviceId: string;
+	portId: string;
+};
+
+export type NetworkData = NodeData & {
+	domain: string;
+};
+
+export type NetworkNode = Node<NetworkData> & {
+	type: "network";
+};
+
+export type GatewayHttpsData = NodeData & {
+	readonly?: boolean;
+	network?: string;
+	subdomain?: string;
+	https?: PortConnectedTo;
+	auth?: {
+		enabled: boolean;
+		groups: string[];
+		noAuthPathPatterns: string[];
+	};
+};
+
+export type GatewayHttpsNode = Node<GatewayHttpsData> & {
+	type: "gateway-https";
+};
+
+export type GatewayTCPData = NodeData & {
+	readonly?: boolean;
+	network?: string;
+	subdomain?: string;
+	exposed: PortConnectedTo[];
+	selected?: {
+		serviceId?: string;
+		portId?: string;
+	};
+};
+
+export type GatewayTCPNode = Node<GatewayTCPData> & {
+	type: "gateway-tcp";
+};
+
+export type Port = {
+	id: string;
+	name: string;
+	value: number;
+};
+
+export type ServiceData = NodeData & {
+	type: ServiceType;
+	repository?:
+		| {
+				id: number;
+				repoNodeId: string;
+		  }
+		| {
+				id: number;
+				repoNodeId: string;
+				branch: string;
+		  }
+		| {
+				id: number;
+				repoNodeId: string;
+				branch: string;
+				rootDir: string;
+		  };
+	env: string[];
+	volume: string[];
+	preBuildCommands: string;
+	isChoosingPortToConnect: boolean;
+	dev?:
+		| {
+				enabled: false;
+				expose?: Domain;
+		  }
+		| {
+				enabled: true;
+				expose?: Domain;
+				codeServerNodeId: string;
+				sshNodeId: string;
+		  };
+	info?: z.infer<typeof serviceAnalyzisSchema>;
+};
+
+export type ServiceNode = Node<ServiceData> & {
+	type: "app";
+};
+
+export type VolumeData = NodeData & {
+	type: VolumeType;
+	size: string;
+	attachedTo: string[];
+};
+
+export type VolumeNode = Node<VolumeData> & {
+	type: "volume";
+};
+
+export type PostgreSQLData = NodeData & {
+	volumeId: string;
+};
+
+export type PostgreSQLNode = Node<PostgreSQLData> & {
+	type: "postgresql";
+};
+
+export type MongoDBData = NodeData & {
+	volumeId: string;
+};
+
+export type MongoDBNode = Node<MongoDBData> & {
+	type: "mongodb";
+};
+
+export type GithubData = NodeData & {
+	repository?: {
+		id: number;
+		sshURL: string;
+		fullName: string;
+	};
+};
+
+export type GithubNode = Node<GithubData> & {
+	type: "github";
+};
+
+export type NANode = Node<NodeData> & {
+	type: undefined;
+};
+
+export type AppNode =
+	| NetworkNode
+	| GatewayHttpsNode
+	| GatewayTCPNode
+	| ServiceNode
+	| VolumeNode
+	| PostgreSQLNode
+	| MongoDBNode
+	| GithubNode
+	| NANode;
+
+export type NodeType = Exclude<Pick<AppNode, "type">["type"], undefined>;
+
+export const networkSchema = z.object({
+	name: z.string().min(1),
+	domain: z.string().min(1),
+	hasAuth: z.boolean(),
+});
+
+export type Network = z.infer<typeof networkSchema>;
+
+export 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 serviceInfoSchema = z.object({
+	name: z.string(),
+	workers: z.array(
+		z.object({
+			id: z.string(),
+			commit: z.optional(
+				z.object({
+					hash: z.string(),
+					message: z.string(),
+				}),
+			),
+			commands: z.optional(
+				z.array(
+					z.object({
+						command: z.string(),
+						state: z.string(),
+					}),
+				),
+			),
+		}),
+	),
+});
+
+export const envSchema = z.object({
+	managerAddr: z.optional(z.string().min(1)),
+	instanceId: z.optional(z.string().min(1)),
+	deployKeyPublic: z.optional(z.nullable(z.string().min(1))),
+	networks: z.array(networkSchema).default([]),
+	integrations: z.object({
+		github: z.boolean(),
+	}),
+	services: z.array(serviceInfoSchema),
+	user: z.object({
+		id: z.string(),
+		username: z.string(),
+	}),
+	access: z.array(accessSchema),
+});
+
+export type ServiceInfo = z.infer<typeof serviceInfoSchema>;
+export type Env = z.infer<typeof envSchema>;