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

Compare commits

..

No commits in common. "3e73d16cce77bc0ae877629c2a3304673d0be7fe" and "8a6cfe0b4d0439ee49b4af9733ac362e11d084ce" have entirely different histories.

20 changed files with 115 additions and 223 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/itchyny/timefmt-go" "github.com/knz/strtime"
"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 = timefmt.Format(dt, app.datePattern) date, _ = strtime.Strftime(dt, app.datePattern)
time = timefmt.Format(dt, app.timePattern) time, _ = strtime.Strftime(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, strconv.FormatInt(currentTime.Unix(), 10)}) newInv.UsedBy = append(newInv.UsedBy, []string{username, app.formatDatetime(currentTime)})
if !del { if !del {
app.storage.invites[code] = newInv app.storage.invites[code] = newInv
} }
@ -870,25 +870,13 @@ 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: inv.Created.Unix(), Created: app.formatDatetime(inv.Created),
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 = map[string]int64{} invite.UsedBy = inv.UsedBy
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 {
@ -1044,8 +1032,6 @@ 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,
@ -1053,15 +1039,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 = jfUser.LastActivityDate.Unix() user.LastActive = app.formatDatetime(jfUser.LastActivityDate.Time)
} }
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)
} }
expiry, ok := app.storage.users[jfUser.ID] if expiry, ok := app.storage.users[jfUser.ID]; ok {
if ok { user.Expiry = app.formatDatetime(expiry)
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 = timefmt.Format(expiry, datePattern) d, _ = strtime.Strftime(expiry, datePattern)
t = timefmt.Format(expiry, timePattern) t, _ = strtime.Strftime(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,8 +129,6 @@ 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=
@ -143,6 +141,8 @@ 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,10 +8,9 @@
"password": "Passwort", "password": "Passwort",
"emailAddress": "E-Mail-Adresse", "emailAddress": "E-Mail-Adresse",
"submit": "Absenden", "submit": "Absenden",
"success": "Erfolgreich", "success": "Erfolg",
"error": "Fehler", "error": "Fehler",
"copy": "Kopieren", "copy": "Kopieren",
"theme": "Thema", "theme": "Thema"
"copied": "Kopiert"
} }
} }

View File

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

View File

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

View File

@ -23,11 +23,10 @@
"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, voer dan onderstaande PIN in.", "ifItWasYou": "Als jij dit was, voor 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,8 +26,7 @@
"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": "Erfolgreich!", "successHeader": "Erfolg!",
"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 zum {date} gültig sein." "yourAccountIsValidUntil": "Dein Konto wird bis am {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": "Admin Standardsprache", "defaultAdminLang": "Standardsprache Admin",
"defaultFormLang": "Kontoerstellung Standardsprache", "defaultFormLang": "Standardsprache Kontoerstellung",
"defaultEmailLang": "E-Mail Standardsprache" "defaultEmailLang": "Standardsprache E-Mail"
}, },
"general": { "general": {
"title": "Allgemein", "title": "Allgemein",

42
main.go
View File

@ -489,47 +489,31 @@ 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 newUsers map[string]time.Time var status int
var status, status2 int var err error
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/users.json file will be modified to match.")) app.info.Println(info("Your build of Jellyfin appears to hypenate user IDs. Your emails.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/users.json file uses hyphens, but the Jellyfin server no longer does. It will be modified.")) app.info.Println(info("Your emails.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 (%d): %v", status, err) app.err.Printf("Failed to get users from Jellyfin: Code %d", status)
app.debug.Printf("Error: %s", err)
app.err.Fatalf("Couldn't upgrade emails.json") app.err.Fatalf("Couldn't upgrade emails.json")
} }
if status2 != 200 || err2 != nil { bakFile := app.storage.emails_path + ".bak"
app.err.Printf("Failed to get users from Jellyfin (%d): %v", status, err) err = storeJSON(bakFile, app.storage.emails)
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: %v", err) app.err.Fatalf("couldn't store emails.json backup: %s", 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: %v", err) app.err.Fatalf("couldn't store emails.json: %s", err)
}
if err2 != nil {
app.err.Fatalf("couldn't store users.json: %v", err)
} }
} }
} }
@ -576,13 +560,11 @@ func start(asDaemon, firstCall bool) {
os.Exit(0) os.Exit(0)
} }
invDaemon := newInviteDaemon(time.Duration(60*time.Second), app) inviteDaemon := newInviteDaemon(time.Duration(60*time.Second), app)
go invDaemon.run() go inviteDaemon.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

