Add swagger injector

This commit is contained in:
Melon 2022-09-09 16:05:46 +01:00
parent 52ef284562
commit 7d9b926eb4
Signed by: melon
GPG Key ID: B0ADD5395BCDAAB6
7 changed files with 196 additions and 32 deletions

1
go.mod
View File

@ -10,6 +10,7 @@ require (
github.com/gorilla/sessions v1.2.1
github.com/joho/godotenv v1.4.0
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a
gopkg.in/yaml.v3 v3.0.1
)
require (

5
go.sum
View File

@ -120,8 +120,10 @@ github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwA
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@ -381,10 +383,13 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -0,0 +1,6 @@
package assets
import "embed"
//go:embed swagger
var SwaggerAssets embed.FS

View File

@ -0,0 +1,21 @@
window.onload = function() {
//<editor-fold desc="Changeable Configuration Block">
// the following lines will be replaced by docker/configurator, when it runs in a docker-container
window.ui = SwaggerUIBundle({
urls: window.loadUrls,
"urls.primaryName": window.loadMain, // default document (if other than the first)
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
});
//</editor-fold>
};

View File

@ -1,7 +1,9 @@
package gitea
import (
"bytes"
"code.gitea.io/sdk/gitea"
"code.mrmelon54.com/melon/tools/module/gitea/assets"
"code.mrmelon54.com/melon/tools/utils"
"context"
_ "embed"
@ -10,15 +12,23 @@ import (
"github.com/google/uuid"
"github.com/gorilla/mux"
"golang.org/x/oauth2"
"gopkg.in/yaml.v3"
"html/template"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"time"
)
//go:embed pages/index.go.html
var indexTemplate string
var (
//go:embed pages/index.go.html
indexTemplate string
//go:embed pages/swagger.go.html
swaggerTemplate string
returnCookie = "melon-tools-return-gitea"
)
type Module struct {
sessionWrapper func(cb func(http.ResponseWriter, *http.Request, *utils.State)) func(rw http.ResponseWriter, req *http.Request)
@ -57,6 +67,25 @@ func (m *Module) SetupModule(router *mux.Router, f func(cb func(http.ResponseWri
}
router.HandleFunc("/", m.getClient(m.homepage))
router.HandleFunc("/login", m.sessionWrapper(m.loginPage))
router.PathPrefix("/swagger").HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
p := filepath.Join("swagger", filepath.Base(req.URL.Path))
open, err := assets.SwaggerAssets.Open(p)
if err != nil {
http.NotFound(rw, req)
return
}
stat, err := open.Stat()
if err != nil {
http.NotFound(rw, req)
return
}
seeker, ok := open.(io.ReadSeeker)
if ok {
http.ServeContent(rw, req, p, stat.ModTime(), seeker)
} else {
http.NotFound(rw, req)
}
})
}
func (m *Module) getClient(cb func(http.ResponseWriter, *http.Request, *utils.State, *gitea.Client)) func(rw http.ResponseWriter, req *http.Request) {
@ -65,11 +94,27 @@ func (m *Module) getClient(cb func(http.ResponseWriter, *http.Request, *utils.St
cb(rw, req, state, v)
return
}
http.SetCookie(rw, &http.Cookie{
Name: returnCookie,
Value: req.RequestURI,
Path: "/gitea",
Expires: time.Now().Add(time.Hour * 1),
MaxAge: 3600,
})
http.Redirect(rw, req, "/gitea/login", http.StatusTemporaryRedirect)
})
}
func (m *Module) homepage(rw http.ResponseWriter, req *http.Request, state *utils.State, giteaClient *gitea.Client) {
cookie, err := req.Cookie(returnCookie)
if err == nil {
if cookie.Valid() != nil {
http.SetCookie(rw, &http.Cookie{Name: returnCookie, Value: "", Path: "/gitea", Expires: time.Now().Add(-time.Hour), MaxAge: 0})
http.Redirect(rw, req, cookie.Value, http.StatusTemporaryRedirect)
return
}
}
myUser, _, err := giteaClient.GetMyUserInfo()
if err != nil {
state.Del(KeyOauthClient)
@ -95,7 +140,10 @@ func (m *Module) homepage(rw http.ResponseWriter, req *http.Request, state *util
selModule := ""
selCommitTime := ""
selCommitHash := ""
mySpecs := make([]string, 0)
mySpecs := make([]struct {
Name string
Code int
}, 0)
q := req.URL.Query()
if q.Has("org") {
@ -150,17 +198,47 @@ func (m *Module) homepage(rw http.ResponseWriter, req *http.Request, state *util
if q.Has("spec") {
spec := q.Get("spec")
specFile, _, err := giteaClient.GetFile(myOrg, selRepo, ref.Object.SHA, spec)
if q.Has("raw") && q.Get("raw") == "true" {
open, _, err := giteaClient.GetFile(myOrg, selRepo, ref.Object.SHA, spec)
if err != nil {
http.Error(rw, "OpenAPI spec raw: "+err.Error(), http.StatusInternalServerError)
return
}
http.ServeContent(rw, req, spec, repo.Updated, bytes.NewReader(open))
return
}
contents, _, err := giteaClient.GetContents(myOrg, selRepo, ref.Object.SHA, spec)
if err != nil {
http.Error(rw, "OpenAPI spec: "+err.Error(), http.StatusInternalServerError)
return
}
rw.Header().Set("Content-Type", "text/html")
rw.WriteHeader(http.StatusOK)
_, _ = fmt.Fprintf(rw, "Showing spec file: '%s'\n", spec)
_, _ = fmt.Fprintf(rw, "\n<pre><code>\n")
_, _ = rw.Write(specFile)
_, _ = fmt.Fprintf(rw, "\n</code></pre>\n")
tmp, err := template.New("swagger").Parse(swaggerTemplate)
if err != nil {
fmt.Println("Template parse error:", err)
return
}
q2 := q
q2.Set("raw", "true")
err = tmp.Execute(rw, struct {
LoadUrls []struct {
Url string `json:"url"`
Name string `json:"name"`
}
LoadMain string
}{
LoadUrls: []struct {
Url string `json:"url"`
Name string `json:"name"`
}{
{Url: "/gitea/?" + q2.Encode(), Name: contents.Name},
},
LoadMain: contents.Name,
})
if err != nil {
fmt.Println("Template execute error:", err)
return
}
return
}
@ -178,6 +256,9 @@ func (m *Module) homepage(rw http.ResponseWriter, req *http.Request, state *util
goModLine := goModStr[:goModIdx]
goModSpace := strings.Index(goModLine, " ")
selModule = goModLine[goModSpace+1:]
if resp.StatusCode == http.StatusNotFound {
selModule = ""
}
trees, resp, err := giteaClient.GetTrees(myOrg, selRepo, ref.Object.SHA, true)
if err != nil {
@ -186,10 +267,31 @@ func (m *Module) homepage(rw http.ResponseWriter, req *http.Request, state *util
return
}
}
for i := range trees.Entries {
switch filepath.Ext(trees.Entries[i].Path) {
for _, i := range trees.Entries {
switch filepath.Ext(i.Path) {
case ".yml", ".yaml":
mySpecs = append(mySpecs, trees.Entries[i].Path)
file, resp, err := giteaClient.GetFile(myOrg, selRepo, ref.Object.SHA, i.Path)
if err != nil {
switch resp.StatusCode {
case http.StatusForbidden, http.StatusUnauthorized, http.StatusNotFound:
mySpecs = append(mySpecs, struct {
Name string
Code int
}{Name: i.Path, Code: resp.StatusCode})
}
continue
}
a := struct {
OpenAPI string `yaml:"openapi"`
}{}
err = yaml.Unmarshal(file, &a)
if err != nil || a.OpenAPI == "" {
continue
}
mySpecs = append(mySpecs, struct {
Name string
Code int
}{Name: i.Path, Code: http.StatusOK})
}
}
}
@ -213,11 +315,14 @@ func (m *Module) homepage(rw http.ResponseWriter, req *http.Request, state *util
SelRepo string
ShowOrg bool
SelModule string
ShowRepo bool
ShowGoMod bool
CommitTime string
CommitHash string
ShowSpec bool
Specs []string
Specs []struct {
Name string
Code int
}
}{
Username: myUser.UserName,
Orgs: orgSimple,
@ -227,7 +332,7 @@ func (m *Module) homepage(rw http.ResponseWriter, req *http.Request, state *util
SelRepo: selRepo,
SelModule: selModule,
ShowOrg: myOrg != "",
ShowRepo: selModule != "",
ShowGoMod: selModule != "",
CommitTime: selCommitTime,
CommitHash: selCommitHash,
ShowSpec: len(mySpecs) > 0,

View File

@ -54,24 +54,28 @@
</ul>
</div>
{{end}}
{{if .ShowRepo}}
<div>
<div>
{{if .ShowGoMod}}
<p>Repository details:</p>
<p>Go import: go get {{.SelModule}}@v0.0.0-{{.CommitTime}}-{{.CommitHash}}</p>
</div>
{{end}}
{{if .ShowSpec}}
<div>
<p>OpenAPI specs:</p>
<ul>
{{range .Specs}}
<li>
<a href="?org={{$.SelOrg}}&repo={{$.SelRepo}}&spec={{.}}" target="_blank">{{.}}</a>
</li>
{{end}}
</ul>
</div>
{{end}}
{{end}}
{{if .ShowSpec}}
<div>
<p>OpenAPI specs:</p>
<ul>
{{range .Specs}}
<li>
{{if not (eq .Code 200)}}
{{.Name}} ({{.Code}})
{{else}}
<a href="?org={{$.SelOrg}}&repo={{$.SelRepo}}&spec={{.Name}}" target="_blank">{{.Name}}</a>
{{end}}
</li>
{{end}}
</ul>
</div>
{{end}}
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en-GB">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Swagger | Gitea | Melon Tools</title>
<link rel="stylesheet" type="text/css" href="swagger/swagger-ui.css" />
<link rel="stylesheet" type="text/css" href="swagger/index.css" />
<link rel="icon" type="image/png" href="swagger/favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="swagger/favicon-16x16.png" sizes="16x16" />
</head>
<body>
<div id="swagger-ui"></div>
<script src="swagger/swagger-ui-bundle.js" charset="UTF-8"></script>
<script src="swagger/swagger-ui-standalone-preset.js" charset="UTF-8"></script>
<script src="swagger/swagger-initializer-custom.js" charset="UTF-8"></script>
<script>
window.loadUrls = {{.LoadUrls}};
window.loadMain = {{.LoadMain}};
</script>
</body>
</html>