1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-10-18 17:10:11 +00:00

Compare commits

...

15 Commits

Author SHA1 Message Date
3e73d16cce
merge language changes 2021-04-06 21:30:14 +01:00
3f8414c70a
use unix timestamp for inv created & usedBy
usedBy is still stored as a string in invites.json to cope with existing
invites with times stored formatted. knz/strtime requires cgo for
strptime, so it has been replaced with the native itchyny/timefmt-go.
2021-04-06 21:25:44 +01:00
6ec2186bdf
switch accounts tab to unix times
should now respect the client's locale.
2021-04-06 20:53:30 +01:00
ClankJake
6dd575b276 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (44 of 44 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/pt_BR/
2021-04-06 19:49:18 +02:00
JoshiJoshiJoshi
1a98946d71 Translated using Weblate (German)
Currently translated at 100.0% (100 of 100 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/setup/de/
2021-04-06 19:49:18 +02:00
ClankJake
8922549bdb Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (11 of 11 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/common-strings/pt_BR/
2021-04-06 19:49:18 +02:00
Richard de Boer
173b49aeb7 Translated using Weblate (Dutch)
Currently translated at 100.0% (11 of 11 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/common-strings/nl/
2021-04-06 19:49:18 +02:00
JoshiJoshiJoshi
eee6046465 Translated using Weblate (German)
Currently translated at 100.0% (11 of 11 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/common-strings/de/
2021-04-06 19:49:18 +02:00
JoshiJoshiJoshi
b76011be4f translation from Weblate (German)
Currently translated at 100.0% (28 of 28 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/form/de/
2021-04-06 19:49:18 +02:00
Richard de Boer
3d93d79b0b Translated using Weblate (Dutch)
Currently translated at 100.0% (44 of 44 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/nl/
2021-04-06 19:49:18 +02:00
7dcc9b20a1
clear user cache when user expires 2021-04-06 18:39:12 +01:00
754b956206
remove extra logs 2021-04-06 18:32:32 +01:00
47ac505cac
shutdown your background workers!
I believe everything #74 was caused by not shutting down the userDaemon
when we do a pseudo-restart. shutdown of it and the invite daemon are
now deferred so this should fix any problems and reduce log spam.
2021-04-06 18:12:06 +01:00
e6e5231f63
add extra logging 2021-04-06 18:02:15 +01:00
78049d4a33
hyphenate/dehyphenate users.json if necessary
doubt this would have caused problems anyway but why not.
2021-04-06 15:46:28 +01:00
20 changed files with 223 additions and 115 deletions

34
api.go
View File

@ -11,7 +11,7 @@ import (
"github.com/dgrijalva/jwt-go" "github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/hrfee/mediabrowser" "github.com/hrfee/mediabrowser"
"github.com/knz/strtime" "github.com/itchyny/timefmt-go"
"github.com/lithammer/shortuuid/v3" "github.com/lithammer/shortuuid/v3"
"gopkg.in/ini.v1" "gopkg.in/ini.v1"
) )
@ -48,8 +48,8 @@ func (app *appContext) loadStrftime() {
} }
func (app *appContext) prettyTime(dt time.Time) (date, time string) { func (app *appContext) prettyTime(dt time.Time) (date, time string) {
date, _ = strtime.Strftime(dt, app.datePattern) date = timefmt.Format(dt, app.datePattern)
time, _ = strtime.Strftime(dt, app.timePattern) time = timefmt.Format(dt, app.timePattern)
return return
} }
@ -189,7 +189,7 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool
// 0 means infinite i guess? // 0 means infinite i guess?
newInv.RemainingUses-- newInv.RemainingUses--
} }
newInv.UsedBy = append(newInv.UsedBy, []string{username, app.formatDatetime(currentTime)}) newInv.UsedBy = append(newInv.UsedBy, []string{username, strconv.FormatInt(currentTime.Unix(), 10)})
if !del { if !del {
app.storage.invites[code] = newInv app.storage.invites[code] = newInv
} }
@ -870,13 +870,25 @@ func (app *appContext) GetInvites(gc *gin.Context) {
UserDays: inv.UserDays, UserDays: inv.UserDays,
UserHours: inv.UserHours, UserHours: inv.UserHours,
UserMinutes: inv.UserMinutes, UserMinutes: inv.UserMinutes,
Created: app.formatDatetime(inv.Created), Created: inv.Created.Unix(),
Profile: inv.Profile, Profile: inv.Profile,
NoLimit: inv.NoLimit, NoLimit: inv.NoLimit,
Label: inv.Label, Label: inv.Label,
} }
if len(inv.UsedBy) != 0 { if len(inv.UsedBy) != 0 {
invite.UsedBy = inv.UsedBy invite.UsedBy = map[string]int64{}
for _, pair := range inv.UsedBy {
// These used to be stored formatted instead of as a unix timestamp.
unix, err := strconv.ParseInt(pair[1], 10, 64)
if err != nil {
date, err := timefmt.Parse(pair[1], app.datePattern+" "+app.timePattern)
if err != nil {
app.err.Printf("Failed to parse usedBy time: %v", err)
}
unix = date.Unix()
}
invite.UsedBy[pair[0]] = unix
}
} }
invite.RemainingUses = 1 invite.RemainingUses = 1
if inv.RemainingUses != 0 { if inv.RemainingUses != 0 {
@ -1032,6 +1044,8 @@ func (app *appContext) GetUsers(gc *gin.Context) {
return return
} }
i := 0 i := 0
app.storage.usersLock.Lock()
defer app.storage.usersLock.Unlock()
for _, jfUser := range users { for _, jfUser := range users {
user := respUser{ user := respUser{
ID: jfUser.ID, ID: jfUser.ID,
@ -1039,15 +1053,15 @@ func (app *appContext) GetUsers(gc *gin.Context) {
Admin: jfUser.Policy.IsAdministrator, Admin: jfUser.Policy.IsAdministrator,
Disabled: jfUser.Policy.IsDisabled, Disabled: jfUser.Policy.IsDisabled,
} }
user.LastActive = "n/a"
if !jfUser.LastActivityDate.IsZero() { if !jfUser.LastActivityDate.IsZero() {
user.LastActive = app.formatDatetime(jfUser.LastActivityDate.Time) user.LastActive = jfUser.LastActivityDate.Unix()
} }
if email, ok := app.storage.emails[jfUser.ID]; ok { if email, ok := app.storage.emails[jfUser.ID]; ok {
user.Email = email.(string) user.Email = email.(string)
} }
if expiry, ok := app.storage.users[jfUser.ID]; ok { expiry, ok := app.storage.users[jfUser.ID]
user.Expiry = app.formatDatetime(expiry) if ok {
user.Expiry = expiry.Unix()
} }
resp.UserList[i] = user resp.UserList[i] = user

View File

@ -19,8 +19,8 @@ import (
"github.com/gomarkdown/markdown" "github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/html" "github.com/gomarkdown/markdown/html"
"github.com/itchyny/timefmt-go"
jEmail "github.com/jordan-wright/email" jEmail "github.com/jordan-wright/email"
"github.com/knz/strtime"
"github.com/mailgun/mailgun-go/v4" "github.com/mailgun/mailgun-go/v4"
) )
@ -101,8 +101,8 @@ type Email struct {
} }
func (emailer *Emailer) formatExpiry(expiry time.Time, tzaware bool, datePattern, timePattern string) (d, t, expiresIn string) { func (emailer *Emailer) formatExpiry(expiry time.Time, tzaware bool, datePattern, timePattern string) (d, t, expiresIn string) {
d, _ = strtime.Strftime(expiry, datePattern) d = timefmt.Format(expiry, datePattern)
t, _ = strtime.Strftime(expiry, timePattern) t = timefmt.Format(expiry, timePattern)
currentTime := time.Now() currentTime := time.Now()
if tzaware { if tzaware {
currentTime = currentTime.UTC() currentTime = currentTime.UTC()

2
go.mod
View File

@ -26,8 +26,8 @@ require (
github.com/hrfee/jfa-go/docs v0.0.0-20201112212552-b6f3cd7c1f71 github.com/hrfee/jfa-go/docs v0.0.0-20201112212552-b6f3cd7c1f71
github.com/hrfee/jfa-go/ombi v0.0.0-20201112212552-b6f3cd7c1f71 github.com/hrfee/jfa-go/ombi v0.0.0-20201112212552-b6f3cd7c1f71
github.com/hrfee/mediabrowser v0.3.3 github.com/hrfee/mediabrowser v0.3.3
github.com/itchyny/timefmt-go v0.1.2
github.com/jordan-wright/email v4.0.1-0.20200917010138-e1c00e156980+incompatible github.com/jordan-wright/email v4.0.1-0.20200917010138-e1c00e156980+incompatible
github.com/knz/strtime v0.0.0-20200924090105-187c67f2bf5e
github.com/lithammer/shortuuid/v3 v3.0.4 github.com/lithammer/shortuuid/v3 v3.0.4
github.com/mailgun/mailgun-go/v4 v4.3.0 github.com/mailgun/mailgun-go/v4 v4.3.0
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect

4
go.sum
View File

@ -129,6 +129,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGa
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/hrfee/mediabrowser v0.3.3 h1:7E05uiol8hh2ytKn3WVLrUIvHAyifYEIy3Y5qtuNh8I= github.com/hrfee/mediabrowser v0.3.3 h1:7E05uiol8hh2ytKn3WVLrUIvHAyifYEIy3Y5qtuNh8I=
github.com/hrfee/mediabrowser v0.3.3/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U= github.com/hrfee/mediabrowser v0.3.3/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U=
github.com/itchyny/timefmt-go v0.1.2 h1:q0Xa4P5it6K6D7ISsbLAMwx1PnWlixDcJL6/sFs93Hs=
github.com/itchyny/timefmt-go v0.1.2/go.mod h1:0osSSCQSASBJMsIZnhAaF1C2fCBTJZXrnj37mG8/c+A=
github.com/jordan-wright/email v4.0.1-0.20200917010138-e1c00e156980+incompatible h1:CL0ooBNfbNyJTJATno+m0h+zM5bW6v7fKlboKUGP/dI= github.com/jordan-wright/email v4.0.1-0.20200917010138-e1c00e156980+incompatible h1:CL0ooBNfbNyJTJATno+m0h+zM5bW6v7fKlboKUGP/dI=
github.com/jordan-wright/email v4.0.1-0.20200917010138-e1c00e156980+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A= github.com/jordan-wright/email v4.0.1-0.20200917010138-e1c00e156980+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
@ -141,8 +143,6 @@ github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/knz/strtime v0.0.0-20200924090105-187c67f2bf5e h1:ViPE0JEOvtw5I0EGUiFSr2VNKGNU+3oBT+oHbDXHbxk=
github.com/knz/strtime v0.0.0-20200924090105-187c67f2bf5e/go.mod h1:4ZxfWkxwtc7dBeifERVVWRy9F9rTU9p0yCDgeCtlius=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=

View File

@ -8,9 +8,10 @@
"password": "Passwort", "password": "Passwort",
"emailAddress": "E-Mail-Adresse", "emailAddress": "E-Mail-Adresse",
"submit": "Absenden", "submit": "Absenden",
"success": "Erfolg", "success": "Erfolgreich",
"error": "Fehler", "error": "Fehler",
"copy": "Kopieren", "copy": "Kopieren",
"theme": "Thema" "theme": "Thema",
"copied": "Kopiert"
} }
} }

View File

@ -11,6 +11,7 @@
"success": "Success", "success": "Success",
"error": "Fout", "error": "Fout",
"copy": "Kopiëer", "copy": "Kopiëer",
"theme": "Thema" "theme": "Thema",
"copied": "Gekopieerd"
} }
} }

View File

@ -11,6 +11,7 @@
"success": "Sucesso", "success": "Sucesso",
"error": "Erro", "error": "Erro",
"copy": "Copiar", "copy": "Copiar",
"theme": "Tema" "theme": "Tema",
"copied": "Copiado"
} }
} }

