mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-22 09:00:10 +00:00
Merge branch 'go1.16'
merge go1.16 changes This includes embedded files for releases (no extra 'data' directory!) and support for a custom language file directory, allowing one to customize the text accross the app.
This commit is contained in:
commit
f794322392
@ -9,7 +9,7 @@ steps:
|
||||
commands:
|
||||
- git fetch --tags
|
||||
- name: release
|
||||
image: golang:latest
|
||||
image: golang:1.16rc1-buster
|
||||
environment:
|
||||
GITHUB_TOKEN:
|
||||
from_secret: github_token
|
||||
@ -64,7 +64,7 @@ type: docker
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: golang:latest
|
||||
image: golang:1.16rc1-buster
|
||||
commands:
|
||||
- apt update -y
|
||||
- apt install build-essential python3-pip curl software-properties-common sed upx -y
|
||||
@ -130,7 +130,7 @@ type: docker
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: golang:latest
|
||||
image: golang:1.16rc1-buster
|
||||
commands:
|
||||
- apt update -y
|
||||
- apt install build-essential python3-pip curl software-properties-common sed upx -y
|
||||
@ -144,4 +144,3 @@ trigger:
|
||||
event:
|
||||
include:
|
||||
- pull_request
|
||||
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,6 +4,7 @@ dist/
|
||||
build/
|
||||
data/
|
||||
version.go
|
||||
embed.go
|
||||
notes
|
||||
docs/*
|
||||
lang/langtostruct.py
|
||||
|
@ -26,6 +26,7 @@ before:
|
||||
- npx esbuild --bundle ts/setup.ts --outfile=./data/web/js/setup.js --minify
|
||||
- go get -u github.com/swaggo/swag/cmd/swag
|
||||
- swag init -g main.go
|
||||
- python3 embed.py internal
|
||||
builds:
|
||||
- dir: ./
|
||||
env:
|
||||
@ -44,10 +45,6 @@ archives:
|
||||
linux: Linux
|
||||
windows: Windows
|
||||
amd64: x86_64
|
||||
files:
|
||||
- data/*
|
||||
- data/**/*
|
||||
- data/**/**/*
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
snapshot:
|
||||
|
@ -6,7 +6,7 @@ RUN apt update -y \
|
||||
&& apt install build-essential python3-pip curl software-properties-common sed -y \
|
||||
&& (curl -sL https://deb.nodesource.com/setup_14.x | bash -) \
|
||||
&& apt install nodejs \
|
||||
&& (cd /opt/build; make configuration npm email version typescript bundle-css swagger copy GOESBUILD=on) \
|
||||
&& (cd /opt/build; make configuration npm email version typescript bundle-css swagger noembed copy GOESBUILD=on) \
|
||||
&& sed -i 's#id="password_resets-watch_directory" placeholder="/config/jellyfin"#id="password_resets-watch_directory" value="/jf" disabled#g' /opt/build/build/data/html/setup.html
|
||||
|
||||
|
||||
|
69
Makefile
69
Makefile
@ -4,6 +4,7 @@ ifeq ($(GOESBUILD), on)
|
||||
else
|
||||
ESBUILD := npx esbuild
|
||||
endif
|
||||
GOBINARY ?= go
|
||||
|
||||
npm:
|
||||
$(info installing npm dependencies)
|
||||
@ -16,34 +17,34 @@ npm:
|
||||
|
||||
configuration:
|
||||
$(info Fixing config-base)
|
||||
-mkdir -p build/data
|
||||
python3 config/fixconfig.py -i config/config-base.json -o build/data/config-base.json
|
||||
-mkdir -p data
|
||||
python3 config/fixconfig.py -i config/config-base.json -o data/config-base.json
|
||||
$(info Generating config-default.ini)
|
||||
python3 config/generate_ini.py -i config/config-base.json -o build/data/config-default.ini
|
||||
python3 config/generate_ini.py -i config/config-base.json -o data/config-default.ini
|
||||
|
||||
email:
|
||||
$(info Generating email html)
|
||||
python3 mail/generate.py -o build/data/
|
||||
python3 mail/generate.py -o data/
|
||||
|
||||
typescript:
|
||||
$(info compiling typescript)
|
||||
-mkdir -p build/data/web/js
|
||||
-$(ESBUILD) --bundle ts/admin.ts --outfile=./build/data/web/js/admin.js --minify
|
||||
-$(ESBUILD) --bundle ts/form.ts --outfile=./build/data/web/js/form.js --minify
|
||||
-$(ESBUILD) --bundle ts/setup.ts --outfile=./build/data/web/js/setup.js --minify
|
||||
-mkdir -p data/web/js
|
||||
-$(ESBUILD) --bundle ts/admin.ts --outfile=./data/web/js/admin.js --minify
|
||||
-$(ESBUILD) --bundle ts/form.ts --outfile=./data/web/js/form.js --minify
|
||||
-$(ESBUILD) --bundle ts/setup.ts --outfile=./data/web/js/setup.js --minify
|
||||
|
||||
ts-debug:
|
||||
$(info compiling typescript w/ sourcemaps)
|
||||
-mkdir -p build/data/web/js
|
||||
-$(ESBUILD) --bundle ts/admin.ts --sourcemap --outfile=./build/data/web/js/admin.js
|
||||
-$(ESBUILD) --bundle ts/form.ts --sourcemap --outfile=./build/data/web/js/form.js
|
||||
-$(ESBUILD) --bundle ts/setup.ts --sourcemap --outfile=./build/data/web/js/setup.js
|
||||
-rm -r build/data/web/js/ts
|
||||
-mkdir -p data/web/js
|
||||
-$(ESBUILD) --bundle ts/admin.ts --sourcemap --outfile=./data/web/js/admin.js
|
||||
-$(ESBUILD) --bundle ts/form.ts --sourcemap --outfile=./data/web/js/form.js
|
||||
-$(ESBUILD) --bundle ts/setup.ts --sourcemap --outfile=./data/web/js/setup.js
|
||||
-rm -r data/web/js/ts
|
||||
$(info copying typescript)
|
||||
cp -r ts build/data/web/js
|
||||
cp -r ts data/web/js
|
||||
|
||||
swagger:
|
||||
go get github.com/swaggo/swag/cmd/swag
|
||||
$(GOBINARY) get github.com/swaggo/swag/cmd/swag
|
||||
swag init -g main.go
|
||||
|
||||
version:
|
||||
@ -51,33 +52,49 @@ version:
|
||||
|
||||
compile:
|
||||
$(info Downloading deps)
|
||||
go mod download
|
||||
$(GOBINARY) mod download
|
||||
$(info Building)
|
||||
mkdir -p build
|
||||
CGO_ENABLED=0 go build -o build/jfa-go *.go
|
||||
cd build && CGO_ENABLED=0 $(GOBINARY) build -ldflags="-s -w" -o ./jfa-go ../*.go
|
||||
|
||||
compile-debug:
|
||||
$(info Downloading deps)
|
||||
$(GOBINARY) mod download
|
||||
$(info Building)
|
||||
mkdir -p build
|
||||
cd build && CGO_ENABLED=0 $(GOBINARY) build -o ./jfa-go ../*.go
|
||||
|
||||
compress:
|
||||
upx --lzma build/jfa-go
|
||||
|
||||
bundle-css:
|
||||
-mkdir -p build/data/web/css
|
||||
-mkdir -p data/web/css
|
||||
$(info bundling css)
|
||||
$(ESBUILD) --bundle css/base.css --outfile=build/data/web/css/bundle.css --external:remixicon.css --minify
|
||||
$(ESBUILD) --bundle css/base.css --outfile=data/web/css/bundle.css --external:remixicon.css --minify
|
||||
|
||||
copy:
|
||||
$(info copying fonts)
|
||||
cp -r node_modules/remixicon/fonts/remixicon.css node_modules/remixicon/fonts/remixicon.woff2 build/data/web/css/
|
||||
cp -r node_modules/remixicon/fonts/remixicon.css node_modules/remixicon/fonts/remixicon.woff2 data/web/css/
|
||||
$(info copying html)
|
||||
cp -r html build/data/
|
||||
cp -r html data/
|
||||
$(info copying static data)
|
||||
-mkdir -p build/data/web
|
||||
cp -r static/* build/data/web/
|
||||
-mkdir -p data/web
|
||||
cp -r static/* data/web/
|
||||
$(info copying language files)
|
||||
cp -r lang build/data/
|
||||
cp -r lang data/
|
||||
|
||||
embed:
|
||||
python embed.py internal
|
||||
|
||||
noembed:
|
||||
python embed.py external
|
||||
-mkdir -p build
|
||||
$(info copying internal data into build/)
|
||||
cp -r data build/
|
||||
|
||||
install:
|
||||
cp -r build $(DESTDIR)/jfa-go
|
||||
|
||||
all: configuration npm email version typescript bundle-css swagger compile copy
|
||||
debug: configuration npm email version ts-debug bundle-css swagger compile copy
|
||||
all: configuration npm email version typescript bundle-css swagger copy embed compile
|
||||
all-external: configuration npm email version ts-debug bundle-css swagger copy noembed compile
|
||||
debug: configuration npm email version ts-debug bundle-css swagger copy noembed compile-debug
|
||||
|
@ -39,9 +39,8 @@ I chose to rewrite the python [jellyfin-accounts](https://github.com/hrfee/jelly
|
||||
|
||||
Available on the AUR as [jfa-go](https://aur.archlinux.org/packages/jfa-go/) or [jfa-go-git](https://aur.archlinux.org/packages/jfa-go-git/).
|
||||
|
||||
For other platforms, grab an archive from the release section for your platform (or nightly builds [here](https://builds.hrfee.pw/view/hrfee/jfa-go)), and extract `jfa-go` and `data` to the same directory.
|
||||
* For linux users, you can place them inside `/opt/jfa-go` and then run
|
||||
`sudo ln -s /opt/jfa-go/jfa-go /usr/bin/jfa-go` to place it in your PATH.
|
||||
For other platforms, grab an archive from the release section for your platform (or nightly builds [here](https://builds.hrfee.pw/view/hrfee/jfa-go)), and extract the `jfa-go` executable to somewhere useful.
|
||||
* For \*nix/macOS users, `chmod +x jfa-go` then place it somewhere in your PATH like `/usr/bin`.
|
||||
|
||||
Run the executable to start.
|
||||
|
||||
|
3
api.go
3
api.go
@ -1330,6 +1330,9 @@ func (app *appContext) GetLanguages(gc *gin.Context) {
|
||||
gc.JSON(200, resp)
|
||||
}
|
||||
|
||||
// @Summary Restarts the program. No response means success.
|
||||
// @Router /restart [post]
|
||||
// @tags Other
|
||||
func (app *appContext) restart(gc *gin.Context) {
|
||||
app.info.Println("Restarting...")
|
||||
err := app.Restart()
|
||||
|
37
config.go
37
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 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")
|
||||
|
@ -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\"",
|
||||
@ -860,6 +860,14 @@
|
||||
"type": "text",
|
||||
"value": "",
|
||||
"description": "Path to directory containing custom versions of web ui pages. See wiki for more info."
|
||||
},
|
||||
"lang_files": {
|
||||
"name": "Custom language files directory",
|
||||
"required": false,
|
||||
"requires_restart": true,
|
||||
"type": "text",
|
||||
"value": "",
|
||||
"description": "The path to a directory which following the same form as the internal 'lang/' directory. See GitHub for more info."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
262
email.go
262
email.go
@ -153,6 +153,28 @@ func (emailer *Emailer) NewSMTP(server string, port int, username, password stri
|
||||
}
|
||||
}
|
||||
|
||||
func (emailer *Emailer) construct(app *appContext, section, keyFragment string, data map[string]interface{}) (html, text string, err error) {
|
||||
var tpl *template.Template
|
||||
for _, key := range []string{"html", "text"} {
|
||||
filesystem, fpath := app.GetPath(section, keyFragment+key)
|
||||
tpl, err = template.ParseFS(filesystem, fpath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var tplData bytes.Buffer
|
||||
err = tpl.Execute(&tplData, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if key == "html" {
|
||||
html = tplData.String()
|
||||
} else {
|
||||
text = tplData.String()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (emailer *Emailer) constructConfirmation(code, username, key string, app *appContext) (*Email, error) {
|
||||
email := &Email{
|
||||
subject: app.config.Section("email_confirmation").Key("subject").MustString(emailer.lang.EmailConfirmation.get("title")),
|
||||
@ -160,30 +182,17 @@ func (emailer *Emailer) constructConfirmation(code, username, key string, app *a
|
||||
message := app.config.Section("email").Key("message").String()
|
||||
inviteLink := app.config.Section("invite_emails").Key("url_base").String()
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var tplData bytes.Buffer
|
||||
err = tpl.Execute(&tplData, map[string]string{
|
||||
"helloUser": emailer.lang.Strings.format("helloUser", username),
|
||||
"clickBelow": emailer.lang.EmailConfirmation.get("clickBelow"),
|
||||
"ifItWasNotYou": emailer.lang.Strings.get("ifItWasNotYou"),
|
||||
"urlVal": inviteLink,
|
||||
"confirmEmail": emailer.lang.EmailConfirmation.get("confirmEmail"),
|
||||
"message": message,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if key == "html" {
|
||||
email.html = tplData.String()
|
||||
} else {
|
||||
email.text = tplData.String()
|
||||
}
|
||||
var err error
|
||||
email.html, email.text, err = emailer.construct(app, "email_confirmation", "email_", map[string]interface{}{
|
||||
"helloUser": emailer.lang.Strings.format("helloUser", username),
|
||||
"clickBelow": emailer.lang.EmailConfirmation.get("clickBelow"),
|
||||
"ifItWasNotYou": emailer.lang.Strings.get("ifItWasNotYou"),
|
||||
"urlVal": inviteLink,
|
||||
"confirmEmail": emailer.lang.EmailConfirmation.get("confirmEmail"),
|
||||
"message": message,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return email, nil
|
||||
}
|
||||
@ -197,31 +206,18 @@ func (emailer *Emailer) constructInvite(code string, invite Invite, app *appCont
|
||||
message := app.config.Section("email").Key("message").String()
|
||||
inviteLink := app.config.Section("invite_emails").Key("url_base").String()
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var tplData bytes.Buffer
|
||||
err = tpl.Execute(&tplData, map[string]string{
|
||||
"hello": emailer.lang.InviteEmail.get("hello"),
|
||||
"youHaveBeenInvited": emailer.lang.InviteEmail.get("youHaveBeenInvited"),
|
||||
"toJoin": emailer.lang.InviteEmail.get("toJoin"),
|
||||
"inviteExpiry": emailer.lang.InviteEmail.format("inviteExpiry", d, t, expiresIn),
|
||||
"linkButton": emailer.lang.InviteEmail.get("linkButton"),
|
||||
"invite_link": inviteLink,
|
||||
"message": message,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if key == "html" {
|
||||
email.html = tplData.String()
|
||||
} else {
|
||||
email.text = tplData.String()
|
||||
}
|
||||
var err error
|
||||
email.html, email.text, err = emailer.construct(app, "invite_emails", "email_", map[string]interface{}{
|
||||
"hello": emailer.lang.InviteEmail.get("hello"),
|
||||
"youHaveBeenInvited": emailer.lang.InviteEmail.get("youHaveBeenInvited"),
|
||||
"toJoin": emailer.lang.InviteEmail.get("toJoin"),
|
||||
"inviteExpiry": emailer.lang.InviteEmail.format("inviteExpiry", d, t, expiresIn),
|
||||
"linkButton": emailer.lang.InviteEmail.get("linkButton"),
|
||||
"invite_link": inviteLink,
|
||||
"message": message,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return email, nil
|
||||
}
|
||||
@ -231,26 +227,14 @@ func (emailer *Emailer) constructExpiry(code string, invite Invite, app *appCont
|
||||
subject: emailer.lang.InviteExpiry.get("title"),
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var tplData bytes.Buffer
|
||||
err = tpl.Execute(&tplData, map[string]string{
|
||||
"inviteExpired": emailer.lang.InviteExpiry.get("inviteExpired"),
|
||||
"expiredAt": emailer.lang.InviteExpiry.format("expiredAt", "\""+code+"\"", expiry),
|
||||
"notificationNotice": emailer.lang.InviteExpiry.get("notificationNotice"),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if key == "html" {
|
||||
email.html = tplData.String()
|
||||
} else {
|
||||
email.text = tplData.String()
|
||||
}
|
||||
var err error
|
||||
email.html, email.text, err = emailer.construct(app, "notifications", "expiry_", map[string]interface{}{
|
||||
"inviteExpired": emailer.lang.InviteExpiry.get("inviteExpired"),
|
||||
"expiredAt": emailer.lang.InviteExpiry.format("expiredAt", "\""+code+"\"", expiry),
|
||||
"notificationNotice": emailer.lang.InviteExpiry.get("notificationNotice"),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return email, nil
|
||||
}
|
||||
@ -266,31 +250,19 @@ func (emailer *Emailer) constructCreated(code, username, address string, invite
|
||||
} else {
|
||||
tplAddress = address
|
||||
}
|
||||
for _, key := range []string{"html", "text"} {
|
||||
fpath := app.config.Section("notifications").Key("created_" + key).String()
|
||||
tpl, err := template.ParseFiles(fpath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var tplData bytes.Buffer
|
||||
err = tpl.Execute(&tplData, map[string]string{
|
||||
"aUserWasCreated": emailer.lang.UserCreated.format("aUserWasCreated", "\""+code+"\""),
|
||||
"name": emailer.lang.Strings.get("name"),
|
||||
"address": emailer.lang.Strings.get("emailAddress"),
|
||||
"time": emailer.lang.UserCreated.get("time"),
|
||||
"nameVal": username,
|
||||
"addressVal": tplAddress,
|
||||
"timeVal": created,
|
||||
"notificationNotice": emailer.lang.UserCreated.get("notificationNotice"),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if key == "html" {
|
||||
email.html = tplData.String()
|
||||
} else {
|
||||
email.text = tplData.String()
|
||||
}
|
||||
var err error
|
||||
email.html, email.text, err = emailer.construct(app, "notifications", "created_", map[string]interface{}{
|
||||
"aUserWasCreated": emailer.lang.UserCreated.format("aUserWasCreated", "\""+code+"\""),
|
||||
"name": emailer.lang.Strings.get("name"),
|
||||
"address": emailer.lang.Strings.get("emailAddress"),
|
||||
"time": emailer.lang.UserCreated.get("time"),
|
||||
"nameVal": username,
|
||||
"addressVal": tplAddress,
|
||||
"timeVal": created,
|
||||
"notificationNotice": emailer.lang.UserCreated.get("notificationNotice"),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return email, nil
|
||||
}
|
||||
@ -301,31 +273,19 @@ 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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var tplData bytes.Buffer
|
||||
err = tpl.Execute(&tplData, map[string]string{
|
||||
"helloUser": emailer.lang.Strings.format("helloUser", pwr.Username),
|
||||
"someoneHasRequestedReset": emailer.lang.PasswordReset.get("someoneHasRequestedReset"),
|
||||
"ifItWasYou": emailer.lang.PasswordReset.get("ifItWasYou"),
|
||||
"codeExpiry": emailer.lang.PasswordReset.format("codeExpiry", d, t, expiresIn),
|
||||
"ifItWasNotYou": emailer.lang.Strings.get("ifItWasNotYou"),
|
||||
"pin": emailer.lang.PasswordReset.get("pin"),
|
||||
"pinVal": pwr.Pin,
|
||||
"message": message,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if key == "html" {
|
||||
email.html = tplData.String()
|
||||
} else {
|
||||
email.text = tplData.String()
|
||||
}
|
||||
var err error
|
||||
email.html, email.text, err = emailer.construct(app, "password_resets", "email_", map[string]interface{}{
|
||||
"helloUser": emailer.lang.Strings.format("helloUser", pwr.Username),
|
||||
"someoneHasRequestedReset": emailer.lang.PasswordReset.get("someoneHasRequestedReset"),
|
||||
"ifItWasYou": emailer.lang.PasswordReset.get("ifItWasYou"),
|
||||
"codeExpiry": emailer.lang.PasswordReset.format("codeExpiry", d, t, expiresIn),
|
||||
"ifItWasNotYou": emailer.lang.Strings.get("ifItWasNotYou"),
|
||||
"pin": emailer.lang.PasswordReset.get("pin"),
|
||||
"pinVal": pwr.Pin,
|
||||
"message": message,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return email, nil
|
||||
}
|
||||
@ -334,26 +294,14 @@ func (emailer *Emailer) constructDeleted(reason string, app *appContext) (*Email
|
||||
email := &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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var tplData bytes.Buffer
|
||||
err = tpl.Execute(&tplData, map[string]string{
|
||||
"yourAccountWasDeleted": emailer.lang.UserDeleted.get("yourAccountWasDeleted"),
|
||||
"reason": emailer.lang.UserDeleted.get("reason"),
|
||||
"reasonVal": reason,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if key == "html" {
|
||||
email.html = tplData.String()
|
||||
} else {
|
||||
email.text = tplData.String()
|
||||
}
|
||||
var err error
|
||||
email.html, email.text, err = emailer.construct(app, "deletion", "email_", map[string]interface{}{
|
||||
"yourAccountWasDeleted": emailer.lang.UserDeleted.get("yourAccountWasDeleted"),
|
||||
"reason": emailer.lang.UserDeleted.get("reason"),
|
||||
"reasonVal": reason,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return email, nil
|
||||
}
|
||||
@ -362,30 +310,18 @@ func (emailer *Emailer) constructWelcome(username string, app *appContext) (*Ema
|
||||
email := &Email{
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var tplData bytes.Buffer
|
||||
err = tpl.Execute(&tplData, map[string]string{
|
||||
"welcome": emailer.lang.WelcomeEmail.get("welcome"),
|
||||
"youCanLoginWith": emailer.lang.WelcomeEmail.get("youCanLoginWith"),
|
||||
"jellyfinURL": emailer.lang.WelcomeEmail.get("jellyfinURL"),
|
||||
"jellyfinURLVal": app.config.Section("jellyfin").Key("public_server").String(),
|
||||
"username": emailer.lang.Strings.get("username"),
|
||||
"usernameVal": username,
|
||||
"message": app.config.Section("email").Key("message").String(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if key == "html" {
|
||||
email.html = tplData.String()
|
||||
} else {
|
||||
email.text = tplData.String()
|
||||
}
|
||||
var err error
|
||||
email.html, email.text, err = emailer.construct(app, "welcome_email", "email_", map[string]interface{}{
|
||||
"welcome": emailer.lang.WelcomeEmail.get("welcome"),
|
||||
"youCanLoginWith": emailer.lang.WelcomeEmail.get("youCanLoginWith"),
|
||||
"jellyfinURL": emailer.lang.WelcomeEmail.get("jellyfinURL"),
|
||||
"jellyfinURLVal": app.config.Section("jellyfin").Key("public_server").String(),
|
||||
"username": emailer.lang.Strings.get("username"),
|
||||
"usernameVal": username,
|
||||
"message": app.config.Section("email").Key("message").String(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return email, nil
|
||||
}
|
||||
|
68
embed.py
Executable file
68
embed.py
Executable file
@ -0,0 +1,68 @@
|
||||
#!/usr/bin/python
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("embed", metavar="<true/false>|<internal/external>|<yes/no>", type=str)
|
||||
trues = ["true", "internal", "yes", "y"]
|
||||
falses = ["false", "external", "no", "n"]
|
||||
|
||||
EMBED = parser.parse_args().embed
|
||||
|
||||
with open("embed.go", "w") as f:
|
||||
if EMBED in trues:
|
||||
f.write("""package main
|
||||
import (
|
||||
"embed"
|
||||
"io/fs"
|
||||
"log"
|
||||
)
|
||||
|
||||
//go:embed data data/html data/web data/web/css data/web/js
|
||||
var loFS embed.FS
|
||||
|
||||
//go:embed lang/common lang/admin lang/email lang/form lang/setup
|
||||
var laFS embed.FS
|
||||
|
||||
var langFS rewriteFS
|
||||
var localFS rewriteFS
|
||||
|
||||
type rewriteFS struct {
|
||||
fs embed.FS
|
||||
prefix string
|
||||
}
|
||||
|
||||
func (l rewriteFS) Open(name string) (fs.File, error) { return l.fs.Open(l.prefix + name) }
|
||||
func (l rewriteFS) ReadDir(name string) ([]fs.DirEntry, error) { return l.fs.ReadDir(l.prefix + name) }
|
||||
func (l rewriteFS) ReadFile(name string) ([]byte, error) { return l.fs.ReadFile(l.prefix + name) }
|
||||
func FSJoin(elem ...string) string {
|
||||
out := ""
|
||||
for _, v := range elem { out += v + "/" }
|
||||
return out[:len(out)-1]
|
||||
}
|
||||
|
||||
func loadFilesystems() {
|
||||
langFS = rewriteFS{laFS, "lang/"}
|
||||
localFS = rewriteFS{loFS, "data/"}
|
||||
log.Println("Using internal storage")
|
||||
}""")
|
||||
elif EMBED in falses:
|
||||
f.write("""package main
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"log"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var localFS fs.FS
|
||||
var langFS fs.FS
|
||||
|
||||
func FSJoin(elem ...string) string { return filepath.Join(elem...) }
|
||||
|
||||
func loadFilesystems() {
|
||||
log.Println("Using external storage")
|
||||
executable, _ := os.Executable()
|
||||
localFS = os.DirFS(filepath.Join(filepath.Dir(executable), "data"))
|
||||
langFS = os.DirFS(filepath.Join(filepath.Dir(executable), "data", "lang"))
|
||||
}""")
|
8
go.mod
8
go.mod
@ -1,6 +1,6 @@
|
||||
module github.com/hrfee/jfa-go
|
||||
|
||||
go 1.14
|
||||
go 1.16
|
||||
|
||||
replace github.com/hrfee/jfa-go/docs => ./docs
|
||||
|
||||
@ -17,8 +17,7 @@ require (
|
||||
github.com/gin-contrib/static v0.0.0-20200916080430-d45d9a37d28e
|
||||
github.com/gin-gonic/gin v1.6.3
|
||||
github.com/go-chi/chi v4.1.2+incompatible // indirect
|
||||
github.com/go-openapi/spec v0.20.1 // indirect
|
||||
github.com/go-openapi/swag v0.19.13 // indirect
|
||||
github.com/go-openapi/spec v0.20.3 // indirect
|
||||
github.com/go-playground/validator/v10 v10.4.1 // indirect
|
||||
github.com/golang/protobuf v1.4.3 // indirect
|
||||
github.com/google/uuid v1.1.2 // indirect
|
||||
@ -31,6 +30,7 @@ require (
|
||||
github.com/lithammer/shortuuid/v3 v3.0.4
|
||||
github.com/logrusorgru/aurora/v3 v3.0.0
|
||||
github.com/mailgun/mailgun-go/v4 v4.3.0
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/smartystreets/goconvey v1.6.4 // indirect
|
||||
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14
|
||||
@ -38,9 +38,7 @@ require (
|
||||
github.com/swaggo/swag v1.7.0 // indirect
|
||||
github.com/ugorji/go v1.2.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9 // indirect
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect
|
||||
golang.org/x/text v0.3.5 // indirect
|
||||
golang.org/x/tools v0.1.0 // indirect
|
||||
google.golang.org/protobuf v1.25.0 // indirect
|
||||
gopkg.in/ini.v1 v1.62.0
|
||||
|
57
go.sum
57
go.sum
@ -11,7 +11,6 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafo
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
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/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=
|
||||
@ -30,7 +29,6 @@ github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQD
|
||||
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=
|
||||
@ -48,42 +46,32 @@ github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmC
|
||||
github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
|
||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||
github.com/go-chi/chi v4.0.0+incompatible h1:SiLLEDyAkqNnw+T/uDTf3aFB9T4FTrwMpuYrgaRcnW4=
|
||||
github.com/go-chi/chi v4.0.0+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
|
||||
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
|
||||
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
|
||||
github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
|
||||
github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
|
||||
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
|
||||
github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o=
|
||||
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
|
||||
github.com/go-openapi/jsonreference v0.19.4 h1:3Vw+rh13uq2JFNxgnMTGE1rnoieU9FmyE1gvnyylsYg=
|
||||
github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
|
||||
github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM=
|
||||
github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
|
||||
github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||
github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo=
|
||||
github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
||||
github.com/go-openapi/spec v0.19.14 h1:r4fbYFo6N4ZelmSX8G6p+cv/hZRXzcuqQIADGT1iNKM=
|
||||
github.com/go-openapi/spec v0.19.14/go.mod h1:gwrgJS15eCUgjLpMjBJmbZezCsw88LmgeEip0M63doA=
|
||||
github.com/go-openapi/spec v0.20.1 h1:5WNKTzPguDN+79wbJw2UE2q+eX+gUmEFsIKSvnSQJlc=
|
||||
github.com/go-openapi/spec v0.20.1/go.mod h1:93x7oh+d+FQsmsieroS4cmR3u0p/ywH649a3qwC9OsQ=
|
||||
github.com/go-openapi/spec v0.20.3 h1:uH9RQ6vdyPSs2pSy9fL8QPspDF2AMIMPtmK5coSSjtQ=
|
||||
github.com/go-openapi/spec v0.20.3/go.mod h1:gG4F8wdEDN+YPBMVnzE85Rbhf+Th2DTvA9nFPQ5AYEg=
|
||||
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.11 h1:RFTu/dlFySpyVvJDfp/7674JY4SDglYWKztbiIGFpmc=
|
||||
github.com/go-openapi/swag v0.19.11/go.mod h1:Uc0gKkdR+ojzsEpjh39QChyu92vPgIr72POcgHMAgSY=
|
||||
github.com/go-openapi/swag v0.19.12 h1:Bc0bnY2c3AoF7Gc+IMIAQQsD8fLHjHpc19wXvYuayQI=
|
||||
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-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng=
|
||||
github.com/go-openapi/swag v0.19.14/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=
|
||||
@ -92,7 +80,6 @@ github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTM
|
||||
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
|
||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
|
||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||
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=
|
||||
@ -101,7 +88,6 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
|
||||
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=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
@ -118,7 +104,6 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
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/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=
|
||||
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@ -131,7 +116,6 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF
|
||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
@ -157,35 +141,31 @@ github.com/mailgun/mailgun-go/v4 v4.3.0/go.mod h1:fWuBI2iaS/pSSyo6+EBpHjatQO3lV8
|
||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
||||
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/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/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/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/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
@ -200,40 +180,32 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd
|
||||
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=
|
||||
github.com/swaggo/gin-swagger v1.2.0 h1:YskZXEiv51fjOMTsXrOetAjrMDfFaXD79PEoQBOe2W0=
|
||||
github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI=
|
||||
github.com/swaggo/gin-swagger v1.3.0 h1:eOmp7r57oUgZPw2dJOjcGNMse9cvXcI4tTqBcnZtPsI=
|
||||
github.com/swaggo/gin-swagger v1.3.0/go.mod h1:oy1BRA6WvgtCp848lhxce7BnWH4C8Bxa0m5SkWx+cS0=
|
||||
github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y=
|
||||
github.com/swaggo/swag v1.6.7 h1:e8GC2xDllJZr3omJkm9YfmK0Y56+rMO3cg0JBKNz09s=
|
||||
github.com/swaggo/swag v1.6.7/go.mod h1:xDhTyuFIujYiN3DKWC/H/83xcfHp+UE/IzWWampG7Zc=
|
||||
github.com/swaggo/swag v1.7.0 h1:5bCA/MTLQoIqDXXyHfOpMeDvL9j68OY/udlK4pQoo4E=
|
||||
github.com/swaggo/swag v1.7.0/go.mod h1:BdPIL73gvS9NBsdi7M1JOxLvlbfvNRaBP8m6WT6Aajo=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go v1.1.13/go.mod h1:jxau1n+/wyTGLQoCkjok9r5zFa/FxT6eI5HiHKQszjc=
|
||||
github.com/ugorji/go v1.2.0 h1:6eXlzYLLwZwXroJx9NyqbYcbv/d93twiOzQLDewE6qM=
|
||||
github.com/ugorji/go v1.2.0/go.mod h1:1ny++pKMXhLWrwWV5Nf+CbOuZJhMoaFD+0GMFfd8fEc=
|
||||
github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ugorji/go/codec v1.1.13/go.mod h1:oNVt3Dq+FO91WNQ/9JnHKQP2QJxTzoN7wCBFCq1OeuU=
|
||||
github.com/ugorji/go/codec v1.2.0 h1:As6RccOIlbm9wHuWYMlB30dErcI+4WiKWsYsmPkyrUw=
|
||||
github.com/ugorji/go/codec v1.2.0/go.mod h1:dXvG35r7zTX6QImXOSFhGMmKtX+wJ7VTWzGvYQGIjBs=
|
||||
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k=
|
||||
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
||||
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
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=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/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=
|
||||
@ -254,14 +226,9 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
|
||||
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/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@ -279,23 +246,16 @@ golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/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/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=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@ -307,10 +267,8 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59 h1:QjA/9ArTfVTLfEhClDCG7SGrZkZixxWpwNCDiwJfh88=
|
||||
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201120155355-20be4ac4bd6e h1:t96dS3DO8DGjawSLJL/HIdz8CycAd2v07XxqB3UPTi0=
|
||||
golang.org/x/tools v0.0.0-20201120155355-20be4ac4bd6e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
@ -333,7 +291,6 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
@ -350,9 +307,7 @@ gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
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=
|
||||
|
225
main.go
225
main.go
@ -8,7 +8,7 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"io/fs"
|
||||
"log"
|
||||
"mime"
|
||||
"net"
|
||||
@ -22,20 +22,27 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-contrib/pprof"
|
||||
"github.com/gin-contrib/static"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/hrfee/jfa-go/common"
|
||||
_ "github.com/hrfee/jfa-go/docs"
|
||||
"github.com/hrfee/jfa-go/mediabrowser"
|
||||
"github.com/hrfee/jfa-go/ombi"
|
||||
"github.com/lithammer/shortuuid/v3"
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
swaggerFiles "github.com/swaggo/files"
|
||||
ginSwagger "github.com/swaggo/gin-swagger"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
PLATFORM string = runtime.GOOS
|
||||
SOCK string = "jfa-go.sock"
|
||||
SRV *http.Server
|
||||
RESTART chan bool
|
||||
DATA, CONFIG, HOST *string
|
||||
PORT *int
|
||||
DEBUG *bool
|
||||
TEST bool
|
||||
SWAGGER *bool
|
||||
)
|
||||
|
||||
var serverTypes = map[string]string{
|
||||
"jellyfin": "Jellyfin",
|
||||
"emby": "Emby (experimental)",
|
||||
@ -50,7 +57,7 @@ type User struct {
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// contains everything the application needs, essentially. Wouldn't do this in the future.
|
||||
// contains (almost) everything the application needs, essentially. This was a dumb design decision imo.
|
||||
type appContext struct {
|
||||
// defaults *Config
|
||||
config *ini.File
|
||||
@ -58,7 +65,8 @@ type appContext struct {
|
||||
configBasePath string
|
||||
configBase settings
|
||||
dataPath string
|
||||
localPath string
|
||||
systemFS fs.FS
|
||||
webFS httpFS
|
||||
cssClass string
|
||||
jellyfinLogin bool
|
||||
users []User
|
||||
@ -80,27 +88,6 @@ type appContext struct {
|
||||
URLBase string
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
app.err.Fatalf("Couldn't access template directory: \"%s\"", templatePath)
|
||||
return
|
||||
}
|
||||
loadFiles := make([]string, len(htmlFiles))
|
||||
for i, f := range htmlFiles {
|
||||
if _, err := os.Stat(filepath.Join(customPath, f.Name())); os.IsNotExist(err) {
|
||||
app.debug.Printf("Using default \"%s\"", f.Name())
|
||||
loadFiles[i] = filepath.Join(templatePath, f.Name())
|
||||
} else {
|
||||
app.info.Printf("Using custom \"%s\"", f.Name())
|
||||
loadFiles[i] = filepath.Join(filepath.Join(customPath, f.Name()))
|
||||
}
|
||||
}
|
||||
router.LoadHTMLFiles(loadFiles...)
|
||||
}
|
||||
|
||||
func generateSecret(length int) (string, error) {
|
||||
bytes := make([]byte, length)
|
||||
_, err := rand.Read(bytes)
|
||||
@ -110,46 +97,6 @@ func generateSecret(length int) (string, error) {
|
||||
return base64.URLEncoding.EncodeToString(bytes), err
|
||||
}
|
||||
|
||||
func setGinLogger(router *gin.Engine, debugMode bool) {
|
||||
if debugMode {
|
||||
router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
|
||||
return fmt.Sprintf("[GIN/DEBUG] %s: %s(%s) => %d in %s; %s\n",
|
||||
param.TimeStamp.Format("15:04:05"),
|
||||
param.Method,
|
||||
param.Path,
|
||||
param.StatusCode,
|
||||
param.Latency,
|
||||
func() string {
|
||||
if param.ErrorMessage != "" {
|
||||
return "Error: " + param.ErrorMessage
|
||||
}
|
||||
return ""
|
||||
}(),
|
||||
)
|
||||
}))
|
||||
} else {
|
||||
router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
|
||||
return fmt.Sprintf("[GIN] %s(%s) => %d\n",
|
||||
param.Method,
|
||||
param.Path,
|
||||
param.StatusCode,
|
||||
)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
PLATFORM string = runtime.GOOS
|
||||
SOCK string = "jfa-go.sock"
|
||||
SRV *http.Server
|
||||
RESTART chan bool
|
||||
DATA, CONFIG, HOST *string
|
||||
PORT *int
|
||||
DEBUG *bool
|
||||
TEST bool
|
||||
SWAGGER *bool
|
||||
)
|
||||
|
||||
func test(app *appContext) {
|
||||
fmt.Printf("\n\n----\n\n")
|
||||
settings := map[string]interface{}{
|
||||
@ -186,15 +133,20 @@ func start(asDaemon, firstCall bool) {
|
||||
app := new(appContext)
|
||||
|
||||
/*
|
||||
set default config, data and local paths
|
||||
also, confusing naming here. data_path is not the internal 'data' directory, rather the users .config/jfa-go folder.
|
||||
local_path is the internal 'data' directory.
|
||||
set default config and data paths
|
||||
data: Contains invites.json, emails.json, user_profile.json, etc.
|
||||
config: config.ini. Usually in data, but can be changed via -config.
|
||||
localFS is jfa-go's internal data. On external builds, the directory is named "data" and placed next to the executable.
|
||||
*/
|
||||
userConfigDir, _ := os.UserConfigDir()
|
||||
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.systemFS = os.DirFS("/")
|
||||
// gin-static doesn't just take a plain http.FileSystem, so we implement it's ServeFileSystem. See static.go.
|
||||
app.webFS = httpFS{
|
||||
hfs: http.FS(localFS),
|
||||
fs: localFS,
|
||||
}
|
||||
|
||||
app.info = log.New(os.Stdout, "[INFO] ", log.Ltime)
|
||||
app.err = log.New(os.Stdout, "[ERROR] ", log.Ltime|log.Lshortfile)
|
||||
@ -251,23 +203,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(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 +236,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,11 +278,17 @@ 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")
|
||||
err := app.storage.loadLang()
|
||||
app.storage.lang.CommonPath = "common"
|
||||
app.storage.lang.FormPath = "form"
|
||||
app.storage.lang.AdminPath = "admin"
|
||||
app.storage.lang.EmailPath = "email"
|
||||
externalLang := app.config.Section("files").Key("lang_files").MustString("")
|
||||
var err error
|
||||
if externalLang == "" {
|
||||
err = app.storage.loadLang(langFS)
|
||||
} else {
|
||||
err = app.storage.loadLang(langFS, os.DirFS(externalLang))
|
||||
}
|
||||
if err != nil {
|
||||
app.info.Fatalf("Failed to load language files: %+v\n", err)
|
||||
}
|
||||
@ -413,8 +367,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(localFS, app.configBasePath)
|
||||
json.Unmarshal(configBase, &app.configBase)
|
||||
|
||||
themes := map[string]string{
|
||||
@ -562,8 +516,8 @@ func start(asDaemon, firstCall bool) {
|
||||
} else {
|
||||
debugMode = false
|
||||
address = "0.0.0.0:8056"
|
||||
app.storage.lang.SetupPath = filepath.Join(app.localPath, "lang", "setup")
|
||||
err := app.storage.loadLangSetup()
|
||||
app.storage.lang.SetupPath = "setup"
|
||||
err := app.storage.loadLangSetup(langFS)
|
||||
if err != nil {
|
||||
app.info.Fatalf("Failed to load language files: %+v\n", err)
|
||||
}
|
||||
@ -572,89 +526,15 @@ func start(asDaemon, firstCall bool) {
|
||||
// workaround for potentially broken windows mime types
|
||||
mime.AddExtensionType(".js", "application/javascript")
|
||||
|
||||
app.info.Println("Initializing router")
|
||||
router := app.loadRouter(address, debugMode)
|
||||
app.info.Println("Loading routes")
|
||||
if debugMode {
|
||||
gin.SetMode(gin.DebugMode)
|
||||
} else {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
router := gin.New()
|
||||
|
||||
setGinLogger(router, debugMode)
|
||||
|
||||
router.Use(gin.Recovery())
|
||||
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)))
|
||||
}
|
||||
app.loadHTML(router)
|
||||
router.NoRoute(app.NoRouteHandler)
|
||||
if debugMode {
|
||||
app.debug.Println("Loading pprof")
|
||||
pprof.Register(router)
|
||||
}
|
||||
for _, p := range routePrefixes {
|
||||
router.GET(p+"/lang/:page", app.GetLanguages)
|
||||
}
|
||||
if !firstRun {
|
||||
for _, p := range routePrefixes {
|
||||
router.GET(p+"/", app.AdminPage)
|
||||
router.GET(p+"/accounts", app.AdminPage)
|
||||
router.GET(p+"/settings", app.AdminPage)
|
||||
router.GET(p+"/lang/:page/:file", app.ServeLang)
|
||||
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.GET(p+"/invite/:invCode", app.InviteProxy)
|
||||
}
|
||||
if *SWAGGER {
|
||||
app.info.Print(aurora.Magenta("\n\nWARNING: Swagger should not be used on a public instance.\n\n"))
|
||||
for _, p := range routePrefixes {
|
||||
router.GET(p+"/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
||||
}
|
||||
}
|
||||
api := router.Group("/", app.webAuth())
|
||||
for _, p := range routePrefixes {
|
||||
router.POST(p+"/logout", app.Logout)
|
||||
api.DELETE(p+"/users", app.DeleteUser)
|
||||
api.GET(p+"/users", app.GetUsers)
|
||||
api.POST(p+"/users", app.NewUserAdmin)
|
||||
api.POST(p+"/invites", app.GenerateInvite)
|
||||
api.GET(p+"/invites", app.GetInvites)
|
||||
api.DELETE(p+"/invites", app.DeleteInvite)
|
||||
api.POST(p+"/invites/profile", app.SetProfile)
|
||||
api.GET(p+"/profiles", app.GetProfiles)
|
||||
api.POST(p+"/profiles/default", app.SetDefaultProfile)
|
||||
api.POST(p+"/profiles", app.CreateProfile)
|
||||
api.DELETE(p+"/profiles", app.DeleteProfile)
|
||||
api.POST(p+"/invites/notify", app.SetNotify)
|
||||
api.POST(p+"/users/emails", app.ModifyEmails)
|
||||
// api.POST(p + "/setDefaults", app.SetDefaults)
|
||||
api.POST(p+"/users/settings", app.ApplySettings)
|
||||
api.GET(p+"/config", app.GetConfig)
|
||||
api.POST(p+"/config", app.ModifyConfig)
|
||||
api.POST(p+"/restart", app.restart)
|
||||
if app.config.Section("ombi").Key("enabled").MustBool(false) {
|
||||
api.GET(p+"/ombi/users", app.OmbiUsers)
|
||||
api.POST(p+"/ombi/defaults", app.SetOmbiDefaults)
|
||||
}
|
||||
}
|
||||
app.info.Printf("Starting router @ %s", address)
|
||||
app.loadRoutes(router)
|
||||
} else {
|
||||
router.GET("/", app.ServeSetup)
|
||||
router.POST("/jellyfin/test", app.TestJF)
|
||||
router.POST("/config", app.ModifyConfig)
|
||||
app.loadSetup(router)
|
||||
app.info.Printf("Loading setup @ %s", address)
|
||||
}
|
||||
|
||||
SRV = &http.Server{
|
||||
Addr: address,
|
||||
Handler: router,
|
||||
}
|
||||
go func() {
|
||||
if app.config.Section("advanced").Key("tls").MustBool(false) {
|
||||
cert := app.config.Section("advanced").Key("tls_cert").MustString("")
|
||||
@ -676,9 +556,9 @@ func start(asDaemon, firstCall bool) {
|
||||
}
|
||||
}()
|
||||
for range RESTART {
|
||||
cntx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
if err := SRV.Shutdown(cntx); err != nil {
|
||||
if err := SRV.Shutdown(ctx); err != nil {
|
||||
app.err.Fatalf("Server shutdown error: %s", err)
|
||||
}
|
||||
return
|
||||
@ -757,6 +637,7 @@ func main() {
|
||||
if flagPassed("test") {
|
||||
TEST = true
|
||||
}
|
||||
loadFilesystems()
|
||||
if flagPassed("start") {
|
||||
args := []string{}
|
||||
for i, f := range os.Args {
|
||||
|
5
package-lock.json
generated
5
package-lock.json
generated
@ -227,11 +227,6 @@
|
||||
"resolved": "https://registry.npm.taobao.org/entities/download/entities-1.1.2.tgz?cache=0&sync_timestamp=1602897347667&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fentities%2Fdownload%2Fentities-1.1.2.tgz",
|
||||
"integrity": "sha1-vfpzUplmTfr9NFKe1PhSKidf6lY="
|
||||
},
|
||||
"esbuild": {
|
||||
"version": "0.8.46",
|
||||
"resolved": "https://registry.npm.taobao.org/esbuild/download/esbuild-0.8.46.tgz",
|
||||
"integrity": "sha1-j8cjDOMBmxLiVTOZ8MA4dacpwms="
|
||||
},
|
||||
"escalade": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npm.taobao.org/escalade/download/escalade-3.1.1.tgz?cache=0&sync_timestamp=1602567437752&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fescalade%2Fdownload%2Fescalade-3.1.1.tgz",
|
||||
|
@ -18,7 +18,6 @@
|
||||
"homepage": "https://github.com/hrfee/jfa-go#readme",
|
||||
"dependencies": {
|
||||
"a17t": "^0.4.0",
|
||||
"esbuild": "^0.8.46",
|
||||
"lodash": "^4.17.19",
|
||||
"mjml": "^4.8.0",
|
||||
"remixicon": "^2.5.0",
|
||||
|
@ -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
|
||||
}
|
||||
|
156
router.go
Normal file
156
router.go
Normal file
@ -0,0 +1,156 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gin-contrib/pprof"
|
||||
"github.com/gin-contrib/static"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
swaggerFiles "github.com/swaggo/files"
|
||||
ginSwagger "github.com/swaggo/gin-swagger"
|
||||
)
|
||||
|
||||
// loads HTML templates. If [files]/html_templates is set, alternative files inside the directory are loaded in place of the internal templates.
|
||||
func (app *appContext) loadHTML(router *gin.Engine) {
|
||||
customPath := app.config.Section("files").Key("html_templates").MustString("")
|
||||
templatePath := "html"
|
||||
htmlFiles, err := fs.ReadDir(localFS, templatePath)
|
||||
if err != nil {
|
||||
app.err.Fatalf("Couldn't access template directory: \"%s\"", templatePath)
|
||||
return
|
||||
}
|
||||
loadFiles := make([]string, len(htmlFiles))
|
||||
for i, f := range htmlFiles {
|
||||
if _, err := os.Stat(filepath.Join(customPath, f.Name())); os.IsNotExist(err) {
|
||||
app.debug.Printf("Using default \"%s\"", f.Name())
|
||||
loadFiles[i] = FSJoin(templatePath, f.Name())
|
||||
} else {
|
||||
app.info.Printf("Using custom \"%s\"", f.Name())
|
||||
loadFiles[i] = filepath.Join(filepath.Join(customPath, f.Name()))
|
||||
}
|
||||
}
|
||||
tmpl, err := template.ParseFS(localFS, loadFiles...)
|
||||
if err != nil {
|
||||
app.err.Fatalf("Failed to load templates: %v", err)
|
||||
}
|
||||
router.SetHTMLTemplate(tmpl)
|
||||
}
|
||||
|
||||
// sets gin logger.
|
||||
func setGinLogger(router *gin.Engine, debugMode bool) {
|
||||
if debugMode {
|
||||
router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
|
||||
return fmt.Sprintf("[GIN/DEBUG] %s: %s(%s) => %d in %s; %s\n",
|
||||
param.TimeStamp.Format("15:04:05"),
|
||||
param.Method,
|
||||
param.Path,
|
||||
param.StatusCode,
|
||||
param.Latency,
|
||||
func() string {
|
||||
if param.ErrorMessage != "" {
|
||||
return "Error: " + param.ErrorMessage
|
||||
}
|
||||
return ""
|
||||
}(),
|
||||
)
|
||||
}))
|
||||
} else {
|
||||
router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
|
||||
return fmt.Sprintf("[GIN] %s(%s) => %d\n",
|
||||
param.Method,
|
||||
param.Path,
|
||||
param.StatusCode,
|
||||
)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
func (app *appContext) loadRouter(address string, debug bool) *gin.Engine {
|
||||
if debug {
|
||||
gin.SetMode(gin.DebugMode)
|
||||
} else {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
router := gin.New()
|
||||
|
||||
setGinLogger(router, debug)
|
||||
|
||||
router.Use(gin.Recovery())
|
||||
app.loadHTML(router)
|
||||
router.Use(static.Serve("/", app.webFS))
|
||||
router.NoRoute(app.NoRouteHandler)
|
||||
if debug {
|
||||
app.debug.Println("Loading pprof")
|
||||
pprof.Register(router)
|
||||
}
|
||||
SRV = &http.Server{
|
||||
Addr: address,
|
||||
Handler: router,
|
||||
}
|
||||
return router
|
||||
}
|
||||
|
||||
func (app *appContext) loadRoutes(router *gin.Engine) {
|
||||
routePrefixes := []string{app.URLBase}
|
||||
if app.URLBase != "" {
|
||||
routePrefixes = append(routePrefixes, "")
|
||||
}
|
||||
for _, p := range routePrefixes {
|
||||
router.GET(p+"/lang/:page", app.GetLanguages)
|
||||
router.Use(static.Serve(p+"/", app.webFS))
|
||||
router.GET(p+"/", app.AdminPage)
|
||||
router.GET(p+"/accounts", app.AdminPage)
|
||||
router.GET(p+"/settings", app.AdminPage)
|
||||
router.GET(p+"/lang/:page/:file", app.ServeLang)
|
||||
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/", app.webFS))
|
||||
router.GET(p+"/invite/:invCode", app.InviteProxy)
|
||||
}
|
||||
if *SWAGGER {
|
||||
app.info.Print(aurora.Magenta("\n\nWARNING: Swagger should not be used on a public instance.\n\n"))
|
||||
for _, p := range routePrefixes {
|
||||
router.GET(p+"/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
||||
}
|
||||
}
|
||||
api := router.Group("/", app.webAuth())
|
||||
for _, p := range routePrefixes {
|
||||
router.POST(p+"/logout", app.Logout)
|
||||
api.DELETE(p+"/users", app.DeleteUser)
|
||||
api.GET(p+"/users", app.GetUsers)
|
||||
api.POST(p+"/users", app.NewUserAdmin)
|
||||
api.POST(p+"/invites", app.GenerateInvite)
|
||||
api.GET(p+"/invites", app.GetInvites)
|
||||
api.DELETE(p+"/invites", app.DeleteInvite)
|
||||
api.POST(p+"/invites/profile", app.SetProfile)
|
||||
api.GET(p+"/profiles", app.GetProfiles)
|
||||
api.POST(p+"/profiles/default", app.SetDefaultProfile)
|
||||
api.POST(p+"/profiles", app.CreateProfile)
|
||||
api.DELETE(p+"/profiles", app.DeleteProfile)
|
||||
api.POST(p+"/invites/notify", app.SetNotify)
|
||||
api.POST(p+"/users/emails", app.ModifyEmails)
|
||||
// api.POST(p + "/setDefaults", app.SetDefaults)
|
||||
api.POST(p+"/users/settings", app.ApplySettings)
|
||||
api.GET(p+"/config", app.GetConfig)
|
||||
api.POST(p+"/config", app.ModifyConfig)
|
||||
api.POST(p+"/restart", app.restart)
|
||||
if app.config.Section("ombi").Key("enabled").MustBool(false) {
|
||||
api.GET(p+"/ombi/users", app.OmbiUsers)
|
||||
api.POST(p+"/ombi/defaults", app.SetOmbiDefaults)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (app *appContext) loadSetup(router *gin.Engine) {
|
||||
router.GET("/lang/:page", app.GetLanguages)
|
||||
router.GET("/", app.ServeSetup)
|
||||
router.POST("/jellyfin/test", app.TestJF)
|
||||
router.POST("/config", app.ModifyConfig)
|
||||
}
|
44
setup.go
44
setup.go
@ -2,7 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@ -70,13 +70,14 @@ func (app *appContext) TestJF(gc *gin.Context) {
|
||||
gc.JSON(200, map[string]bool{"success": true})
|
||||
}
|
||||
|
||||
func (st *Storage) loadLangSetup() error {
|
||||
// The first filesystem passed should be the localFS, to ensure the local lang files are loaded first.
|
||||
func (st *Storage) loadLangSetup(filesystems ...fs.FS) error {
|
||||
st.lang.Setup = map[string]setupLang{}
|
||||
var english setupLang
|
||||
load := func(fname string) error {
|
||||
load := func(filesystem fs.FS, fname string) error {
|
||||
index := strings.TrimSuffix(fname, filepath.Ext(fname))
|
||||
lang := setupLang{}
|
||||
f, err := ioutil.ReadFile(filepath.Join(st.lang.SetupPath, fname))
|
||||
f, err := fs.ReadFile(filesystem, FSJoin(st.lang.SetupPath, fname))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -107,22 +108,35 @@ func (st *Storage) loadLangSetup() error {
|
||||
st.lang.Setup[index] = lang
|
||||
return nil
|
||||
}
|
||||
err := load("en-us.json")
|
||||
if err != nil {
|
||||
engFound := false
|
||||
var err error
|
||||
for _, filesystem := range filesystems {
|
||||
err = load(filesystem, "en-us.json")
|
||||
if err == nil {
|
||||
engFound = true
|
||||
}
|
||||
}
|
||||
if !engFound {
|
||||
return err
|
||||
}
|
||||
english = st.lang.Setup["en-us"]
|
||||
files, err := ioutil.ReadDir(st.lang.SetupPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, f := range files {
|
||||
if f.Name() != "en-us.json" {
|
||||
err = load(f.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
setupLoaded := false
|
||||
for _, filesystem := range filesystems {
|
||||
files, err := fs.ReadDir(filesystem, st.lang.SetupPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, f := range files {
|
||||
if f.Name() != "en-us.json" {
|
||||
err = load(filesystem, f.Name())
|
||||
if err == nil {
|
||||
setupLoaded = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !setupLoaded {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
32
static.go
Normal file
32
static.go
Normal file
@ -0,0 +1,32 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Since the gin-static middleware uses a version of http.Filesystem with an extra Exists() func, we extend it here.
|
||||
|
||||
type httpFS struct {
|
||||
hfs http.FileSystem // Created by converting fs.FS using http.FS()
|
||||
fs fs.FS
|
||||
}
|
||||
|
||||
func (f httpFS) Open(name string) (http.File, error) {
|
||||
return f.hfs.Open("web" + 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, "web/"+p)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if stats.IsDir() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
253
storage.go
253
storage.go
@ -2,8 +2,9 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -42,7 +43,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 {
|
||||
@ -62,20 +63,20 @@ type Lang struct {
|
||||
Setup setupLangs
|
||||
}
|
||||
|
||||
func (st *Storage) loadLang() (err error) {
|
||||
err = st.loadLangCommon()
|
||||
func (st *Storage) loadLang(filesystems ...fs.FS) (err error) {
|
||||
err = st.loadLangCommon(filesystems...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = st.loadLangAdmin()
|
||||
err = st.loadLangAdmin(filesystems...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = st.loadLangForm()
|
||||
err = st.loadLangForm(filesystems...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = st.loadLangEmail()
|
||||
err = st.loadLangEmail(filesystems...)
|
||||
return
|
||||
}
|
||||
|
||||
@ -120,13 +121,13 @@ func patchQuantityStrings(english, other *map[string]quantityString) {
|
||||
}
|
||||
}
|
||||
|
||||
func (st *Storage) loadLangCommon() error {
|
||||
func (st *Storage) loadLangCommon(filesystems ...fs.FS) error {
|
||||
st.lang.Common = map[string]commonLang{}
|
||||
var english commonLang
|
||||
load := func(fname string) error {
|
||||
load := func(filesystem fs.FS, fname string) error {
|
||||
index := strings.TrimSuffix(fname, filepath.Ext(fname))
|
||||
lang := commonLang{}
|
||||
f, err := ioutil.ReadFile(filepath.Join(st.lang.CommonPath, fname))
|
||||
f, err := fs.ReadFile(filesystem, FSJoin(st.lang.CommonPath, fname))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -143,33 +144,46 @@ func (st *Storage) loadLangCommon() error {
|
||||
st.lang.Common[index] = lang
|
||||
return nil
|
||||
}
|
||||
err := load("en-us.json")
|
||||
if err != nil {
|
||||
engFound := false
|
||||
var err error
|
||||
for _, filesystem := range filesystems {
|
||||
err = load(filesystem, "en-us.json")
|
||||
if err == nil {
|
||||
engFound = true
|
||||
}
|
||||
}
|
||||
if !engFound {
|
||||
return err
|
||||
}
|
||||
english = st.lang.Common["en-us"]
|
||||
files, err := ioutil.ReadDir(st.lang.CommonPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, f := range files {
|
||||
if f.Name() != "en-us.json" {
|
||||
err = load(f.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
commonLoaded := false
|
||||
for _, filesystem := range filesystems {
|
||||
files, err := fs.ReadDir(filesystem, st.lang.CommonPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, f := range files {
|
||||
if f.Name() != "en-us.json" {
|
||||
err = load(filesystem, f.Name())
|
||||
if err == nil {
|
||||
commonLoaded = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !commonLoaded {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *Storage) loadLangAdmin() error {
|
||||
func (st *Storage) loadLangAdmin(filesystems ...fs.FS) error {
|
||||
st.lang.Admin = map[string]adminLang{}
|
||||
var english adminLang
|
||||
load := func(fname string) error {
|
||||
load := func(filesystem fs.FS, fname string) error {
|
||||
index := strings.TrimSuffix(fname, filepath.Ext(fname))
|
||||
lang := adminLang{}
|
||||
f, err := ioutil.ReadFile(filepath.Join(st.lang.AdminPath, fname))
|
||||
f, err := fs.ReadFile(filesystem, FSJoin(st.lang.AdminPath, fname))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -194,33 +208,46 @@ func (st *Storage) loadLangAdmin() error {
|
||||
st.lang.Admin[index] = lang
|
||||
return nil
|
||||
}
|
||||
err := load("en-us.json")
|
||||
if err != nil {
|
||||
engFound := false
|
||||
var err error
|
||||
for _, filesystem := range filesystems {
|
||||
err = load(filesystem, "en-us.json")
|
||||
if err == nil {
|
||||
engFound = true
|
||||
}
|
||||
}
|
||||
if !engFound {
|
||||
return err
|
||||
}
|
||||
english = st.lang.Admin["en-us"]
|
||||
files, err := ioutil.ReadDir(st.lang.AdminPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, f := range files {
|
||||
if f.Name() != "en-us.json" {
|
||||
err = load(f.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
adminLoaded := false
|
||||
for _, filesystem := range filesystems {
|
||||
files, err := fs.ReadDir(filesystem, st.lang.AdminPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, f := range files {
|
||||
if f.Name() != "en-us.json" {
|
||||
err = load(filesystem, f.Name())
|
||||
if err == nil {
|
||||
adminLoaded = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !adminLoaded {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *Storage) loadLangForm() error {
|
||||
func (st *Storage) loadLangForm(filesystems ...fs.FS) error {
|
||||
st.lang.Form = map[string]formLang{}
|
||||
var english formLang
|
||||
load := func(fname string) error {
|
||||
load := func(filesystem fs.FS, fname string) error {
|
||||
index := strings.TrimSuffix(fname, filepath.Ext(fname))
|
||||
lang := formLang{}
|
||||
f, err := ioutil.ReadFile(filepath.Join(st.lang.FormPath, fname))
|
||||
f, err := fs.ReadFile(filesystem, FSJoin(st.lang.FormPath, fname))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -250,33 +277,46 @@ func (st *Storage) loadLangForm() error {
|
||||
st.lang.Form[index] = lang
|
||||
return nil
|
||||
}
|
||||
err := load("en-us.json")
|
||||
if err != nil {
|
||||
engFound := false
|
||||
var err error
|
||||
for _, filesystem := range filesystems {
|
||||
err = load(filesystem, "en-us.json")
|
||||
if err == nil {
|
||||
engFound = true
|
||||
}
|
||||
}
|
||||
if !engFound {
|
||||
return err
|
||||
}
|
||||
english = st.lang.Form["en-us"]
|
||||
files, err := ioutil.ReadDir(st.lang.FormPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, f := range files {
|
||||
if f.Name() != "en-us.json" {
|
||||
err = load(f.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
formLoaded := false
|
||||
for _, filesystem := range filesystems {
|
||||
files, err := fs.ReadDir(filesystem, st.lang.FormPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, f := range files {
|
||||
if f.Name() != "en-us.json" {
|
||||
err = load(filesystem, f.Name())
|
||||
if err == nil {
|
||||
formLoaded = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !formLoaded {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *Storage) loadLangEmail() error {
|
||||
func (st *Storage) loadLangEmail(filesystems ...fs.FS) error {
|
||||
st.lang.Email = map[string]emailLang{}
|
||||
var english emailLang
|
||||
load := func(fname string) error {
|
||||
load := func(filesystem fs.FS, fname string) error {
|
||||
index := strings.TrimSuffix(fname, filepath.Ext(fname))
|
||||
lang := emailLang{}
|
||||
f, err := ioutil.ReadFile(filepath.Join(st.lang.EmailPath, fname))
|
||||
f, err := fs.ReadFile(filesystem, FSJoin(st.lang.EmailPath, fname))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -299,23 +339,36 @@ func (st *Storage) loadLangEmail() error {
|
||||
st.lang.Email[index] = lang
|
||||
return nil
|
||||
}
|
||||
err := load("en-us.json")
|
||||
if err != nil {
|
||||
engFound := false
|
||||
var err error
|
||||
for _, filesystem := range filesystems {
|
||||
err = load(filesystem, "en-us.json")
|
||||
if err == nil {
|
||||
engFound = true
|
||||
}
|
||||
}
|
||||
if !engFound {
|
||||
return err
|
||||
}
|
||||
english = st.lang.Email["en-us"]
|
||||
files, err := ioutil.ReadDir(st.lang.EmailPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, f := range files {
|
||||
if f.Name() != "en-us.json" {
|
||||
err = load(f.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
emailLoaded := false
|
||||
for _, filesystem := range filesystems {
|
||||
files, err := fs.ReadDir(filesystem, st.lang.EmailPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, f := range files {
|
||||
if f.Name() != "en-us.json" {
|
||||
err = load(filesystem, f.Name())
|
||||
if err == nil {
|
||||
emailLoaded = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !emailLoaded {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -329,76 +382,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 +478,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 +494,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)
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
|
||||
try:
|
||||
version = sys.argv[1].replace('v', '')
|
||||
except IndexError:
|
||||
|
22
views.go
22
views.go
@ -52,14 +52,19 @@ func (app *appContext) pushResources(gc *gin.Context, admin bool) {
|
||||
gc.Header("Link", cssHeader)
|
||||
}
|
||||
|
||||
func (app *appContext) AdminPage(gc *gin.Context) {
|
||||
app.pushResources(gc, true)
|
||||
func (app *appContext) getLang(gc *gin.Context, chosen string) string {
|
||||
lang := gc.Query("lang")
|
||||
if lang == "" {
|
||||
lang = app.storage.lang.chosenAdminLang
|
||||
lang = chosen
|
||||
} else if _, ok := app.storage.lang.Admin[lang]; !ok {
|
||||
lang = app.storage.lang.chosenAdminLang
|
||||
lang = chosen
|
||||
}
|
||||
return lang
|
||||
}
|
||||
|
||||
func (app *appContext) AdminPage(gc *gin.Context) {
|
||||
app.pushResources(gc, true)
|
||||
lang := app.getLang(gc, app.storage.lang.chosenAdminLang)
|
||||
emailEnabled, _ := app.config.Section("invite_emails").Key("enabled").Bool()
|
||||
notificationsEnabled, _ := app.config.Section("notifications").Key("enabled").Bool()
|
||||
ombiEnabled := app.config.Section("ombi").Key("enabled").MustBool(false)
|
||||
@ -82,12 +87,7 @@ func (app *appContext) AdminPage(gc *gin.Context) {
|
||||
func (app *appContext) InviteProxy(gc *gin.Context) {
|
||||
app.pushResources(gc, false)
|
||||
code := gc.Param("invCode")
|
||||
lang := gc.Query("lang")
|
||||
if lang == "" {
|
||||
lang = app.storage.lang.chosenFormLang
|
||||
} else if _, ok := app.storage.lang.Form[lang]; !ok {
|
||||
lang = app.storage.lang.chosenFormLang
|
||||
}
|
||||
lang := app.getLang(gc, app.storage.lang.chosenFormLang)
|
||||
/* Don't actually check if the invite is valid, just if it exists, just so the page loads quicker. Invite is actually checked on submit anyway. */
|
||||
// if app.checkInvite(code, false, "") {
|
||||
inv, ok := app.storage.invites[code]
|
||||
@ -98,7 +98,7 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
|
||||
})
|
||||
return
|
||||
}
|
||||
if key := gc.Query("key"); key != "" {
|
||||
if key := gc.Query("key"); key != "" && app.config.Section("email_confirmation").Key("enabled").MustBool(false) {
|
||||
validKey := false
|
||||
keyIndex := -1
|
||||
for i, k := range inv.Keys {
|
||||
|
Loading…
Reference in New Issue
Block a user