1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-12-29 12:30:11 +00:00

Compare commits

..

No commits in common. "6308db495a0aee206d2d9cd6047183ec9901291e" and "86c37fb4237bc645e5fcb4a52f5908ae23a9f154" have entirely different histories.

42 changed files with 552 additions and 577 deletions

View File

@ -8,8 +8,46 @@ release:
name_template: "v{{.Version}}" name_template: "v{{.Version}}"
before: before:
hooks: hooks:
- npm i - go mod download
- make precompile INTERNAL=on - rm -rf data/web
- mkdir -p data/web/css
- cp images/banner.svg static/banner.svg
- bash -c 'cp -r static/* data/web/'
- npm install
- npm install esbuild
- cp node_modules/remixicon/fonts/remixicon.css node_modules/remixicon/fonts/remixicon.woff2 data/web/css/
- cp -r html data/
- node scripts/missing-colors.js html data/html
- cp -r lang data/
- cp LICENSE data/
- cp jfa-go.service data/
- python3 scripts/enumerate_config.py -i config/config-base.json -o data/config-base.json
- python3 scripts/generate_ini.py -i config/config-base.json -o data/config-default.ini
- python3 scripts/compile_mjml.py -o data/
- rm -rf tempts
- cp -r ts tempts
- scripts/dark-variant.sh tempts
- scripts/dark-variant.sh tempts/modules
- mkdir -p data/web/js
- npx esbuild --target=es6 --bundle tempts/admin.ts {{.Env.JFA_GO_SOURCEMAP}} --outfile=./data/web/js/admin.js {{.Env.JFA_GO_MINIFY}}
- npx esbuild --target=es6 --bundle tempts/user.ts {{.Env.JFA_GO_SOURCEMAP}} --outfile=./data/web/js/user.js {{.Env.JFA_GO_MINIFY}}
- npx esbuild --target=es6 --bundle tempts/pwr.ts {{.Env.JFA_GO_SOURCEMAP}} --outfile=./data/web/js/pwr.js {{.Env.JFA_GO_MINIFY}}
- npx esbuild --target=es6 --bundle tempts/pwr-pin.ts {{.Env.JFA_GO_SOURCEMAP}} --outfile=./data/web/js/pwr-pin.js {{.Env.JFA_GO_MINIFY}}
- npx esbuild --target=es6 --bundle tempts/form.ts {{.Env.JFA_GO_SOURCEMAP}} --outfile=./data/web/js/form.js {{.Env.JFA_GO_MINIFY}}
- npx esbuild --target=es6 --bundle tempts/setup.ts {{.Env.JFA_GO_SOURCEMAP}} --outfile=./data/web/js/setup.js {{.Env.JFA_GO_MINIFY}}
- npx esbuild --target=es6 --bundle tempts/crash.ts {{.Env.JFA_GO_SOURCEMAP}} --outfile=./data/crash.js {{.Env.JFA_GO_MINIFY}}
- bash -c "{{.Env.JFA_GO_COPYTS}}"
- rm -r tempts
- npx esbuild --bundle css/base.css --outfile=./data/web/css/bundle.css --external:remixicon.css --external:../fonts/hanken* --minify
- cp html/crash.html data/
- npx tailwindcss -i data/web/css/bundle.css -o data/bundle.css --content "html/crash.html"
- node scripts/inline.js root data data/crash.html data/crash.html
- rm data/bundle.css
- npx tailwindcss -i data/web/css/bundle.css -o data/web/css/bundle.css
- mv data/crash.html data/html/
- go install github.com/swaggo/swag/cmd/swag@latest
- swag init -g main.go
- mv data/web/css/bundle.css data/web/css/{{.Env.JFA_GO_CSS_VERSION}}bundle.css
builds: builds:
- id: notray - id: notray
dir: ./ dir: ./

View File

@ -12,6 +12,14 @@ clone:
depth: 0 depth: 0
steps: steps:
- name: redoc
image: docker.io/hrfee/jfa-go-build-docker:latest
environment:
REDOC_SSH_ID:
from_secret: REDOC_SSH_ID
commands:
- sh -c "echo \"$REDOC_SSH_ID\" > /tmp/id_redoc && chmod 600 /tmp/id_redoc"
- bash -c 'sftp -P 3625 -i /tmp/id_redoc -o StrictHostKeyChecking=no redoc@api.jfa-go.com:/home/redoc <<< $"put docs/swagger.json jfa-go.json"'
- name: build - name: build
image: docker.io/hrfee/jfa-go-build-docker:latest image: docker.io/hrfee/jfa-go-build-docker:latest
environment: environment:
@ -22,14 +30,6 @@ steps:
- curl -sfL https://goreleaser.com/static/run > goreleaser - curl -sfL https://goreleaser.com/static/run > goreleaser
- chmod +x goreleaser - chmod +x goreleaser
- ./scripts/version.sh ./goreleaser --snapshot --skip=publish --clean - ./scripts/version.sh ./goreleaser --snapshot --skip=publish --clean
- name: redoc
image: docker.io/hrfee/jfa-go-build-docker:latest
environment:
REDOC_SSH_ID:
from_secret: REDOC_SSH_ID
commands:
- sh -c "echo \"$REDOC_SSH_ID\" > /tmp/id_redoc && chmod 600 /tmp/id_redoc"
- bash -c 'sftp -P 3625 -i /tmp/id_redoc -o StrictHostKeyChecking=no redoc@api.jfa-go.com:/home/redoc <<< $"put docs/swagger.json jfa-go.json"'
- name: deb-repo - name: deb-repo
image: docker.io/hrfee/jfa-go-build-docker:latest image: docker.io/hrfee/jfa-go-build-docker:latest
environment: environment:

View File

