DodoApp: Add network definitions to app.schema.json

Change-Id: Id4c0b9b3e8cfc0475f3ecc0d9b47a6dbf676da6a
diff --git a/core/installer/server/dodo-app/static/schemas/app.schema.json b/core/installer/server/dodo-app/schemas/app.schema.json
similarity index 99%
rename from core/installer/server/dodo-app/static/schemas/app.schema.json
rename to core/installer/server/dodo-app/schemas/app.schema.json
index 5450358..60e4f0c 100644
--- a/core/installer/server/dodo-app/static/schemas/app.schema.json
+++ b/core/installer/server/dodo-app/schemas/app.schema.json
@@ -202,7 +202,7 @@
       "properties": {
         "network": {
           "type": "string",
-          "minLength": 1
+          "oneOf": {{ .Networks }}
         },
         "subdomain": {
           "type": "string",
diff --git a/core/installer/server/dodo-app/server.go b/core/installer/server/dodo-app/server.go
index 858afa8..609ce9f 100644
--- a/core/installer/server/dodo-app/server.go
+++ b/core/installer/server/dodo-app/server.go
@@ -16,6 +16,7 @@
 	"strconv"
 	"strings"
 	"sync"
+	ttemplate "text/template"
 	"time"
 
 	"golang.org/x/crypto/bcrypt"
@@ -40,8 +41,8 @@
 //go:embed static/*
 var staticAssets embed.FS
 
-//go:embed static/schemas/app.schema.json
-var dodoAppJsonSchema []byte
+//go:embed schemas/app.schema.json
+var dodoAppJsonSchema string
 
 const (
 	ConfigRepoName = "config"
@@ -123,6 +124,7 @@
 	fetchUsersAddr    string
 	reconciler        tasks.Reconciler
 	logs              map[string]string
+	schemaTmpl        *ttemplate.Template
 }
 
 type appConfig struct {
@@ -166,6 +168,10 @@
 	if err != nil {
 		return nil, err
 	}
+	schemaTmpl, err := ttemplate.New("app.schema.json").Parse(dodoAppJsonSchema)
+	if err != nil {
+		return nil, err
+	}
 	s := &Server{
 		&sync.Mutex{},
 		st,
@@ -194,6 +200,7 @@
 		fetchUsersAddr,
 		reconciler,
 		map[string]string{},
+		schemaTmpl,
 	}
 	config, err := client.GetRepo(ConfigRepoName)
 	if err != nil {
@@ -230,7 +237,7 @@
 	go func() {
 		r := mux.NewRouter()
 		r.Use(s.mwAuth)
-		r.HandleFunc(schemasPath+"app.schema.json", s.handleSchema).Methods(http.MethodGet)
+		r.HandleFunc(schemasPath+"{user}/app.schema.json", s.handleSchema).Methods(http.MethodGet)
 		r.PathPrefix(staticPath).Handler(server.NewCachingHandler(http.FileServer(http.FS(staticAssets))))
 		r.HandleFunc(logoutPath, s.handleLogout).Methods(http.MethodGet)
 		r.HandleFunc(apiPublicData, s.handleAPIPublicData)
@@ -356,9 +363,38 @@
 	})
 }
 
+type schemaNetwork struct {
+	Value string `json:"const"`
+}
+
 func (s *Server) handleSchema(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/schema+json")
-	w.Write(dodoAppJsonSchema)
+	vars := mux.Vars(r)
+	user, ok := vars["user"]
+	if !ok {
+		http.Error(w, "no user", http.StatusBadRequest)
+		return
+	}
+	networks, err := s.getNetworks(user)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+	var names []schemaNetwork
+	for _, n := range networks {
+		names = append(names, schemaNetwork{n.Name})
+	}
+	var tmp strings.Builder
+	if err := json.NewEncoder(&tmp).Encode(names); err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+	if err := s.schemaTmpl.Execute(w, map[string]any{
+		"Networks": tmp.String(),
+	}); err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
 }
 
 func (s *Server) handleLogout(w http.ResponseWriter, r *http.Request) {
@@ -1074,7 +1110,7 @@
 	if err != nil {
 		return err
 	}
-	files, err := s.renderAppConfigTemplate(appType, n, subdomain)
+	files, err := s.renderAppConfigTemplate(user, appType, n, subdomain)
 	if err != nil {
 		return err
 	}
@@ -1093,7 +1129,7 @@
 	if err != nil {
 		return err
 	}
-	network, branchCfg, err := createDevBranchAppConfig(appCfg, toBranch, user)
+	network, branchCfg, err := createDevBranchAppConfig(appCfg, toBranch, user, s.selfPublic)
 	if err != nil {
 		return err
 	}
@@ -1444,13 +1480,13 @@
 	return ret, nil
 }
 
-func (s *Server) renderAppConfigTemplate(appType string, network installer.Network, subdomain string) (map[string][]byte, error) {
+func (s *Server) renderAppConfigTemplate(user, appType string, network installer.Network, subdomain string) (map[string][]byte, error) {
 	appType = strings.Replace(appType, ":", "-", 1)
 	appTmpl, err := s.appTmpls.Find(appType)
 	if err != nil {
 		return nil, err
 	}
-	return appTmpl.Render(fmt.Sprintf("%s/schemas/app.schema.json", s.selfPublic), network, subdomain)
+	return appTmpl.Render(fmt.Sprintf("%s/schemas/%s/app.schema.json", s.selfPublic, user), network, subdomain)
 }
 
 func generatePassword() string {
@@ -1825,7 +1861,7 @@
 	return ret, nil
 }
 
-func createDevBranchAppConfig(from []byte, branch, username string) (string, []byte, error) {
+func createDevBranchAppConfig(from []byte, branch, username, publicAddr string) (string, []byte, error) {
 	cfg, err := installer.ParseCueAppConfig(installer.CueAppData{
 		"app.cue": from,
 	})
@@ -1864,6 +1900,7 @@
 		"enabled":  true,
 		"username": username,
 	}
+	newCfg["$schema"] = fmt.Sprintf("%s/schemas/%s/app.schema.json", publicAddr, username)
 	buf, err := json.MarshalIndent(newCfg, "", "\t")
 	if err != nil {
 		return "", nil, err
diff --git a/core/installer/server/dodo-app/server_test.go b/core/installer/server/dodo-app/server_test.go
index 8bc0c59..513594e 100644
--- a/core/installer/server/dodo-app/server_test.go
+++ b/core/installer/server/dodo-app/server_test.go
@@ -18,7 +18,7 @@
 		}
 	}
 }`)
-	network, newCfg, err := createDevBranchAppConfig(cfg, "foo", "bar")
+	network, newCfg, err := createDevBranchAppConfig(cfg, "foo", "bar", "https://foo.bar")
 	if err != nil {
 		t.Fatal(err)
 	}