DodoApp: Use JSON file for configuration.

Specify json schema so code editors can validate user input.
Update auth proxy to disable auth on specified paths.

Change-Id: Ic6667d802a9553444d3630c4ff73f4b33304ccfd
diff --git a/core/installer/welcome/dodo_app.go b/core/installer/welcome/dodo_app.go
index 4307866..d71bf83 100644
--- a/core/installer/welcome/dodo_app.go
+++ b/core/installer/welcome/dodo_app.go
@@ -35,12 +35,16 @@
 //go:embed all:app-tmpl
 var appTmplsFS embed.FS
 
+//go:embed stat/schemas/app.schema.json
+var dodoAppJsonSchema []byte
+
 const (
 	ConfigRepoName = "config"
 	appConfigsFile = "/apps.json"
 	loginPath      = "/login"
 	logoutPath     = "/logout"
 	staticPath     = "/stat/"
+	schemasPath    = "/schemas/"
 	apiPublicData  = "/api/public-data"
 	apiCreateApp   = "/api/apps"
 	sessionCookie  = "dodo-app-session"
@@ -94,6 +98,7 @@
 	port              int
 	apiPort           int
 	self              string
+	selfPublic        string
 	repoPublicAddr    string
 	sshKey            string
 	gitRepoPublicKey  string
@@ -128,6 +133,7 @@
 	port int,
 	apiPort int,
 	self string,
+	selfPublic string,
 	repoPublicAddr string,
 	sshKey string,
 	gitRepoPublicKey string,
@@ -163,6 +169,7 @@
 		port,
 		apiPort,
 		self,
+		selfPublic,
 		repoPublicAddr,
 		sshKey,
 		gitRepoPublicKey,
@@ -218,6 +225,7 @@
 	go func() {
 		r := mux.NewRouter()
 		r.Use(s.mwAuth)
+		r.HandleFunc(schemasPath+"app.schema.json", s.handleSchema).Methods(http.MethodGet)
 		r.PathPrefix(staticPath).Handler(cachingHandler{http.FileServer(http.FS(statAssets))})
 		r.HandleFunc(logoutPath, s.handleLogout).Methods(http.MethodGet)
 		r.HandleFunc(apiPublicData, s.handleAPIPublicData)
@@ -320,6 +328,7 @@
 		if strings.HasSuffix(r.URL.Path, loginPath) ||
 			strings.HasPrefix(r.URL.Path, logoutPath) ||
 			strings.HasPrefix(r.URL.Path, staticPath) ||
+			strings.HasPrefix(r.URL.Path, schemasPath) ||
 			strings.HasPrefix(r.URL.Path, apiPublicData) ||
 			strings.HasPrefix(r.URL.Path, apiCreateApp) {
 			next.ServeHTTP(w, r)
@@ -340,6 +349,11 @@
 	})
 }
 
+func (s *DodoAppServer) handleSchema(w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Content-Type", "application/schema+json")
+	w.Write(dodoAppJsonSchema)
+}
+
 func (s *DodoAppServer) handleLogout(w http.ResponseWriter, r *http.Request) {
 	// TODO(gio): move to UserGetter
 	http.SetCookie(w, &http.Cookie{
@@ -1017,7 +1031,7 @@
 	if err != nil {
 		return err
 	}
-	appCfg, err := soft.ReadFile(appRepo, "app.cue")
+	appCfg, err := soft.ReadFile(appRepo, "app.json")
 	if err != nil {
 		return err
 	}
@@ -1025,7 +1039,7 @@
 	if err != nil {
 		return err
 	}
-	return s.createAppForBranch(appRepo, appName, toBranch, user, network, map[string][]byte{"app.cue": branchCfg})
+	return s.createAppForBranch(appRepo, appName, toBranch, user, network, map[string][]byte{"app.json": branchCfg})
 }
 
 func (s *DodoAppServer) createAppForBranch(
@@ -1238,7 +1252,7 @@
 	if err != nil {
 		return installer.ReleaseResources{}, err
 	}
-	appCfg, err := soft.ReadFile(repo, "app.cue")
+	appCfg, err := soft.ReadFile(repo, "app.json")
 	if err != nil {
 		return installer.ReleaseResources{}, err
 	}
@@ -1316,7 +1330,7 @@
 	if err != nil {
 		return nil, err
 	}
-	return appTmpl.Render(network, subdomain)
+	return appTmpl.Render(fmt.Sprintf("%s/stat/schemas/dodo_app.jsonschema", s.selfPublic), network, subdomain)
 }
 
 func generatePassword() string {
@@ -1677,7 +1691,9 @@
 }
 
 func createDevBranchAppConfig(from []byte, branch, username string) (string, []byte, error) {
-	cfg, err := installer.ParseCueAppConfig(installer.CueAppData{"app.cue": from})
+	cfg, err := installer.ParseCueAppConfig(installer.CueAppData{
+		"app.cue": from,
+	})
 	if err != nil {
 		return "", nil, err
 	}