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

Compare commits

..

No commits in common. "311ecb7030b44f8615851e7af7171980b3d37d48" and "729552a827a11d634b006d202148dab80d03093f" have entirely different histories.

54 changed files with 69 additions and 263 deletions

View File

@ -14,7 +14,7 @@ import (
) )
const ( const (
REFERRAL_EXPIRY_DAYS = 90 REFERRAL_EXPIRY_DAYS = 365
) )
// @Summary Returns the logged-in user's Jellyfin ID & Username, and other details. // @Summary Returns the logged-in user's Jellyfin ID & Username, and other details.
@ -81,25 +81,6 @@ func (app *appContext) MyDetails(gc *gin.Context) {
} }
} }
if app.config.Section("user_page").Key("referrals").MustBool(false) {
// 1. Look for existing template bound to this Jellyfin ID
// If one exists, that means its just for us and so we
// can use it directly.
inv := Invite{}
err := app.storage.db.FindOne(&inv, badgerhold.Where("ReferrerJellyfinID").Eq(resp.Id))
if err == nil {
resp.HasReferrals = true
} else {
// 2. Look for a template matching the key found in the user storage
// Since this key is shared between users in a profile, we make a copy.
user, ok := app.storage.GetEmailsKey(gc.GetString("jfId"))
err = app.storage.db.Get(user.ReferralTemplateKey, &inv)
if ok && err == nil {
resp.HasReferrals = true
}
}
}
gc.JSON(200, resp) gc.JSON(200, resp)
} }
@ -704,6 +685,6 @@ func (app *appContext) GetMyReferral(gc *gin.Context) {
Code: inv.Code, Code: inv.Code,
RemainingUses: inv.RemainingUses, RemainingUses: inv.RemainingUses,
NoLimit: inv.NoLimit, NoLimit: inv.NoLimit,
Expiry: inv.ValidTill.Unix(), Expiry: inv.ValidTill,
}) })
} }

View File

@ -304,12 +304,6 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
} }
} }
id := user.ID id := user.ID
emailStore := EmailAddress{
Addr: req.Email,
Contact: (req.Email != ""),
}
var profile Profile var profile Profile
if invite.Profile != "" { if invite.Profile != "" {
app.debug.Printf("Applying settings from profile \"%s\"", invite.Profile) app.debug.Printf("Applying settings from profile \"%s\"", invite.Profile)
@ -331,15 +325,10 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
if !((status == 200 || status == 204) && err == nil) { if !((status == 200 || status == 204) && err == nil) {
app.err.Printf("%s: Failed to set configuration template (%d): %v", req.Code, status, err) app.err.Printf("%s: Failed to set configuration template (%d): %v", req.Code, status, err)
} }
if app.config.Section("user_page").Key("enabled").MustBool(false) && app.config.Section("user_page").Key("referrals").MustBool(false) && profile.ReferralTemplateKey != "" {
emailStore.ReferralTemplateKey = profile.ReferralTemplateKey
// Store here, just incase email are disabled (whether this is even possible, i don't know)
app.storage.SetEmailsKey(id, emailStore)
}
} }
// if app.config.Section("password_resets").Key("enabled").MustBool(false) { // if app.config.Section("password_resets").Key("enabled").MustBool(false) {
if req.Email != "" { if req.Email != "" {
app.storage.SetEmailsKey(id, emailStore) app.storage.SetEmailsKey(id, EmailAddress{Addr: req.Email, Contact: true})
} }
expiry := time.Time{} expiry := time.Time{}
if invite.UserExpiry { if invite.UserExpiry {
@ -645,7 +634,6 @@ func (app *appContext) ExtendExpiry(gc *gin.Context) {
// @Summary Enable referrals for the given user(s) based on the rules set in the given invite code, or profile. // @Summary Enable referrals for the given user(s) based on the rules set in the given invite code, or profile.
// @Produce json // @Produce json
// @Param EnableDisableReferralDTO body EnableDisableReferralDTO true "List of users"
// @Param mode path string true "mode of template sourcing from 'invite' or 'profile'." // @Param mode path string true "mode of template sourcing from 'invite' or 'profile'."
// @Param source path string true "invite code or profile name, depending on what mode is." // @Param source path string true "invite code or profile name, depending on what mode is."
// @Success 200 {object} boolResponse // @Success 200 {object} boolResponse
@ -701,30 +689,6 @@ func (app *appContext) EnableReferralForUsers(gc *gin.Context) {
} }
} }
// @Summary Disable referrals for the given user(s).
// @Produce json
// @Param EnableDisableReferralDTO body EnableDisableReferralDTO true "List of users"
// @Success 200 {object} boolResponse
// @Router /users/referral [delete]
// @Security Bearer
// @tags Users
func (app *appContext) DisableReferralForUsers(gc *gin.Context) {
var req EnableDisableReferralDTO
gc.BindJSON(&req)
for _, u := range req.Users {
// 1. Delete directly bound template
app.storage.db.DeleteMatching(Invite{}, badgerhold.Where("ReferrerJellyfinID").Eq(u))
// 2. Check for and delete profile-attached template
user, ok := app.storage.GetEmailsKey(u)
if !ok {
continue
}
user.ReferralTemplateKey = ""
app.storage.SetEmailsKey(u, user)
}
respondBool(200, true, gc)
}
// @Summary Send an announcement via email to a given list of users. // @Summary Send an announcement via email to a given list of users.
// @Produce json // @Produce json
// @Param announcementDTO body announcementDTO true "Announcement request object" // @Param announcementDTO body announcementDTO true "Announcement request object"

View File