View File

@ -23,10 +23,11 @@
"passwordReset": { "passwordReset": {
"title": "Wachtwoordreset aangevraagd - Jellyfin", "title": "Wachtwoordreset aangevraagd - Jellyfin",
"someoneHasRequestedReset": "Iemand heeft recentelijk een wachtwoordreset aangevraagd in Jellyfin.", "someoneHasRequestedReset": "Iemand heeft recentelijk een wachtwoordreset aangevraagd in Jellyfin.",
"ifItWasYou": "Als jij dit was, voor dan onderstaande PIN in.", "ifItWasYou": "Als jij dit was, voer dan onderstaande PIN in.",
"codeExpiry": "De code verloopt op {date}, op {time} UTC, dat is over {expiresInMinutes}.", "codeExpiry": "De code verloopt op {date}, op {time} UTC, dat is over {expiresInMinutes}.",
"pin": "PIN", "pin": "PIN",
"name": "Wachtwoordreset" "name": "Wachtwoordreset",
"ifItWasYouLink": "Als jij dit was, klik dan op onderstaande link."
}, },
"userDeleted": { "userDeleted": {
"title": "Je account is verwijderd - Jellyfin", "title": "Je account is verwijderd - Jellyfin",

View File

@ -26,7 +26,8 @@
"ifItWasYou": "Se foi você, insira o PIN abaixo.", "ifItWasYou": "Se foi você, insira o PIN abaixo.",
"codeExpiry": "O código irá expirar em {date}, ás {time}, que está em {expiresInMinutes}.", "codeExpiry": "O código irá expirar em {date}, ás {time}, que está em {expiresInMinutes}.",
"pin": "PIN", "pin": "PIN",
"name": "Redefinir senha" "name": "Redefinir senha",
"ifItWasYouLink": "Se foi você, clique no link abaixo."
}, },
"userDeleted": { "userDeleted": {
"title": "Sua conta foi excluída - Jellyfin", "title": "Sua conta foi excluída - Jellyfin",

View File

@ -13,11 +13,11 @@
"reEnterPasswordInvalid": "Passwörter stimmen nicht überein.", "reEnterPasswordInvalid": "Passwörter stimmen nicht überein.",
"createAccountButton": "Konto erstellen", "createAccountButton": "Konto erstellen",
"passwordRequirementsHeader": "Passwortanforderungen", "passwordRequirementsHeader": "Passwortanforderungen",
"successHeader": "Erfolg!", "successHeader": "Erfolgreich!",
"successContinueButton": "Weiter", "successContinueButton": "Weiter",
"confirmationRequired": "E-Mail-Bestätigung erforderlich", "confirmationRequired": "E-Mail-Bestätigung erforderlich",
"confirmationRequiredMessage": "Bitte überprüfe dein Posteingang und bestätige deine E-Mail-Adresse.", "confirmationRequiredMessage": "Bitte überprüfe dein Posteingang und bestätige deine E-Mail-Adresse.",
"yourAccountIsValidUntil": "Dein Konto wird bis am {date} gültig sein." "yourAccountIsValidUntil": "Dein Konto wird bis zum {date} gültig sein."
}, },
"validationStrings": { "validationStrings": {
"length": { "length": {

View File

@ -31,9 +31,9 @@
"language": { "language": {
"title": "Sprache", "title": "Sprache",
"description": "Gemeinschaftsübersetzungen sind für die meisten Teile von jfa-go verfügbar. Du kannst unten die Standardsprachen auswählen, aber Benutzer können dies immer noch ändern, wenn sie wollen. Wenn du helfen willst zu übersetzen, melde dich bei {n} an, um anzufangen, etwas beizutragen!", "description": "Gemeinschaftsübersetzungen sind für die meisten Teile von jfa-go verfügbar. Du kannst unten die Standardsprachen auswählen, aber Benutzer können dies immer noch ändern, wenn sie wollen. Wenn du helfen willst zu übersetzen, melde dich bei {n} an, um anzufangen, etwas beizutragen!",
"defaultAdminLang": "Standardsprache Admin", "defaultAdminLang": "Admin Standardsprache",
"defaultFormLang": "Standardsprache Kontoerstellung", "defaultFormLang": "Kontoerstellung Standardsprache",
"defaultEmailLang": "Standardsprache E-Mail" "defaultEmailLang": "E-Mail Standardsprache"
}, },
"general": { "general": {
"title": "Allgemein", "title": "Allgemein",

42
main.go
View File

@ -489,31 +489,47 @@ func start(asDaemon, firstCall bool) {
} }
if noHyphens == app.jf.Hyphens { if noHyphens == app.jf.Hyphens {
var newEmails map[string]interface{} var newEmails map[string]interface{}
var status int var newUsers map[string]time.Time
var err error var status, status2 int
var err, err2 error
if app.jf.Hyphens { if app.jf.Hyphens {
app.info.Println(info("Your build of Jellyfin appears to hypenate user IDs. Your emails.json file will be modified to match.")) app.info.Println(info("Your build of Jellyfin appears to hypenate user IDs. Your emails.json/users.json file will be modified to match."))
time.Sleep(time.Second * time.Duration(3)) time.Sleep(time.Second * time.Duration(3))
newEmails, status, err = app.hyphenateEmailStorage(app.storage.emails) newEmails, status, err = app.hyphenateEmailStorage(app.storage.emails)
newUsers, status2, err2 = app.hyphenateUserStorage(app.storage.users)
} else { } else {
app.info.Println(info("Your emails.json file uses hyphens, but the Jellyfin server no longer does. It will be modified.")) app.info.Println(info("Your emails.json/users.json file uses hyphens, but the Jellyfin server no longer does. It will be modified."))
time.Sleep(time.Second * time.Duration(3)) time.Sleep(time.Second * time.Duration(3))
newEmails, status, err = app.deHyphenateEmailStorage(app.storage.emails) newEmails, status, err = app.deHyphenateEmailStorage(app.storage.emails)
newUsers, status2, err2 = app.deHyphenateUserStorage(app.storage.users)
} }
if status != 200 || err != nil { if status != 200 || err != nil {
app.err.Printf("Failed to get users from Jellyfin: Code %d", status) app.err.Printf("Failed to get users from Jellyfin (%d): %v", status, err)
app.debug.Printf("Error: %s", err)
app.err.Fatalf("Couldn't upgrade emails.json") app.err.Fatalf("Couldn't upgrade emails.json")
} }
bakFile := app.storage.emails_path + ".bak" if status2 != 200 || err2 != nil {
err = storeJSON(bakFile, app.storage.emails) app.err.Printf("Failed to get users from Jellyfin (%d): %v", status, err)
app.err.Fatalf("Couldn't upgrade users.json")
}
emailBakFile := app.storage.emails_path + ".bak"
usersBakFile := app.storage.users_path + ".bak"
err = storeJSON(emailBakFile, app.storage.emails)
err2 = storeJSON(usersBakFile, app.storage.users)
if err != nil { if err != nil {
app.err.Fatalf("couldn't store emails.json backup: %s", err) app.err.Fatalf("couldn't store emails.json backup: %v", err)
}
if err2 != nil {
app.err.Fatalf("couldn't store users.json backup: %v", err)
} }
app.storage.emails = newEmails app.storage.emails = newEmails
app.storage.users = newUsers
err = app.storage.storeEmails() err = app.storage.storeEmails()
err2 = app.storage.storeUsers()
if err != nil { if err != nil {
app.err.Fatalf("couldn't store emails.json: %s", err) app.err.Fatalf("couldn't store emails.json: %v", err)
}
if err2 != nil {
app.err.Fatalf("couldn't store users.json: %v", err)
} }
} }
} }
@ -560,11 +576,13 @@ func start(asDaemon, firstCall bool) {
os.Exit(0) os.Exit(0)
} }
inviteDaemon := newInviteDaemon(time.Duration(60*time.Second), app) invDaemon := newInviteDaemon(time.Duration(60*time.Second), app)
go inviteDaemon.run() go invDaemon.run()
defer invDaemon.shutdown()
userDaemon := newUserDaemon(time.Duration(60*time.Second), app) userDaemon := newUserDaemon(time.Duration(60*time.Second), app)
go userDaemon.run() go userDaemon.run()
defer userDaemon.shutdown()
if app.config.Section("password_resets").Key("enabled").MustBool(false) && serverType == mediabrowser.JellyfinServer { if app.config.Section("password_resets").Key("enabled").MustBool(false) && serverType == mediabrowser.JellyfinServer {
go app.StartPWR() go app.StartPWR()

View File

@ -80,9 +80,9 @@ type inviteDTO struct {
UserDays int `json:"user-days,omitempty" example:"1"` // Number of days till user expiry UserDays int `json:"user-days,omitempty" example:"1"` // Number of days till user expiry
UserHours int `json:"user-hours,omitempty" example:"2"` // Number of hours till user expiry UserHours int `json:"user-hours,omitempty" example:"2"` // Number of hours till user expiry
UserMinutes int `json:"user-minutes,omitempty" example:"3"` // Number of minutes till user expiry UserMinutes int `json:"user-minutes,omitempty" example:"3"` // Number of minutes till user expiry
Created string `json:"created" example:"01/01/20 12:00"` // Date of creation Created int64 `json:"created" example:"1617737207510"` // Date of creation
Profile string `json:"profile" example:"DefaultProfile"` // Profile used on this invite Profile string `json:"profile" example:"DefaultProfile"` // Profile used on this invite
UsedBy [][]string `json:"used-by,omitempty"` // Users who have used this invite UsedBy map[string]int64 `json:"used-by,omitempty"` // Users who have used this invite mapped to their creation time in Epoch/Unix time
NoLimit bool `json:"no-limit,omitempty"` // If true, invite can be used any number of times NoLimit bool `json:"no-limit,omitempty"` // If true, invite can be used any number of times
RemainingUses int `json:"remaining-uses,omitempty"` // Remaining number of uses (if applicable) RemainingUses int `json:"remaining-uses,omitempty"` // Remaining number of uses (if applicable)
Email string `json:"email,omitempty"` // Email the invite was sent to (if applicable) Email string `json:"email,omitempty"` // Email the invite was sent to (if applicable)
@ -112,9 +112,9 @@ type respUser struct {
ID string `json:"id" example:"fdgsdfg45534fa"` // userID of user ID string `json:"id" example:"fdgsdfg45534fa"` // userID of user
Name string `json:"name" example:"jeff"` // Username of user Name string `json:"name" example:"jeff"` // Username of user
Email string `json:"email,omitempty" example:"jeff@jellyf.in"` // Email address of user (if available) Email string `json:"email,omitempty" example:"jeff@jellyf.in"` // Email address of user (if available)
LastActive string `json:"last_active"` // Time of last activity on Jellyfin LastActive int64 `json:"last_active" example:"1617737207510"` // Time of last activity on Jellyfin
Admin bool `json:"admin" example:"false"` // Whether or not the user is Administrator Admin bool `json:"admin" example:"false"` // Whether or not the user is Administrator
Expiry string `json:"expiry" example:"01/02/21 12:00"` // Expiry time of user, if applicable. Expiry int64 `json:"expiry" example:"1617737207510"` // Expiry time of user as Epoch/Unix time.
Disabled bool `json:"disabled"` // Whether or not the user is disabled. Disabled bool `json:"disabled"` // Whether or not the user is disabled.
} }

View File

@ -68,6 +68,7 @@ type Invite struct {
UserHours int `json:"user-hours,omitempty"` UserHours int `json:"user-hours,omitempty"`
UserMinutes int `json:"user-minutes,omitempty"` UserMinutes int `json:"user-minutes,omitempty"`
Email string `json:"email"` Email string `json:"email"`
// Used to be stored as formatted time, now as Unix.
UsedBy [][]string `json:"used-by"` UsedBy [][]string `json:"used-by"`
Notify map[string]map[string]bool `json:"notify"` Notify map[string]map[string]bool `json:"notify"`
Profile string `json:"profile"` Profile string `json:"profile"`
@ -480,11 +481,11 @@ func (st *Storage) storeInvites() error {
} }
func (st *Storage) loadUsers() error { func (st *Storage) loadUsers() error {
st.usersLock.Lock()
defer st.usersLock.Unlock()
if st.users == nil { if st.users == nil {
st.users = map[string]time.Time{} st.users = map[string]time.Time{}
} }
st.usersLock.Lock()
defer st.usersLock.Unlock()
temp := map[string]time.Time{} temp := map[string]time.Time{}
err := loadJSON(st.users_path, &temp) err := loadJSON(st.users_path, &temp)
if err != nil { if err != nil {
@ -638,7 +639,7 @@ func hyphenate(userID string) string {
return userID[:8] + "-" + userID[8:12] + "-" + userID[12:16] + "-" + userID[16:20] + "-" + userID[20:] return userID[:8] + "-" + userID[8:12] + "-" + userID[12:16] + "-" + userID[16:20] + "-" + userID[20:]
} }
func (app *appContext) deHyphenateEmailStorage(old map[string]interface{}) (map[string]interface{}, int, error) { func (app *appContext) deHyphenateStorage(old map[string]interface{}) (map[string]interface{}, int, error) {
jfUsers, status, err := app.jf.GetUsers(false) jfUsers, status, err := app.jf.GetUsers(false)
if status != 200 || err != nil { if status != 200 || err != nil {
return nil, status, err return nil, status, err
@ -647,15 +648,15 @@ func (app *appContext) deHyphenateEmailStorage(old map[string]interface{}) (map[
for _, user := range jfUsers { for _, user := range jfUsers {
unHyphenated := user.ID unHyphenated := user.ID
hyphenated := hyphenate(unHyphenated) hyphenated := hyphenate(unHyphenated)
email, ok := old[hyphenated] val, ok := old[hyphenated]
if ok { if ok {
newEmails[unHyphenated] = email newEmails[unHyphenated] = val
} }
} }
return newEmails, status, err return newEmails, status, err
} }
func (app *appContext) hyphenateEmailStorage(old map[string]interface{}) (map[string]interface{}, int, error) { func (app *appContext) hyphenateStorage(old map[string]interface{}) (map[string]interface{}, int, error) {
jfUsers, status, err := app.jf.GetUsers(false) jfUsers, status, err := app.jf.GetUsers(false)
if status != 200 || err != nil { if status != 200 || err != nil {
return nil, status, err return nil, status, err
@ -664,10 +665,50 @@ func (app *appContext) hyphenateEmailStorage(old map[string]interface{}) (map[st
for _, user := range jfUsers { for _, user := range jfUsers {
unstripped := user.ID unstripped := user.ID
stripped := strings.ReplaceAll(unstripped, "-", "") stripped := strings.ReplaceAll(unstripped, "-", "")
email, ok := old[stripped] val, ok := old[stripped]
if ok { if ok {
newEmails[unstripped] = email newEmails[unstripped] = val
} }
} }
return newEmails, status, err return newEmails, status, err
} }
func (app *appContext) hyphenateEmailStorage(old map[string]interface{}) (map[string]interface{}, int, error) {
return app.hyphenateStorage(old)
}
func (app *appContext) deHyphenateEmailStorage(old map[string]interface{}) (map[string]interface{}, int, error) {
return app.deHyphenateStorage(old)
}
func (app *appContext) hyphenateUserStorage(old map[string]time.Time) (map[string]time.Time, int, error) {
asInterface := map[string]interface{}{}
for k, v := range old {
asInterface[k] = v
}
fixed, status, err := app.hyphenateStorage(asInterface)
if err != nil {
return nil, status, err
}
out := map[string]time.Time{}
for k, v := range fixed {
out[k] = v.(time.Time)
}
return out, status, err
}
func (app *appContext) deHyphenateUserStorage(old map[string]time.Time) (map[string]time.Time, int, error) {
asInterface := map[string]interface{}{}
for k, v := range old {
asInterface[k] = v
}
fixed, status, err := app.deHyphenateStorage(asInterface)
if err != nil {
return nil, status, err
}
out := map[string]time.Time{}
for k, v := range fixed {
out[k] = v.(time.Time)
}
return out, status, err
}

View File

@ -1,13 +1,13 @@
import { _get, _post, _delete, toggleLoader } from "../modules/common.js"; import { _get, _post, _delete, toggleLoader, toDateString } from "../modules/common.js";
interface User { interface User {
id: string; id: string;
name: string; name: string;
email: string | undefined; email: string | undefined;
last_active: string; last_active: number;
admin: boolean; admin: boolean;
disabled: boolean; disabled: boolean;
expiry: string; expiry: number;
} }
class user implements User { class user implements User {
@ -20,7 +20,9 @@ class user implements User {
private _emailAddress: string; private _emailAddress: string;
private _emailEditButton: HTMLElement; private _emailEditButton: HTMLElement;
private _expiry: HTMLTableDataCellElement; private _expiry: HTMLTableDataCellElement;
private _expiryUnix: number;
private _lastActive: HTMLTableDataCellElement; private _lastActive: HTMLTableDataCellElement;
private _lastActiveUnix: number;
id: string; id: string;
private _selected: boolean; private _selected: boolean;
@ -67,11 +69,25 @@ class user implements User {
} }
} }
get expiry(): string { return this._expiry.textContent; } get expiry(): number { return this._expiryUnix; }
set expiry(value: string) { this._expiry.textContent = value; } set expiry(unix: number) {
this._expiryUnix = unix;
if (unix == 0) {
this._expiry.textContent = "";
} else {
this._expiry.textContent = toDateString(new Date(unix*1000));
}
}
get last_active(): string { return this._lastActive.textContent; } get last_active(): number { return this._lastActiveUnix; }
set last_active(value: string) { this._lastActive.textContent = value; } set last_active(unix: number) {
this._lastActiveUnix = unix;
if (unix == 0) {
this._lastActive.textContent == "";
} else {
this._lastActive.textContent = toDateString(new Date(unix*1000));
}
}
private _checkEvent = new CustomEvent("accountCheckEvent"); private _checkEvent = new CustomEvent("accountCheckEvent");
private _uncheckEvent = new CustomEvent("accountUncheckEvent"); private _uncheckEvent = new CustomEvent("accountUncheckEvent");

View File

@ -6,6 +6,13 @@ export function createEl(html: string): HTMLElement {
return div.firstElementChild as HTMLElement; return div.firstElementChild as HTMLElement;
} }
export function toDateString(date: Date): string {
return date.toLocaleDateString() + " " + date.toLocaleString([], {
hour: "2-digit",
minute: "2-digit"
})
}
export function serializeForm(id: string): Object { export function serializeForm(id: string): Object {
const form = document.getElementById(id) as HTMLFormElement; const form = document.getElementById(id) as HTMLFormElement;
let formData = {}; let formData = {};

View File

@ -1,4 +1,4 @@
import { _get, _post, _delete, toClipboard, toggleLoader } from "../modules/common.js"; import { _get, _post, _delete, toClipboard, toggleLoader, toDateString } from "../modules/common.js";
export class DOMInvite implements Invite { export class DOMInvite implements Invite {
updateNotify = (checkbox: HTMLInputElement) => { updateNotify = (checkbox: HTMLInputElement) => {
@ -116,10 +116,9 @@ export class DOMInvite implements Invite {
tooltip.textContent = address; tooltip.textContent = address;
} }
private _usedBy: string[][]; private _usedBy: { [name: string]: number };
get usedBy(): string[][] { return this._usedBy; } get usedBy(): { [name: string]: number } { return this._usedBy; }
set usedBy(uB: string[][]) { set usedBy(uB: { [name: string]: number }) {
// ub[i][0]: username, ub[i][1]: date
this._usedBy = uB; this._usedBy = uB;
if (uB.length == 0) { if (uB.length == 0) {
this._right.classList.add("empty"); this._right.classList.add("empty");
@ -137,11 +136,11 @@ export class DOMInvite implements Invite {
</thead> </thead>
<tbody> <tbody>
`; `;
for (let user of uB) { for (let username in uB) {
innerHTML += ` innerHTML += `
<tr> <tr>
<td>${user[0]}</td> <td>${username}</td>
<td>${user[1]}</td> <td>${toDateString(new Date(uB[username] * 1000))}</td>
</tr> </tr>
`; `;
} }
@ -152,11 +151,16 @@ export class DOMInvite implements Invite {
this._userTable.innerHTML = innerHTML; this._userTable.innerHTML = innerHTML;
} }
private _created: string; private _createdUnix: number;
get created(): string { return this._created; } get created(): number { return this._createdUnix; }
set created(created: string) { set created(unix: number) {
this._created = created; this._createdUnix = unix;
this._middle.querySelector("strong.inv-created").textContent = created; const el = this._middle.querySelector("strong.inv-created");
if (unix == 0) {
el.textContent = "n/a";
} else {
el.textContent = toDateString(new Date(unix*1000));
}
} }
private _notifyExpiry: boolean = false; private _notifyExpiry: boolean = false;

View File

@ -1,4 +1,4 @@
import { _get, _post, toggleLoader } from "../modules/common.js"; import { _get, _post, toggleLoader, toDateString } from "../modules/common.js";
import { Marked, Renderer } from "@ts-stack/markdown"; import { Marked, Renderer } from "@ts-stack/markdown";
interface updateDTO { interface updateDTO {
@ -29,7 +29,7 @@ export class Updater implements updater {
get date(): number { return Math.floor(this._date.getTime() / 1000); } get date(): number { return Math.floor(this._date.getTime() / 1000); }
set date(unix: number) { set date(unix: number) {
this._date = new Date(unix * 1000); this._date = new Date(unix * 1000);
document.getElementById("update-date").textContent = this._date.toDateString() + " " + this._date.toLocaleTimeString(); document.getElementById("update-date").textContent = toDateString(this._date);
} }
get description(): string { return this._update.description; } get description(): string { return this._update.description; }

View File

@ -104,8 +104,8 @@ interface Invite {
expiresIn?: string; expiresIn?: string;
remainingUses?: string; remainingUses?: string;
email?: string; email?: string;
usedBy?: string[][]; usedBy?: { [name: string]: number };
created?: string; created?: number;
notifyExpiry?: boolean; notifyExpiry?: boolean;
notifyCreation?: boolean; notifyCreation?: boolean;
profile?: string; profile?: string;

View File

@ -35,7 +35,6 @@ func (rt *userDaemon) run() {
break break
} }
started := time.Now() started := time.Now()
rt.app.storage.loadInvites()
rt.app.checkUsers() rt.app.checkUsers()
finished := time.Now() finished := time.Now()
duration := finished.Sub(started) duration := finished.Sub(started)
@ -43,6 +42,13 @@ func (rt *userDaemon) run() {
} }
} }
func (rt *userDaemon) shutdown() {
rt.Stopped = true
rt.ShutdownChannel <- "Down"
<-rt.ShutdownChannel
close(rt.ShutdownChannel)
}
func (app *appContext) checkUsers() { func (app *appContext) checkUsers() {
if err := app.storage.loadUsers(); err != nil { if err := app.storage.loadUsers(); err != nil {
app.err.Printf("Failed to load user expiries: %v", err) app.err.Printf("Failed to load user expiries: %v", err)
@ -76,12 +82,8 @@ func (app *appContext) checkUsers() {
} }
for id, expiry := range app.storage.users { for id, expiry := range app.storage.users {
if _, ok := userExists[id]; !ok { if _, ok := userExists[id]; !ok {
app.debug.Printf("Deleting expiry for non-existent user \"%s\"", id) app.info.Printf("Deleting expiry for non-existent user \"%s\"", id)
delete(app.storage.users, id) delete(app.storage.users, id)
err = app.storage.storeUsers()
if err != nil {
app.err.Printf("Failed to store user duration: %s", err)
}
} else if time.Now().After(expiry) { } else if time.Now().After(expiry) {
found := false found := false
var user mediabrowser.User var user mediabrowser.User
@ -111,10 +113,7 @@ func (app *appContext) checkUsers() {
continue continue
} }
delete(app.storage.users, id) delete(app.storage.users, id)
err = app.storage.storeUsers() app.jf.CacheExpiry = time.Now()
if err != nil {
app.err.Printf("Failed to store user duration: %s", err)
}
if email { if email {
address, ok := app.storage.emails[id] address, ok := app.storage.emails[id]
if !ok { if !ok {
@ -131,4 +130,8 @@ func (app *appContext) checkUsers() {
} }
} }
} }
err = app.storage.storeUsers()
if err != nil {
app.err.Printf("Failed to store user expiries: %s", err)
}
} }