From fefe2d82a4fdddaddd485fd2f843e677327aa845 Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Sun, 31 Jan 2021 23:12:50 +0000 Subject: [PATCH] rebase 12/02, use go1.16rc1 in make, remove ioutil, start switching to io/fs for file i/o ioutil's contents are now in io and os. Eventually jfa-go's files will be embedded in the binary with go1.16's new embed feature. Using io/fs will provide abstraction for accessing these files, and allow for both embedded and non-embedded versions. Also, internal paths to things like email templates, etc. will be prefixed with "jfa-go:" to indicate to use the app's own Filesystem instead of reading the file normally. This also allows for custom files to continue to be used as they are currently. --- Makefile | 6 +-- config.go | 37 ++++++++++------ config/config-base.json | 2 +- email.go | 28 ++++++------ go.sum | 45 ++++++++++++++++++++ main.go | 63 ++++++++++++++++----------- pwreset.go | 3 +- setup.go | 6 +-- static.go | 30 +++++++++++++ storage.go | 94 ++++++----------------------------------- 10 files changed, 170 insertions(+), 144 deletions(-) create mode 100644 static.go diff --git a/Makefile b/Makefile index 71108ff..9365aff 100644 --- a/Makefile +++ b/Makefile @@ -43,7 +43,7 @@ ts-debug: cp -r ts build/data/web/js swagger: - go get github.com/swaggo/swag/cmd/swag + go1.16rc1 get github.com/swaggo/swag/cmd/swag swag init -g main.go version: @@ -51,10 +51,10 @@ version: compile: $(info Downloading deps) - go mod download + go1.16rc1 mod download $(info Building) mkdir -p build - CGO_ENABLED=0 go build -o build/jfa-go *.go + CGO_ENABLED=0 go1.16rc1 build -o build/jfa-go *.go compress: upx --lzma build/jfa-go diff --git a/config.go b/config.go index e9db6ea..89e56ab 100644 --- a/config.go +++ b/config.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "io/fs" "path/filepath" "strconv" "strings" @@ -11,6 +12,14 @@ import ( var emailEnabled = false +func (app *appContext) GetPath(sect, key string) (fs.FS, string) { + val := app.config.Section(sect).Key(key).MustString("") + if strings.HasPrefix(val, "jfa-go:") { + return app.localFS, strings.TrimPrefix(val, "jfa-go:") + } + return app.systemFS, val +} + func (app *appContext) loadConfig() error { var err error app.config, err = ini.Load(app.configPath) @@ -31,26 +40,26 @@ func (app *appContext) loadConfig() error { app.URLBase = strings.TrimSuffix(app.config.Section("ui").Key("url_base").MustString(""), "/") app.config.Section("email").Key("no_username").SetValue(strconv.FormatBool(app.config.Section("email").Key("no_username").MustBool(false))) - app.config.Section("password_resets").Key("email_html").SetValue(app.config.Section("password_resets").Key("email_html").MustString(filepath.Join(app.localPath, "email.html"))) - app.config.Section("password_resets").Key("email_text").SetValue(app.config.Section("password_resets").Key("email_text").MustString(filepath.Join(app.localPath, "email.txt"))) + app.config.Section("password_resets").Key("email_html").SetValue(app.config.Section("password_resets").Key("email_html").MustString("jfa-go:" + "email.html")) + app.config.Section("password_resets").Key("email_text").SetValue(app.config.Section("password_resets").Key("email_text").MustString("jfa-go:" + "email.txt")) - app.config.Section("invite_emails").Key("email_html").SetValue(app.config.Section("invite_emails").Key("email_html").MustString(filepath.Join(app.localPath, "invite-email.html"))) - app.config.Section("invite_emails").Key("email_text").SetValue(app.config.Section("invite_emails").Key("email_text").MustString(filepath.Join(app.localPath, "invite-email.txt"))) + app.config.Section("invite_emails").Key("email_html").SetValue(app.config.Section("invite_emails").Key("email_html").MustString("jfa-go:" + "invite-email.html")) + app.config.Section("invite_emails").Key("email_text").SetValue(app.config.Section("invite_emails").Key("email_text").MustString("jfa-go:" + "invite-email.txt")) - app.config.Section("email_confirmation").Key("email_html").SetValue(app.config.Section("email_confirmation").Key("email_html").MustString(filepath.Join(app.localPath, "confirmation.html"))) - app.config.Section("email_confirmation").Key("email_text").SetValue(app.config.Section("email_confirmation").Key("email_text").MustString(filepath.Join(app.localPath, "confirmation.txt"))) + app.config.Section("email_confirmation").Key("email_html").SetValue(app.config.Section("email_confirmation").Key("email_html").MustString("jfa-go:" + "confirmation.html")) + app.config.Section("email_confirmation").Key("email_text").SetValue(app.config.Section("email_confirmation").Key("email_text").MustString("jfa-go:" + "confirmation.txt")) - app.config.Section("notifications").Key("expiry_html").SetValue(app.config.Section("notifications").Key("expiry_html").MustString(filepath.Join(app.localPath, "expired.html"))) - app.config.Section("notifications").Key("expiry_text").SetValue(app.config.Section("notifications").Key("expiry_text").MustString(filepath.Join(app.localPath, "expired.txt"))) + app.config.Section("notifications").Key("expiry_html").SetValue(app.config.Section("notifications").Key("expiry_html").MustString("jfa-go:" + "expired.html")) + app.config.Section("notifications").Key("expiry_text").SetValue(app.config.Section("notifications").Key("expiry_text").MustString("jfa-go:" + "expired.txt")) - app.config.Section("notifications").Key("created_html").SetValue(app.config.Section("notifications").Key("created_html").MustString(filepath.Join(app.localPath, "created.html"))) - app.config.Section("notifications").Key("created_text").SetValue(app.config.Section("notifications").Key("created_text").MustString(filepath.Join(app.localPath, "created.txt"))) + app.config.Section("notifications").Key("created_html").SetValue(app.config.Section("notifications").Key("created_html").MustString("jfa-go:" + "created.html")) + app.config.Section("notifications").Key("created_text").SetValue(app.config.Section("notifications").Key("created_text").MustString("jfa-go:" + "created.txt")) - app.config.Section("deletion").Key("email_html").SetValue(app.config.Section("deletion").Key("email_html").MustString(filepath.Join(app.localPath, "deleted.html"))) - app.config.Section("deletion").Key("email_text").SetValue(app.config.Section("deletion").Key("email_text").MustString(filepath.Join(app.localPath, "deleted.txt"))) + app.config.Section("deletion").Key("email_html").SetValue(app.config.Section("deletion").Key("email_html").MustString("jfa-go:" + "deleted.html")) + app.config.Section("deletion").Key("email_text").SetValue(app.config.Section("deletion").Key("email_text").MustString("jfa-go:" + "deleted.txt")) - app.config.Section("welcome_email").Key("email_html").SetValue(app.config.Section("welcome_email").Key("email_html").MustString(filepath.Join(app.localPath, "welcome.html"))) - app.config.Section("welcome_email").Key("email_text").SetValue(app.config.Section("welcome_email").Key("email_text").MustString(filepath.Join(app.localPath, "welcome.txt"))) + app.config.Section("welcome_email").Key("email_html").SetValue(app.config.Section("welcome_email").Key("email_html").MustString("jfa-go:" + "welcome.html")) + app.config.Section("welcome_email").Key("email_text").SetValue(app.config.Section("welcome_email").Key("email_text").MustString("jfa-go:" + "welcome.txt")) app.config.Section("jellyfin").Key("version").SetValue(VERSION) app.config.Section("jellyfin").Key("device").SetValue("jfa-go") diff --git a/config/config-base.json b/config/config-base.json index e72821c..837ed3f 100644 --- a/config/config-base.json +++ b/config/config-base.json @@ -65,7 +65,7 @@ ["emby", "Emby"] ], "value": "jellyfin", - "description": "Note: Emby integration works is missing some features, such as Password Resets." + "description": "Note: Emby integration works but is missing some features, such as Password Resets." }, "substitute_jellyfin_strings": { "name": "Substitute occurrences of \"Jellyfin\"", diff --git a/email.go b/email.go index 2c1f95b..6cfc788 100644 --- a/email.go +++ b/email.go @@ -162,8 +162,8 @@ func (emailer *Emailer) constructConfirmation(code, username, key string, app *a inviteLink = fmt.Sprintf("%s/%s?key=%s", inviteLink, code, key) for _, key := range []string{"html", "text"} { - fpath := app.config.Section("email_confirmation").Key("email_" + key).String() - tpl, err := template.ParseFiles(fpath) + filesystem, fpath := app.GetPath("email_confirmation", "email_"+key) + tpl, err := template.ParseFS(filesystem, fpath) if err != nil { return nil, err } @@ -199,8 +199,8 @@ func (emailer *Emailer) constructInvite(code string, invite Invite, app *appCont inviteLink = fmt.Sprintf("%s/%s", inviteLink, code) for _, key := range []string{"html", "text"} { - fpath := app.config.Section("invite_emails").Key("email_" + key).String() - tpl, err := template.ParseFiles(fpath) + filesystem, fpath := app.GetPath("invite_emails", "email_"+key) + tpl, err := template.ParseFS(filesystem, fpath) if err != nil { return nil, err } @@ -232,8 +232,8 @@ func (emailer *Emailer) constructExpiry(code string, invite Invite, app *appCont } expiry := app.formatDatetime(invite.ValidTill) for _, key := range []string{"html", "text"} { - fpath := app.config.Section("notifications").Key("expiry_" + key).String() - tpl, err := template.ParseFiles(fpath) + filesystem, fpath := app.GetPath("notifications", "expiry_"+key) + tpl, err := template.ParseFS(filesystem, fpath) if err != nil { return nil, err } @@ -267,8 +267,8 @@ func (emailer *Emailer) constructCreated(code, username, address string, invite tplAddress = address } for _, key := range []string{"html", "text"} { - fpath := app.config.Section("notifications").Key("created_" + key).String() - tpl, err := template.ParseFiles(fpath) + filesystem, fpath := app.GetPath("notifications", "created_"+key) + tpl, err := template.ParseFS(filesystem, fpath) if err != nil { return nil, err } @@ -302,8 +302,8 @@ func (emailer *Emailer) constructReset(pwr PasswordReset, app *appContext) (*Ema d, t, expiresIn := emailer.formatExpiry(pwr.Expiry, true, app.datePattern, app.timePattern) message := app.config.Section("email").Key("message").String() for _, key := range []string{"html", "text"} { - fpath := app.config.Section("password_resets").Key("email_" + key).String() - tpl, err := template.ParseFiles(fpath) + filesystem, fpath := app.GetPath("password_resets", "email_"+key) + tpl, err := template.ParseFS(filesystem, fpath) if err != nil { return nil, err } @@ -335,8 +335,8 @@ func (emailer *Emailer) constructDeleted(reason string, app *appContext) (*Email subject: app.config.Section("deletion").Key("subject").MustString(emailer.lang.UserDeleted.get("title")), } for _, key := range []string{"html", "text"} { - fpath := app.config.Section("deletion").Key("email_" + key).String() - tpl, err := template.ParseFiles(fpath) + filesystem, fpath := app.GetPath("deletion", "email_"+key) + tpl, err := template.ParseFS(filesystem, fpath) if err != nil { return nil, err } @@ -363,8 +363,8 @@ func (emailer *Emailer) constructWelcome(username string, app *appContext) (*Ema subject: app.config.Section("welcome_email").Key("subject").MustString(emailer.lang.WelcomeEmail.get("title")), } for _, key := range []string{"html", "text"} { - fpath := app.config.Section("welcome_email").Key("email_" + key).String() - tpl, err := template.ParseFiles(fpath) + filesystem, fpath := app.GetPath("welcome_email", "email_"+key) + tpl, err := template.ParseFS(filesystem, fpath) if err != nil { return nil, err } diff --git a/go.sum b/go.sum index b2b2fdf..fd39922 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,6 @@ +cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= @@ -9,30 +11,41 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v1.0.2 h1:KPldsxuKGsS2FPWsNeg9ZO18aCrGKujPoWXn2yo+KQM= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk= github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473 h1:4cmBvAEBNJaGARUEs3/suWRyfyBfhf7I60WBZq+bv2w= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanw/esbuild v0.8.44 h1:9svHk3MxC3T8ThKkUJ71GcPXYGMhxhO5iCfg2hrU0PU= github.com/evanw/esbuild v0.8.44/go.mod h1:y2AFBAGVelPqPodpdtxWWqe6n2jYf5FrsJbligmRmuw= +github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ= github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= +github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= +github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y= github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc= github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w= github.com/gin-contrib/pprof v1.3.0 h1:G9eK6HnbkSqDZBYbzG4wrjCsA4e+cvYAHUZw6W+W9K0= github.com/gin-contrib/pprof v1.3.0/go.mod h1:waMjT1H9b179t3CxuG1cV3DHpga6ybizwfBaM5OXaB0= @@ -102,6 +115,7 @@ github.com/go-openapi/swag v0.19.12 h1:Bc0bnY2c3AoF7Gc+IMIAQQsD8fLHjHpc19wXvYuay github.com/go-openapi/swag v0.19.12/go.mod h1:eFdyEBkTdoAf/9RXBvj4cr1nH7GD8Kzo5HTt47gr72M= github.com/go-openapi/swag v0.19.13 h1:233UVgMy1DlmCYYfOiFpta6e2urloh+sEs5id6lyzog= github.com/go-openapi/swag v0.19.13/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= @@ -117,8 +131,11 @@ github.com/go-playground/validator/v10 v10.4.0 h1:72qIR/m8ybvL8L5TIyfgrigqkrw7kV github.com/go-playground/validator/v10 v10.4.0/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -139,7 +156,9 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -164,10 +183,13 @@ github.com/knz/strtime v0.0.0-20200318182718-be999391ffa9 h1:GQE1iatYDRrIidq4Zf/ github.com/knz/strtime v0.0.0-20200318182718-be999391ffa9/go.mod h1:4ZxfWkxwtc7dBeifERVVWRy9F9rTU9p0yCDgeCtlius= github.com/knz/strtime v0.0.0-20200924090105-187c67f2bf5e h1:ViPE0JEOvtw5I0EGUiFSr2VNKGNU+3oBT+oHbDXHbxk= github.com/knz/strtime v0.0.0-20200924090105-187c67f2bf5e/go.mod h1:4ZxfWkxwtc7dBeifERVVWRy9F9rTU9p0yCDgeCtlius= +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/pty v1.1.5 h1:hyz3dwM5QLc1Rfoz4FuWJQG5BN7tc6K1MndAUnGpQr4= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= @@ -218,6 +240,7 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLD github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pdrum/swagger-automation v0.0.0-20190629163613-c8c7c80ba858 h1:lgbJiJQx8bXo+eM88AFdd0VxUvaTLzCBXpK+H9poJ+Y= github.com/pdrum/swagger-automation v0.0.0-20190629163613-c8c7c80ba858/go.mod h1:y02HeaN0visd95W6cEX2NXDv5sCwyqfzucWTdDGEwYY= @@ -225,20 +248,25 @@ github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 h1:PyYN9JH5jY9j6av01SpfRMb+1DWg/i3MbGOKPxJ2wjM= github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E= @@ -286,6 +314,7 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -297,10 +326,13 @@ golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rB golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9 h1:umElSU9WZirRdgu2yFHY0ayQkEnKiOC1TtM3fWXFnoU= golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4 h1:c2HOrn5iMezYjSlGPncknSEr/8x5LELb/ilJbXi9DEA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -335,12 +367,14 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLD golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -374,6 +408,7 @@ golang.org/x/sys v0.0.0-20210123111255-9b0068b26619 h1:yLLDsUUPDliIQpKl7BjVb1igw golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= @@ -422,14 +457,18 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -444,9 +483,13 @@ google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4 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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= +gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc= gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -467,6 +510,8 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/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-20190523083050-ea95bdfd59fc h1:/hemPrYIhOhy8zYrNj+069zDB68us2sMGsfkFJO0iZs= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/main.go b/main.go index 6c745fc..e36d3a3 100644 --- a/main.go +++ b/main.go @@ -7,8 +7,9 @@ import ( "encoding/json" "flag" "fmt" + "html/template" "io" - "io/ioutil" + "io/fs" "log" "mime" "net" @@ -58,7 +59,9 @@ type appContext struct { configBasePath string configBase settings dataPath string - localPath string + systemFS fs.FS + localFS fs.FS + webFS httpFS cssClass string jellyfinLogin bool users []User @@ -82,8 +85,8 @@ type appContext struct { func (app *appContext) loadHTML(router *gin.Engine) { customPath := app.config.Section("files").Key("html_templates").MustString("") - templatePath := filepath.Join(app.localPath, "html") - htmlFiles, err := ioutil.ReadDir(templatePath) + templatePath := "html" + htmlFiles, err := fs.ReadDir(app.localFS, templatePath) if err != nil { app.err.Fatalf("Couldn't access template directory: \"%s\"", templatePath) return @@ -98,7 +101,11 @@ func (app *appContext) loadHTML(router *gin.Engine) { loadFiles[i] = filepath.Join(filepath.Join(customPath, f.Name())) } } - router.LoadHTMLFiles(loadFiles...) + tmpl, err := template.ParseFS(app.localFS, loadFiles...) + if err != nil { + app.err.Fatalf("Failed to load templates: %v", err) + } + router.SetHTMLTemplate(tmpl) } func generateSecret(length int) (string, error) { @@ -194,7 +201,14 @@ func start(asDaemon, firstCall bool) { app.dataPath = filepath.Join(userConfigDir, "jfa-go") app.configPath = filepath.Join(app.dataPath, "config.ini") executable, _ := os.Executable() - app.localPath = filepath.Join(filepath.Dir(executable), "data") + app.localFS = os.DirFS(filepath.Join(filepath.Dir(executable), "data")) + app.systemFS = os.DirFS("/") + wfs := os.DirFS(filepath.Join(filepath.Dir(executable), "data", "web")) + app.webFS = httpFS{ + hfs: http.FS(wfs), + fs: wfs, + } + app.webFS.fs = wfs app.info = log.New(os.Stdout, "[INFO] ", log.Ltime) app.err = log.New(os.Stdout, "[ERROR] ", log.Ltime|log.Lshortfile) @@ -251,23 +265,19 @@ func start(asDaemon, firstCall bool) { } if _, err := os.Stat(app.configPath); os.IsNotExist(err) { firstRun = true - dConfigPath := filepath.Join(app.localPath, "config-default.ini") - var dConfig *os.File - dConfig, err = os.Open(dConfigPath) + dConfig, err := fs.ReadFile(app.localFS, "config-default.ini") if err != nil { - app.err.Fatalf("Couldn't find default config file \"%s\"", dConfigPath) + app.err.Fatalf("Couldn't find default config file") } - defer dConfig.Close() - var nConfig *os.File nConfig, err := os.Create(app.configPath) if err != nil { app.err.Printf("Couldn't open config file for writing: \"%s\"", app.configPath) app.err.Fatalf("Error: %s", err) } defer nConfig.Close() - _, err = io.Copy(nConfig, dConfig) + _, err = nConfig.Write(dConfig) if err != nil { - app.err.Fatalf("Couldn't copy default config. To do this manually, copy\n%s\nto\n%s", dConfigPath, app.configPath) + app.err.Fatalf("Couldn't copy default config.") } app.info.Printf("Copied default configuration to \"%s\"", app.configPath) } @@ -288,7 +298,7 @@ func start(asDaemon, firstCall bool) { app.info.Print(aurora.Magenta("\n\nWARNING: Don't use debug mode in production, as it exposes pprof on the network.\n\n")) app.debug = log.New(os.Stdout, "[DEBUG] ", log.Ltime|log.Lshortfile) } else { - app.debug = log.New(ioutil.Discard, "", 0) + app.debug = log.New(io.Discard, "", 0) } if asDaemon { @@ -330,10 +340,10 @@ func start(asDaemon, firstCall bool) { }() } - app.storage.lang.CommonPath = filepath.Join(app.localPath, "lang", "common") - app.storage.lang.FormPath = filepath.Join(app.localPath, "lang", "form") - app.storage.lang.AdminPath = filepath.Join(app.localPath, "lang", "admin") - app.storage.lang.EmailPath = filepath.Join(app.localPath, "lang", "email") + app.storage.lang.CommonPath = filepath.Join("lang", "common") + app.storage.lang.FormPath = filepath.Join("lang", "form") + app.storage.lang.AdminPath = filepath.Join("lang", "admin") + app.storage.lang.EmailPath = filepath.Join("lang", "email") err := app.storage.loadLang() if err != nil { app.info.Fatalf("Failed to load language files: %+v\n", err) @@ -413,8 +423,8 @@ func start(asDaemon, firstCall bool) { } - app.configBasePath = filepath.Join(app.localPath, "config-base.json") - configBase, _ := ioutil.ReadFile(app.configBasePath) + app.configBasePath = "config-base.json" + configBase, _ := fs.ReadFile(app.localFS, app.configBasePath) json.Unmarshal(configBase, &app.configBase) themes := map[string]string{ @@ -562,7 +572,7 @@ func start(asDaemon, firstCall bool) { } else { debugMode = false address = "0.0.0.0:8056" - app.storage.lang.SetupPath = filepath.Join(app.localPath, "lang", "setup") + app.storage.lang.SetupPath = filepath.Join("lang", "setup") err := app.storage.loadLangSetup() if err != nil { app.info.Fatalf("Failed to load language files: %+v\n", err) @@ -583,14 +593,17 @@ func start(asDaemon, firstCall bool) { setGinLogger(router, debugMode) router.Use(gin.Recovery()) + // Move to router.go routePrefixes := []string{app.URLBase} if app.URLBase != "" { routePrefixes = append(routePrefixes, "") } for _, p := range routePrefixes { - router.Use(static.Serve(p+"/", static.LocalFile(filepath.Join(app.localPath, "web"), false))) + router.Use(static.Serve(p+"/", app.webFS)) } + // app.loadHTML(router) + router.Use(static.Serve("/", app.webFS)) router.NoRoute(app.NoRouteHandler) if debugMode { app.debug.Println("Loading pprof") @@ -600,6 +613,7 @@ func start(asDaemon, firstCall bool) { router.GET(p+"/lang/:page", app.GetLanguages) } if !firstRun { + // Move to router for _, p := range routePrefixes { router.GET(p+"/", app.AdminPage) router.GET(p+"/accounts", app.AdminPage) @@ -608,7 +622,7 @@ func start(asDaemon, firstCall bool) { router.GET(p+"/token/login", app.getTokenLogin) router.GET(p+"/token/refresh", app.getTokenRefresh) router.POST(p+"/newUser", app.NewUser) - router.Use(static.Serve(p+"/invite/", static.LocalFile(filepath.Join(app.localPath, "web"), false))) + router.Use(static.Serve(p+"/invite/", app.webFS)) router.GET(p+"/invite/:invCode", app.InviteProxy) } if *SWAGGER { @@ -650,7 +664,6 @@ func start(asDaemon, firstCall bool) { router.POST("/config", app.ModifyConfig) app.info.Printf("Loading setup @ %s", address) } - SRV = &http.Server{ Addr: address, Handler: router, diff --git a/pwreset.go b/pwreset.go index 401ddd1..98e9fad 100644 --- a/pwreset.go +++ b/pwreset.go @@ -2,7 +2,6 @@ package main import ( "encoding/json" - "io/ioutil" "os" "strings" "time" @@ -53,7 +52,7 @@ func pwrMonitor(app *appContext, watcher *fsnotify.Watcher) { } if event.Op&fsnotify.Write == fsnotify.Write && strings.Contains(event.Name, "passwordreset") { var pwr PasswordReset - data, err := ioutil.ReadFile(event.Name) + data, err := os.ReadFile(event.Name) if err != nil { return } diff --git a/setup.go b/setup.go index 083669e..a79857e 100644 --- a/setup.go +++ b/setup.go @@ -2,7 +2,7 @@ package main import ( "encoding/json" - "io/ioutil" + "os" "path/filepath" "strings" @@ -76,7 +76,7 @@ func (st *Storage) loadLangSetup() error { load := func(fname string) error { index := strings.TrimSuffix(fname, filepath.Ext(fname)) lang := setupLang{} - f, err := ioutil.ReadFile(filepath.Join(st.lang.SetupPath, fname)) + f, err := os.ReadFile(filepath.Join(st.lang.SetupPath, fname)) if err != nil { return err } @@ -112,7 +112,7 @@ func (st *Storage) loadLangSetup() error { return err } english = st.lang.Setup["en-us"] - files, err := ioutil.ReadDir(st.lang.SetupPath) + files, err := os.ReadDir(st.lang.SetupPath) if err != nil { return err } diff --git a/static.go b/static.go new file mode 100644 index 0000000..3ef9436 --- /dev/null +++ b/static.go @@ -0,0 +1,30 @@ +package main + +import ( + "io/fs" + "net/http" + "strings" +) + +type httpFS struct { + hfs http.FileSystem + fs fs.FS +} + +func (f httpFS) Open(name string) (http.File, error) { + return f.hfs.Open(name) +} + +func (f httpFS) Exists(prefix string, filepath string) bool { + if p := strings.TrimPrefix(filepath, prefix); len(p) < len(filepath) { + stats, err := fs.Stat(f.fs, p) + if err != nil { + return false + } + if stats.IsDir() { + return false + } + return true + } + return false +} diff --git a/storage.go b/storage.go index f0562f7..3f3e716 100644 --- a/storage.go +++ b/storage.go @@ -2,8 +2,8 @@ package main import ( "encoding/json" - "io/ioutil" "log" + "os" "path/filepath" "strconv" "strings" @@ -42,7 +42,7 @@ type Invite struct { Notify map[string]map[string]bool `json:"notify"` Profile string `json:"profile"` Label string `json:"label,omitempty"` - Keys []string `json"keys,omitempty"` + Keys []string `json"keys,omitempty"` } type Lang struct { @@ -126,7 +126,7 @@ func (st *Storage) loadLangCommon() error { load := func(fname string) error { index := strings.TrimSuffix(fname, filepath.Ext(fname)) lang := commonLang{} - f, err := ioutil.ReadFile(filepath.Join(st.lang.CommonPath, fname)) + f, err := os.ReadFile(filepath.Join(st.lang.CommonPath, fname)) if err != nil { return err } @@ -148,7 +148,7 @@ func (st *Storage) loadLangCommon() error { return err } english = st.lang.Common["en-us"] - files, err := ioutil.ReadDir(st.lang.CommonPath) + files, err := os.ReadDir(st.lang.CommonPath) if err != nil { return err } @@ -169,7 +169,7 @@ func (st *Storage) loadLangAdmin() error { load := func(fname string) error { index := strings.TrimSuffix(fname, filepath.Ext(fname)) lang := adminLang{} - f, err := ioutil.ReadFile(filepath.Join(st.lang.AdminPath, fname)) + f, err := os.ReadFile(filepath.Join(st.lang.AdminPath, fname)) if err != nil { return err } @@ -199,7 +199,7 @@ func (st *Storage) loadLangAdmin() error { return err } english = st.lang.Admin["en-us"] - files, err := ioutil.ReadDir(st.lang.AdminPath) + files, err := os.ReadDir(st.lang.AdminPath) if err != nil { return err } @@ -220,7 +220,7 @@ func (st *Storage) loadLangForm() error { load := func(fname string) error { index := strings.TrimSuffix(fname, filepath.Ext(fname)) lang := formLang{} - f, err := ioutil.ReadFile(filepath.Join(st.lang.FormPath, fname)) + f, err := os.ReadFile(filepath.Join(st.lang.FormPath, fname)) if err != nil { return err } @@ -255,7 +255,7 @@ func (st *Storage) loadLangForm() error { return err } english = st.lang.Form["en-us"] - files, err := ioutil.ReadDir(st.lang.FormPath) + files, err := os.ReadDir(st.lang.FormPath) if err != nil { return err } @@ -276,7 +276,7 @@ func (st *Storage) loadLangEmail() error { load := func(fname string) error { index := strings.TrimSuffix(fname, filepath.Ext(fname)) lang := emailLang{} - f, err := ioutil.ReadFile(filepath.Join(st.lang.EmailPath, fname)) + f, err := os.ReadFile(filepath.Join(st.lang.EmailPath, fname)) if err != nil { return err } @@ -304,7 +304,7 @@ func (st *Storage) loadLangEmail() error { return err } english = st.lang.Email["en-us"] - files, err := ioutil.ReadDir(st.lang.EmailPath) + files, err := os.ReadDir(st.lang.EmailPath) if err != nil { return err } @@ -329,76 +329,6 @@ func (st *Storage) storeInvites() error { return storeJSON(st.invite_path, st.invites) } -// func (st *Storage) loadLang() error { -// loadData := func(path string, stringJson bool) (map[string]string, map[string]map[string]interface{}, error) { -// files, err := ioutil.ReadDir(path) -// outString := map[string]string{} -// out := map[string]map[string]interface{}{} -// if err != nil { -// return nil, nil, err -// } -// for _, f := range files { -// index := strings.TrimSuffix(f.Name(), filepath.Ext(f.Name())) -// var data map[string]interface{} -// var file []byte -// var err error -// file, err = ioutil.ReadFile(filepath.Join(path, f.Name())) -// if err != nil { -// file = []byte("{}") -// } -// // Replace Jellyfin with something if necessary -// if substituteStrings != "" { -// fileString := strings.ReplaceAll(string(file), "Jellyfin", substituteStrings) -// file = []byte(fileString) -// } -// err = json.Unmarshal(file, &data) -// if err != nil { -// log.Printf("ERROR: Failed to read \"%s\": %s", path, err) -// return nil, nil, err -// } -// if stringJson { -// stringJSON, err := json.Marshal(data) -// if err != nil { -// return nil, nil, err -// } -// outString[index] = string(stringJSON) -// } -// out[index] = data -// -// } -// return outString, out, nil -// } -// _, form, err := loadData(st.lang.FormPath, false) -// if err != nil { -// return err -// } -// for index, lang := range form { -// validationStrings := lang["validationStrings"].(map[string]interface{}) -// vS, err := json.Marshal(validationStrings) -// if err != nil { -// return err -// } -// lang["validationStrings"] = string(vS) -// form[index] = lang -// } -// st.lang.Form = form -// adminJSON, admin, err := loadData(st.lang.AdminPath, true) -// st.lang.Admin = admin -// st.lang.AdminJSON = adminJSON -// -// _, emails, err := loadData(st.lang.EmailPath, false) -// fixedEmails := map[string]map[string]map[string]interface{}{} -// for lang, e := range emails { -// f := map[string]map[string]interface{}{} -// for field, vals := range e { -// f[field] = vals.(map[string]interface{}) -// } -// fixedEmails[lang] = f -// } -// st.lang.Email = fixedEmails -// return err -// } - func (st *Storage) loadEmails() error { return loadJSON(st.emails_path, &st.emails) } @@ -495,7 +425,7 @@ func (st *Storage) migrateToProfile() error { func loadJSON(path string, obj interface{}) error { var file []byte var err error - file, err = ioutil.ReadFile(path) + file, err = os.ReadFile(path) if err != nil { file = []byte("{}") } @@ -511,7 +441,7 @@ func storeJSON(path string, obj interface{}) error { if err != nil { return err } - err = ioutil.WriteFile(path, data, 0644) + err = os.WriteFile(path, data, 0644) if err != nil { log.Printf("ERROR: Failed to write to \"%s\": %s", path, err) }