From 279cc484fc7c654d54162c8f1de71a07358577a2 Mon Sep 17 00:00:00 2001 From: Derrick Hammer Date: Wed, 7 Feb 2024 19:18:11 -0500 Subject: [PATCH] refactor: merge flowchartsman/swaggerui into our own code base to simplify routing --- api/s5/embed/.gitkeep | 0 api/s5/generate.go | 139 ++++++++++++++++++++++++++++++++++++++++++ api/s5/s5.go | 26 +++++--- 3 files changed, 157 insertions(+), 8 deletions(-) create mode 100644 api/s5/embed/.gitkeep create mode 100644 api/s5/generate.go diff --git a/api/s5/embed/.gitkeep b/api/s5/embed/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/api/s5/generate.go b/api/s5/generate.go new file mode 100644 index 0000000..a3a552b --- /dev/null +++ b/api/s5/generate.go @@ -0,0 +1,139 @@ +// This program downloads the dist assets for the current swagger-ui version and places them into the embed directory +// TODO: Compress? + +//go:build ignore +// +build ignore + +package main + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "encoding/json" + "errors" + "fmt" + "io" + "io/fs" + "io/ioutil" + "log" + "net/http" + "os" + "path/filepath" + "regexp" + "strings" +) + +type releaseResp []struct { + // TagName is a release tag name + TagName string `json:"tag_name"` +} + +func main() { + log.SetFlags(0) + releases := releaseResp{} + // get the releases so we can download the latest one + req, _ := http.NewRequest("GET", "https://api.github.com/repos/swagger-api/swagger-ui/releases", nil) + req.Header.Set("Accept", "application/vnd.github.v3+json") + resp, err := http.DefaultClient.Do(req) + if err != nil { + log.Fatalf("error getting release list: %v", err) + } + if resp.StatusCode != http.StatusOK { + log.Fatalf("got status [%s] on release list download", resp.Status) + } + if err := json.NewDecoder(resp.Body).Decode(&releases); err != nil { + log.Fatalf("error decoding response: %v", err) + } + resp.Body.Close() + if len(releases) == 0 { + log.Fatal("somehow got no releases, nothing to do") + } + tag := releases[0].TagName + + current, err := ioutil.ReadFile("current_version.txt") + if err != nil { + switch { + case errors.Is(err, fs.ErrNotExist): + // no problem, just do it + default: + log.Fatalf("unable to check version in current_version.txt: %v", err) + } + } + cv := string(bytes.TrimRight(current, "\n")) + + if cv == tag { + log.Print("version is current, nothing to do") + os.Exit(0) + } + + log.Printf("downloading release %s...", tag) + + resp, err = http.Get(fmt.Sprintf("https://github.com/swagger-api/swagger-ui/archive/%s.tar.gz", tag)) + if err != nil { + log.Fatalf("error downloading release archive: %v", err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + log.Fatalf("got status [%s] on release archive download", resp.Status) + } + zr, err := gzip.NewReader(resp.Body) + if err != nil { + log.Fatalf("error opening file as gzip: %v", err) + } + if err := os.RemoveAll("embed"); err != nil { + log.Fatalf("error removing old embed directory") + } + if err := os.Mkdir("embed", 0o700); err != nil { + log.Fatalf("error recreating embed directory") + } + tr := tar.NewReader(zr) + for { + header, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + log.Fatalf("tar parsing error: %v", err) + } + if header.Typeflag == tar.TypeReg { + // got a file, remove version directory + fname := header.Name[strings.Index(header.Name, `/`):] + if strings.HasPrefix(fname, `/dist`) { + fname = strings.TrimPrefix(fname, `/dist`) + out, err := os.Create(filepath.Join("embed", fname)) + if err != nil { + log.Fatalf("error create output file: %v", err) + } + if _, err := io.Copy(out, tr); err != nil { + log.Fatalf("error writing output file: %v", err) + } + } + } + } + // replace the hard-coded JSON file with a generic file and disable the topbar + initFile, err := os.ReadFile(filepath.Join("embed", "swagger-initializer.js")) + if err != nil { + log.Fatalf("error opening swagger-initializer.js for templating :%v", err) + } + newInit := regexp.MustCompile(`url:\s+"[^"]*"`).ReplaceAllLiteral(initFile, []byte(`url: "./swagger_spec"`)) + newInit = regexp.MustCompile(`,?\s+SwaggerUIStandalonePreset.*\n`).ReplaceAllLiteral(newInit, []byte("\n")) + newInit = regexp.MustCompile(`(?s),\s+plugins: \[.*],\n`).ReplaceAllLiteral(newInit, []byte("\n")) + newInit = regexp.MustCompile(`\n\s*layout:.*\n`).ReplaceAllLiteral(newInit, []byte("\n")) + //fmt.Println(string(newInit)) + newinitFile, err := os.Create(filepath.Join("embed", "swagger-initializer.js")) + if err != nil { + log.Fatalf("error re-creating swagger-initializer.js file: %v", err) + } + defer newinitFile.Close() + if _, err := newinitFile.Write(newInit); err != nil { + log.Fatalf("unable to write to swagger-initializer.js: %v", err) + } + newcv, err := os.Create("current_version.txt") + if err != nil { + log.Fatalf("can't update current_version.txt: %v", err) + } + defer newcv.Close() + newcv.WriteString(tag) + log.Printf("updated swaggerui from %s => %s, please check templated swagger-initializer.js and retag repo", cv, tag) +} diff --git a/api/s5/s5.go b/api/s5/s5.go index cbf49e5..cab7b6e 100644 --- a/api/s5/s5.go +++ b/api/s5/s5.go @@ -3,6 +3,7 @@ package s5 import ( "context" "crypto/ed25519" + "embed" _ "embed" "fmt" "git.lumeweb.com/LumeWeb/portal/account" @@ -11,12 +12,12 @@ import ( protoRegistry "git.lumeweb.com/LumeWeb/portal/protocols/registry" "git.lumeweb.com/LumeWeb/portal/protocols/s5" "git.lumeweb.com/LumeWeb/portal/storage" - "github.com/flowchartsman/swaggerui" "github.com/getkin/kin-openapi/openapi3" "github.com/rs/cors" "github.com/spf13/viper" "go.sia.tech/jape" "go.uber.org/fx" + "io/fs" "net/http" "net/url" ) @@ -28,6 +29,11 @@ var ( //go:embed swagger.yaml var spec []byte +//go:generate go run generate.go + +//go:embed embed +var swagfs embed.FS + type S5API struct { config *viper.Viper identity ed25519.PrivateKey @@ -104,6 +110,12 @@ func (s S5API) Stop(ctx context.Context) error { return nil } +func byteHandler(b []byte) jape.Handler { + return func(c jape.Context) { + c.ResponseWriter.Write(b) + } +} + func getRoutes(s *S5API) map[string]jape.Handler { tusHandler := BuildS5TusApi(s.identity, s.accounts, s.storage) @@ -131,12 +143,10 @@ func getRoutes(s *S5API) map[string]jape.Handler { wrappedTusHandler := middleware.ApplyMiddlewares(tusOptionsHandler, tusCors, middleware.AuthMiddleware(s.identity, s.accounts)) + swaggerFiles, _ := fs.Sub(swagfs, "embed") + swaggerServ := http.FileServer(http.FS(swaggerFiles)) swaggerHandler := func(c jape.Context) { - swaggerui.Handler(jsonDoc).ServeHTTP(c.ResponseWriter, c.Request) - } - - swaggerStripPrefix := func(h http.Handler) http.Handler { - return http.StripPrefix("/swagger", h) + swaggerServ.ServeHTTP(c.ResponseWriter, c.Request) } return map[string]jape.Handler{ @@ -180,8 +190,8 @@ func getRoutes(s *S5API) map[string]jape.Handler { "POST /s5/registry": middleware.ApplyMiddlewares(s.httpHandler.RegistrySet, middleware.AuthMiddleware(s.identity, s.accounts)), "GET /s5/registry/subscription": middleware.ApplyMiddlewares(s.httpHandler.RegistrySubscription, middleware.AuthMiddleware(s.identity, s.accounts)), - "GET /swagger": middleware.ApplyMiddlewares(swaggerHandler, middleware.AdaptMiddleware(swaggerStripPrefix)), - "GET /swagger/swagger_spec": middleware.ApplyMiddlewares(swaggerHandler, middleware.AdaptMiddleware(swaggerStripPrefix)), + "GET /swagger/swagger_spec": middleware.ApplyMiddlewares(byteHandler(jsonDoc)), + "GET /swagger": middleware.ApplyMiddlewares(swaggerHandler), } }