@ -72,23 +72,23 @@ type newProfileDTO struct {
} }
type inviteDTO struct { type inviteDTO struct {
Code string `json:"code" example:"sajdlj23423j23"` // Invite code Code string `json:"code" example:"sajdlj23423j23"` // Invite code
Days int `json:"days" example:"1"` // Number of days till expiry Days int `json:"days" example:"1"` // Number of days till expiry
Hours int `json:"hours" example:"2"` // Number of hours till expiry Hours int `json:"hours" example:"2"` // Number of hours till expiry
Minutes int `json:"minutes" example:"3"` // Number of minutes till expiry Minutes int `json:"minutes" example:"3"` // Number of minutes till expiry
UserExpiry bool `json:"user-expiry"` // Whether or not user expiry is enabled UserExpiry bool `json:"user-expiry"` // Whether or not user expiry is enabled
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 int64 `json:"created" example:"1617737207510"` // Date of creation Created string `json:"created" example:"01/01/20 12:00"` // 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 map[string]int64 `json:"used-by,omitempty"` // Users who have used this invite mapped to their creation time in Epoch/Unix time UsedBy [][]string `json:"used-by,omitempty"` // Users who have used this invite
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)
NotifyExpiry bool `json:"notify-expiry,omitempty"` // Whether to notify the requesting user of expiry or not NotifyExpiry bool `json:"notify-expiry,omitempty"` // Whether to notify the requesting user of expiry or not
NotifyCreation bool `json:"notify-creation,omitempty"` // Whether to notify the requesting user of account creation or not NotifyCreation bool `json:"notify-creation,omitempty"` // Whether to notify the requesting user of account creation or not
Label string `json:"label,omitempty" example:"For Friends"` // Optional label for the invite Label string `json:"label,omitempty" example:"For Friends"` // Optional label for the invite
} }
type getInvitesDTO struct { type getInvitesDTO struct {
@ -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 int64 `json:"last_active" example:"1617737207510"` // Time of last activity on Jellyfin LastActive string `json:"last_active"` // 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 int64 `json:"expiry" example:"1617737207510"` // Expiry time of user as Epoch/Unix time. Expiry string `json:"expiry" example:"01/02/21 12:00"` // Expiry time of user, if applicable.
Disabled bool `json:"disabled"` // Whether or not the user is disabled. Disabled bool `json:"disabled"` // Whether or not the user is disabled.
} }

View File