@ -1,6 +1,6 @@
# Use this instead if hrfee/jfa-go-build-docker doesn't support your architecture # Use this instead if hrfee/jfa-go-build-docker doesn't support your architecture
# FROM --platform=$BUILDPLATFORM golang:latest AS support # FROM --platform=$BUILDPLATFORM golang:latest AS support
FROM --platform=$BUILDPLATFORM docker.io/hrfee/jfa-go-build-docker:latest AS support FROM --platform=$BUILDPLATFORM hrfee/jfa-go-build-docker AS support
COPY . /opt/build COPY . /opt/build
@ -9,10 +9,10 @@ COPY . /opt/build
# && apt-get install build-essential python3-pip -y \ # && apt-get install build-essential python3-pip -y \
# && (curl -sL https://deb.nodesource.com/setup_current.x | bash -) \ # && (curl -sL https://deb.nodesource.com/setup_current.x | bash -) \
# && apt-get install nodejs # && apt-get install nodejs
RUN (cd /opt/build; npm i; make precompile INTERNAL=off GOESBUILD=off) \ RUN (cd /opt/build; make configuration npm email typescript variants-html bundle-css inline-css swagger copy INTERNAL=off 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 && 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
FROM --platform=$BUILDPLATFORM docker.io/golang:latest AS build FROM --platform=$BUILDPLATFORM golang:latest AS build
ARG TARGETARCH ARG TARGETARCH
ENV GOARCH=$TARGETARCH ENV GOARCH=$TARGETARCH
ARG BUILT_BY ARG BUILT_BY
@ -30,3 +30,5 @@ EXPOSE 8056
EXPOSE 8057 EXPOSE 8057
CMD [ "/opt/jfa-go/jfa-go", "-data", "/data" ] CMD [ "/opt/jfa-go/jfa-go", "-data", "/data" ]

145
Makefile
View File

@ -1,7 +1,3 @@
.PHONY: configuration email typescript swagger copy compile compress tailwind bundle-css inline-css variants-html install clean npm config-description config-default precompile
all: compile
GOESBUILD ?= off GOESBUILD ?= off
ifeq ($(GOESBUILD), on) ifeq ($(GOESBUILD), on)
ESBUILD := esbuild ESBUILD := esbuild
@ -11,7 +7,6 @@ endif
GOBINARY ?= go GOBINARY ?= go
CSSVERSION ?= v3 CSSVERSION ?= v3
CSS_BUNDLE = $(DATA)/web/css/$(CSSVERSION)bundle.css
VERSION ?= $(shell git describe --exact-match HEAD 2> /dev/null || echo vgit) VERSION ?= $(shell git describe --exact-match HEAD 2> /dev/null || echo vgit)
VERSION := $(shell echo $(VERSION) | sed 's/v//g') VERSION := $(shell echo $(VERSION) | sed 's/v//g')
@ -62,7 +57,7 @@ ifeq ($(DEBUG), on)
TYPECHECK := npx tsc -noEmit --project ts/tsconfig.json TYPECHECK := npx tsc -noEmit --project ts/tsconfig.json
# jank # jank
COPYTS := rm -r $(DATA)/web/js/ts; cp -r tempts $(DATA)/web/js/ts COPYTS := rm -r $(DATA)/web/js/ts; cp -r tempts $(DATA)/web/js/ts
UNCSS := cp $(CSS_BUNDLE) $(DATA)/bundle.css UNCSS := cp $(DATA)/web/css/bundle.css $(DATA)/bundle.css
# TAILWIND := --content "" # TAILWIND := --content ""
else else
LDFLAGS := -s -w $(LDFLAGS) LDFLAGS := -s -w $(LDFLAGS)
@ -70,7 +65,7 @@ else
MINIFY := --minify MINIFY := --minify
COPYTS := COPYTS :=
TYPECHECK := TYPECHECK :=
UNCSS := npx tailwindcss -i $(CSS_BUNDLE) -o $(DATA)/bundle.css --content "html/crash.html" UNCSS := npx tailwindcss -i $(DATA)/web/css/bundle.css -o $(DATA)/bundle.css --content "html/crash.html"
# UNCSS := npx uncss $(DATA)/crash.html --csspath web/css --output $(DATA)/bundle.css # UNCSS := npx uncss $(DATA)/crash.html --csspath web/css --output $(DATA)/bundle.css
TAILWIND := TAILWIND :=
endif endif
@ -101,104 +96,77 @@ else
SWAGINSTALL := SWAGINSTALL :=
endif endif
CONFIG_BASE = config/config-base.json npm:
$(info installing npm dependencies)
npm install $(NPMOPTS)
CONFIG_DESCRIPTION = $(DATA)/config-base.json configuration:
CONFIG_DEFAULT = $(DATA)/config-default.ini
$(CONFIG_DESCRIPTION) &: $(CONFIG_BASE)
$(info Fixing config-base) $(info Fixing config-base)
-mkdir -p $(DATA) -mkdir -p $(DATA)
python3 scripts/enumerate_config.py -i config/config-base.json -o $(DATA)/config-base.json python3 scripts/enumerate_config.py -i config/config-base.json -o $(DATA)/config-base.json
$(CONFIG_DEFAULT) &: $(CONFIG_BASE)
$(info Generating config-default.ini) $(info Generating config-default.ini)
python3 scripts/generate_ini.py -i config/config-base.json -o $(DATA)/config-default.ini python3 scripts/generate_ini.py -i config/config-base.json -o $(DATA)/config-default.ini
configuration: $(CONFIG_DESCRIPTION) $(CONFIG_DEFAULT) email:
EMAIL_SRC_MJML = $(wildcard mail/*.mjml)
EMAIL_SRC_TXT = $(wildcard mail/*.txt)
EMAIL_DATA_MJML = $(EMAIL_SRC_MJML:mail/%=data/%)
EMAIL_HTML = $(EMAIL_DATA_MJML:.mjml=.html)
EMAIL_TXT = $(EMAIL_SRC_TXT:mail/%=data/%)
EMAIL_ALL = $(EMAIL_HTML) $(EMAIL_TXT)
EMAIL_TARGET = mail/confirmation.html
$(EMAIL_TARGET): $(EMAIL_SRC_MJML) $(EMAIL_SRC_TXT)
$(info Generating email html) $(info Generating email html)
python3 scripts/compile_mjml.py -o $(DATA)/ python3 scripts/compile_mjml.py -o $(DATA)/
TYPESCRIPT_FULLSRC = $(shell find ts/ -type f -name "*.ts") typescript:
TYPESCRIPT_SRC = $(wildcard ts/*.ts)
TYPESCRIPT_TEMPSRC = $(TYPESCRIPT_SRC:ts/%=tempts/%)
# TYPESCRIPT_TARGET = $(patsubst %.ts,%.js,$(subst tempts/,./$(DATA)/web/js/,$(TYPESCRIPT_TEMPSRC)))
TYPESCRIPT_TARGET = $(DATA)/web/js/admin.js
$(TYPESCRIPT_TARGET): $(TYPESCRIPT_FULLSRC) ts/tsconfig.json
$(TYPECHECK) $(TYPECHECK)
$(adding dark variants to typescript)
rm -rf tempts rm -rf tempts
cp -r ts tempts cp -r ts tempts
$(adding dark variants to typescript)
scripts/dark-variant.sh tempts scripts/dark-variant.sh tempts
scripts/dark-variant.sh tempts/modules scripts/dark-variant.sh tempts/modules
$(info compiling typescript) $(info compiling typescript)
mkdir -p $(DATA)/web/js mkdir -p $(DATA)/web/js
$(foreach tempsrc,$(TYPESCRIPT_TEMPSRC),$(ESBUILD) --target=es6 --bundle $(tempsrc) $(SOURCEMAP) --outfile=$(patsubst %.ts,%.js,$(subst tempts/,./$(DATA)/web/js/,$(tempsrc))) $(MINIFY);) $(ESBUILD) --target=es6 --bundle tempts/admin.ts $(SOURCEMAP) --outfile=./$(DATA)/web/js/admin.js $(MINIFY)
mv $(DATA)/web/js/crash.js $(DATA)/ $(ESBUILD) --target=es6 --bundle tempts/user.ts $(SOURCEMAP) --outfile=./$(DATA)/web/js/user.js $(MINIFY)
$(ESBUILD) --target=es6 --bundle tempts/pwr.ts $(SOURCEMAP) --outfile=./$(DATA)/web/js/pwr.js $(MINIFY)
$(ESBUILD) --target=es6 --bundle tempts/pwr-pin.ts $(SOURCEMAP) --outfile=./$(DATA)/web/js/pwr-pin.js $(MINIFY)
$(ESBUILD) --target=es6 --bundle tempts/form.ts $(SOURCEMAP) --outfile=./$(DATA)/web/js/form.js $(MINIFY)
$(ESBUILD) --target=es6 --bundle tempts/setup.ts $(SOURCEMAP) --outfile=./$(DATA)/web/js/setup.js $(MINIFY)
$(ESBUILD) --target=es6 --bundle tempts/crash.ts --outfile=./$(DATA)/crash.js $(MINIFY)
$(COPYTS) $(COPYTS)
SWAGGER_SRC = $(wildcard api*.go) $(wildcard *auth.go) views.go swagger:
SWAGGER_TARGET = docs/docs.go
$(SWAGGER_TARGET): $(SWAGGER_SRC)
$(SWAGINSTALL) $(SWAGINSTALL)
swag init -g main.go swag init -g main.go
VARIANTS_SRC = $(wildcard html/*.html) compile:
VARIANTS_TARGET = $(DATA)/html/admin.html $(info Downloading deps)
$(VARIANTS_TARGET): $(VARIANTS_SRC) $(GOBINARY) mod download
$(info Building)
mkdir -p build
$(GOBINARY) build $(RACEDETECTOR) -ldflags="$(LDFLAGS)" $(TAGS) -o build/jfa-go
compress:
upx --lzma build/jfa-go
bundle-css:
mkdir -p $(DATA)/web/css
$(info copying fonts)
cp -r node_modules/remixicon/fonts/remixicon.css node_modules/remixicon/fonts/remixicon.woff2 $(DATA)/web/css/
$(info bundling css)
$(ESBUILD) --bundle css/base.css --outfile=$(DATA)/web/css/bundle.css --external:remixicon.css --external:../fonts/hanken* --minify
npx tailwindcss -i $(DATA)/web/css/bundle.css -o $(DATA)/web/css/bundle.css $(TAILWIND)
# npx postcss -o $(DATA)/web/css/bundle.css $(DATA)/web/css/bundle.css
inline-css:
cp html/crash.html $(DATA)/crash.html
$(UNCSS)
node scripts/inline.js root $(DATA) $(DATA)/crash.html $(DATA)/crash.html
rm $(DATA)/bundle.css
variants-html:
$(info copying html) $(info copying html)
cp -r html $(DATA)/ cp -r html $(DATA)/
$(info adding dark variants to html) $(info adding dark variants to html)
node scripts/missing-colors.js html $(DATA)/html node scripts/missing-colors.js html $(DATA)/html
ICON_SRC = node_modules/remixicon/fonts/remixicon.css node_modules/remixicon/fonts/remixicon.woff2 copy:
ICON_TARGET = $(ICON_SRC:node_modules/remixicon/fonts/%=$(DATA)/web/css/%)
CSS_SRC = $(wildcard css/*.css)
CSS_TARGET = $(DATA)/web/css/part-bundle.css
CSS_FULLTARGET = $(CSS_BUNDLE)
ALL_CSS_SRC = $(ICON_SRC) $(CSS_SRC)
ALL_CSS_TARGET = $(ICON_TARGET)
$(CSS_FULLTARGET): $(TYPESCRIPT_TARGET) $(VARIANTS_TARGET) $(ALL_CSS_SRC) $(wildcard html/*.html)
mkdir -p $(DATA)/web/css
$(info copying fonts)
cp -r node_modules/remixicon/fonts/remixicon.css node_modules/remixicon/fonts/remixicon.woff2 $(DATA)/web/css/
$(info bundling css)
$(ESBUILD) --bundle css/base.css --outfile=$(CSS_TARGET) --external:remixicon.css --external:../fonts/hanken* --minify
npx tailwindcss -i $(CSS_TARGET) -o $(CSS_FULLTARGET) $(TAILWIND)
rm $(CSS_TARGET)
# mv $(CSS_BUNDLE) $(DATA)/web/css/$(CSSVERSION)bundle.css
# npx postcss -o $(CSS_TARGET) $(CSS_TARGET)
bundle-css: tailwind
INLINE_SRC = html/crash.html
INLINE_TARGET = $(DATA)/crash.html
$(INLINE_TARGET): $(CSS_FULLTARGET) $(INLINE_SRC)
cp html/crash.html $(DATA)/crash.html
$(UNCSS) # generates $(DATA)/bundle.css for us
node scripts/inline.js root $(DATA) $(DATA)/crash.html $(DATA)/crash.html
rm $(DATA)/bundle.css
LANG_SRC = $(shell find ./lang)
LANG_TARGET = $(LANG_SRC:lang/%=$(DATA)/lang/%)
STATIC_SRC = $(wildcard static/*)
STATIC_TARGET = $(STATIC_SRC:static/%=$(DATA)/web/%)
COPY_SRC = images/banner.svg jfa-go.service LICENSE $(LANG_SRC) $(STATIC_SRC)
COPY_TARGET = $(DATA)/jfa-go.service
# $(DATA)/LICENSE $(LANG_TARGET) $(STATIC_TARGET) $(DATA)/web/css/$(CSSVERSION)bundle.css
$(COPY_TARGET): $(INLINE_TARGET) $(STATIC_SRC)
$(info copying crash page) $(info copying crash page)
cp $(DATA)/crash.html $(DATA)/html/ mv $(DATA)/crash.html $(DATA)/html/
$(info copying static data) $(info copying static data)
mkdir -p $(DATA)/web mkdir -p $(DATA)/web
cp images/banner.svg static/banner.svg cp images/banner.svg static/banner.svg
@ -208,22 +176,7 @@ $(COPY_TARGET): $(INLINE_TARGET) $(STATIC_SRC)
$(info copying language files) $(info copying language files)
cp -r lang $(DATA)/ cp -r lang $(DATA)/
cp LICENSE $(DATA)/ cp LICENSE $(DATA)/
mv $(DATA)/web/css/bundle.css $(DATA)/web/css/$(CSSVERSION)bundle.css
precompile: $(CONFIG_DESCRIPTION) $(CONFIG_DEFAULT) $(EMAIL_TARGET) $(COPY_TARGET) $(SWAGGER_TARGET)
GO_SRC = $(shell find ./ -name "*.go")
GO_TARGET = build/jfa-go
$(GO_TARGET): $(CONFIG_DESCRIPTION) $(CONFIG_DEFAULT) $(EMAIL_TARGET) $(COPY_TARGET) $(SWAGGER_TARGET) $(GO_SRC) go.mod go.sum
$(info Downloading deps)
$(GOBINARY) mod download
$(info Building)
mkdir -p build
$(GOBINARY) build $(RACEDETECTOR) -ldflags="$(LDFLAGS)" $(TAGS) -o $(GO_TARGET)
compile: $(GO_TARGET)
compress:
upx --lzma build/jfa-go
# internal-files: # internal-files:
# python3 scripts/embed.py internal # python3 scripts/embed.py internal
@ -244,6 +197,6 @@ clean:
-rm docs/docs.go docs/swagger.json docs/swagger.yaml -rm docs/docs.go docs/swagger.json docs/swagger.yaml
go clean go clean
npm: quick: configuration typescript variants-html bundle-css inline-css copy compile
$(info installing npm dependencies)
npm install $(NPMOPTS) all: configuration npm email typescript variants-html bundle-css inline-css swagger copy compile

View File

@ -144,13 +144,13 @@ func (app *appContext) GetActivities(gc *gin.Context) {
if act.Type == ActivityDeletion || act.Type == ActivityCreation { if act.Type == ActivityDeletion || act.Type == ActivityCreation {
resp.Activities[i].Username = act.Value resp.Activities[i].Username = act.Value
resp.Activities[i].Value = "" resp.Activities[i].Value = ""
} else if user, err := app.jf.UserByID(act.UserID, false); err == nil { } else if user, status, err := app.jf.UserByID(act.UserID, false); status == 200 && err == nil {
resp.Activities[i].Username = user.Name resp.Activities[i].Username = user.Name
} }
if (act.SourceType == ActivityUser || act.SourceType == ActivityAdmin) && act.Source != "" { if (act.SourceType == ActivityUser || act.SourceType == ActivityAdmin) && act.Source != "" {
user, err := app.jf.UserByID(act.Source, false) user, status, err := app.jf.UserByID(act.Source, false)
if err == nil { if status == 200 && err == nil {
resp.Activities[i].SourceUsername = user.Name resp.Activities[i].SourceUsername = user.Name
} }
} }

View File

@ -1,35 +1,34 @@
package main package main
import ( import (
"errors"
"fmt" "fmt"
"net/url" "net/url"
"strings" "strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/hrfee/jfa-go/common"
lm "github.com/hrfee/jfa-go/logmessages" lm "github.com/hrfee/jfa-go/logmessages"
"github.com/hrfee/jfa-go/ombi" "github.com/hrfee/jfa-go/ombi"
"github.com/hrfee/mediabrowser" "github.com/hrfee/mediabrowser"
) )
func (app *appContext) getOmbiUser(jfID string) (map[string]interface{}, error) { func (app *appContext) getOmbiUser(jfID string) (map[string]interface{}, int, error) {
jfUser, err := app.jf.UserByID(jfID, false) jfUser, code, err := app.jf.UserByID(jfID, false)
if err != nil { if err != nil || code != 200 {
return nil, err return nil, code, err
} }
username := jfUser.Name username := jfUser.Name
email := "" email := ""
if e, ok := app.storage.GetEmailsKey(jfID); ok { if e, ok := app.storage.GetEmailsKey(jfID); ok {
email = e.Addr email = e.Addr
} }
user, err := app.ombi.getUser(username, email) return app.ombi.getUser(username, email)
return user, err
} }
func (ombi *OmbiWrapper) getUser(username string, email string) (map[string]interface{}, error) { func (ombi *OmbiWrapper) getUser(username string, email string) (map[string]interface{}, int, error) {
ombiUsers, err := ombi.GetUsers() ombiUsers, code, err := ombi.GetUsers()
if err != nil { if err != nil || code != 200 {
return nil, err return nil, code, err
} }
for _, ombiUser := range ombiUsers { for _, ombiUser := range ombiUsers {
ombiAddr := "" ombiAddr := ""
@ -37,19 +36,18 @@ func (ombi *OmbiWrapper) getUser(username string, email string) (map[string]inte
ombiAddr = a.(string) ombiAddr = a.(string)
} }
if ombiUser["userName"].(string) == username || (ombiAddr == email && email != "") { if ombiUser["userName"].(string) == username || (ombiAddr == email && email != "") {
return ombiUser, err return ombiUser, code, err
} }
} }
// Gets a generic "not found" type error return nil, 400, errors.New(lm.NotFound)
return nil, common.GenericErr(404, err)
} }
// Returns a user with the given name who has been imported from Jellyfin/Emby by Ombi // Returns a user with the given name who has been imported from Jellyfin/Emby by Ombi
func (ombi *OmbiWrapper) getImportedUser(name string) (map[string]interface{}, error) { func (ombi *OmbiWrapper) getImportedUser(name string) (map[string]interface{}, int, error) {
// Ombi User Types: 3/4 = Emby, 5 = Jellyfin // Ombi User Types: 3/4 = Emby, 5 = Jellyfin
ombiUsers, err := ombi.GetUsers() ombiUsers, code, err := ombi.GetUsers()
if err != nil { if err != nil || code != 200 {
return nil, err return nil, code, err
} }
for _, ombiUser := range ombiUsers { for _, ombiUser := range ombiUsers {
if ombiUser["userName"].(string) == name { if ombiUser["userName"].(string) == name {
@ -62,11 +60,10 @@ func (ombi *OmbiWrapper) getImportedUser(name string) (map[string]interface{}, e
} else if uType != 3 && uType != 4 { // Emby } else if uType != 3 && uType != 4 { // Emby
continue continue
} }
return ombiUser, err return ombiUser, code, err
} }
} }
// Gets a generic "not found" type error return nil, 400, fmt.Errorf("couldn't find user")
return nil, common.GenericErr(404, err)
} }
// @Summary Get a list of Ombi users. // @Summary Get a list of Ombi users.
@ -77,8 +74,8 @@ func (ombi *OmbiWrapper) getImportedUser(name string) (map[string]interface{}, e
// @Security Bearer // @Security Bearer
// @tags Ombi // @tags Ombi
func (app *appContext) OmbiUsers(gc *gin.Context) { func (app *appContext) OmbiUsers(gc *gin.Context) {
users, err := app.ombi.GetUsers() users, status, err := app.ombi.GetUsers()
if err != nil { if err != nil || status != 200 {
app.err.Printf(lm.FailedGetUsers, lm.Ombi, err) app.err.Printf(lm.FailedGetUsers, lm.Ombi, err)
respond(500, "Couldn't get users", gc) respond(500, "Couldn't get users", gc)
return return
@ -113,8 +110,8 @@ func (app *appContext) SetOmbiProfile(gc *gin.Context) {
respondBool(400, false, gc) respondBool(400, false, gc)
return return
} }
template, err := app.ombi.TemplateByID(req.ID) template, code, err := app.ombi.TemplateByID(req.ID)
if err != nil || len(template) == 0 { if err != nil || code != 200 || len(template) == 0 {
app.err.Printf(lm.FailedGetUsers, lm.Ombi, err) app.err.Printf(lm.FailedGetUsers, lm.Ombi, err)
respond(500, "Couldn't get user", gc) respond(500, "Couldn't get user", gc)
return return
@ -150,7 +147,7 @@ type OmbiWrapper struct {
*ombi.Ombi *ombi.Ombi
} }
func (ombi *OmbiWrapper) applyProfile(user map[string]interface{}, profile map[string]interface{}) (err error) { func (ombi *OmbiWrapper) applyProfile(user map[string]interface{}, profile map[string]interface{}) (status int, err error) {
for k, v := range profile { for k, v := range profile {
switch v.(type) { switch v.(type) {
case map[string]interface{}, []interface{}: case map[string]interface{}, []interface{}:
@ -161,21 +158,22 @@ func (ombi *OmbiWrapper) applyProfile(user map[string]interface{}, profile map[s
} }
} }
} }
err = ombi.ModifyUser(user) status, err = ombi.ModifyUser(user)
return return
} }
func (ombi *OmbiWrapper) ImportUser(jellyfinID string, req newUserDTO, profile Profile) (err error, ok bool) { func (ombi *OmbiWrapper) ImportUser(jellyfinID string, req newUserDTO, profile Profile) (err error, ok bool) {
errors, err := ombi.NewUser(req.Username, req.Password, req.Email, profile.Ombi) errors, code, err := ombi.NewUser(req.Username, req.Password, req.Email, profile.Ombi)
var ombiUser map[string]interface{} var ombiUser map[string]interface{}
if err != nil { var status int
if err != nil || code != 200 {
// Check if on the off chance, Ombi's user importer has already added the account. // Check if on the off chance, Ombi's user importer has already added the account.
ombiUser, err = ombi.getImportedUser(req.Username) ombiUser, status, err = ombi.getImportedUser(req.Username)
if err == nil { if status == 200 && err == nil {
// app.info.Println(lm.Ombi + " " + lm.UserExists) // app.info.Println(lm.Ombi + " " + lm.UserExists)
profile.Ombi["password"] = req.Password profile.Ombi["password"] = req.Password
err = ombi.applyProfile(ombiUser, profile.Ombi) status, err = ombi.applyProfile(ombiUser, profile.Ombi)
if err != nil { if status != 200 || err != nil {
err = fmt.Errorf(lm.FailedApplyProfile, lm.Ombi, req.Username, err) err = fmt.Errorf(lm.FailedApplyProfile, lm.Ombi, req.Username, err)
} }
} else { } else {
@ -191,8 +189,9 @@ func (ombi *OmbiWrapper) ImportUser(jellyfinID string, req newUserDTO, profile P
func (ombi *OmbiWrapper) AddContactMethods(jellyfinID string, req newUserDTO, discord *DiscordUser, telegram *TelegramUser) (err error) { func (ombi *OmbiWrapper) AddContactMethods(jellyfinID string, req newUserDTO, discord *DiscordUser, telegram *TelegramUser) (err error) {
var ombiUser map[string]interface{} var ombiUser map[string]interface{}
ombiUser, err = ombi.getUser(req.Username, req.Email) var status int
if err != nil { ombiUser, status, err = ombi.getUser(req.Username, req.Email)
if status != 200 || err != nil {
return return
} }
if discordEnabled || telegramEnabled { if discordEnabled || telegramEnabled {
@ -205,8 +204,9 @@ func (ombi *OmbiWrapper) AddContactMethods(jellyfinID string, req newUserDTO, di
tUser = telegram.Username tUser = telegram.Username
} }
var resp string var resp string
resp, err = ombi.SetNotificationPrefs(ombiUser, dID, tUser) var status int
if err != nil { resp, status, err = ombi.SetNotificationPrefs(ombiUser, dID, tUser)
if !(status == 200 || status == 204) || err != nil {
if resp != "" { if resp != "" {
err = fmt.Errorf("%v, %s", err, resp) err = fmt.Errorf("%v, %s", err, resp)
} }

View File

@ -84,8 +84,8 @@ func (app *appContext) CreateProfile(gc *gin.Context) {
var req newProfileDTO var req newProfileDTO
gc.BindJSON(&req) gc.BindJSON(&req)
app.jf.CacheExpiry = time.Now() app.jf.CacheExpiry = time.Now()
user, err := app.jf.UserByID(req.ID, false) user, status, err := app.jf.UserByID(req.ID, false)
if err != nil { if !(status == 200 || status == 204) || err != nil {
app.err.Printf(lm.FailedGetUsers, lm.Jellyfin, err) app.err.Printf(lm.FailedGetUsers, lm.Jellyfin, err)
respond(500, "Couldn't get user", gc) respond(500, "Couldn't get user", gc)
return return
@ -98,8 +98,8 @@ func (app *appContext) CreateProfile(gc *gin.Context) {
app.debug.Printf(lm.CreateProfileFromUser, user.Name) app.debug.Printf(lm.CreateProfileFromUser, user.Name)
if req.Homescreen { if req.Homescreen {
profile.Configuration = user.Configuration profile.Configuration = user.Configuration
profile.Displayprefs, err = app.jf.GetDisplayPreferences(req.ID) profile.Displayprefs, status, err = app.jf.GetDisplayPreferences(req.ID)
if err != nil { if !(status == 200 || status == 204) || err != nil {
app.err.Printf(lm.FailedGetJellyfinDisplayPrefs, req.ID, err) app.err.Printf(lm.FailedGetJellyfinDisplayPrefs, req.ID, err)
respond(500, "Couldn't get displayprefs", gc) respond(500, "Couldn't get displayprefs", gc)
return return

View File

@ -29,8 +29,8 @@ func (app *appContext) MyDetails(gc *gin.Context) {
Id: gc.GetString("jfId"), Id: gc.GetString("jfId"),
} }
user, err := app.jf.UserByID(resp.Id, false) user, status, err := app.jf.UserByID(resp.Id, false)
if err != nil { if status != 200 || err != nil {
app.err.Printf(lm.FailedGetUsers, lm.Jellyfin, err) app.err.Printf(lm.FailedGetUsers, lm.Jellyfin, err)
respond(500, "Failed to get user", gc) respond(500, "Failed to get user", gc)
return return
@ -259,9 +259,9 @@ func (app *appContext) ModifyMyEmail(gc *gin.Context) {
} }
if emailEnabled && app.config.Section("email_confirmation").Key("enabled").MustBool(false) { if emailEnabled && app.config.Section("email_confirmation").Key("enabled").MustBool(false) {
user, err := app.jf.UserByID(id, false) user, status, err := app.jf.UserByID(id, false)
name := "" name := ""
if err == nil { if status == 200 && err == nil {
name = user.Name name = user.Name
} }
app.debug.Printf(lm.EmailConfirmationRequired, id) app.debug.Printf(lm.EmailConfirmationRequired, id)
@ -688,20 +688,20 @@ func (app *appContext) ChangeMyPassword(gc *gin.Context) {
return return
} }
} }
user, err := app.jf.UserByID(gc.GetString("jfId"), false) user, status, err := app.jf.UserByID(gc.GetString("jfId"), false)
if err != nil { if status != 200 || err != nil {
app.err.Printf(lm.FailedGetUser, gc.GetString("jfId"), lm.Jellyfin, err) app.err.Printf(lm.FailedGetUser, gc.GetString("jfId"), lm.Jellyfin, err)
respondBool(500, false, gc) respondBool(500, false, gc)
return return
} }
// Authenticate as user to confirm old password. // Authenticate as user to confirm old password.
user, err = app.authJf.Authenticate(user.Name, req.Old) user, status, err = app.authJf.Authenticate(user.Name, req.Old)
if err != nil { if status != 200 || err != nil {
respondBool(401, false, gc) respondBool(401, false, gc)
return return
} }
err = app.jf.SetPassword(gc.GetString("jfId"), req.Old, req.New) status, err = app.jf.SetPassword(gc.GetString("jfId"), req.Old, req.New)
if err != nil { if (status != 200 && status != 204) || err != nil {
respondBool(500, false, gc) respondBool(500, false, gc)
return return
} }
@ -716,14 +716,14 @@ func (app *appContext) ChangeMyPassword(gc *gin.Context) {
if app.config.Section("ombi").Key("enabled").MustBool(false) { if app.config.Section("ombi").Key("enabled").MustBool(false) {
func() { func() {
ombiUser, err := app.getOmbiUser(gc.GetString("jfId")) ombiUser, status, err := app.getOmbiUser(gc.GetString("jfId"))
if err != nil { if status != 200 || err != nil {
app.err.Printf(lm.FailedGetUser, user.Name, lm.Ombi, err) app.err.Printf(lm.FailedGetUser, user.Name, lm.Ombi, err)
return return
} }
ombiUser["password"] = req.New ombiUser["password"] = req.New
err = app.ombi.ModifyUser(ombiUser) status, err = app.ombi.ModifyUser(ombiUser)
if err != nil { if status != 200 || err != nil {
app.err.Printf(lm.FailedChangePassword, lm.Ombi, ombiUser["userName"], err) app.err.Printf(lm.FailedChangePassword, lm.Ombi, ombiUser["userName"], err)
return return
} }

View File

@ -379,9 +379,9 @@ func (app *appContext) EnableDisableUsers(gc *gin.Context) {
activityType = ActivityEnabled activityType = ActivityEnabled
} }
for _, userID := range req.Users { for _, userID := range req.Users {
user, err := app.jf.UserByID(userID, false) user, status, err := app.jf.UserByID(userID, false)
if err != nil { if status != 200 || err != nil {
errors["GetUser"][user.ID] = err.Error() errors["GetUser"][user.ID] = fmt.Sprintf("%d %v", status, err)
app.err.Printf(lm.FailedGetUser, user.ID, lm.Jellyfin, err) app.err.Printf(lm.FailedGetUser, user.ID, lm.Jellyfin, err)
continue continue
} }
@ -440,7 +440,10 @@ func (app *appContext) DeleteUsers(gc *gin.Context) {
} }
} }
for _, userID := range req.Users { for _, userID := range req.Users {
user, err := app.jf.UserByID(userID, false) user, status, err := app.jf.UserByID(userID, false)
if status != 200 && err == nil {
err = fmt.Errorf("failed (code %d)", status)
}
if err != nil { if err != nil {
app.err.Printf(lm.FailedGetUser, user.ID, lm.Jellyfin, err) app.err.Printf(lm.FailedGetUser, user.ID, lm.Jellyfin, err)
errors[userID] = err.Error() errors[userID] = err.Error()
@ -499,6 +502,7 @@ func (app *appContext) DeleteUsers(gc *gin.Context) {
func (app *appContext) ExtendExpiry(gc *gin.Context) { func (app *appContext) ExtendExpiry(gc *gin.Context) {
var req extendExpiryDTO var req extendExpiryDTO
gc.BindJSON(&req) gc.BindJSON(&req)
app.info.Printf("Expiry extension requested for %d user(s)", len(req.Users))
if req.Months <= 0 && req.Days <= 0 && req.Hours <= 0 && req.Minutes <= 0 && req.Timestamp <= 0 { if req.Months <= 0 && req.Days <= 0 && req.Hours <= 0 && req.Minutes <= 0 && req.Timestamp <= 0 {
respondBool(400, false, gc) respondBool(400, false, gc)
return return
@ -518,8 +522,8 @@ func (app *appContext) ExtendExpiry(gc *gin.Context) {
app.storage.SetUserExpiryKey(id, expiry) app.storage.SetUserExpiryKey(id, expiry)
if messagesEnabled && req.Notify { if messagesEnabled && req.Notify {
go func(uid string, exp time.Time) { go func(uid string, exp time.Time) {
user, err := app.jf.UserByID(uid, false) user, status, err := app.jf.UserByID(uid, false)
if err != nil { if status != 200 || err != nil {
return return
} }
msg, err := app.email.constructExpiryAdjusted(user.Name, exp, req.Reason, app, false) msg, err := app.email.constructExpiryAdjusted(user.Name, exp, req.Reason, app, false)
@ -651,8 +655,8 @@ func (app *appContext) Announce(gc *gin.Context) {
unique := strings.Contains(req.Message, "{username}") unique := strings.Contains(req.Message, "{username}")
if unique { if unique {
for _, userID := range req.Users { for _, userID := range req.Users {
user, err := app.jf.UserByID(userID, false) user, status, err := app.jf.UserByID(userID, false)
if err != nil { if status != 200 || err != nil {
app.err.Printf(lm.FailedGetUser, userID, lm.Jellyfin, err) app.err.Printf(lm.FailedGetUser, userID, lm.Jellyfin, err)
continue continue
} }
@ -831,9 +835,9 @@ func (app *appContext) AdminPasswordReset(gc *gin.Context) {
// @tags Users // @tags Users
func (app *appContext) GetUsers(gc *gin.Context) { func (app *appContext) GetUsers(gc *gin.Context) {
var resp getUsersDTO var resp getUsersDTO
users, err := app.jf.GetUsers(false) users, status, err := app.jf.GetUsers(false)
resp.UserList = make([]respUser, len(users)) resp.UserList = make([]respUser, len(users))
if err != nil { if !(status == 200 || status == 204) || err != nil {
app.err.Printf(lm.FailedGetUsers, lm.Jellyfin, err) app.err.Printf(lm.FailedGetUsers, lm.Jellyfin, err)
respond(500, "Couldn't get users", gc) respond(500, "Couldn't get users", gc)
return return
@ -906,8 +910,8 @@ func (app *appContext) GetUsers(gc *gin.Context) {
func (app *appContext) SetAccountsAdmin(gc *gin.Context) { func (app *appContext) SetAccountsAdmin(gc *gin.Context) {
var req setAccountsAdminDTO var req setAccountsAdminDTO
gc.BindJSON(&req) gc.BindJSON(&req)
users, err := app.jf.GetUsers(false) users, status, err := app.jf.GetUsers(false)
if err != nil { if !(status == 200 || status == 204) || err != nil {
app.err.Printf(lm.FailedGetUsers, lm.Jellyfin, err) app.err.Printf(lm.FailedGetUsers, lm.Jellyfin, err)
respond(500, "Couldn't get users", gc) respond(500, "Couldn't get users", gc)
return return
@ -938,8 +942,8 @@ func (app *appContext) SetAccountsAdmin(gc *gin.Context) {
func (app *appContext) ModifyLabels(gc *gin.Context) { func (app *appContext) ModifyLabels(gc *gin.Context) {
var req modifyEmailsDTO var req modifyEmailsDTO
gc.BindJSON(&req) gc.BindJSON(&req)
users, err := app.jf.GetUsers(false) users, status, err := app.jf.GetUsers(false)
if err != nil { if !(status == 200 || status == 204) || err != nil {
app.err.Printf(lm.FailedGetUsers, lm.Jellyfin, err) app.err.Printf(lm.FailedGetUsers, lm.Jellyfin, err)
respond(500, "Couldn't get users", gc) respond(500, "Couldn't get users", gc)
return return
@ -972,11 +976,11 @@ func (app *appContext) modifyEmail(jfID string, addr string) {
emailStore.Addr = addr emailStore.Addr = addr
app.storage.SetEmailsKey(jfID, emailStore) app.storage.SetEmailsKey(jfID, emailStore)
if app.config.Section("ombi").Key("enabled").MustBool(false) { if app.config.Section("ombi").Key("enabled").MustBool(false) {
ombiUser, err := app.getOmbiUser(jfID) ombiUser, code, err := app.getOmbiUser(jfID)
if err == nil { if code == 200 && err == nil {
ombiUser["emailAddress"] = addr ombiUser["emailAddress"] = addr
err = app.ombi.ModifyUser(ombiUser) code, err = app.ombi.ModifyUser(ombiUser)
if err != nil { if code != 200 || err != nil {
app.err.Printf(lm.FailedSetEmailAddress, lm.Ombi, jfID, err) app.err.Printf(lm.FailedSetEmailAddress, lm.Ombi, jfID, err)
} }
} }
@ -1008,8 +1012,8 @@ func (app *appContext) modifyEmail(jfID string, addr string) {
func (app *appContext) ModifyEmails(gc *gin.Context) { func (app *appContext) ModifyEmails(gc *gin.Context) {
var req modifyEmailsDTO var req modifyEmailsDTO
gc.BindJSON(&req) gc.BindJSON(&req)
users, err := app.jf.GetUsers(false) users, status, err := app.jf.GetUsers(false)
if err != nil { if !(status == 200 || status == 204) || err != nil {
app.err.Printf(lm.FailedGetUsers, lm.Jellyfin, err) app.err.Printf(lm.FailedGetUsers, lm.Jellyfin, err)
respond(500, "Couldn't get users", gc) respond(500, "Couldn't get users", gc)
return return
@ -1095,8 +1099,8 @@ func (app *appContext) ApplySettings(gc *gin.Context) {
} else if req.From == "user" { } else if req.From == "user" {
applyingFromType = lm.User applyingFromType = lm.User
app.jf.CacheExpiry = time.Now() app.jf.CacheExpiry = time.Now()
user, err := app.jf.UserByID(req.ID, false) user, status, err := app.jf.UserByID(req.ID, false)
if err != nil { if !(status == 200 || status == 204) || err != nil {
app.err.Printf(lm.FailedGetUser, req.ID, lm.Jellyfin, err) app.err.Printf(lm.FailedGetUser, req.ID, lm.Jellyfin, err)
respond(500, "Couldn't get user", gc) respond(500, "Couldn't get user", gc)
return return
@ -1106,8 +1110,8 @@ func (app *appContext) ApplySettings(gc *gin.Context) {
policy = user.Policy policy = user.Policy
} }
if req.Homescreen { if req.Homescreen {
displayprefs, err = app.jf.GetDisplayPreferences(req.ID) displayprefs, status, err = app.jf.GetDisplayPreferences(req.ID)
if err != nil { if !(status == 200 || status == 204) || err != nil {
app.err.Printf(lm.FailedGetJellyfinDisplayPrefs, req.ID, err) app.err.Printf(lm.FailedGetJellyfinDisplayPrefs, req.ID, err)
respond(500, "Couldn't get displayprefs", gc) respond(500, "Couldn't get displayprefs", gc)
return return
@ -1132,25 +1136,26 @@ func (app *appContext) ApplySettings(gc *gin.Context) {
app.debug.Printf(lm.DelayingRequests, requestDelayThreshold) app.debug.Printf(lm.DelayingRequests, requestDelayThreshold)
} }
for _, id := range req.ApplyTo { for _, id := range req.ApplyTo {
var status int
var err error var err error
if req.Policy { if req.Policy {
err = app.jf.SetPolicy(id, policy) status, err = app.jf.SetPolicy(id, policy)
if err != nil { if !(status == 200 || status == 204) || err != nil {
errors["policy"][id] = err.Error() errors["policy"][id] = fmt.Sprintf("%d: %s", status, err)
} }
} }
if shouldDelay { if shouldDelay {
time.Sleep(250 * time.Millisecond) time.Sleep(250 * time.Millisecond)
} }
if req.Homescreen { if req.Homescreen {
err = app.jf.SetConfiguration(id, configuration) status, err = app.jf.SetConfiguration(id, configuration)
errorString := "" errorString := ""
if err != nil { if !(status == 200 || status == 204) || err != nil {
errorString += fmt.Sprintf("Configuration: %v", err) errorString += fmt.Sprintf("Configuration %d: %v ", status, err)
} else { } else {
err = app.jf.SetDisplayPreferences(id, displayprefs) status, err = app.jf.SetDisplayPreferences(id, displayprefs)
if err != nil { if !(status == 200 || status == 204) || err != nil {
errorString += fmt.Sprintf("Displayprefs;) %v ", err) errorString += fmt.Sprintf("Displayprefs %d: %v ", status, err)
} }
} }
if errorString != "" { if errorString != "" {
@ -1159,18 +1164,18 @@ func (app *appContext) ApplySettings(gc *gin.Context) {
} }
if ombi != nil { if ombi != nil {
errorString := "" errorString := ""
user, err := app.getOmbiUser(id) user, status, err := app.getOmbiUser(id)
if err != nil { if status != 200 || err != nil {
errorString += fmt.Sprintf("Ombi GetUser: %v ", err) errorString += fmt.Sprintf("Ombi GetUser %d: %v ", status, err)
} else { } else {
// newUser := ombi // newUser := ombi
// newUser["id"] = user["id"] // newUser["id"] = user["id"]
// newUser["userName"] = user["userName"] // newUser["userName"] = user["userName"]
// newUser["alias"] = user["alias"] // newUser["alias"] = user["alias"]
// newUser["emailAddress"] = user["emailAddress"] // newUser["emailAddress"] = user["emailAddress"]
err = app.ombi.applyProfile(user, ombi) status, err = app.ombi.applyProfile(user, ombi)
if err != nil { if status != 200 || err != nil {
errorString += fmt.Sprintf("Apply: %v ", err) errorString += fmt.Sprintf("Apply %d: %v ", status, err)
} }
} }
if errorString != "" { if errorString != "" {

31
api.go
View File

@ -148,18 +148,18 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
userID = reset.ID userID = reset.ID
username = reset.Username username = reset.Username
err := app.jf.ResetPasswordAdmin(userID) status, err := app.jf.ResetPasswordAdmin(userID)
if err != nil { if !(status == 200 || status == 204) || err != nil {
app.err.Printf(lm.FailedChangePassword, lm.Jellyfin, userID, err) app.err.Printf(lm.FailedChangePassword, lm.Jellyfin, userID, err)
respondBool(500, false, gc) respondBool(status, false, gc)
return return
} }
delete(app.internalPWRs, req.PIN) delete(app.internalPWRs, req.PIN)
} else { } else {
resp, err := app.jf.ResetPassword(req.PIN) resp, status, err := app.jf.ResetPassword(req.PIN)
if err != nil || !resp.Success { if status != 200 || err != nil || !resp.Success {
app.err.Printf(lm.FailedChangePassword, lm.Jellyfin, userID, err) app.err.Printf(lm.FailedChangePassword, lm.Jellyfin, userID, err)
respondBool(500, false, gc) respondBool(status, false, gc)
return return
} }
if req.Password == "" || len(resp.UsersReset) == 0 { if req.Password == "" || len(resp.UsersReset) == 0 {
@ -170,13 +170,14 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
} }
var user mediabrowser.User var user mediabrowser.User
var status int
var err error var err error
if isInternal { if isInternal {
user, err = app.jf.UserByID(userID, false) user, status, err = app.jf.UserByID(userID, false)
} else { } else {
user, err = app.jf.UserByName(username, false) user, status, err = app.jf.UserByName(username, false)
} }
if err != nil { if status != 200 || err != nil {
app.err.Printf(lm.FailedGetUser, userID, lm.Jellyfin, err) app.err.Printf(lm.FailedGetUser, userID, lm.Jellyfin, err)
respondBool(500, false, gc) respondBool(500, false, gc)
return return
@ -194,8 +195,8 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
if isInternal { if isInternal {
prevPassword = "" prevPassword = ""
} }
err = app.jf.SetPassword(user.ID, prevPassword, req.Password) status, err = app.jf.SetPassword(user.ID, prevPassword, req.Password)
if err != nil { if !(status == 200 || status == 204) || err != nil {
app.err.Printf(lm.FailedChangePassword, lm.Jellyfin, user.ID, err) app.err.Printf(lm.FailedChangePassword, lm.Jellyfin, user.ID, err)
respondBool(500, false, gc) respondBool(500, false, gc)
return return
@ -209,15 +210,15 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
respondBool(200, true, gc) respondBool(200, true, gc)
return return
} */ } */
ombiUser, err := app.getOmbiUser(user.ID) ombiUser, status, err := app.getOmbiUser(user.ID)
if err != nil { if status != 200 || err != nil {
app.err.Printf(lm.FailedGetUser, user.ID, lm.Ombi, err) app.err.Printf(lm.FailedGetUser, user.ID, lm.Ombi, err)
respondBool(200, true, gc) respondBool(200, true, gc)
return return
} }
ombiUser["password"] = req.Password ombiUser["password"] = req.Password
err = app.ombi.ModifyUser(ombiUser) status, err = app.ombi.ModifyUser(ombiUser)
if err != nil { if status != 200 || err != nil {
app.err.Printf(lm.FailedChangePassword, lm.Ombi, user.ID, err) app.err.Printf(lm.FailedChangePassword, lm.Ombi, user.ID, err)
respondBool(200, true, gc) respondBool(200, true, gc)
return return

11
auth.go
View File

@ -2,7 +2,6 @@ package main
import ( import (
"encoding/base64" "encoding/base64"
"errors"
"fmt" "fmt"
"os" "os"
"strings" "strings"
@ -167,19 +166,19 @@ func (app *appContext) decodeValidateLoginHeader(gc *gin.Context, userpage bool)
func (app *appContext) validateJellyfinCredentials(username, password string, gc *gin.Context, userpage bool) (user mediabrowser.User, ok bool) { func (app *appContext) validateJellyfinCredentials(username, password string, gc *gin.Context, userpage bool) (user mediabrowser.User, ok bool) {
ok = false ok = false
user, err := app.authJf.Authenticate(username, password) user, status, err := app.authJf.Authenticate(username, password)
if err != nil { if status != 200 || err != nil {
if errors.Is(err, mediabrowser.ErrUnauthorized{}) { if status == 401 || status == 400 {
app.logIpInfo(gc, userpage, fmt.Sprintf(lm.FailedAuthRequest, lm.InvalidUserOrPass)) app.logIpInfo(gc, userpage, fmt.Sprintf(lm.FailedAuthRequest, lm.InvalidUserOrPass))
respond(401, "Unauthorized", gc) respond(401, "Unauthorized", gc)
return return
} }
if errors.Is(err, mediabrowser.ErrForbidden{}) { if status == 403 {
app.logIpInfo(gc, userpage, fmt.Sprintf(lm.FailedAuthRequest, lm.UserDisabled)) app.logIpInfo(gc, userpage, fmt.Sprintf(lm.FailedAuthRequest, lm.UserDisabled))
respond(403, "yourAccountWasDisabled", gc) respond(403, "yourAccountWasDisabled", gc)
return return
} }
app.authLog(fmt.Sprintf(lm.FailedAuthJellyfin, app.jf.Server, err)) app.authLog(fmt.Sprintf(lm.FailedAuthJellyfin, app.jf.Server, status, err))
respond(500, "Jellyfin error", gc) respond(500, "Jellyfin error", gc)
return return
} }

View File

@ -1,11 +1,8 @@
package common package common
import ( import (
"errors"
"fmt" "fmt"
"log" "log"
lm "github.com/hrfee/jfa-go/logmessages"
) )
// TimeoutHandler recovers from an http timeout or panic. // TimeoutHandler recovers from an http timeout or panic.
@ -15,7 +12,7 @@ type TimeoutHandler func()
func NewTimeoutHandler(name, addr string, noFail bool) TimeoutHandler { func NewTimeoutHandler(name, addr string, noFail bool) TimeoutHandler {
return func() { return func() {
if r := recover(); r != nil { if r := recover(); r != nil {
out := fmt.Sprintf(lm.FailedAuth, name, addr, 0, lm.TimedOut) out := fmt.Sprintf("Failed to authenticate with %s @ \"%s\": Timed out", name, addr)
if noFail { if noFail {
log.Print(out) log.Print(out)
} else { } else {
@ -24,50 +21,3 @@ func NewTimeoutHandler(name, addr string, noFail bool) TimeoutHandler {
} }
} }
} }
// most 404 errors are from UserNotFound, so this generic error doesn't really need any detail.
type ErrNotFound error
type ErrUnauthorized struct{}
func (err ErrUnauthorized) Error() string {
return lm.Unauthorized
}
type ErrForbidden struct{}
func (err ErrForbidden) Error() string {
return lm.Forbidden
}
var (
NotFound ErrNotFound = errors.New(lm.NotFound)
)
type ErrUnknown struct {
code int
}
func (err ErrUnknown) Error() string {
msg := fmt.Sprintf(lm.FailedGenericWithCode, err.code)
return msg
}
// GenericErr returns an error appropriate to the given HTTP status (or actual error, if given).
func GenericErr(status int, err error) error {
if err != nil {
return err
}
switch status {
case 200, 204, 201:
return nil
case 401, 400:
return ErrUnauthorized{}
case 404:
return NotFound
case 403:
return ErrForbidden{}
default:
return ErrUnknown{code: status}
}
}

View File

@ -1,7 +1,3 @@
module github.com/hrfee/jfa-go/common module github.com/hrfee/jfa-go/common
replace github.com/hrfee/jfa-go/logmessages => ../logmessages go 1.15
go 1.22.4
require github.com/hrfee/jfa-go/logmessages v0.0.0-20240805130902-86c37fb4237b

View File

@ -62,7 +62,18 @@ html:not(.dark) .card.\@low:not(.\~neutral):not(.\~positive):not(.\~urge):not(.\
display: initial; display: initial;
} }
@media screen and (max-width: 1024px) { .page-container {
margin: 5% 20% 5% 20%;
}
@media (max-width: 1100px) {
.page-container {
margin: 2%;
margin-top: 5rem;
}
}
@media screen and (max-width: 1000px) {
:root { :root {
font-size: 0.9rem; font-size: 0.9rem;
} }

View File

@ -968,10 +968,11 @@ func (app *appContext) getAddressOrName(jfID string) string {
// returns "" if none found. returns only the first match, might be an issue if there are users with the same contact method usernames. // returns "" if none found. returns only the first match, might be an issue if there are users with the same contact method usernames.
func (app *appContext) ReverseUserSearch(address string, matchUsername, matchEmail, matchContactMethod bool) (user mediabrowser.User, ok bool) { func (app *appContext) ReverseUserSearch(address string, matchUsername, matchEmail, matchContactMethod bool) (user mediabrowser.User, ok bool) {
ok = false ok = false
var status int
var err error = nil var err error = nil
if matchUsername { if matchUsername {
user, err = app.jf.UserByName(address, false) user, status, err = app.jf.UserByName(address, false)
if err == nil { if status == 200 && err == nil {
ok = true ok = true
return return
} }
@ -982,8 +983,8 @@ func (app *appContext) ReverseUserSearch(address string, matchUsername, matchEma
err = app.storage.db.Find(&emailAddresses, badgerhold.Where("Addr").Eq(address)) err = app.storage.db.Find(&emailAddresses, badgerhold.Where("Addr").Eq(address))
if err == nil && len(emailAddresses) > 0 { if err == nil && len(emailAddresses) > 0 {
for _, emailUser := range emailAddresses { for _, emailUser := range emailAddresses {
user, err = app.jf.UserByID(emailUser.JellyfinID, false) user, status, err = app.jf.UserByID(emailUser.JellyfinID, false)
if err == nil { if status == 200 && err == nil {
ok = true ok = true
return return
} }
@ -996,8 +997,8 @@ func (app *appContext) ReverseUserSearch(address string, matchUsername, matchEma
if matchContactMethod { if matchContactMethod {
for _, dcUser := range app.storage.GetDiscord() { for _, dcUser := range app.storage.GetDiscord() {
if RenderDiscordUsername(dcUser) == strings.ToLower(address) { if RenderDiscordUsername(dcUser) == strings.ToLower(address) {
user, err = app.jf.UserByID(dcUser.JellyfinID, false) user, status, err = app.jf.UserByID(dcUser.JellyfinID, false)
if err == nil { if status == 200 && err == nil {
ok = true ok = true
return return
} }
@ -1008,8 +1009,8 @@ func (app *appContext) ReverseUserSearch(address string, matchUsername, matchEma
err = app.storage.db.Find(&telegramUsers, badgerhold.Where("Username").Eq(tgUsername)) err = app.storage.db.Find(&telegramUsers, badgerhold.Where("Username").Eq(tgUsername))
if err == nil && len(telegramUsers) > 0 { if err == nil && len(telegramUsers) > 0 {
for _, telegramUser := range telegramUsers { for _, telegramUser := range telegramUsers {
user, err = app.jf.UserByID(telegramUser.JellyfinID, false) user, status, err = app.jf.UserByID(telegramUser.JellyfinID, false)
if err == nil { if status == 200 && err == nil {
ok = true ok = true
return return
} }
@ -1019,8 +1020,8 @@ func (app *appContext) ReverseUserSearch(address string, matchUsername, matchEma
err = app.storage.db.Find(&matrixUsers, badgerhold.Where("UserID").Eq(address)) err = app.storage.db.Find(&matrixUsers, badgerhold.Where("UserID").Eq(address))
if err == nil && len(matrixUsers) > 0 { if err == nil && len(matrixUsers) > 0 {
for _, matrixUser := range matrixUsers { for _, matrixUser := range matrixUsers {
user, err = app.jf.UserByID(matrixUser.JellyfinID, false) user, status, err = app.jf.UserByID(matrixUser.JellyfinID, false)
if err == nil { if status == 200 && err == nil {
ok = true ok = true
return return
} }

10
go.mod
View File

@ -33,15 +33,13 @@ require (
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
github.com/golang-jwt/jwt v3.2.2+incompatible github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/gomarkdown/markdown v0.0.0-20230322041520-c84983bdbf2a github.com/gomarkdown/markdown v0.0.0-20230322041520-c84983bdbf2a
github.com/hrfee/jfa-go/common v0.0.0-20240805130902-86c37fb4237b github.com/hrfee/jfa-go/common v0.0.0-20240728190513-dabef831d769
github.com/hrfee/jfa-go/docs v0.0.0-20230626224816-f72960635dc3 github.com/hrfee/jfa-go/docs v0.0.0-20230626224816-f72960635dc3
github.com/hrfee/jfa-go/easyproxy v0.0.0-00010101000000-000000000000 github.com/hrfee/jfa-go/easyproxy v0.0.0-00010101000000-000000000000
github.com/hrfee/jfa-go/jellyseerr v0.0.0-20240805130902-86c37fb4237b
github.com/hrfee/jfa-go/linecache v0.0.0-20230626224816-f72960635dc3 github.com/hrfee/jfa-go/linecache v0.0.0-20230626224816-f72960635dc3
github.com/hrfee/jfa-go/logger v0.0.0-20240731152135-2d066ea7cd32 github.com/hrfee/jfa-go/logger v0.0.0-20240731152135-2d066ea7cd32
github.com/hrfee/jfa-go/logmessages v0.0.0-20240805130902-86c37fb4237b github.com/hrfee/jfa-go/ombi v0.0.0-20230626224816-f72960635dc3
github.com/hrfee/jfa-go/ombi v0.0.0-20240805130902-86c37fb4237b github.com/hrfee/mediabrowser v0.3.14
github.com/hrfee/mediabrowser v0.3.18
github.com/itchyny/timefmt-go v0.1.5 github.com/itchyny/timefmt-go v0.1.5
github.com/lithammer/shortuuid/v3 v3.0.7 github.com/lithammer/shortuuid/v3 v3.0.7
github.com/mailgun/mailgun-go/v4 v4.9.1 github.com/mailgun/mailgun-go/v4 v4.9.1
@ -94,6 +92,8 @@ require (
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect github.com/gorilla/mux v1.8.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect
github.com/hrfee/jfa-go/jellyseerr v0.0.0-00010101000000-000000000000 // indirect
github.com/hrfee/jfa-go/logmessages v0.0.0-00010101000000-000000000000 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.16.6 // indirect github.com/klauspost/compress v1.16.6 // indirect

16
go.sum
View File

@ -1,7 +1,6 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
@ -95,7 +94,6 @@ github.com/getlantern/systray v1.2.2/go.mod h1:pXFOI1wwqwYXEhLPm9ZGjS2u/vVELeIgN
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w= github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
github.com/gin-contrib/pprof v1.4.0 h1:XxiBSf5jWZ5i16lNOPbMTVdgHBdhfGRD5PZ1LWazzvg= github.com/gin-contrib/pprof v1.4.0 h1:XxiBSf5jWZ5i16lNOPbMTVdgHBdhfGRD5PZ1LWazzvg=
github.com/gin-contrib/pprof v1.4.0/go.mod h1:RrehPJasUVBPK6yTUwOl8/NP6i0vbUgmxtis+Z5KE90= github.com/gin-contrib/pprof v1.4.0/go.mod h1:RrehPJasUVBPK6yTUwOl8/NP6i0vbUgmxtis+Z5KE90=
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
@ -142,7 +140,6 @@ github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogB
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
@ -216,7 +213,6 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -228,10 +224,10 @@ github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hrfee/mediabrowser v0.3.17 h1:gerX/sxQ8V64mZ13I0zuK9hpCJnScdTpAzD2j0a7bvE= github.com/hrfee/mediabrowser v0.3.13 h1:NgQNbq+JWwsP68BdWXL/rwbpfE/oO5LJ5KVkE+aNbX8=
github.com/hrfee/mediabrowser v0.3.17/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U= github.com/hrfee/mediabrowser v0.3.13/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U=
github.com/hrfee/mediabrowser v0.3.18 h1:6UDyae0srEVjiMG+utPQfJJp4UId6/T3WN9EDCQKRTk= github.com/hrfee/mediabrowser v0.3.14 h1:/hxtwefV4pA63fyHussk00JBFry6p+uDDOumA49M1eY=
github.com/hrfee/mediabrowser v0.3.18/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U= github.com/hrfee/mediabrowser v0.3.14/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE= github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE=
github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8= github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8=
@ -293,7 +289,6 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -356,7 +351,6 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E= github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E=
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
@ -426,7 +420,6 @@ go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
@ -462,7 +455,6 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=

View File

@ -16,7 +16,7 @@ func (app *appContext) clearEmails() {
app.debug.Println(lm.HousekeepingEmail) app.debug.Println(lm.HousekeepingEmail)
emails := app.storage.GetEmails() emails := app.storage.GetEmails()
for _, email := range emails { for _, email := range emails {
_, err := app.jf.UserByID(email.JellyfinID, false) _, _, err := app.jf.UserByID(email.JellyfinID, false)
// Make sure the user doesn't exist, and no other error has occured // Make sure the user doesn't exist, and no other error has occured
switch err.(type) { switch err.(type) {
case mediabrowser.ErrUserNotFound: case mediabrowser.ErrUserNotFound:
@ -32,7 +32,7 @@ func (app *appContext) clearDiscord() {
app.debug.Println(lm.HousekeepingDiscord) app.debug.Println(lm.HousekeepingDiscord)
discordUsers := app.storage.GetDiscord() discordUsers := app.storage.GetDiscord()
for _, discordUser := range discordUsers { for _, discordUser := range discordUsers {
user, err := app.jf.UserByID(discordUser.JellyfinID, false) user, _, err := app.jf.UserByID(discordUser.JellyfinID, false)
// Make sure the user doesn't exist, and no other error has occured // Make sure the user doesn't exist, and no other error has occured
switch err.(type) { switch err.(type) {
case mediabrowser.ErrUserNotFound: case mediabrowser.ErrUserNotFound:
@ -53,7 +53,7 @@ func (app *appContext) clearMatrix() {
app.debug.Println(lm.HousekeepingMatrix) app.debug.Println(lm.HousekeepingMatrix)
matrixUsers := app.storage.GetMatrix() matrixUsers := app.storage.GetMatrix()
for _, matrixUser := range matrixUsers { for _, matrixUser := range matrixUsers {
_, err := app.jf.UserByID(matrixUser.JellyfinID, false) _, _, err := app.jf.UserByID(matrixUser.JellyfinID, false)
// Make sure the user doesn't exist, and no other error has occured // Make sure the user doesn't exist, and no other error has occured
switch err.(type) { switch err.(type) {
case mediabrowser.ErrUserNotFound: case mediabrowser.ErrUserNotFound:
@ -69,7 +69,7 @@ func (app *appContext) clearTelegram() {
app.debug.Println(lm.HousekeepingTelegram) app.debug.Println(lm.HousekeepingTelegram)
telegramUsers := app.storage.GetTelegram() telegramUsers := app.storage.GetTelegram()
for _, telegramUser := range telegramUsers { for _, telegramUser := range telegramUsers {
_, err := app.jf.UserByID(telegramUser.JellyfinID, false) _, _, err := app.jf.UserByID(telegramUser.JellyfinID, false)
// Make sure the user doesn't exist, and no other error has occured // Make sure the user doesn't exist, and no other error has occured
switch err.(type) { switch err.(type) {
case mediabrowser.ErrUserNotFound: case mediabrowser.ErrUserNotFound:

View File

@ -6,7 +6,7 @@
<title>404 - jfa-go</title> <title>404 - jfa-go</title>
</head> </head>
<body class="section"> <body class="section">
<div class="page-container m-2 lg:my-20 lg:mx-64"> <div class="page-container">
<div class="card"> <div class="card">
<h1 class="heading">Page not found.</h1> <h1 class="heading">Page not found.</h1>
<p class="content"> <p class="content">

View File

@ -44,7 +44,7 @@
</form> </form>
</div> </div>
<div id="modal-about" class="modal"> <div id="modal-about" class="modal">
<div class="relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/2 content card"> <div class="relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3 content card">
<img src="{{ .urlBase }}/banner.svg" class="banner header" alt="jfa-go banner"> <img src="{{ .urlBase }}/banner.svg" class="banner header" alt="jfa-go banner">
<span class="heading"><span class="modal-close">&times;</span></span> <span class="heading"><span class="modal-close">&times;</span></span>
<p>{{ .strings.version }} <span class="text-black dark:text-white font-mono bg-inherit">{{ .version }}</span></p> <p>{{ .strings.version }} <span class="text-black dark:text-white font-mono bg-inherit">{{ .version }}</span></p>
@ -535,56 +535,58 @@
</form> </form>
</div> </div>
<div id="notification-box"></div> <div id="notification-box"></div>
<div class="page-container m-2 lg:my-20 lg:mx-64 flex flex-col gap-4"> <div class="top-4 left-4 absolute flex flex-row gap-2">
<div class="top-2 inset-x-2 lg:absolute flex flex-row justify-between"> <span class="dropdown z-[11]" tabindex="0" id="lang-dropdown">
<div class="flex flex-row gap-2"> <span class="button ~urge dropdown-button">
<span class="dropdown z-[11]" tabindex="0" id="lang-dropdown"> <i class="ri-global-line"></i>
<span class="button ~urge dropdown-button"> <span class="ml-2 chev"></span>
<i class="ri-global-line"></i> </span>
<span class="ml-2 chev"></span> <div class="dropdown-display">
</span> <div class="card ~neutral @low">
<div class="dropdown-display"> <label class="switch pb-4">
<div class="card ~neutral @low"> <input type="radio" name="lang-time" id="lang-12h">
<label class="switch pb-4"> <span>{{ .strings.time12h }}</span>
<input type="radio" name="lang-time" id="lang-12h"> </label>
<span>{{ .strings.time12h }}</span> <label class="switch pb-4">
</label> <input type="radio" name="lang-time" id="lang-24h">
<label class="switch pb-4"> <span>{{ .strings.time24h }}</span>
<input type="radio" name="lang-time" id="lang-24h"> </label>
<span>{{ .strings.time24h }}</span> <div id="lang-list"></div>
</label> </div>
<div id="lang-list"></div>
</div>
</div>
</span>
<span class="button ~warning" alt="{{ .strings.theme }}" id="button-theme"><i class="ri-sun-line"></i></span>
</div> </div>
<div class="flex flex-row gap-2"> </span>
<span class="button ~critical @low unfocused" id="logout-button">{{ .strings.logout }}</span> <span class="button ~warning" alt="{{ .strings.theme }}" id="button-theme"><i class="ri-sun-line"></i></span>
{{ if .userPageEnabled }} </div>
<div class=""> {{ if .userPageEnabled }}
<a class="button ~info" href="{{ .urlBase }}/my/account"><i class="ri-account-circle-fill mr-2"></i>{{ .strings.myAccount }}</a> <div class="top-4 right-4 absolute">
</div> <a class="button ~info" href="{{ .urlBase }}/my/account"><i class="ri-account-circle-fill mr-2"></i>{{ .strings.myAccount }}</a>
{{ end }} </div>
{{ end }}
<div class="page-container">
<div class="mb-4">
<header>
<div class="flex flex-row overflow-x-scroll items-center">
<span id="button-tab-invites" class="text-3xl button portal ~neutral dark:~d_neutral @low mr-2 mb-2 px-5">{{ .strings.invites }}</span>
<span id="button-tab-accounts" class="text-3xl button portal ~neutral dark:~d_neutral @low mr-2 mb-2 px-5">{{ .strings.accounts }}</span>
<span id="button-tab-activity" class="text-3xl button portal ~neutral dark:~d_neutral @low mr-2 mb-2 px-5">{{ .strings.activity }}</span>
<span id="button-tab-settings" class="text-3xl button portal ~neutral dark:~d_neutral @low mr-2 mb-2 px-5">{{ .strings.settings }}</span>
</div>
</header>
</div>
<div class="mb-4">
<div>
<span class="button ~critical @low mb-4 unfocused" id="logout-button">{{ .strings.logout }}</span>
</div> </div>
</div> </div>
<header> <div id="tab-invites">
<div class="flex flex-row overflow-x-scroll items-center gap-2"> <div class="card @low invites dark:~d_neutral mb-4 overflow-visible">
<span id="button-tab-invites" class="text-3xl button portal ~neutral dark:~d_neutral @low px-5">{{ .strings.invites }}</span>
<span id="button-tab-accounts" class="text-3xl button portal ~neutral dark:~d_neutral @low px-5">{{ .strings.accounts }}</span>
<span id="button-tab-activity" class="text-3xl button portal ~neutral dark:~d_neutral @low px-5">{{ .strings.activity }}</span>
<span id="button-tab-settings" class="text-3xl button portal ~neutral dark:~d_neutral @low px-5">{{ .strings.settings }}</span>
</div>
</header>
<div id="tab-invites" class="flex flex-col gap-4">
<div class="card @low dark:~d_neutral flex flex-col gap-2 overflow-visible invites">
<span class="heading">{{ .strings.invites }}</span> <span class="heading">{{ .strings.invites }}</span>
<div id="invites"></div> <div id="invites" class="mt-2"></div>
</div> </div>
<div class="card @low dark:~d_neutral flex flex-col gap-2"> <div class="card @low dark:~d_neutral">
<span class="heading">{{ .strings.create }}</span> <span class="heading">{{ .strings.create }}</span>
<div class="flex flex-col md:flex-row gap-3" id="create-inv"> <div class="flex flex-col md:flex-row gap-3 mt-2" id="create-inv">
<div class="card ~neutral @low flex flex-col gap-2 flex-1"> <div class="card ~neutral @low flex flex-col gap-2 grow">
<div class="flex flex-row gap-2"> <div class="flex flex-row gap-2">
<label class="w-1/2"> <label class="w-1/2">
<input type="radio" name="duration" class="unfocused" id="radio-inv-duration" checked> <input type="radio" name="duration" class="unfocused" id="radio-inv-duration" checked>
@ -692,7 +694,7 @@
<input type="text" id="create-user-label" class="input ~neutral @low"> <input type="text" id="create-user-label" class="input ~neutral @low">
</div> </div>
</div> </div>
<div class="card ~neutral @low flex flex-col justify-between gap-2 flex-1"> <div class="card ~neutral @low flex flex-col justify-between gap-2 grow">
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<label class="label supra" for="create-uses">{{ .strings.inviteNumberOfUses }}</label> <label class="label supra" for="create-uses">{{ .strings.inviteNumberOfUses }}</label>
@ -736,7 +738,7 @@
</div> </div>
</div> </div>
</div> </div>
<div id="tab-accounts" class="flex flex-col gap-4 unfocused"> <div id="tab-accounts" class="unfocused">
<div class="card @low dark:~d_neutral accounts mb-4 overflow-visible"> <div class="card @low dark:~d_neutral accounts mb-4 overflow-visible">
<div id="accounts-filter-dropdown" class="dropdown manual z-10 w-full"> <div id="accounts-filter-dropdown" class="dropdown manual z-10 w-full">
<div class="flex flex-col md:flex-row align-middle gap-2"> <div class="flex flex-col md:flex-row align-middle gap-2">
@ -835,7 +837,7 @@
</div> </div>
</div> </div>
</div> </div>
<div id="tab-activity" class="flex flex-col gap-4 unfocused"> <div id="tab-activity" class="unfocused">
<div class="card @low dark:~d_neutral activity mb-4 overflow-visible"> <div class="card @low dark:~d_neutral activity mb-4 overflow-visible">
<div id="activity-filter-dropdown" class="dropdown manual z-10 w-full" tabindex="0"> <div id="activity-filter-dropdown" class="dropdown manual z-10 w-full" tabindex="0">
<div class="flex flex-col md:flex-row align-middle gap-2"> <div class="flex flex-col md:flex-row align-middle gap-2">
@ -892,8 +894,8 @@
</div> </div>
</div> </div>
</div> </div>
<div id="tab-settings" class="flex flex-col gap-4 unfocused"> <div id="tab-settings" class="unfocused">
<div class="card @low dark:~d_neutral settings overflow flex flex-col gap-2"> <div class="card @low dark:~d_neutral settings overflow">
<div class="flex flex-col md:flex-row align-middle gap-2"> <div class="flex flex-col md:flex-row align-middle gap-2">
<div class="flex flex-row align-middle justify-between md:justify-normal"> <div class="flex flex-row align-middle justify-between md:justify-normal">
<span class="heading">{{ .strings.settings }}</span> <span class="heading">{{ .strings.settings }}</span>
@ -910,18 +912,16 @@
</div> </div>
</div> </div>
<div class="flex flex-col md:flex-row gap-3"> <div class="flex flex-col md:flex-row gap-3">
<div class="md:card @low dark:~d_neutral flex md:flex flex-col gap-2 flex-1" id="settings-sidebar"> <div class="card @low dark:~d_neutral col" id="settings-sidebar">
<div class="flex flex-row justify-between"> <div class="flex flex-row justify-between">
<input type="search" class="field ~neutral @low input settings-section-button justify-between" id="settings-search" placeholder="{{ .strings.search }}"> <input type="search" class="field ~neutral @low input settings-section-button justify-between mb-2" id="settings-search" placeholder="{{ .strings.search }}">
<button class="button ~neutral @low center -ml-10 rounded-s-none settings-search-clear" aria-label="{{ .strings.clearSearch }}" text="{{ .strings.clearSearch }}"><i class="ri-close-line"></i></button> <button class="button ~neutral @low center -ml-10 rounded-s-none mb-2 settings-search-clear" aria-label="{{ .strings.clearSearch }}" text="{{ .strings.clearSearch }}"><i class="ri-close-line"></i></button>
</div>
<aside class="aside sm ~urge dark:~d_info @low" id="settings-message">Note: <span class="badge ~critical">*</span> indicates a required field, <span class="badge ~info dark:~d_warning">R</span> indicates changes require a restart.</aside>
<div id="settings-loader" class="flex flex-row gap-2">
<span class="button ~neutral @low settings-section-button justify-center grow" id="setting-about"><span class="flex">{{ .strings.aboutProgram }} <i class="ri-information-line ml-2"></i></span></span>
<span class="button ~neutral @low settings-section-button justify-center grow" id="setting-profiles"><span class="flex">{{ .strings.userProfiles }} <i class="ri-user-line ml-2"></i></span></span>
</div> </div>
<aside class="aside sm ~urge dark:~d_info mb-2 @low" id="settings-message">Note: <span class="badge ~critical">*</span> indicates a required field, <span class="badge ~info dark:~d_warning">R</span> indicates changes require a restart.</aside>
<span class="button ~neutral @low settings-section-button justify-between mb-2" id="setting-about"><span class="flex">{{ .strings.aboutProgram }} <i class="ri-information-line ml-2"></i></span></span>
<span class="button ~neutral @low settings-section-button justify-between mb-2" id="setting-profiles"><span class="flex">{{ .strings.userProfiles }} <i class="ri-user-line ml-2"></i></span></span>
</div> </div>
<div class="card ~neutral @low overflow flex-1" id="settings-panel"> <div class="card ~neutral @low col overflow" id="settings-panel">
<div class="settings-section unfocused h-[100%]" id="settings-not-found"> <div class="settings-section unfocused h-[100%]" id="settings-not-found">
<div class="flex flex-col h-[100%] justify-center items-center"> <div class="flex flex-col h-[100%] justify-center items-center">
<span class="text-2xl font-medium italic mb-2">{{ .strings.noResultsFound }}</span> <span class="text-2xl font-medium italic mb-2">{{ .strings.noResultsFound }}</span>

View File

@ -6,7 +6,7 @@
<title>Crash report</title> <title>Crash report</title>
</head> </head>
<body> <body>
<div class="page-container m-2 lg:my-20 lg:mx-64"> <div class="page-container">
<div class="card ~critical sectioned"> <div class="card ~critical sectioned">
<section class="section ~critical"> <section class="section ~critical">
<span class="heading">Crash report for jfa-go</span> <span class="heading">Crash report for jfa-go</span>

View File

@ -6,7 +6,7 @@
<title>{{ .strings.successHeader }} - jfa-go</title> <title>{{ .strings.successHeader }} - jfa-go</title>
</head> </head>
<body class="section"> <body class="section">
<div class="page-container m-2 lg:my-20 lg:mx-64"> <div class="page-container">
<div class="card ~neutral @low mb-4"> <div class="card ~neutral @low mb-4">
<span class="heading mb-4">{{ .strings.successHeader }}</span> <span class="heading mb-4">{{ .strings.successHeader }}</span>
<p class="content my-4">{{ .successMessage }}</p> <p class="content my-4">{{ .successMessage }}</p>

View File

@ -35,7 +35,7 @@
</div> </div>
</div> </div>
{{ template "account-linking.html" . }} {{ template "account-linking.html" . }}
<div class="top-2 left-2 absolute"> <div class="top-4 left-4 absolute">
<span class="dropdown" tabindex="0" id="lang-dropdown"> <span class="dropdown" tabindex="0" id="lang-dropdown">
<span class="button ~urge dropdown-button"> <span class="button ~urge dropdown-button">
<i class="ri-global-line"></i> <i class="ri-global-line"></i>
@ -48,7 +48,7 @@
</span> </span>
</div> </div>
<div id="notification-box"></div> <div id="notification-box"></div>
<div class="page-container m-2 lg:my-20 lg:mx-64"> <div class="page-container">
<div class="card dark:~d_neutral @low"> <div class="card dark:~d_neutral @low">
<div class="flex flex-col md:flex-row gap-3 items-baseline mb-2"> <div class="flex flex-col md:flex-row gap-3 items-baseline mb-2">
<span class="heading mr-5"> <span class="heading mr-5">

View File

@ -6,7 +6,7 @@
<title>Invalid Code - jfa-go</title> <title>Invalid Code - jfa-go</title>
</head> </head>
<body class="section"> <body class="section">
<div class="page-container m-2 lg:my-20 lg:mx-64"> <div class="page-container">
<div class="card"> <div class="card">
<h1 class="text-3xl font-semibold">Invalid invite code.</h1> <h1 class="text-3xl font-semibold">Invalid invite code.</h1>
<p class="content">The code above was either incorrect, or has expired.</p> <p class="content">The code above was either incorrect, or has expired.</p>

View File

@ -11,7 +11,7 @@
<span id="copy-notification" class="unfocused">{{ .strings.copied }}</span> <span id="copy-notification" class="unfocused">{{ .strings.copied }}</span>
</div> </div>
{{ end }} {{ end }}
<div class="page-container m-2 lg:my-20 lg:mx-64"> <div class="page-container">
<div class="card ~neutral @low mb-4"> <div class="card ~neutral @low mb-4">
<span class="heading mb-4"> <span class="heading mb-4">
{{ if .success }} {{ if .success }}

View File

@ -7,7 +7,7 @@
</head> </head>
<body class="max-w-full overflow-x-hidden section"> <body class="max-w-full overflow-x-hidden section">
<div id="notification-box"></div> <div id="notification-box"></div>
<div class="top-2 left-2 absolute"> <div class="top-4 left-4 absolute">
<span class="dropdown" tabindex="0" id="lang-dropdown"> <span class="dropdown" tabindex="0" id="lang-dropdown">
<span class="button ~urge dropdown-button"> <span class="button ~urge dropdown-button">
<i class="ri-global-line"></i> <i class="ri-global-line"></i>
@ -19,7 +19,7 @@
</div> </div>
</span> </span>
</div> </div>
<div class="page-container m-2 lg:my-20 lg:mx-64" id="page-container"> <div class="page-container" id="page-container">
<div class="card ~neutral @low mb-2"> <div class="card ~neutral @low mb-2">
<div class="row"> <div class="row">
<img class="banner header" src="banner.svg" alt="jfa-go" /> <img class="banner header" src="banner.svg" alt="jfa-go" />

View File

@ -79,7 +79,7 @@
{{ template "login-modal.html" . }} {{ template "login-modal.html" . }}
{{ template "account-linking.html" . }} {{ template "account-linking.html" . }}
<div id="notification-box"></div> <div id="notification-box"></div>
<div class="top-2 left-2 absolute"> <div class="top-4 left-4 absolute">
<span class="dropdown" tabindex="0" id="lang-dropdown"> <span class="dropdown" tabindex="0" id="lang-dropdown">
<span class="button ~urge dropdown-button"> <span class="button ~urge dropdown-button">
<i class="ri-global-line"></i> <i class="ri-global-line"></i>
@ -102,10 +102,10 @@
<span class="button ~warning" alt="{{ .strings.theme }}" id="button-theme"><i class="ri-sun-line"></i></span> <span class="button ~warning" alt="{{ .strings.theme }}" id="button-theme"><i class="ri-sun-line"></i></span>
<span class="button ~critical @low mb-4 unfocused" id="logout-button">{{ .strings.logout }}</span> <span class="button ~critical @low mb-4 unfocused" id="logout-button">{{ .strings.logout }}</span>
</div> </div>
<div class="top-2 right-2 absolute"> <div class="top-4 right-4 absolute">
<a class="button ~info unfocused" href="/" id="admin-back-button"><i class="ri-arrow-left-fill mr-2"></i>{{ .strings.admin }}</a> <a class="button ~info unfocused" href="/" id="admin-back-button"><i class="ri-arrow-left-fill mr-2"></i>{{ .strings.admin }}</a>
</div> </div>
<div class="page-container m-2 lg:my-20 lg:mx-64 unfocused"> <div class="page-container unfocused">
<div class="card @low dark:~d_neutral mb-4" id="card-user"> <div class="card @low dark:~d_neutral mb-4" id="card-user">
<span class="heading mb-2"></span> <span class="heading mb-2"></span>
</div> </div>

View File

@ -58,8 +58,8 @@ func (app *appContext) SynchronizeJellyseerrUser(jfID string) {
} }
func (app *appContext) SynchronizeJellyseerrUsers() { func (app *appContext) SynchronizeJellyseerrUsers() {
users, err := app.jf.GetUsers(false) users, status, err := app.jf.GetUsers(false)
if err != nil { if err != nil || status != 200 {
app.err.Printf(lm.FailedGetUsers, lm.Jellyfin, err) app.err.Printf(lm.FailedGetUsers, lm.Jellyfin, err)
return return
} }

View File

@ -12,7 +12,7 @@ import (
"strings" "strings"
"time" "time"
co "github.com/hrfee/jfa-go/common" "github.com/hrfee/jfa-go/common"
) )
const ( const (
@ -28,13 +28,13 @@ type Jellyseerr struct {
userCache map[string]User // Map of jellyfin IDs to users userCache map[string]User // Map of jellyfin IDs to users
cacheExpiry time.Time cacheExpiry time.Time
cacheLength time.Duration cacheLength time.Duration
timeoutHandler co.TimeoutHandler timeoutHandler common.TimeoutHandler
LogRequestBodies bool LogRequestBodies bool
AutoImportUsers bool AutoImportUsers bool
} }
// NewJellyseerr returns an Ombi object. // NewJellyseerr returns an Ombi object.
func NewJellyseerr(server, key string, timeoutHandler co.TimeoutHandler) *Jellyseerr { func NewJellyseerr(server, key string, timeoutHandler common.TimeoutHandler) *Jellyseerr {
if !strings.HasSuffix(server, API_SUFFIX) { if !strings.HasSuffix(server, API_SUFFIX) {
server = server + API_SUFFIX server = server + API_SUFFIX
} }
@ -82,23 +82,25 @@ func (js *Jellyseerr) req(mode string, uri string, data any, queryParams url.Val
} }
} }
resp, err := js.httpClient.Do(req) resp, err := js.httpClient.Do(req)
err = co.GenericErr(resp.StatusCode, err) reqFailed := err != nil || !(resp.StatusCode == 200 || resp.StatusCode == 201)
defer js.timeoutHandler() defer js.timeoutHandler()
var responseText string var responseText string
defer resp.Body.Close() defer resp.Body.Close()
if response || err != nil { if response || reqFailed {
responseText, err = js.decodeResp(resp) responseText, err = js.decodeResp(resp)
if err != nil { if err != nil {
return responseText, resp.StatusCode, err return responseText, resp.StatusCode, err
} }
} }
if err != nil { if reqFailed {
var msg ErrorDTO var msg ErrorDTO
err = json.Unmarshal([]byte(responseText), &msg) err = json.Unmarshal([]byte(responseText), &msg)
if err != nil { if err != nil {
return responseText, resp.StatusCode, err return responseText, resp.StatusCode, err
} }
if msg.Message != "" { if msg.Message == "" {
err = fmt.Errorf("failed (error %d)", resp.StatusCode)
} else {
err = fmt.Errorf("got %d: %s", resp.StatusCode, msg.Message) err = fmt.Errorf("got %d: %s", resp.StatusCode, msg.Message)
} }
return responseText, resp.StatusCode, err return responseText, resp.StatusCode, err
@ -143,11 +145,14 @@ func (js *Jellyseerr) ImportFromJellyfin(jfIDs ...string) ([]User, error) {
params := map[string]interface{}{ params := map[string]interface{}{
"jellyfinUserIds": jfIDs, "jellyfinUserIds": jfIDs,
} }
resp, _, err := js.post(js.server+"/user/import-from-jellyfin", params, true) resp, status, err := js.post(js.server+"/user/import-from-jellyfin", params, true)
var data []User var data []User
if err != nil { if err != nil {
return data, err return data, err
} }
if status != 200 && status != 201 {
return data, fmt.Errorf("failed (error %d)", status)
}
err = json.Unmarshal([]byte(resp), &data) err = json.Unmarshal([]byte(resp), &data)
for _, u := range data { for _, u := range data {
if u.JellyfinUserID != "" { if u.JellyfinUserID != "" {
@ -192,11 +197,15 @@ func (js *Jellyseerr) getUserPage(page int) (GetUsersDTO, error) {
if js.LogRequestBodies { if js.LogRequestBodies {
fmt.Printf("Jellyseerr API Client: Sending with URL params \"%+v\"\n", params) fmt.Printf("Jellyseerr API Client: Sending with URL params \"%+v\"\n", params)
} }
resp, _, err := js.get(js.server+"/user", nil, params) resp, status, err := js.get(js.server+"/user", nil, params)
var data GetUsersDTO var data GetUsersDTO
if err == nil { if status != 200 {
err = json.Unmarshal([]byte(resp), &data) return data, fmt.Errorf("failed (error %d)", status)
} }
if err != nil {
return data, err
}
err = json.Unmarshal([]byte(resp), &data)
return data, err return data, err
} }
@ -252,9 +261,12 @@ func (js *Jellyseerr) getUser(jfID string) (User, error) {
} }
func (js *Jellyseerr) Me() (User, error) { func (js *Jellyseerr) Me() (User, error) {
resp, _, err := js.get(js.server+"/auth/me", nil, url.Values{}) resp, status, err := js.get(js.server+"/auth/me", nil, url.Values{})
var data User var data User
data.ID = -1 data.ID = -1
if status != 200 {
return data, fmt.Errorf("failed (error %d)", status)
}
if err != nil { if err != nil {
return data, err return data, err
} }
@ -269,10 +281,13 @@ func (js *Jellyseerr) GetPermissions(jfID string) (Permissions, error) {
return data.Permissions, err return data.Permissions, err
} }
resp, _, err := js.get(fmt.Sprintf(js.server+"/user/%d/settings/permissions", u.ID), nil, url.Values{}) resp, status, err := js.get(fmt.Sprintf(js.server+"/user/%d/settings/permissions", u.ID), nil, url.Values{})
if err != nil { if err != nil {
return data.Permissions, err return data.Permissions, err
} }
if status != 200 {
return data.Permissions, fmt.Errorf("failed (error %d)", status)
}
err = json.Unmarshal([]byte(resp), &data) err = json.Unmarshal([]byte(resp), &data)
return data.Permissions, err return data.Permissions, err
} }
@ -283,10 +298,13 @@ func (js *Jellyseerr) SetPermissions(jfID string, perm Permissions) error {
return err return err
} }
_, _, err = js.post(fmt.Sprintf(js.server+"/user/%d/settings/permissions", u.ID), permissionsDTO{Permissions: perm}, false) _, status, err := js.post(fmt.Sprintf(js.server+"/user/%d/settings/permissions", u.ID), permissionsDTO{Permissions: perm}, false)
if err != nil { if err != nil {
return err return err
} }
if status != 200 && status != 201 {
return fmt.Errorf("failed (error %d)", status)
}
u.Permissions = perm u.Permissions = perm
js.userCache[jfID] = u js.userCache[jfID] = u
return nil return nil
@ -298,10 +316,13 @@ func (js *Jellyseerr) ApplyTemplateToUser(jfID string, tmpl UserTemplate) error
return err return err
} }
_, _, err = js.put(fmt.Sprintf(js.server+"/user/%d", u.ID), tmpl, false) _, status, err := js.put(fmt.Sprintf(js.server+"/user/%d", u.ID), tmpl, false)
if err != nil { if err != nil {
return err return err
} }
if status != 200 && status != 201 {
return fmt.Errorf("failed (error %d)", status)
}
u.UserTemplate = tmpl u.UserTemplate = tmpl
js.userCache[jfID] = u js.userCache[jfID] = u
return nil return nil
@ -316,10 +337,13 @@ func (js *Jellyseerr) ModifyUser(jfID string, conf map[UserField]any) error {
return err return err
} }
_, _, err = js.put(fmt.Sprintf(js.server+"/user/%d", u.ID), conf, false) _, status, err := js.put(fmt.Sprintf(js.server+"/user/%d", u.ID), conf, false)
if err != nil { if err != nil {
return err return err
} }
if status != 200 && status != 201 {
return fmt.Errorf("failed (error %d)", status)
}
// Lazily just invalidate the cache. // Lazily just invalidate the cache.
js.cacheExpiry = time.Now() js.cacheExpiry = time.Now()
return nil return nil
@ -331,7 +355,10 @@ func (js *Jellyseerr) DeleteUser(jfID string) error {
return err return err
} }
_, err = js.delete(fmt.Sprintf(js.server+"/user/%d", u.ID), nil) status, err := js.delete(fmt.Sprintf(js.server+"/user/%d", u.ID), nil)
if status != 200 && status != 201 {
return fmt.Errorf("failed (error %d)", status)
}
if err != nil { if err != nil {
return err return err
} }
@ -349,10 +376,13 @@ func (js *Jellyseerr) GetNotificationPreferences(jfID string) (Notifications, er
func (js *Jellyseerr) GetNotificationPreferencesByID(jellyseerrID int64) (Notifications, error) { func (js *Jellyseerr) GetNotificationPreferencesByID(jellyseerrID int64) (Notifications, error) {
var data Notifications var data Notifications
resp, _, err := js.get(fmt.Sprintf(js.server+"/user/%d/settings/notifications", jellyseerrID), nil, url.Values{}) resp, status, err := js.get(fmt.Sprintf(js.server+"/user/%d/settings/notifications", jellyseerrID), nil, url.Values{})
if err != nil { if err != nil {
return data, err return data, err
} }
if status != 200 {
return data, fmt.Errorf("failed (error %d)", status)
}
err = json.Unmarshal([]byte(resp), &data) err = json.Unmarshal([]byte(resp), &data)
return data, err return data, err
} }
@ -367,10 +397,13 @@ func (js *Jellyseerr) ApplyNotificationsTemplateToUser(jfID string, tmpl Notific
return err return err
} }
_, _, err = js.post(fmt.Sprintf(js.server+"/user/%d/settings/notifications", u.ID), tmpl, false) _, status, err := js.post(fmt.Sprintf(js.server+"/user/%d/settings/notifications", u.ID), tmpl, false)
if err != nil { if err != nil {
return err return err
} }
if status != 200 && status != 201 {
return fmt.Errorf("failed (error %d)", status)
}
return nil return nil
} }
@ -380,10 +413,13 @@ func (js *Jellyseerr) ModifyNotifications(jfID string, conf map[NotificationsFie
return err return err
} }
_, _, err = js.post(fmt.Sprintf(js.server+"/user/%d/settings/notifications", u.ID), conf, false) _, status, err := js.post(fmt.Sprintf(js.server+"/user/%d/settings/notifications", u.ID), conf, false)
if err != nil { if err != nil {
return err return err
} }
if status != 200 && status != 201 {
return fmt.Errorf("failed (error %d)", status)
}
return nil return nil
} }
@ -393,8 +429,11 @@ func (js *Jellyseerr) GetUsers() (map[string]User, error) {
} }
func (js *Jellyseerr) UserByID(jellyseerrID int64) (User, error) { func (js *Jellyseerr) UserByID(jellyseerrID int64) (User, error) {
resp, _, err := js.get(js.server+fmt.Sprintf("/user/%d", jellyseerrID), nil, url.Values{}) resp, status, err := js.get(js.server+fmt.Sprintf("/user/%d", jellyseerrID), nil, url.Values{})
var data User var data User
if status != 200 {
return data, fmt.Errorf("failed (error %d)", status)
}
if err != nil { if err != nil {
return data, err return data, err
} }
@ -408,10 +447,13 @@ func (js *Jellyseerr) ModifyMainUserSettings(jfID string, conf MainUserSettings)
return err return err
} }
_, _, err = js.post(fmt.Sprintf(js.server+"/user/%d/settings/main", u.ID), conf, false) _, status, err := js.post(fmt.Sprintf(js.server+"/user/%d/settings/main", u.ID), conf, false)
if err != nil { if err != nil {
return err return err
} }
if status != 200 && status != 201 {
return fmt.Errorf("failed (error %d)", status)
}
// Lazily just invalidate the cache. // Lazily just invalidate the cache.
js.cacheExpiry = time.Now() js.cacheExpiry = time.Now()
return nil return nil

View File

@ -1,3 +1,3 @@
module github.com/hrfee/jfa-go/logmessages module github.com/hrfee/logmessages
go 1.22.4 go 1.22.4

View File

@ -45,7 +45,7 @@ const (
UsingTLS = "Using TLS/HTTP2" UsingTLS = "Using TLS/HTTP2"
UsingOmbi = "Starting " + Ombi + " client" UsingOmbi = "Starting " + " + Ombi + " + " client"
UsingJellyseerr = "Starting " + Jellyseerr + " client" UsingJellyseerr = "Starting " + Jellyseerr + " client"
UsingEmby = "Using Emby server type (EXPERIMENTAL: PWRs are not available, and support is limited.)" UsingEmby = "Using Emby server type (EXPERIMENTAL: PWRs are not available, and support is limited.)"
UsingJellyfin = "Using " + Jellyfin + " server type" UsingJellyfin = "Using " + Jellyfin + " server type"
@ -54,13 +54,6 @@ const (
AuthJellyfin = "Authenticated with " + Jellyfin + " @ \"%s\"" AuthJellyfin = "Authenticated with " + Jellyfin + " @ \"%s\""
FailedAuthJellyfin = "Failed to authenticate with " + Jellyfin + " @ \"%s\" (code %d): %v" FailedAuthJellyfin = "Failed to authenticate with " + Jellyfin + " @ \"%s\" (code %d): %v"
FailedAuth = "Failed to authenticate with %s @ \"%s\" (code %d): %v"
Unauthorized = "unauthorized, check credentials"
Forbidden = "forbidden, the user may not have correct permissions"
NotFound = "not found"
TimedOut = "timed out"
FailedGenericWithCode = "failed (code %d)"
InitDiscord = "Initialized Discord daemon" InitDiscord = "Initialized Discord daemon"
FailedInitDiscord = "Failed to initialize Discord daemon: %v" FailedInitDiscord = "Failed to initialize Discord daemon: %v"
@ -220,6 +213,7 @@ const (
FailedGetDiscordChannel = "Failed to get " + Discord + " channel \"%s\": %v" FailedGetDiscordChannel = "Failed to get " + Discord + " channel \"%s\": %v"
MonitorAllDiscordChannels = "Will monitor all " + Discord + " channels" MonitorAllDiscordChannels = "Will monitor all " + Discord + " channels"
FailedCreateDiscordDMChannel = "Failed to create " + Discord + " private DM channel with \"%s\": %v" FailedCreateDiscordDMChannel = "Failed to create " + Discord + " private DM channel with \"%s\": %v"
NotFound = "not found"
RegisterDiscordChoice = "Registered " + Discord + " %s choice \"%s\"" RegisterDiscordChoice = "Registered " + Discord + " %s choice \"%s\""
FailedRegisterDiscordChoices = "Failed to register " + Discord + " %s choices: %v" FailedRegisterDiscordChoices = "Failed to register " + Discord + " %s choices: %v"
FailedDeregDiscordChoice = "Failed to deregister " + Discord + " %s choice \"%s\": %v" FailedDeregDiscordChoice = "Failed to deregister " + Discord + " %s choice \"%s\": %v"

12
main.go
View File

@ -154,8 +154,8 @@ func test(app *appContext) {
for n, v := range settings { for n, v := range settings {
fmt.Println(n, ":", v) fmt.Println(n, ":", v)
} }
users, err := app.jf.GetUsers(false) users, status, err := app.jf.GetUsers(false)
fmt.Printf("GetUsers: err %s maplength %d\n", err, len(users)) fmt.Printf("GetUsers: code %d err %s maplength %d\n", status, err, len(users))
fmt.Printf("View output? [y/n]: ") fmt.Printf("View output? [y/n]: ")
var choice string var choice string
fmt.Scanln(&choice) fmt.Scanln(&choice)
@ -166,8 +166,8 @@ func test(app *appContext) {
fmt.Printf("Enter a user to grab: ") fmt.Printf("Enter a user to grab: ")
var username string var username string
fmt.Scanln(&username) fmt.Scanln(&username)
user, err := app.jf.UserByName(username, false) user, status, err := app.jf.UserByName(username, false)
fmt.Printf("UserByName (%s): code %d err %s", username, err) fmt.Printf("UserByName (%s): code %d err %s", username, status, err)
out, _ := json.MarshalIndent(user, "", " ") out, _ := json.MarshalIndent(user, "", " ")
fmt.Print(string(out)) fmt.Print(string(out))
} }
@ -436,8 +436,8 @@ func start(asDaemon, firstCall bool) {
RetryGap: time.Duration(app.config.Section("advanced").Key("auth_retry_gap").MustInt(10)) * time.Second, RetryGap: time.Duration(app.config.Section("advanced").Key("auth_retry_gap").MustInt(10)) * time.Second,
LogFailures: true, LogFailures: true,
} }
_, err = app.jf.MustAuthenticate(app.config.Section("jellyfin").Key("username").String(), app.config.Section("jellyfin").Key("password").String(), retryOpts) _, status, err = app.jf.MustAuthenticate(app.config.Section("jellyfin").Key("username").String(), app.config.Section("jellyfin").Key("password").String(), retryOpts)
if err != nil { if status != 200 || err != nil {
app.err.Fatalf(lm.FailedAuthJellyfin, server, status, err) app.err.Fatalf(lm.FailedAuthJellyfin, server, status, err)
} }
app.info.Printf(lm.AuthJellyfin, server) app.info.Printf(lm.AuthJellyfin, server)

View File

@ -185,14 +185,14 @@ func linkExistingOmbiDiscordTelegram(app *appContext) error {
idList[user.JellyfinID] = vals idList[user.JellyfinID] = vals
} }
for jfID, ids := range idList { for jfID, ids := range idList {
ombiUser, err := app.getOmbiUser(jfID) ombiUser, status, err := app.getOmbiUser(jfID)
if err != nil { if status != 200 || err != nil {
app.debug.Printf("Failed to get Ombi user with Discord/Telegram \"%s\"/\"%s\": %v", ids[0], ids[1], err) app.debug.Printf("Failed to get Ombi user with Discord/Telegram \"%s\"/\"%s\" (%d): %v", ids[0], ids[1], status, err)
continue continue
} }
_, err = app.ombi.SetNotificationPrefs(ombiUser, ids[0], ids[1]) _, status, err = app.ombi.SetNotificationPrefs(ombiUser, ids[0], ids[1])
if err != nil { if status != 200 || err != nil {
app.debug.Printf("Failed to set prefs for Ombi user \"%s\": %v", ombiUser["userName"].(string), err) app.debug.Printf("Failed to set prefs for Ombi user \"%s\" (%d): %v", ombiUser["userName"].(string), status, err)
continue continue
} }
} }

View File

@ -3,5 +3,3 @@ module github.com/hrfee/jfa-go/ombi
replace github.com/hrfee/jfa-go/common => ../common replace github.com/hrfee/jfa-go/common => ../common
go 1.15 go 1.15
require github.com/hrfee/jfa-go/common v0.0.0-20240805130902-86c37fb4237b // indirect

View File

@ -10,7 +10,7 @@ import (
"strings" "strings"
"time" "time"
co "github.com/hrfee/jfa-go/common" "github.com/hrfee/jfa-go/common"
) )
const ( const (
@ -26,11 +26,11 @@ type Ombi struct {
userCache []map[string]interface{} userCache []map[string]interface{}
cacheExpiry time.Time cacheExpiry time.Time
cacheLength int cacheLength int
timeoutHandler co.TimeoutHandler timeoutHandler common.TimeoutHandler
} }
// NewOmbi returns an Ombi object. // NewOmbi returns an Ombi object.
func NewOmbi(server, key string, timeoutHandler co.TimeoutHandler) *Ombi { func NewOmbi(server, key string, timeoutHandler common.TimeoutHandler) *Ombi {
return &Ombi{ return &Ombi{
server: server, server: server,
key: key, key: key,
@ -133,19 +133,17 @@ func (ombi *Ombi) put(url string, data map[string]interface{}, response bool) (s
} }
// ModifyUser applies the given modified user object to the corresponding user. // ModifyUser applies the given modified user object to the corresponding user.
func (ombi *Ombi) ModifyUser(user map[string]interface{}) (err error) { func (ombi *Ombi) ModifyUser(user map[string]interface{}) (status int, err error) {
if _, ok := user["id"]; !ok { if _, ok := user["id"]; !ok {
err = fmt.Errorf("No ID provided") err = fmt.Errorf("No ID provided")
return return
} }
var status int
_, status, err = ombi.put(ombi.server+"/api/v1/Identity/", user, false) _, status, err = ombi.put(ombi.server+"/api/v1/Identity/", user, false)
err = co.GenericErr(status, err)
return return
} }
// DeleteUser deletes the user corresponding to the given ID. // DeleteUser deletes the user corresponding to the given ID.
func (ombi *Ombi) DeleteUser(id string) (err error) { func (ombi *Ombi) DeleteUser(id string) (code int, err error) {
url := fmt.Sprintf("%s/api/v1/Identity/%s", ombi.server, id) url := fmt.Sprintf("%s/api/v1/Identity/%s", ombi.server, id)
req, _ := http.NewRequest("DELETE", url, nil) req, _ := http.NewRequest("DELETE", url, nil)
req.Header.Add("Content-Type", "application/json") req.Header.Add("Content-Type", "application/json")
@ -154,19 +152,18 @@ func (ombi *Ombi) DeleteUser(id string) (err error) {
} }
resp, err := ombi.httpClient.Do(req) resp, err := ombi.httpClient.Do(req)
defer ombi.timeoutHandler() defer ombi.timeoutHandler()
return co.GenericErr(resp.StatusCode, err) return resp.StatusCode, err
} }
// UserByID returns the user corresponding to the provided ID. // UserByID returns the user corresponding to the provided ID.
func (ombi *Ombi) UserByID(id string) (result map[string]interface{}, err error) { func (ombi *Ombi) UserByID(id string) (result map[string]interface{}, code int, err error) {
resp, code, err := ombi.getJSON(fmt.Sprintf("%s/api/v1/Identity/User/%s", ombi.server, id), nil) resp, code, err := ombi.getJSON(fmt.Sprintf("%s/api/v1/Identity/User/%s", ombi.server, id), nil)
err = co.GenericErr(code, err)
json.Unmarshal([]byte(resp), &result) json.Unmarshal([]byte(resp), &result)
return return
} }
// GetUsers returns all users on the Ombi instance. // GetUsers returns all users on the Ombi instance.
func (ombi *Ombi) GetUsers() ([]map[string]interface{}, error) { func (ombi *Ombi) GetUsers() ([]map[string]interface{}, int, error) {
if time.Now().After(ombi.cacheExpiry) { if time.Now().After(ombi.cacheExpiry) {
resp, code, err := ombi.getJSON(fmt.Sprintf("%s/api/v1/Identity/Users", ombi.server), nil) resp, code, err := ombi.getJSON(fmt.Sprintf("%s/api/v1/Identity/Users", ombi.server), nil)
var result []map[string]interface{} var result []map[string]interface{}
@ -175,10 +172,9 @@ func (ombi *Ombi) GetUsers() ([]map[string]interface{}, error) {
if (code == 200 || code == 204) && err == nil { if (code == 200 || code == 204) && err == nil {
ombi.cacheExpiry = time.Now().Add(time.Minute * time.Duration(ombi.cacheLength)) ombi.cacheExpiry = time.Now().Add(time.Minute * time.Duration(ombi.cacheLength))
} }
err = co.GenericErr(code, err) return result, code, err
return result, err
} }
return ombi.userCache, nil return ombi.userCache, 200, nil
} }
// Strip these from a user when saving as a template. // Strip these from a user when saving as a template.
@ -194,9 +190,9 @@ var stripFromOmbi = []string{
} }
// TemplateByID returns a template based on the user corresponding to the provided ID's settings. // TemplateByID returns a template based on the user corresponding to the provided ID's settings.
func (ombi *Ombi) TemplateByID(id string) (result map[string]interface{}, err error) { func (ombi *Ombi) TemplateByID(id string) (result map[string]interface{}, code int, err error) {
result, err = ombi.UserByID(id) result, code, err = ombi.UserByID(id)
if err != nil { if err != nil || code != 200 {
return return
} }
for _, key := range stripFromOmbi { for _, key := range stripFromOmbi {
@ -213,25 +209,24 @@ func (ombi *Ombi) TemplateByID(id string) (result map[string]interface{}, err er
} }
// NewUser creates a new user with the given username, password and email address. // NewUser creates a new user with the given username, password and email address.
func (ombi *Ombi) NewUser(username, password, email string, template map[string]interface{}) ([]string, error) { func (ombi *Ombi) NewUser(username, password, email string, template map[string]interface{}) ([]string, int, error) {
url := fmt.Sprintf("%s/api/v1/Identity", ombi.server) url := fmt.Sprintf("%s/api/v1/Identity", ombi.server)
user := template user := template
user["userName"] = username user["userName"] = username
user["password"] = password user["password"] = password
user["emailAddress"] = email user["emailAddress"] = email
resp, code, err := ombi.post(url, user, true) resp, code, err := ombi.post(url, user, true)
err = co.GenericErr(code, err)
var data map[string]interface{} var data map[string]interface{}
json.Unmarshal([]byte(resp), &data) json.Unmarshal([]byte(resp), &data)
if err != nil { if err != nil || code != 200 {
var lst []string var lst []string
if data["errors"] != nil { if data["errors"] != nil {
lst = data["errors"].([]string) lst = data["errors"].([]string)
} }
return lst, err return lst, code, err
} }
ombi.cacheExpiry = time.Now() ombi.cacheExpiry = time.Now()
return nil, err return nil, code, err
} }
type NotificationPref struct { type NotificationPref struct {
@ -241,7 +236,7 @@ type NotificationPref struct {
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
} }
func (ombi *Ombi) SetNotificationPrefs(user map[string]interface{}, discordID, telegramUser string) (result string, err error) { func (ombi *Ombi) SetNotificationPrefs(user map[string]interface{}, discordID, telegramUser string) (result string, code int, err error) {
id := user["id"].(string) id := user["id"].(string)
url := fmt.Sprintf("%s/api/v1/Identity/NotificationPreferences", ombi.server) url := fmt.Sprintf("%s/api/v1/Identity/NotificationPreferences", ombi.server)
data := []NotificationPref{} data := []NotificationPref{}
@ -251,8 +246,6 @@ func (ombi *Ombi) SetNotificationPrefs(user map[string]interface{}, discordID, t
if telegramUser != "" { if telegramUser != "" {
data = append(data, NotificationPref{NotifAgentTelegram, id, telegramUser, true}) data = append(data, NotificationPref{NotifAgentTelegram, id, telegramUser, true})
} }
var code int
result, code, err = ombi.send("POST", url, data, true, map[string]string{"UserName": user["userName"].(string)}) result, code, err = ombi.send("POST", url, data, true, map[string]string{"UserName": user["userName"].(string)})
err = co.GenericErr(code, err)
return return
} }

View File

@ -14,8 +14,8 @@ import (
// GenInternalReset generates a local password reset PIN, for use with the PWR option on the Admin page. // GenInternalReset generates a local password reset PIN, for use with the PWR option on the Admin page.
func (app *appContext) GenInternalReset(userID string) (InternalPWR, error) { func (app *appContext) GenInternalReset(userID string) (InternalPWR, error) {
pin := genAuthToken() pin := genAuthToken()
user, err := app.jf.UserByID(userID, false) user, status, err := app.jf.UserByID(userID, false)
if err != nil { if err != nil || status != 200 {
return InternalPWR{}, err return InternalPWR{}, err
} }
pwr := InternalPWR{ pwr := InternalPWR{
@ -95,8 +95,8 @@ func pwrMonitor(app *appContext, watcher *fsnotify.Watcher) {
} }
app.info.Printf("New password reset for user \"%s\"", pwr.Username) app.info.Printf("New password reset for user \"%s\"", pwr.Username)
if currentTime := time.Now(); pwr.Expiry.After(currentTime) { if currentTime := time.Now(); pwr.Expiry.After(currentTime) {
user, err := app.jf.UserByName(pwr.Username, false) user, status, err := app.jf.UserByName(pwr.Username, false)
if err != nil || user.ID == "" { if !(status == 200 || status == 204) || err != nil || user.ID == "" {
app.err.Printf(lm.FailedGetUser, pwr.Username, lm.Jellyfin, err) app.err.Printf(lm.FailedGetUser, pwr.Username, lm.Jellyfin, err)
return return
} }

View File

@ -91,22 +91,19 @@ func (app *appContext) TestJF(gc *gin.Context) {
tempjf.SetTransport(transport) tempjf.SetTransport(transport)
} }
user, err := tempjf.Authenticate(req.Username, req.Password) user, status, err := tempjf.Authenticate(req.Username, req.Password)
if err != nil { if !(status == 200 || status == 204) || err != nil {
msg := "" msg := ""
status := 500 switch status {
switch err.(type) { case 0:
case mediabrowser.ErrUnauthorized:
msg = "errorInvalidUserPass"
status = 401
case mediabrowser.ErrForbidden:
msg = "errorUserDisabled"
status = 403
case mediabrowser.ErrNotFound:
msg = "error404"
status = 404
default:
msg = "errorConnectionRefused" msg = "errorConnectionRefused"
status = 500
case 401:
msg = "errorInvalidUserPass"
case 403:
msg = "errorUserDisabled"
case 404:
msg = "error404"
} }
app.err.Printf(lm.FailedAuthJellyfin, req.Server, status, err) app.err.Printf(lm.FailedAuthJellyfin, req.Server, status, err)
if msg != "" { if msg != "" {

View File

@ -52,7 +52,7 @@ sudo apt-get install jfa-go-tray
</div> </div>
</div> </div>
</div> </div>
<div class="page-container m-2 lg:my-20 lg:mx-72" id="page-container"> <div class="page-container" id="page-container">
<div class="card ~neutral @low mb-1"> <div class="card ~neutral @low mb-1">
<div class="row col flex center"> <div class="row col flex center">
<span class="heading welcome">jellyfin-accounts (go)</span> <span class="heading welcome">jellyfin-accounts (go)</span>

View File

@ -627,8 +627,6 @@ export class settingsList {
private _saveNoRestart = document.getElementById("settings-apply-no-restart") as HTMLSpanElement; private _saveNoRestart = document.getElementById("settings-apply-no-restart") as HTMLSpanElement;
private _saveRestart = document.getElementById("settings-apply-restart") as HTMLSpanElement; private _saveRestart = document.getElementById("settings-apply-restart") as HTMLSpanElement;
private _loader = document.getElementById("settings-loader") as HTMLDivElement;
private _panel = document.getElementById("settings-panel") as HTMLDivElement; private _panel = document.getElementById("settings-panel") as HTMLDivElement;
private _sidebar = document.getElementById("settings-sidebar") as HTMLDivElement; private _sidebar = document.getElementById("settings-sidebar") as HTMLDivElement;
private _visibleSection: string; private _visibleSection: string;
@ -652,7 +650,7 @@ export class settingsList {
this._sections[name] = section; this._sections[name] = section;
this._panel.appendChild(this._sections[name].asElement()); this._panel.appendChild(this._sections[name].asElement());
const button = document.createElement("span") as HTMLSpanElement; const button = document.createElement("span") as HTMLSpanElement;
button.classList.add("button", "~neutral", "@low", "settings-section-button", "justify-between"); button.classList.add("button", "~neutral", "@low", "settings-section-button", "justify-between", "mb-2");
button.textContent = s.meta.name; button.textContent = s.meta.name;
if (subButton) { button.appendChild(subButton); } if (subButton) { button.appendChild(subButton); }
button.onclick = () => { this._showPanel(name); }; button.onclick = () => { this._showPanel(name); };
@ -907,73 +905,63 @@ export class settingsList {
window.modals.matrix.show(); window.modals.matrix.show();
} }
reload = () => { reload = () => _get("/config", null, (req: XMLHttpRequest) => {
for (let i = 0; i < this._loader.children.length; i++) { if (req.readyState == 4) {
this._loader.children[i].classList.add("invisible"); if (req.status != 200) {
} window.notifications.customError("settingsLoadError", window.lang.notif("errorLoadSettings"));
addLoader(this._loader, false, true); return;
_get("/config", null, (req: XMLHttpRequest) => { }
if (req.readyState == 4) { this._settings = req.response as Settings;
if (req.status != 200) { for (let name of this._settings.order) {
window.notifications.customError("settingsLoadError", window.lang.notif("errorLoadSettings")); if (name in this._sections) {
return; this._sections[name].update(this._settings.sections[name]);
} } else {
this._settings = req.response as Settings; if (name == "messages" || name == "user_page") {
for (let name of this._settings.order) { const editButton = document.createElement("div");
if (name in this._sections) { editButton.classList.add("tooltip", "left");
this._sections[name].update(this._settings.sections[name]); editButton.innerHTML = `
} else { <span class="button ~neutral @low">
if (name == "messages" || name == "user_page") { <i class="icon ri-edit-line"></i>
const editButton = document.createElement("div"); </span>
editButton.classList.add("tooltip", "left"); <span class="content sm">
editButton.innerHTML = ` ${window.lang.get("strings", "customizeMessages")}
<span class="button ~neutral @low"> </span>
<i class="icon ri-edit-line"></i> `;
</span> (editButton.querySelector("span.button") as HTMLSpanElement).onclick = () => {
<span class="content sm"> this._messageEditor.showList(name == "messages" ? "email" : "user");
${window.lang.get("strings", "customizeMessages")} };
</span> this.addSection(name, this._settings.sections[name], editButton);
`; } else if (name == "updates") {
(editButton.querySelector("span.button") as HTMLSpanElement).onclick = () => { const icon = document.createElement("span") as HTMLSpanElement;
this._messageEditor.showList(name == "messages" ? "email" : "user"); if (window.updater.updateAvailable) {
}; icon.classList.add("button", "~urge");
this.addSection(name, this._settings.sections[name], editButton); icon.innerHTML = `<i class="ri-download-line" title="${window.lang.strings("update")}"></i>`;
} else if (name == "updates") { icon.onclick = () => window.updater.checkForUpdates(window.modals.updateInfo.show);
const icon = document.createElement("span") as HTMLSpanElement;
if (window.updater.updateAvailable) {
icon.classList.add("button", "~urge");
icon.innerHTML = `<i class="ri-download-line" title="${window.lang.strings("update")}"></i>`;
icon.onclick = () => window.updater.checkForUpdates(window.modals.updateInfo.show);
}
this.addSection(name, this._settings.sections[name], icon);
} else if (name == "matrix" && !window.matrixEnabled) {
const addButton = document.createElement("div");
addButton.classList.add("tooltip", "left");
addButton.innerHTML = `
<span class="button ~neutral @low">+</span>
<span class="content sm">
${window.lang.strings("linkMatrix")}
</span>
`;
(addButton.querySelector("span.button") as HTMLSpanElement).onclick = this._addMatrix;
this.addSection(name, this._settings.sections[name], addButton);
} else {
this.addSection(name, this._settings.sections[name]);
} }
this.addSection(name, this._settings.sections[name], icon);
} else if (name == "matrix" && !window.matrixEnabled) {
const addButton = document.createElement("div");
addButton.classList.add("tooltip", "left");
addButton.innerHTML = `
<span class="button ~neutral @low">+</span>
<span class="content sm">
${window.lang.strings("linkMatrix")}
</span>
`;
(addButton.querySelector("span.button") as HTMLSpanElement).onclick = this._addMatrix;
this.addSection(name, this._settings.sections[name], addButton);
} else {
this.addSection(name, this._settings.sections[name]);
} }
} }
removeLoader(this._loader);
for (let i = 0; i < this._loader.children.length; i++) {
this._loader.children[i].classList.remove("invisible");
}
this._showPanel(this._settings.order[0]);
document.dispatchEvent(new CustomEvent("settings-loaded"));
document.dispatchEvent(new CustomEvent("settings-advancedState", { detail: false }));
this._saveButton.classList.add("unfocused");
this._needsRestart = false;
} }
}) this._showPanel(this._settings.order[0]);
}; document.dispatchEvent(new CustomEvent("settings-loaded"));
document.dispatchEvent(new CustomEvent("settings-advancedState", { detail: false }));
this._saveButton.classList.add("unfocused");
this._needsRestart = false;
}
})
// FIXME: Search "About" & "User profiles", pseudo-search "User profiles" for things like "Ombi", "Referrals", etc. // FIXME: Search "About" & "User profiles", pseudo-search "User profiles" for things like "Ombi", "Referrals", etc.
search = (query: string) => { search = (query: string) => {

View File

@ -24,8 +24,8 @@ func (app *appContext) checkUsers() {
} }
app.info.Println(lm.CheckUserExpiries) app.info.Println(lm.CheckUserExpiries)
users, err := app.jf.GetUsers(false) users, status, err := app.jf.GetUsers(false)
if err != nil { if err != nil || status != 200 {
app.err.Printf(lm.FailedGetUsers, lm.Jellyfin, err) app.err.Printf(lm.FailedGetUsers, lm.Jellyfin, err)
return return
} }
@ -89,7 +89,7 @@ func (app *appContext) checkUsers() {
err, _, _ = app.SetUserDisabled(user, true) err, _, _ = app.SetUserDisabled(user, true)
activity.Type = ActivityDisabled activity.Type = ActivityDisabled
} }
if err != nil { if !(status == 200 || status == 204) || err != nil {
app.err.Printf(lm.FailedDeleteOrDisableExpiredUser, user.ID, err) app.err.Printf(lm.FailedDeleteOrDisableExpiredUser, user.ID, err)
continue continue
} }

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"fmt"
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -67,7 +68,7 @@ func (app *appContext) NewUserPostVerification(p NewUserParams) (out NewUserData
} }
} }
existingUser, _ := app.jf.UserByName(p.Req.Username, false) existingUser, _, _ := app.jf.UserByName(p.Req.Username, false)
if existingUser.Name != "" { if existingUser.Name != "" {
out.Message = lm.UserExists out.Message = lm.UserExists
deferLogInfo(lm.FailedCreateUser, lm.Jellyfin, p.Req.Username, out.Message) deferLogInfo(lm.FailedCreateUser, lm.Jellyfin, p.Req.Username, out.Message)
@ -75,9 +76,10 @@ func (app *appContext) NewUserPostVerification(p NewUserParams) (out NewUserData
return return
} }
var status int
var err error var err error
out.User, err = app.jf.NewUser(p.Req.Username, p.Req.Password) out.User, status, err = app.jf.NewUser(p.Req.Username, p.Req.Password)
if err != nil { if !(status == 200 || status == 204) || err != nil {
out.Message = err.Error() out.Message = err.Error()
deferLogError(lm.FailedCreateUser, lm.Jellyfin, p.Req.Username, out.Message) deferLogError(lm.FailedCreateUser, lm.Jellyfin, p.Req.Username, out.Message)
out.Status = 401 out.Status = 401
@ -98,15 +100,15 @@ func (app *appContext) NewUserPostVerification(p NewUserParams) (out NewUserData
}, p.ContextForIPLogging, (p.SourceType != ActivityAdmin)) }, p.ContextForIPLogging, (p.SourceType != ActivityAdmin))
if p.Profile != nil { if p.Profile != nil {
err = app.jf.SetPolicy(out.User.ID, p.Profile.Policy) status, err = app.jf.SetPolicy(out.User.ID, p.Profile.Policy)
if err != nil { if !((status == 200 || status == 204) && err == nil) {
app.err.Printf(lm.FailedApplyTemplate, "policy", lm.Jellyfin, out.User.ID, err) app.err.Printf(lm.FailedApplyTemplate, "policy", lm.Jellyfin, out.User.ID, err)
} }
err = app.jf.SetConfiguration(out.User.ID, p.Profile.Configuration) status, err = app.jf.SetConfiguration(out.User.ID, p.Profile.Configuration)
if err == nil { if (status == 200 || status == 204) && err == nil {
err = app.jf.SetDisplayPreferences(out.User.ID, p.Profile.Displayprefs) status, err = app.jf.SetDisplayPreferences(out.User.ID, p.Profile.Displayprefs)
} }
if err != nil { if !((status == 200 || status == 204) && err == nil) {
app.err.Printf(lm.FailedApplyTemplate, "configuration", lm.Jellyfin, out.User.ID, err) app.err.Printf(lm.FailedApplyTemplate, "configuration", lm.Jellyfin, out.User.ID, err)
} }
@ -162,7 +164,11 @@ func (app *appContext) SetUserDisabled(user mediabrowser.User, disabled bool) (e
change = user.Policy.IsDisabled != disabled change = user.Policy.IsDisabled != disabled
user.Policy.IsDisabled = disabled user.Policy.IsDisabled = disabled
err = app.jf.SetPolicy(user.ID, user.Policy) var status int
status, err = app.jf.SetPolicy(user.ID, user.Policy)
if !(status == 200 || status == 204) && err == nil {
err = fmt.Errorf("failed (code %d)", status)
}
if err != nil { if err != nil {
return return
} }
@ -179,12 +185,16 @@ func (app *appContext) SetUserDisabled(user mediabrowser.User, disabled bool) (e
} }
func (app *appContext) DeleteUser(user mediabrowser.User) (err error, deleted bool) { func (app *appContext) DeleteUser(user mediabrowser.User) (err error, deleted bool) {
var status int
if app.ombi != nil { if app.ombi != nil {
var tpUser map[string]any var tpUser map[string]any
tpUser, err = app.getOmbiUser(user.ID) tpUser, status, err = app.getOmbiUser(user.ID)
if err == nil { if status == 200 && err == nil {
if id, ok := tpUser["id"]; ok { if id, ok := tpUser["id"]; ok {
err = app.ombi.DeleteUser(id.(string)) status, err = app.ombi.DeleteUser(id.(string))
if status != 200 && err == nil {
err = fmt.Errorf("failed (code %d)", status)
}
if err != nil { if err != nil {
app.err.Printf(lm.FailedDeleteUser, lm.Ombi, user.ID, err) app.err.Printf(lm.FailedDeleteUser, lm.Ombi, user.ID, err)
} }
@ -201,7 +211,10 @@ func (app *appContext) DeleteUser(user mediabrowser.User) (err error, deleted bo
} }
} }
err = app.jf.DeleteUser(user.ID) status, err = app.jf.DeleteUser(user.ID)
if status != 200 && status != 204 && err == nil {
err = fmt.Errorf("failed (code %d)", status)
}
if err != nil { if err != nil {
return return
} }

View File

@ -17,7 +17,6 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt" "github.com/golang-jwt/jwt"
"github.com/gomarkdown/markdown" "github.com/gomarkdown/markdown"
"github.com/hrfee/jfa-go/common"
lm "github.com/hrfee/jfa-go/logmessages" lm "github.com/hrfee/jfa-go/logmessages"
"github.com/hrfee/mediabrowser" "github.com/hrfee/mediabrowser"
"github.com/lithammer/shortuuid/v3" "github.com/lithammer/shortuuid/v3"
@ -334,25 +333,26 @@ func (app *appContext) ResetPassword(gc *gin.Context) {
// data["pin"] = pin // data["pin"] = pin
// } // }
var resp mediabrowser.PasswordResetResponse var resp mediabrowser.PasswordResetResponse
var status int
var err error var err error
var username string var username string
if !isInternal && !setPassword { if !isInternal && !setPassword {
resp, err = app.jf.ResetPassword(pin) resp, status, err = app.jf.ResetPassword(pin)
} else if time.Now().After(pwr.Expiry) { } else if time.Now().After(pwr.Expiry) {
app.debug.Printf(lm.FailedChangePassword, lm.Jellyfin, "?", fmt.Sprintf(lm.ExpiredPIN, pin)) app.debug.Printf(lm.FailedChangePassword, lm.Jellyfin, "?", fmt.Sprintf(lm.ExpiredPIN, pin))
app.NoRouteHandler(gc) app.NoRouteHandler(gc)
return return
} else { } else {
err = app.jf.ResetPasswordAdmin(pwr.ID) status, err = app.jf.ResetPasswordAdmin(pwr.ID)
if err != nil { if !(status == 200 || status == 204) || err != nil {
app.err.Printf(lm.FailedChangePassword, lm.Jellyfin, "?", err) app.err.Printf(lm.FailedChangePassword, lm.Jellyfin, "?", err)
} else { } else {
err = app.jf.SetPassword(pwr.ID, "", pin) status, err = app.jf.SetPassword(pwr.ID, "", pin)
} }
username = pwr.Username username = pwr.Username
} }
if err == nil && (isInternal || resp.Success) { if (status == 200 || status == 204) && err == nil && (isInternal || resp.Success) {
data["success"] = true data["success"] = true
data["pin"] = pin data["pin"] = pin
if !isInternal { if !isInternal {
@ -364,8 +364,8 @@ func (app *appContext) ResetPassword(gc *gin.Context) {
// Only log PWRs we know the user for. // Only log PWRs we know the user for.
if username != "" { if username != "" {
jfUser, err := app.jf.UserByName(username, false) jfUser, status, err := app.jf.UserByName(username, false)
if err == nil { if err == nil && status == 200 {
app.storage.SetActivityKey(shortuuid.New(), Activity{ app.storage.SetActivityKey(shortuuid.New(), Activity{
Type: ActivityResetPassword, Type: ActivityResetPassword,
UserID: jfUser.ID, UserID: jfUser.ID,
@ -377,19 +377,19 @@ func (app *appContext) ResetPassword(gc *gin.Context) {
} }
if app.config.Section("ombi").Key("enabled").MustBool(false) { if app.config.Section("ombi").Key("enabled").MustBool(false) {
jfUser, err := app.jf.UserByName(username, false) jfUser, status, err := app.jf.UserByName(username, false)
if err != nil { if status != 200 || err != nil {
app.err.Printf(lm.FailedGetUser, username, lm.Jellyfin, err) app.err.Printf(lm.FailedGetUser, username, lm.Jellyfin, err)
return return
} }
ombiUser, err := app.getOmbiUser(jfUser.ID) ombiUser, status, err := app.getOmbiUser(jfUser.ID)
if err != nil { if status != 200 || err != nil {
app.err.Printf(lm.FailedGetUser, username, lm.Ombi, err) app.err.Printf(lm.FailedGetUser, username, lm.Ombi, err)
return return
} }
ombiUser["password"] = pin ombiUser["password"] = pin
err = app.ombi.ModifyUser(ombiUser) status, err = app.ombi.ModifyUser(ombiUser)
if err != nil { if status != 200 || err != nil {
app.err.Printf(lm.FailedChangePassword, lm.Ombi, ombiUser["userName"], err) app.err.Printf(lm.FailedChangePassword, lm.Ombi, ombiUser["userName"], err)
return return
} }
@ -539,7 +539,9 @@ func (app *appContext) verifyCaptcha(code, id, text string, isPWR bool) bool {
req.Header.Add("Content-Type", "application/x-www-form-urlencoded") req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req) resp, err := http.DefaultClient.Do(req)
err = common.GenericErr(resp.StatusCode, err) if err == nil && resp.StatusCode != 200 {
err = fmt.Errorf("failed (error %d)", resp.StatusCode)
}
if err != nil { if err != nil {
app.err.Printf(lm.FailedVerifyReCAPTCHA, err) app.err.Printf(lm.FailedVerifyReCAPTCHA, err)
return false return false
@ -747,8 +749,8 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
fromUser := "" fromUser := ""
if invite.ReferrerJellyfinID != "" { if invite.ReferrerJellyfinID != "" {
sender, err := app.jf.UserByID(invite.ReferrerJellyfinID, false) sender, status, err := app.jf.UserByID(invite.ReferrerJellyfinID, false)
if err == nil { if status == 200 && err == nil {
fromUser = sender.Name fromUser = sender.Name
} }
} }