@ -10,7 +10,7 @@ func (app *appContext) clearEmails() {
emails := app.storage.GetEmails() emails := app.storage.GetEmails()
for _, email := range emails { for _, email := range emails {
_, status, err := app.jf.UserByID(email.JellyfinID, false) _, status, err := app.jf.UserByID(email.JellyfinID, false)
if status == 200 && err == nil { if status == 200 && err != nil {
continue continue
} }
app.storage.DeleteEmailsKey(email.JellyfinID) app.storage.DeleteEmailsKey(email.JellyfinID)
@ -23,7 +23,7 @@ func (app *appContext) clearDiscord() {
discordUsers := app.storage.GetDiscord() discordUsers := app.storage.GetDiscord()
for _, discordUser := range discordUsers { for _, discordUser := range discordUsers {
_, status, err := app.jf.UserByID(discordUser.JellyfinID, false) _, status, err := app.jf.UserByID(discordUser.JellyfinID, false)
if status == 200 && err == nil { if status == 200 && err != nil {
continue continue
} }
app.storage.DeleteDiscordKey(discordUser.JellyfinID) app.storage.DeleteDiscordKey(discordUser.JellyfinID)
@ -36,7 +36,7 @@ func (app *appContext) clearMatrix() {
matrixUsers := app.storage.GetMatrix() matrixUsers := app.storage.GetMatrix()
for _, matrixUser := range matrixUsers { for _, matrixUser := range matrixUsers {
_, status, err := app.jf.UserByID(matrixUser.JellyfinID, false) _, status, err := app.jf.UserByID(matrixUser.JellyfinID, false)
if status == 200 && err == nil { if status == 200 && err != nil {
continue continue
} }
app.storage.DeleteMatrixKey(matrixUser.JellyfinID) app.storage.DeleteMatrixKey(matrixUser.JellyfinID)
@ -49,7 +49,7 @@ func (app *appContext) clearTelegram() {
telegramUsers := app.storage.GetTelegram() telegramUsers := app.storage.GetTelegram()
for _, telegramUser := range telegramUsers { for _, telegramUser := range telegramUsers {
_, status, err := app.jf.UserByID(telegramUser.JellyfinID, false) _, status, err := app.jf.UserByID(telegramUser.JellyfinID, false)
if status == 200 && err == nil { if status == 200 && err != nil {
continue continue
} }
app.storage.DeleteTelegramKey(telegramUser.JellyfinID) app.storage.DeleteTelegramKey(telegramUser.JellyfinID)

View File

@ -24,7 +24,6 @@
window.matrixRequired = {{ .matrixRequired }}; window.matrixRequired = {{ .matrixRequired }};
window.matrixUserID = "{{ .matrixUser }}"; window.matrixUserID = "{{ .matrixUser }}";
window.validationStrings = JSON.parse({{ .validationStrings }}); window.validationStrings = JSON.parse({{ .validationStrings }});
window.referralsEnabled = {{ .referralsEnabled }};
</script> </script>
{{ template "header.html" . }} {{ template "header.html" . }}
<title>{{ .strings.myAccount }}</title> <title>{{ .strings.myAccount }}</title>
@ -151,20 +150,6 @@
<div class="user-expiry-countdown"></div> <div class="user-expiry-countdown"></div>
</div> </div>
</div> </div>
{{ if .referralsEnabled }}
<div>
<div class="card @low dark:~d_neutral unfocused" id="card-referrals">
<span class="heading mb-2">{{ .strings.referrals }}</span>
<aside class="aside ~neutral my-4 col">{{ .strings.referralsDescription }}</aside>
<div class="row flex-expand">
<div class="user-referrals-info"></div>
<div class="grid my-2">
<button type="button" class="user-referrals-button button ~info dark:~d_info @low" title="Copy">{{ .strings.copyReferral }}<i class="ri-file-copy-line ml-2"></i></button>
</div>
</div>
</div>
</div>
{{ end }}
</div> </div>
</div> </div>
<script src="{{ .urlBase }}/js/user.js" type="module"></script> <script src="{{ .urlBase }}/js/user.js" type="module"></script>

View File

@ -79,6 +79,7 @@
"inviteUsersCreated": "Oprettet brugere", "inviteUsersCreated": "Oprettet brugere",
"inviteNoProfile": "Ingen Profil", "inviteNoProfile": "Ingen Profil",
"inviteDateCreated": "Oprettet", "inviteDateCreated": "Oprettet",
"inviteRemainingUses": "Resterende anvendelser",
"inviteNoInvites": "Ingen", "inviteNoInvites": "Ingen",
"inviteExpiresInTime": "Udløber om {n}", "inviteExpiresInTime": "Udløber om {n}",
"notifyEvent": "Meddel den:", "notifyEvent": "Meddel den:",

View File

@ -53,6 +53,7 @@
"inviteUsersCreated": "Erstellte Benutzer", "inviteUsersCreated": "Erstellte Benutzer",
"inviteNoProfile": "Kein Profil", "inviteNoProfile": "Kein Profil",
"inviteDateCreated": "Erstellt", "inviteDateCreated": "Erstellt",
"inviteRemainingUses": "Verbleibende Verwendungen",
"inviteNoInvites": "Keine", "inviteNoInvites": "Keine",
"inviteExpiresInTime": "Läuft in {n} ab", "inviteExpiresInTime": "Läuft in {n} ab",
"notifyEvent": "Benachrichtigen bei:", "notifyEvent": "Benachrichtigen bei:",

View File

@ -56,6 +56,7 @@
"inviteUsersCreated": "Δημιουργηθέντες χρήστες", "inviteUsersCreated": "Δημιουργηθέντες χρήστες",
"inviteNoProfile": "Κανένα Προφίλ", "inviteNoProfile": "Κανένα Προφίλ",
"inviteDateCreated": "Δημιουργηθέντα", "inviteDateCreated": "Δημιουργηθέντα",
"inviteRemainingUses": "Εναπομείναντες χρήσεις",
"inviteNoInvites": "Καμία", "inviteNoInvites": "Καμία",
"inviteExpiresInTime": "Λήγει σε {n}", "inviteExpiresInTime": "Λήγει σε {n}",
"notifyEvent": "Ενημέρωση όταν:", "notifyEvent": "Ενημέρωση όταν:",

View File

@ -124,6 +124,7 @@
"addProfileStoreHomescreenLayout": "Store homescreen layout", "addProfileStoreHomescreenLayout": "Store homescreen layout",
"inviteNoUsersCreated": "None yet!", "inviteNoUsersCreated": "None yet!",
"inviteUsersCreated": "Created users", "inviteUsersCreated": "Created users",
"inviteRemainingUses": "Remaining uses",
"inviteNoInvites": "None", "inviteNoInvites": "None",
"inviteExpiresInTime": "Expires in {n}", "inviteExpiresInTime": "Expires in {n}",
"notifyEvent": "Notify on:", "notifyEvent": "Notify on:",

View File

@ -65,7 +65,6 @@
"modifySettings": "Modify Settings", "modifySettings": "Modify Settings",
"modifySettingsDescription": "Apply settings from an existing profile, or source them directly from a user.", "modifySettingsDescription": "Apply settings from an existing profile, or source them directly from a user.",
"enableReferrals": "Enable Referrals", "enableReferrals": "Enable Referrals",
"disableReferrals": "Disable Referrals",
"enableReferralsDescription": "Give users a personal referral link similiar to an invite, to send to friends/family. Can be sourced from a referral template in a profile, or from an existing invite.", "enableReferralsDescription": "Give users a personal referral link similiar to an invite, to send to friends/family. Can be sourced from a referral template in a profile, or from an existing invite.",
"enableReferralsProfileDescription": "Give users created with this profile a personal referral link similiar to an invite, to send to friends/family. Create an invite with the desired settings, then select it here. Each referral will then be based on this invite. You can delete the invite once complete.", "enableReferralsProfileDescription": "Give users created with this profile a personal referral link similiar to an invite, to send to friends/family. Create an invite with the desired settings, then select it here. Each referral will then be based on this invite. You can delete the invite once complete.",
"applyHomescreenLayout": "Apply homescreen layout", "applyHomescreenLayout": "Apply homescreen layout",
@ -95,6 +94,7 @@
"inviteUsersCreated": "Created users", "inviteUsersCreated": "Created users",
"inviteNoProfile": "No Profile", "inviteNoProfile": "No Profile",
"inviteDateCreated": "Created", "inviteDateCreated": "Created",
"inviteRemainingUses": "Remaining uses",
"inviteNoInvites": "None", "inviteNoInvites": "None",
"inviteExpiresInTime": "Expires in {n}", "inviteExpiresInTime": "Expires in {n}",
"notifyEvent": "Notify on:", "notifyEvent": "Notify on:",
@ -157,7 +157,6 @@
"errorSendWelcomeEmail": "Failed to send welcome message (check console/logs)", "errorSendWelcomeEmail": "Failed to send welcome message (check console/logs)",
"errorApplyUpdate": "Failed to apply update, try manually.", "errorApplyUpdate": "Failed to apply update, try manually.",
"errorCheckUpdate": "Failed to check for update.", "errorCheckUpdate": "Failed to check for update.",
"errorNoReferralTemplate": "Profile doesn't contain referral template, add one in settings.",
"updateAvailable": "A new update is available, check settings.", "updateAvailable": "A new update is available, check settings.",
"noUpdatesAvailable": "No new updates available." "noUpdatesAvailable": "No new updates available."
}, },

View File

@ -75,6 +75,7 @@
"inviteUsersCreated": "Usuarios creados", "inviteUsersCreated": "Usuarios creados",
"inviteNoProfile": "Sin perfil", "inviteNoProfile": "Sin perfil",
"inviteDateCreated": "Creado", "inviteDateCreated": "Creado",
"inviteRemainingUses": "Usos restantes",
"inviteNoInvites": "Ninguno", "inviteNoInvites": "Ninguno",
"inviteExpiresInTime": "Caduca en {n}", "inviteExpiresInTime": "Caduca en {n}",
"notifyEvent": "Notificar en:", "notifyEvent": "Notificar en:",

View File

@ -55,6 +55,7 @@
"inviteUsersCreated": "Utilisateurs créés", "inviteUsersCreated": "Utilisateurs créés",
"inviteNoProfile": "Aucun profil", "inviteNoProfile": "Aucun profil",
"inviteDateCreated": "Créer", "inviteDateCreated": "Créer",
"inviteRemainingUses": "Utilisations restantes",
"inviteNoInvites": "Aucune", "inviteNoInvites": "Aucune",
"inviteExpiresInTime": "Expires dans {n}", "inviteExpiresInTime": "Expires dans {n}",
"notifyEvent": "Notifier sur :", "notifyEvent": "Notifier sur :",

View File

@ -87,6 +87,7 @@
"inviteUsersCreated": "", "inviteUsersCreated": "",
"inviteNoProfile": "", "inviteNoProfile": "",
"inviteDateCreated": "", "inviteDateCreated": "",
"inviteRemainingUses": "",
"inviteNoInvites": "", "inviteNoInvites": "",
"inviteExpiresInTime": "", "inviteExpiresInTime": "",
"notifyEvent": "", "notifyEvent": "",

View File

@ -56,6 +56,7 @@
"inviteUsersCreated": "Pengguna yang telah dibuat", "inviteUsersCreated": "Pengguna yang telah dibuat",
"inviteNoProfile": "Tidak ada profil", "inviteNoProfile": "Tidak ada profil",
"inviteDateCreated": "Dibuat", "inviteDateCreated": "Dibuat",
"inviteRemainingUses": "Penggunaan yang tersisa",
"inviteNoInvites": "Tidak ada", "inviteNoInvites": "Tidak ada",
"inviteExpiresInTime": "Kadaluarsa dalam {n}", "inviteExpiresInTime": "Kadaluarsa dalam {n}",
"notifyEvent": "Beritahu pada:", "notifyEvent": "Beritahu pada:",

View File

@ -53,6 +53,7 @@
"inviteUsersCreated": "Aangemaakte gebruikers", "inviteUsersCreated": "Aangemaakte gebruikers",
"inviteNoProfile": "Geen profiel", "inviteNoProfile": "Geen profiel",
"inviteDateCreated": "Aangemaakt", "inviteDateCreated": "Aangemaakt",
"inviteRemainingUses": "Resterend aantal keer te gebruiken",
"inviteNoInvites": "Geen", "inviteNoInvites": "Geen",
"inviteExpiresInTime": "Verloopt over {n}", "inviteExpiresInTime": "Verloopt over {n}",
"notifyEvent": "Meldingen:", "notifyEvent": "Meldingen:",

View File

@ -87,6 +87,7 @@
"inviteUsersCreated": "", "inviteUsersCreated": "",
"inviteNoProfile": "", "inviteNoProfile": "",
"inviteDateCreated": "Utworzone", "inviteDateCreated": "Utworzone",
"inviteRemainingUses": "",
"inviteNoInvites": "", "inviteNoInvites": "",
"inviteExpiresInTime": "", "inviteExpiresInTime": "",
"notifyEvent": "", "notifyEvent": "",

View File

@ -54,6 +54,7 @@
"inviteUsersCreated": "Usuários criado", "inviteUsersCreated": "Usuários criado",
"inviteNoProfile": "Sem Perfil", "inviteNoProfile": "Sem Perfil",
"inviteDateCreated": "Criado", "inviteDateCreated": "Criado",
"inviteRemainingUses": "Uso restantes",
"inviteNoInvites": "Nenhum", "inviteNoInvites": "Nenhum",
"inviteExpiresInTime": "Expira em {n}", "inviteExpiresInTime": "Expira em {n}",
"notifyEvent": "Notificar em:", "notifyEvent": "Notificar em:",

View File

@ -65,6 +65,7 @@
"inviteUsersCreated": "Skapade användare", "inviteUsersCreated": "Skapade användare",
"inviteNoProfile": "Ingen profil", "inviteNoProfile": "Ingen profil",
"inviteDateCreated": "Skapad", "inviteDateCreated": "Skapad",
"inviteRemainingUses": "Återstående användningar",
"inviteNoInvites": "Ingen", "inviteNoInvites": "Ingen",
"inviteExpiresInTime": "Går ut om {n}", "inviteExpiresInTime": "Går ut om {n}",
"notifyEvent": "Meddela den:", "notifyEvent": "Meddela den:",

View File

@ -86,6 +86,7 @@
"inviteUsersCreated": "Người dùng đã tạo", "inviteUsersCreated": "Người dùng đã tạo",
"inviteNoProfile": "Không có Tài khoản mẫu", "inviteNoProfile": "Không có Tài khoản mẫu",
"inviteDateCreated": "Tạo", "inviteDateCreated": "Tạo",
"inviteRemainingUses": "Số lần sử dụng còn lại",
"inviteNoInvites": "Không có", "inviteNoInvites": "Không có",
"inviteExpiresInTime": "Hết hạn trong {n}", "inviteExpiresInTime": "Hết hạn trong {n}",
"notifyEvent": "Thông báo khi:", "notifyEvent": "Thông báo khi:",

View File

@ -80,6 +80,7 @@
"inviteUsersCreated": "已创建的用户", "inviteUsersCreated": "已创建的用户",
"inviteNoProfile": "没有个人资料", "inviteNoProfile": "没有个人资料",
"inviteDateCreated": "已创建", "inviteDateCreated": "已创建",
"inviteRemainingUses": "剩余使用次数",
"inviteNoInvites": "无", "inviteNoInvites": "无",
"inviteExpiresInTime": "在 {n} 到期", "inviteExpiresInTime": "在 {n} 到期",
"notifyEvent": "通知:", "notifyEvent": "通知:",

View File

@ -87,6 +87,7 @@
"inviteUsersCreated": "創建的帳戶", "inviteUsersCreated": "創建的帳戶",
"inviteNoProfile": "無資料", "inviteNoProfile": "無資料",
"inviteDateCreated": "已創建", "inviteDateCreated": "已創建",
"inviteRemainingUses": "剩餘使用次數",
"inviteNoInvites": "無", "inviteNoInvites": "無",
"inviteExpiresInTime": "在 {n} 到期", "inviteExpiresInTime": "在 {n} 到期",
"notifyEvent": "通知:", "notifyEvent": "通知:",

View File

@ -35,8 +35,7 @@
"expiry": "Udløb", "expiry": "Udløb",
"add": "Tilføj", "add": "Tilføj",
"edit": "Rediger", "edit": "Rediger",
"delete": "Slet", "delete": "Slet"
"inviteRemainingUses": "Resterende anvendelser"
}, },
"notifications": { "notifications": {
"errorLoginBlank": "Brugernavnet og/eller adgangskoden blev efterladt tomme.", "errorLoginBlank": "Brugernavnet og/eller adgangskoden blev efterladt tomme.",

View File

@ -35,8 +35,7 @@
"expiry": "Ablaufdatum", "expiry": "Ablaufdatum",
"add": "Hinzufügen", "add": "Hinzufügen",
"edit": "Bearbeiten", "edit": "Bearbeiten",
"delete": "Löschen", "delete": "Löschen"
"inviteRemainingUses": "Verbleibende Verwendungen"
}, },
"notifications": { "notifications": {
"errorLoginBlank": "Der Benutzername und/oder das Passwort wurden nicht ausgefüllt.", "errorLoginBlank": "Der Benutzername und/oder das Passwort wurden nicht ausgefüllt.",

View File

@ -25,8 +25,7 @@
"disable": "Απενεργοποίηση", "disable": "Απενεργοποίηση",
"expiry": "Λήξη", "expiry": "Λήξη",
"edit": "Επεξεργασία", "edit": "Επεξεργασία",
"delete": "Διαγραφή", "delete": "Διαγραφή"
"inviteRemainingUses": "Εναπομείναντες χρήσεις"
}, },
"notifications": { "notifications": {
"errorLoginBlank": "Το όνομα χρήστη και/ή ο κωδικός ήταν κενά.", "errorLoginBlank": "Το όνομα χρήστη και/ή ο κωδικός ήταν κενά.",

View File

@ -35,8 +35,7 @@
"expiry": "Expiry", "expiry": "Expiry",
"add": "Add", "add": "Add",
"edit": "Edit", "edit": "Edit",
"delete": "Delete", "delete": "Delete"
"inviteRemainingUses": "Remaining uses"
}, },
"notifications": { "notifications": {
"errorLoginBlank": "The username and/or password was left blank.", "errorLoginBlank": "The username and/or password was left blank.",

View File

@ -40,8 +40,7 @@
"edit": "Edit", "edit": "Edit",
"delete": "Delete", "delete": "Delete",
"myAccount": "My Account", "myAccount": "My Account",
"referrals": "Referrals", "referrals": "Referrals"
"inviteRemainingUses": "Remaining uses"
}, },
"notifications": { "notifications": {
"errorLoginBlank": "The username and/or password were left blank.", "errorLoginBlank": "The username and/or password were left blank.",

View File

@ -35,8 +35,7 @@
"expiry": "Expiración", "expiry": "Expiración",
"add": "Agregar", "add": "Agregar",
"edit": "Editar", "edit": "Editar",
"delete": "Eliminar", "delete": "Eliminar"
"inviteRemainingUses": "Usos restantes"
}, },
"notifications": { "notifications": {
"errorLoginBlank": "El nombre de usuario y/o la contraseña se dejaron en blanco.", "errorLoginBlank": "El nombre de usuario y/o la contraseña se dejaron en blanco.",

View File

@ -35,8 +35,7 @@
"expiry": "Expiration", "expiry": "Expiration",
"add": "Ajouter", "add": "Ajouter",
"edit": "Éditer", "edit": "Éditer",
"delete": "Effacer", "delete": "Effacer"
"inviteRemainingUses": "Utilisations restantes"
}, },
"notifications": { "notifications": {
"errorLoginBlank": "Le nom d'utilisateur et/ou le mot de passe sont vides.", "errorLoginBlank": "Le nom d'utilisateur et/ou le mot de passe sont vides.",

View File

@ -19,8 +19,7 @@
"login": "Masuk", "login": "Masuk",
"logout": "Keluar", "logout": "Keluar",
"edit": "Edit", "edit": "Edit",
"delete": "Hapus", "delete": "Hapus"
"inviteRemainingUses": "Penggunaan yang tersisa"
}, },
"notifications": { "notifications": {
"errorLoginBlank": "Nama pengguna dan / atau sandi kosong.", "errorLoginBlank": "Nama pengguna dan / atau sandi kosong.",

View File

@ -1,8 +0,0 @@
{
"meta": {
"name": "Nedderdütsch (NDS)"
},
"strings": {},
"notifications": {},
"quantityStrings": {}
}

View File

@ -35,8 +35,7 @@
"expiry": "Verloop", "expiry": "Verloop",
"add": "Voeg toe", "add": "Voeg toe",
"edit": "Bewerken", "edit": "Bewerken",
"delete": "Verwijderen", "delete": "Verwijderen"
"inviteRemainingUses": "Resterend aantal keer te gebruiken"
}, },
"notifications": { "notifications": {
"errorLoginBlank": "De gebruikersnaam en/of wachtwoord is leeg.", "errorLoginBlank": "De gebruikersnaam en/of wachtwoord is leeg.",

View File

@ -35,8 +35,7 @@
"expiry": "Expira", "expiry": "Expira",
"add": "Adicionar", "add": "Adicionar",
"edit": "Editar", "edit": "Editar",
"delete": "Deletar", "delete": "Deletar"
"inviteRemainingUses": "Uso restantes"
}, },
"notifications": { "notifications": {
"errorLoginBlank": "O nome de usuário e/ou senha foram deixados em branco.", "errorLoginBlank": "O nome de usuário e/ou senha foram deixados em branco.",

View File

@ -22,8 +22,7 @@
"disabled": "Inaktiverad", "disabled": "Inaktiverad",
"expiry": "Löper ut", "expiry": "Löper ut",
"edit": "Redigera", "edit": "Redigera",
"delete": "Radera", "delete": "Radera"
"inviteRemainingUses": "Återstående användningar"
}, },
"notifications": { "notifications": {
"errorLoginBlank": "Användarnamnet och/eller lösenordet lämnades tomt.", "errorLoginBlank": "Användarnamnet och/eller lösenordet lämnades tomt.",

View File

@ -13,8 +13,7 @@
"expiry": "Hết hạn", "expiry": "Hết hạn",
"add": "Thêm", "add": "Thêm",
"edit": "Chỉnh sửa", "edit": "Chỉnh sửa",
"delete": "Xóa", "delete": "Xóa"
"inviteRemainingUses": "Số lần sử dụng còn lại"
}, },
"notifications": { "notifications": {
"errorConnection": "Không thể kết nối với jfa-go.", "errorConnection": "Không thể kết nối với jfa-go.",

View File

@ -35,8 +35,7 @@
"expiry": "到期", "expiry": "到期",
"add": "添加", "add": "添加",
"edit": "编辑", "edit": "编辑",
"delete": "删除", "delete": "删除"
"inviteRemainingUses": "剩余使用次数"
}, },
"notifications": { "notifications": {
"errorLoginBlank": "用户名/密码留空。", "errorLoginBlank": "用户名/密码留空。",

View File

@ -35,8 +35,7 @@
"expiry": "到期", "expiry": "到期",
"add": "添加", "add": "添加",
"edit": "編輯", "edit": "編輯",
"delete": "刪除", "delete": "刪除"
"inviteRemainingUses": "剩餘使用次數"
}, },
"notifications": { "notifications": {
"errorLoginBlank": "帳戶名稱和/或密碼留空。", "errorLoginBlank": "帳戶名稱和/或密碼留空。",

View File

@ -34,9 +34,7 @@
"resetPasswordThroughLink": "To reset your password, enter your username, email address or a linked contact method username, and submit. A link will be sent to reset your password.", "resetPasswordThroughLink": "To reset your password, enter your username, email address or a linked contact method username, and submit. A link will be sent to reset your password.",
"resetSent": "Reset Sent.", "resetSent": "Reset Sent.",
"resetSentDescription": "If an account with the given username/contact method exists, a password reset link has been sent via all contact methods available. The code will expire in 30 minutes.", "resetSentDescription": "If an account with the given username/contact method exists, a password reset link has been sent via all contact methods available. The code will expire in 30 minutes.",
"changePassword": "Change Password", "changePassword": "Change Password"
"referralsDescription": "Invite friends & family to Jellyfin with this link. Come back here for a new one if it expires.",
"copyReferral": "Copy Link"
}, },
"notifications": { "notifications": {
"errorUserExists": "User already exists.", "errorUserExists": "User already exists.",

View File

@ -390,7 +390,6 @@ type MyDetailsDTO struct {
Discord *MyDetailsContactMethodsDTO `json:"discord,omitempty"` Discord *MyDetailsContactMethodsDTO `json:"discord,omitempty"`
Telegram *MyDetailsContactMethodsDTO `json:"telegram,omitempty"` Telegram *MyDetailsContactMethodsDTO `json:"telegram,omitempty"`
Matrix *MyDetailsContactMethodsDTO `json:"matrix,omitempty"` Matrix *MyDetailsContactMethodsDTO `json:"matrix,omitempty"`
HasReferrals bool `json:"has_referrals,omitempty"`
} }
type MyDetailsContactMethodsDTO struct { type MyDetailsContactMethodsDTO struct {
@ -420,9 +419,9 @@ type ChangeMyPasswordDTO struct {
type GetMyReferralRespDTO struct { type GetMyReferralRespDTO struct {
Code string `json:"code"` Code string `json:"code"`
RemainingUses int `json:"remaining_uses"` RemainingUses int `json:"remaining-uses"`
NoLimit bool `json:"no_limit"` NoLimit bool `json:"no-limit"`
Expiry int64 `json:"expiry"` // Come back after this time to get a new referral Expiry time.Time `json:"expiry"` // Come back after this time to get a new referral
} }
type EnableDisableReferralDTO struct { type EnableDisableReferralDTO struct {

View File

@ -228,7 +228,6 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
api.POST(p+"/matrix/login", app.MatrixLogin) api.POST(p+"/matrix/login", app.MatrixLogin)
if app.config.Section("user_page").Key("referrals").MustBool(false) { if app.config.Section("user_page").Key("referrals").MustBool(false) {
api.POST(p+"/users/referral/:mode/:source", app.EnableReferralForUsers) api.POST(p+"/users/referral/:mode/:source", app.EnableReferralForUsers)
api.DELETE(p+"/users/referral", app.DisableReferralForUsers)
api.POST(p+"/profiles/referral/:profile/:invite", app.EnableReferralForProfile) api.POST(p+"/profiles/referral/:profile/:invite", app.EnableReferralForProfile)
api.DELETE(p+"/profiles/referral/:profile", app.DisableReferralForProfile) api.DELETE(p+"/profiles/referral/:profile", app.DisableReferralForProfile)
} }

View File

@ -38,10 +38,7 @@
"expiry": "common", "expiry": "common",
"add": "common", "add": "common",
"edit": "common", "edit": "common",
"delete": "common", "delete": "admin"
"myAccount": "common",
"referrals": "common",
"inviteRemainingUses": "admin"
}, },
"notifications": { "notifications": {
"errorLoginBlank": "common", "errorLoginBlank": "common",

View File

@ -935,14 +935,6 @@ export class accountsList {
bool: true, bool: true,
string: false, string: false,
date: true date: true
},
"referrals-enabled": {
name: window.lang.strings("referrals"),
getter: "referrals_enabled",
bool: true,
string: false,
date: false,
dependsOnTableHeader: "accounts-header-referrals"
} }
} }
@ -1225,7 +1217,6 @@ export class accountsList {
let anyNonExpiries = list.length == 0 ? true : false; let anyNonExpiries = list.length == 0 ? true : false;
let allNonExpiries = true; let allNonExpiries = true;
let noContactCount = 0; let noContactCount = 0;
let referralState = Number(this._users[list[0]].referrals_enabled); // -1 = hide, 0 = show "enable", 1 = show "disable"
// Only show enable/disable button if all selected have the same state. // Only show enable/disable button if all selected have the same state.
this._shouldEnable = this._users[list[0]].disabled this._shouldEnable = this._users[list[0]].disabled
let showDisableEnable = true; let showDisableEnable = true;
@ -1245,9 +1236,6 @@ export class accountsList {
if (!this._users[id].lastNotifyMethod()) { if (!this._users[id].lastNotifyMethod()) {
noContactCount++; noContactCount++;
} }
if (window.referralsEnabled && referralState != -1 && Number(this._users[id].referrals_enabled) != referralState) {
referralState = -1;
}
} }
this._settingExpiry = false; this._settingExpiry = false;
if (!anyNonExpiries && !allNonExpiries) { if (!anyNonExpiries && !allNonExpiries) {
@ -1281,22 +1269,6 @@ export class accountsList {
this._disableEnable.parentElement.classList.remove("unfocused"); this._disableEnable.parentElement.classList.remove("unfocused");
this._disableEnable.textContent = message; this._disableEnable.textContent = message;
} }
if (window.referralsEnabled) {
if (referralState == -1) {
this._enableReferrals.classList.add("unfocused");
} else {
this._enableReferrals.classList.remove("unfocused");
}
if (referralState == 0) {
this._enableReferrals.classList.add("~urge");
this._enableReferrals.classList.remove("~warning");
this._enableReferrals.textContent = window.lang.strings("enableReferrals");
} else if (referralState == 1) {
this._enableReferrals.classList.add("~warning");
this._enableReferrals.classList.remove("~urge");
this._enableReferrals.textContent = window.lang.strings("disableReferrals");
}
}
} }
} }
@ -1728,17 +1700,6 @@ export class accountsList {
const modalHeader = document.getElementById("header-enable-referrals-user"); const modalHeader = document.getElementById("header-enable-referrals-user");
modalHeader.textContent = window.lang.quantity("enableReferralsFor", this._collectUsers().length) modalHeader.textContent = window.lang.quantity("enableReferralsFor", this._collectUsers().length)
let list = this._collectUsers(); let list = this._collectUsers();
// Check if we're disabling or enabling
if (this._users[list[0]].referrals_enabled) {
_delete("/users/referral", {"users": list}, (req: XMLHttpRequest) => {
if (req.readyState != 4 || req.status != 200) return;
window.notifications.customSuccess("disabledReferralsSuccess", window.lang.quantity("appliedSettings", list.length));
this.reload();
});
return;
}
(() => { (() => {
_get("/invites", null, (req: XMLHttpRequest) => { _get("/invites", null, (req: XMLHttpRequest) => {
if (req.readyState != 4 || req.status != 200) return; if (req.readyState != 4 || req.status != 200) return;
@ -1794,9 +1755,9 @@ export class accountsList {
if (req.readyState == 4) { if (req.readyState == 4) {
toggleLoader(button); toggleLoader(button);
if (req.status == 400) { if (req.status == 400) {
window.notifications.customError("noReferralTemplateError", window.lang.notif("errorNoReferralTemplate")); window.notifications.customError("unknownError", window.lang.notif("errorUnknown"));
} else if (req.status == 200 || req.status == 204) { } else if (req.status == 200 || req.status == 204) {
window.notifications.customSuccess("enableReferralsSuccess", window.lang.quantity("appliedSettings", list.length)); window.notifications.customSuccess("enableReferralsSuccess", window.lang.quantity("appliedSettings", this._collectUsers().length));
} }
this.reload(); this.reload();
window.modals.enableReferralsUser.close(); window.modals.enableReferralsUser.close();

View File

@ -1,7 +1,7 @@
import { ThemeManager } from "./modules/theme.js"; import { ThemeManager } from "./modules/theme.js";
import { lang, LangFile, loadLangSelector } from "./modules/lang.js"; import { lang, LangFile, loadLangSelector } from "./modules/lang.js";
import { Modal } from "./modules/modal.js"; import { Modal } from "./modules/modal.js";
import { _get, _post, _delete, notificationBox, whichAnimationEvent, toDateString, toggleLoader, addLoader, removeLoader, toClipboard } from "./modules/common.js"; import { _get, _post, _delete, notificationBox, whichAnimationEvent, toDateString, toggleLoader, addLoader, removeLoader } from "./modules/common.js";
import { Login } from "./modules/login.js"; import { Login } from "./modules/login.js";
import { Discord, Telegram, Matrix, ServiceConfiguration, MatrixConfiguration } from "./modules/account-linking.js"; import { Discord, Telegram, Matrix, ServiceConfiguration, MatrixConfiguration } from "./modules/account-linking.js";
import { Validator, ValidatorConf, ValidatorRespDTO } from "./modules/validator.js"; import { Validator, ValidatorConf, ValidatorRespDTO } from "./modules/validator.js";
@ -18,7 +18,6 @@ interface userWindow extends Window {
matrixUserID: string; matrixUserID: string;
discordSendPINMessage: string; discordSendPINMessage: string;
pwrEnabled: string; pwrEnabled: string;
referralsEnabled: boolean;
} }
declare var window: userWindow; declare var window: userWindow;
@ -108,14 +107,6 @@ interface MyDetails {
discord?: MyDetailsContactMethod; discord?: MyDetailsContactMethod;
telegram?: MyDetailsContactMethod; telegram?: MyDetailsContactMethod;
matrix?: MyDetailsContactMethod; matrix?: MyDetailsContactMethod;
has_referrals: boolean;
}
interface MyReferral {
code: string;
remaining_uses: string;
no_limit: boolean;
expiry: number;
} }
interface ContactDTO { interface ContactDTO {
@ -372,8 +363,7 @@ const discordConf: ServiceConfiguration = {
} }
}; };
let discord: Discord; let discord = new Discord(discordConf);
if (window.discordEnabled) discord = new Discord(discordConf);
const telegramConf: ServiceConfiguration = { const telegramConf: ServiceConfiguration = {
modal: window.modals.telegram as Modal, modal: window.modals.telegram as Modal,
@ -388,8 +378,7 @@ const telegramConf: ServiceConfiguration = {
} }
}; };
let telegram: Telegram; let telegram = new Telegram(telegramConf);
if (window.telegramEnabled) telegram = new Telegram(telegramConf);
const matrixConf: MatrixConfiguration = { const matrixConf: MatrixConfiguration = {
modal: window.modals.matrix as Modal, modal: window.modals.matrix as Modal,
@ -404,8 +393,7 @@ const matrixConf: MatrixConfiguration = {
} }
}; };
let matrix: Matrix; let matrix = new Matrix(matrixConf);
if (window.matrixEnabled) matrix = new Matrix(matrixConf);
const oldPasswordField = document.getElementById("user-old-password") as HTMLInputElement; const oldPasswordField = document.getElementById("user-old-password") as HTMLInputElement;
@ -480,15 +468,14 @@ document.addEventListener("details-reload", () => {
// Note the weird format of the functions for discord/telegram: // Note the weird format of the functions for discord/telegram:
// "this" was being redefined within the onclick() method, so // "this" was being redefined within the onclick() method, so
// they had to be wrapped in an anonymous function. // they had to be wrapped in an anonymous function.
const contactMethods: { name: string, icon: string, f: (add: boolean) => void, required: boolean, enabled: boolean }[] = [ const contactMethods: { name: string, icon: string, f: (add: boolean) => void, required: boolean }[] = [
{name: "email", icon: `<i class="ri-mail-fill ri-lg"></i>`, f: addEditEmail, required: true, enabled: true}, {name: "email", icon: `<i class="ri-mail-fill ri-lg"></i>`, f: addEditEmail, required: true},
{name: "discord", icon: `<i class="ri-discord-fill ri-lg"></i>`, f: (add: boolean) => { discord.onclick(); }, required: window.discordRequired, enabled: window.discordEnabled}, {name: "discord", icon: `<i class="ri-discord-fill ri-lg"></i>`, f: (add: boolean) => { discord.onclick(); }, required: window.discordRequired},
{name: "telegram", icon: `<i class="ri-telegram-fill ri-lg"></i>`, f: (add: boolean) => { telegram.onclick() }, required: window.telegramRequired, enabled: window.telegramEnabled}, {name: "telegram", icon: `<i class="ri-telegram-fill ri-lg"></i>`, f: (add: boolean) => { telegram.onclick() }, required: window.telegramRequired},
{name: "matrix", icon: `<span class="font-bold">[m]</span>`, f: (add: boolean) => { matrix.show(); }, required: window.matrixRequired, enabled: window.matrixEnabled} {name: "matrix", icon: `<span class="font-bold">[m]</span>`, f: (add: boolean) => { matrix.show(); }, required: window.matrixRequired}
]; ];
for (let method of contactMethods) { for (let method of contactMethods) {
if (!(method.enabled)) continue;
if (method.name in details) { if (method.name in details) {
contactMethodList.append(method.name, details[method.name], method.icon, method.f, method.required); contactMethodList.append(method.name, details[method.name], method.icon, method.f, method.required);
} }
@ -522,62 +509,6 @@ document.addEventListener("details-reload", () => {
} else if (!statusCard.classList.contains("unfocused")) { } else if (!statusCard.classList.contains("unfocused")) {
setBestRowSpan(passwordCard, true); setBestRowSpan(passwordCard, true);
} }
let referralCard = document.getElementById("card-referrals");
if (window.referralsEnabled && typeof(referralCard) != "undefined" && referralCard != null) {
if (details.has_referrals) {
_get("/my/referral", null, (req: XMLHttpRequest) => {
if (req.readyState != 4 || req.status != 200) return;
const referral: MyReferral = req.response as MyReferral;
const infoArea = referralCard.querySelector(".user-referrals-info") as HTMLDivElement;
infoArea.innerHTML = `
<div class="row my-3">
<div class="inline baseline">
<span class="text-2xl">${referral.no_limit ? "∞" : referral.remaining_uses}</span> <span class="text-gray-400 text-lg">${window.lang.strings("inviteRemainingUses")}</span>
</div>
</div>
<div class="row my-3">
<div class="inline baseline">
<span class="text-gray-400 text-lg">${window.lang.strings("expiry")}</span> <span class="text-2xl">${toDateString(new Date(referral.expiry * 1000))}</span>
<div>
</div>
`;
const linkButton = referralCard.querySelector(".user-referrals-button") as HTMLButtonElement;
let codeLink = window.location.href;
for (let split of ["#", "?", "account", "my"]) {
codeLink = codeLink.split(split)[0];
}
if (codeLink.slice(-1) != "/") { codeLink += "/"; }
codeLink = codeLink + "invite/" + referral.code;
linkButton.addEventListener("click", () => {
toClipboard(codeLink);
const content = linkButton.innerHTML;
linkButton.innerHTML = `
${window.lang.strings("copied")} <i class="ri-check-line ml-2"></i>
`;
linkButton.classList.add("~positive");
linkButton.classList.remove("~info");
setTimeout(() => {
linkButton.classList.add("~info");
linkButton.classList.remove("~positive");
linkButton.innerHTML = content;
}, 2000);
});
referralCard.classList.remove("unfocused");
});
} else {
referralCard.classList.add("unfocused");
}
}
} }
}); });
}); });

View File

@ -204,7 +204,6 @@ func (app *appContext) MyUserPage(gc *gin.Context) {
"langName": lang, "langName": lang,
"jfLink": app.config.Section("ui").Key("redirect_url").String(), "jfLink": app.config.Section("ui").Key("redirect_url").String(),
"requirements": app.validator.getCriteria(), "requirements": app.validator.getCriteria(),
"referralsEnabled": app.config.Section("user_page").Key("enabled").MustBool(false) && app.config.Section("user_page").Key("referrals").MustBool(false),
} }
if telegramEnabled { if telegramEnabled {
data["telegramUsername"] = app.telegram.username data["telegramUsername"] = app.telegram.username