@ -59,21 +59,20 @@ type Profile struct {
} }
type Invite struct { type Invite struct {
Created time.Time `json:"created"` Created time.Time `json:"created"`
NoLimit bool `json:"no-limit"` NoLimit bool `json:"no-limit"`
RemainingUses int `json:"remaining-uses"` RemainingUses int `json:"remaining-uses"`
ValidTill time.Time `json:"valid_till"` ValidTill time.Time `json:"valid_till"`
UserExpiry bool `json:"user-duration"` UserExpiry bool `json:"user-duration"`
UserDays int `json:"user-days,omitempty"` UserDays int `json:"user-days,omitempty"`
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"` Label string `json:"label,omitempty"`
Label string `json:"label,omitempty"` Keys []string `json:"keys,omitempty"`
Keys []string `json:"keys,omitempty"`
} }
type Lang struct { type Lang struct {
@ -481,11 +480,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 {
@ -639,7 +638,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) deHyphenateStorage(old map[string]interface{}) (map[string]interface{}, int, error) { func (app *appContext) deHyphenateEmailStorage(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
@ -648,15 +647,15 @@ func (app *appContext) deHyphenateStorage(old map[string]interface{}) (map[strin
for _, user := range jfUsers { for _, user := range jfUsers {
unHyphenated := user.ID unHyphenated := user.ID
hyphenated := hyphenate(unHyphenated) hyphenated := hyphenate(unHyphenated)
val, ok := old[hyphenated] email, ok := old[hyphenated]
if ok { if ok {
newEmails[unHyphenated] = val newEmails[unHyphenated] = email
} }
} }
return newEmails, status, err return newEmails, status, err
} }
func (app *appContext) hyphenateStorage(old map[string]interface{}) (map[string]interface{}, int, error) { func (app *appContext) hyphenateEmailStorage(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
@ -665,50 +664,10 @@ func (app *appContext) hyphenateStorage(old map[string]interface{}) (map[string]
for _, user := range jfUsers { for _, user := range jfUsers {
unstripped := user.ID unstripped := user.ID
stripped := strings.ReplaceAll(unstripped, "-", "") stripped := strings.ReplaceAll(unstripped, "-", "")
val, ok := old[stripped] email, ok := old[stripped]
if ok { if ok {
newEmails[unstripped] = val newEmails[unstripped] = email
} }
} }
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, toDateString } from "../modules/common.js"; import { _get, _post, _delete, toggleLoader } from "../modules/common.js";
interface User { interface User {
id: string; id: string;
name: string; name: string;
email: string | undefined; email: string | undefined;
last_active: number; last_active: string;
admin: boolean; admin: boolean;
disabled: boolean; disabled: boolean;
expiry: number; expiry: string;
} }
class user implements User { class user implements User {
@ -20,9 +20,7 @@ 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;
@ -69,25 +67,11 @@ class user implements User {
} }
} }
get expiry(): number { return this._expiryUnix; } get expiry(): string { return this._expiry.textContent; }
set expiry(unix: number) { set expiry(value: string) { this._expiry.textContent = value; }
this._expiryUnix = unix;
if (unix == 0) {
this._expiry.textContent = "";
} else {
this._expiry.textContent = toDateString(new Date(unix*1000));
}
}
get last_active(): number { return this._lastActiveUnix; } get last_active(): string { return this._lastActive.textContent; }
set last_active(unix: number) { set last_active(value: string) { this._lastActive.textContent = value; }
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,13 +6,6 @@ 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, toDateString } from "../modules/common.js"; import { _get, _post, _delete, toClipboard, toggleLoader } from "../modules/common.js";
export class DOMInvite implements Invite { export class DOMInvite implements Invite {
updateNotify = (checkbox: HTMLInputElement) => { updateNotify = (checkbox: HTMLInputElement) => {
@ -116,9 +116,10 @@ export class DOMInvite implements Invite {
tooltip.textContent = address; tooltip.textContent = address;
} }
private _usedBy: { [name: string]: number }; private _usedBy: string[][];
get usedBy(): { [name: string]: number } { return this._usedBy; } get usedBy(): string[][] { return this._usedBy; }
set usedBy(uB: { [name: string]: number }) { set usedBy(uB: string[][]) {
// 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");
@ -136,11 +137,11 @@ export class DOMInvite implements Invite {
</thead> </thead>
<tbody> <tbody>
`; `;
for (let username in uB) { for (let user of uB) {
innerHTML += ` innerHTML += `
<tr> <tr>
<td>${username}</td> <td>${user[0]}</td>
<td>${toDateString(new Date(uB[username] * 1000))}</td> <td>${user[1]}</td>
</tr> </tr>
`; `;
} }
@ -151,16 +152,11 @@ export class DOMInvite implements Invite {
this._userTable.innerHTML = innerHTML; this._userTable.innerHTML = innerHTML;
} }
private _createdUnix: number; private _created: string;
get created(): number { return this._createdUnix; } get created(): string { return this._created; }
set created(unix: number) { set created(created: string) {
this._createdUnix = unix; this._created = created;
const el = this._middle.querySelector("strong.inv-created"); this._middle.querySelector("strong.inv-created").textContent = 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, toDateString } from "../modules/common.js"; import { _get, _post, toggleLoader } 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 = toDateString(this._date); document.getElementById("update-date").textContent = this._date.toDateString() + " " + this._date.toLocaleTimeString();
} }
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?: { [name: string]: number }; usedBy?: string[][];
created?: number; created?: string;
notifyExpiry?: boolean; notifyExpiry?: boolean;
notifyCreation?: boolean; notifyCreation?: boolean;
profile?: string; profile?: string;

View File

@ -35,6 +35,7 @@ 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)
@ -42,13 +43,6 @@ 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)
@ -82,8 +76,12 @@ 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.info.Printf("Deleting expiry for non-existent user \"%s\"", id) app.debug.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
@ -113,7 +111,10 @@ func (app *appContext) checkUsers() {
continue continue
} }
delete(app.storage.users, id) delete(app.storage.users, id)
app.jf.CacheExpiry = time.Now() err = app.storage.storeUsers()
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 {
@ -130,8 +131,4 @@ func (app *appContext) checkUsers() {
} }
} }
} }
err = app.storage.storeUsers()
if err != nil {
app.err.Printf("Failed to store user expiries: %s", err)
}
} }