package main import ( "flag" "io" "os" "sort" "text/template" "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/config" ) var ( directory = flag.String("directory", "./", "working directory: asset.yaml must be in this directory, files will be generated into parent directory") assetsDocs = flag.String("assets", "../../../../apps/docs/content/apis/assets/assets.mdx", "path where the assets.mdx will be generated") assetPrefix = flag.String("handler-prefix", "/assets/v1", "prefix of the handler paths") ) func main() { flag.Parse() configFile := *directory + "asset.yaml" authz, err := os.OpenFile(*directory+"../authz.go", os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0755) logging.OnError(err).Fatal("cannot open authz file") router, err := os.OpenFile(*directory+"../router.go", os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0755) logging.OnError(err).Fatal("cannot open router file") docs, err := os.OpenFile(*assetsDocs, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0755) logging.OnError(err).Fatal("cannot open docs file") GenerateAssetHandler(configFile, *assetPrefix, authz, router, docs) } type Method struct { Path string HasDarkMode bool Handlers []Handler } type Handler struct { Name string Comment string Type HandlerType Permission string } func (a Handler) Method() string { if a.Type == MethodTypeUpload { return "POST" } return "GET" } func (a Handler) PathSuffix() string { if a.Type == MethodTypePreview { return "/_preview" } return "" } func (a Handler) MethodReturn() string { if a.Type == MethodTypeUpload { return "Uploader" } if a.Type == MethodTypeDownload { return "Downloader" } if a.Type == MethodTypePreview { return "Downloader" } return "" } func (a Handler) HandlerType() string { if a.Type == MethodTypeUpload { return "UploadHandleFunc" } if a.Type == MethodTypeDownload { return "DownloadHandleFunc" } if a.Type == MethodTypePreview { return "DownloadHandleFunc" } return "" } type HandlerType string const ( MethodTypeUpload = "upload" MethodTypeDownload = "download" MethodTypePreview = "preview" ) type Services map[string]Service type Service struct { Prefix string Methods map[string]Method } type SortedService struct { Prefix string Methods []SortedMethod } type SortedMethod struct { Name string Method } func (s Services) toSorted() []SortedService { keys := make([]string, 0, len(s)) for k := range s { keys = append(keys, k) } sort.Strings(keys) sorted := make([]SortedService, len(keys)) for i, k := range keys { sorted[i] = SortedService{ Prefix: s[k].Prefix, Methods: s[k].toSortedMethods(), } } return sorted } func (s Service) toSortedMethods() []SortedMethod { keys := make([]string, 0, len(s.Methods)) for k := range s.Methods { keys = append(keys, k) } sort.Strings(keys) sorted := make([]SortedMethod, len(keys)) for i, k := range keys { sorted[i] = SortedMethod{ Name: k, Method: s.Methods[k], } } return sorted } func GenerateAssetHandler(configFilePath, handlerPrefix string, authz, router, docs io.Writer) { conf := new(struct { Services Services }) err := config.Read(conf, configFilePath) logging.Log("ASSETS-Dgbn4").OnError(err).Fatal("cannot read config") tmplAuthz, err := template.New("").Parse(authzTmpl) logging.Log("ASSETS-BGbbg").OnError(err).Fatal("cannot parse authz template") tmplRouter, err := template.New("").Parse(routerTmpl) logging.Log("ASSETS-gh4rq").OnError(err).Fatal("cannot parse router template") tmplDocs, err := template.New("").Parse(docsTmpl) logging.Log("ASSETS-FGdgs").OnError(err).Fatal("cannot parse docs template") data := &struct { GoPkgName string Name string Prefix string Services []SortedService }{ GoPkgName: "assets", Name: "AssetsService", Prefix: handlerPrefix, Services: conf.Services.toSorted(), } err = tmplAuthz.Execute(authz, data) logging.Log("ASSETS-BHngj").OnError(err).Fatal("cannot generate authz") err = tmplRouter.Execute(router, data) logging.Log("ASSETS-Bfd41").OnError(err).Fatal("cannot generate router") err = tmplDocs.Execute(docs, data) logging.Log("ASSETS-Bfd41").OnError(err).Fatal("cannot generate docs") } const authzTmpl = `// Code generated by assets generator. DO NOT EDIT. package {{.GoPkgName}} import ( "github.com/zitadel/zitadel/internal/api/authz" ) /** * {{.Name}} */ {{ $prefix := .Prefix }} var {{.Name}}_AuthMethods = authz.MethodMapping { {{ range $service := .Services}} {{ range $method := .Methods}} {{ range $handler := .Handlers}} {{ if $handler.Permission }} "{{$handler.Method}}:{{$prefix}}{{$service.Prefix}}{{$method.Path}}{{$handler.PathSuffix}}": authz.Option{ Permission: "{{$handler.Permission}}", }, {{ if $method.HasDarkMode }} "{{$handler.Method}}:{{$prefix}}{{$service.Prefix}}{{$method.Path}}/dark{{$handler.PathSuffix}}": authz.Option{ Permission: "{{$handler.Permission}}", }, {{end}} {{end}} {{end}} {{end}} {{end}} } ` const routerTmpl = `// Code generated by assets generator. DO NOT EDIT. package {{.GoPkgName}} import ( "github.com/gorilla/mux" http_mw "github.com/zitadel/zitadel/internal/api/http/middleware" "github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/static" ) type {{.Name}} interface { AuthInterceptor() *http_mw.AuthInterceptor Commands() *command.Commands ErrorHandler() ErrorHandler Storage() static.Storage {{ range $service := .Services}} {{ range $method := .Methods}} {{ range $handler := .Handlers}} {{$handler.Name}}{{$method.Name}}() {{if $handler.MethodReturn}}{{$handler.MethodReturn}}{{end}} {{ if $method.HasDarkMode }} {{$handler.Name}}{{$method.Name}}Dark() {{if $handler.MethodReturn}}{{$handler.MethodReturn}}{{end}} {{ end }} {{ end }} {{ end }} {{ end }} } func RegisterRoutes(router *mux.Router, s {{.Name}}) { router.Use(s.AuthInterceptor().Handler(HandlerPrefix)) {{ range $service := .Services}} {{ range $method := .Methods}} {{ range $handler := .Handlers}} router.Path("{{$service.Prefix}}{{$method.Path}}{{$handler.PathSuffix}}").Methods("{{$handler.Method}}").HandlerFunc({{if $handler.HandlerType}}{{$handler.HandlerType}}(s, {{end}}s.{{$handler.Name}}{{$method.Name}}(){{if $handler.HandlerType}}){{end}}) {{ if $method.HasDarkMode }} router.Path("{{$service.Prefix}}{{$method.Path}}/dark{{$handler.PathSuffix}}").Methods("{{$handler.Method}}").HandlerFunc({{if $handler.HandlerType}}{{$handler.HandlerType}}(s, {{end}}s.{{$handler.Name}}{{$method.Name}}Dark(){{if $handler.HandlerType}}){{end}}) {{ end }} {{ end }} {{ end }} {{ end }} } ` const docsTmpl = `--- title: zitadel/assets description: "Learn how to upload and manage instance, organization, or user assets such as logos and avatars using ZITADEL's AssetsService API endpoints. Full reference and examples included." --- {/* Code generated by assets generator. DO NOT EDIT. This file is checked in to support building docs without a Go environment. */} ## {{.Name}} {{ range $service := .Services}} {{ range $method := .Methods}} {{ range $handler := .Handlers}} ### {{$handler.Name}}{{$method.Name}}() > {{$handler.Name}}{{$method.Name}}() {{$handler.Method}}: {{$service.Prefix}}{{$method.Path}}{{$handler.PathSuffix}} {{ if $method.HasDarkMode }} ### {{$handler.Name}}{{$method.Name}}Dark() > {{$handler.Name}}{{$method.Name}}Dark() {{$handler.Method}}: {{$service.Prefix}}{{$method.Path}}/dark{{$handler.PathSuffix}} {{ end }} {{ end }} {{ end }} {{ end }} `