feat: add invite registration links with configurable usage limits
Admins can create one-time registration links (1–5× or unlimited uses) with optional expiry (1d–14d or never). Recipients can register even when public registration is disabled. Atomic usage counting prevents race conditions, all endpoints are rate-limited.
This commit is contained in:
@@ -274,6 +274,24 @@ const de: Record<string, string | { name: string; category: string }[]> = {
|
||||
'admin.toast.createError': 'Fehler beim Erstellen des Benutzers',
|
||||
'admin.toast.fieldsRequired': 'Benutzername, E-Mail und Passwort sind erforderlich',
|
||||
'admin.createUser': 'Benutzer anlegen',
|
||||
'admin.invite.title': 'Einladungslinks',
|
||||
'admin.invite.subtitle': 'Einmal-Links für die Registrierung erstellen',
|
||||
'admin.invite.create': 'Link erstellen',
|
||||
'admin.invite.createAndCopy': 'Erstellen & kopieren',
|
||||
'admin.invite.empty': 'Noch keine Einladungslinks erstellt',
|
||||
'admin.invite.maxUses': 'Max. Nutzungen',
|
||||
'admin.invite.expiry': 'Gültig für',
|
||||
'admin.invite.uses': 'genutzt',
|
||||
'admin.invite.expiresAt': 'läuft ab am',
|
||||
'admin.invite.createdBy': 'von',
|
||||
'admin.invite.active': 'Aktiv',
|
||||
'admin.invite.expired': 'Abgelaufen',
|
||||
'admin.invite.usedUp': 'Aufgebraucht',
|
||||
'admin.invite.copied': 'Einladungslink in Zwischenablage kopiert',
|
||||
'admin.invite.copyLink': 'Link kopieren',
|
||||
'admin.invite.deleted': 'Einladungslink gelöscht',
|
||||
'admin.invite.createError': 'Fehler beim Erstellen des Einladungslinks',
|
||||
'admin.invite.deleteError': 'Fehler beim Löschen des Einladungslinks',
|
||||
'admin.tabs.settings': 'Einstellungen',
|
||||
'admin.allowRegistration': 'Registrierung erlauben',
|
||||
'admin.allowRegistrationHint': 'Neue Benutzer können sich selbst registrieren',
|
||||
|
||||
@@ -274,6 +274,24 @@ const en: Record<string, string | { name: string; category: string }[]> = {
|
||||
'admin.toast.createError': 'Failed to create user',
|
||||
'admin.toast.fieldsRequired': 'Username, email and password are required',
|
||||
'admin.createUser': 'Create User',
|
||||
'admin.invite.title': 'Invite Links',
|
||||
'admin.invite.subtitle': 'Create one-time registration links',
|
||||
'admin.invite.create': 'Create Link',
|
||||
'admin.invite.createAndCopy': 'Create & Copy',
|
||||
'admin.invite.empty': 'No invite links created yet',
|
||||
'admin.invite.maxUses': 'Max. Uses',
|
||||
'admin.invite.expiry': 'Expires after',
|
||||
'admin.invite.uses': 'used',
|
||||
'admin.invite.expiresAt': 'expires',
|
||||
'admin.invite.createdBy': 'by',
|
||||
'admin.invite.active': 'Active',
|
||||
'admin.invite.expired': 'Expired',
|
||||
'admin.invite.usedUp': 'Used up',
|
||||
'admin.invite.copied': 'Invite link copied to clipboard',
|
||||
'admin.invite.copyLink': 'Copy link',
|
||||
'admin.invite.deleted': 'Invite link deleted',
|
||||
'admin.invite.createError': 'Failed to create invite link',
|
||||
'admin.invite.deleteError': 'Failed to delete invite link',
|
||||
'admin.tabs.settings': 'Settings',
|
||||
'admin.allowRegistration': 'Allow Registration',
|
||||
'admin.allowRegistrationHint': 'New users can register themselves',
|
||||
|
||||
@@ -272,6 +272,24 @@ const es: Record<string, string> = {
|
||||
'admin.toast.createError': 'No se pudo crear el usuario',
|
||||
'admin.toast.fieldsRequired': 'Usuario, correo y contraseña son obligatorios',
|
||||
'admin.createUser': 'Crear usuario',
|
||||
'admin.invite.title': 'Enlaces de invitación',
|
||||
'admin.invite.subtitle': 'Crear enlaces de registro de un solo uso',
|
||||
'admin.invite.create': 'Crear enlace',
|
||||
'admin.invite.createAndCopy': 'Crear y copiar',
|
||||
'admin.invite.empty': 'No se han creado enlaces de invitación',
|
||||
'admin.invite.maxUses': 'Usos máx.',
|
||||
'admin.invite.expiry': 'Expira después de',
|
||||
'admin.invite.uses': 'usado(s)',
|
||||
'admin.invite.expiresAt': 'expira el',
|
||||
'admin.invite.createdBy': 'por',
|
||||
'admin.invite.active': 'Activo',
|
||||
'admin.invite.expired': 'Expirado',
|
||||
'admin.invite.usedUp': 'Agotado',
|
||||
'admin.invite.copied': 'Enlace de invitación copiado',
|
||||
'admin.invite.copyLink': 'Copiar enlace',
|
||||
'admin.invite.deleted': 'Enlace de invitación eliminado',
|
||||
'admin.invite.createError': 'Error al crear el enlace',
|
||||
'admin.invite.deleteError': 'Error al eliminar el enlace',
|
||||
'admin.tabs.settings': 'Ajustes',
|
||||
'admin.allowRegistration': 'Permitir el registro',
|
||||
'admin.allowRegistrationHint': 'Los nuevos usuarios pueden registrarse por sí mismos',
|
||||
|
||||
@@ -274,6 +274,24 @@ const fr: Record<string, string> = {
|
||||
'admin.toast.createError': 'Échec de la création de l\'utilisateur',
|
||||
'admin.toast.fieldsRequired': 'Le nom d\'utilisateur, l\'e-mail et le mot de passe sont requis',
|
||||
'admin.createUser': 'Créer un utilisateur',
|
||||
'admin.invite.title': 'Liens d\'invitation',
|
||||
'admin.invite.subtitle': 'Créer des liens d\'inscription à usage unique',
|
||||
'admin.invite.create': 'Créer un lien',
|
||||
'admin.invite.createAndCopy': 'Créer et copier',
|
||||
'admin.invite.empty': 'Aucun lien d\'invitation créé',
|
||||
'admin.invite.maxUses': 'Utilisations max.',
|
||||
'admin.invite.expiry': 'Expire après',
|
||||
'admin.invite.uses': 'utilisé(s)',
|
||||
'admin.invite.expiresAt': 'expire le',
|
||||
'admin.invite.createdBy': 'par',
|
||||
'admin.invite.active': 'Actif',
|
||||
'admin.invite.expired': 'Expiré',
|
||||
'admin.invite.usedUp': 'Épuisé',
|
||||
'admin.invite.copied': 'Lien d\'invitation copié',
|
||||
'admin.invite.copyLink': 'Copier le lien',
|
||||
'admin.invite.deleted': 'Lien d\'invitation supprimé',
|
||||
'admin.invite.createError': 'Erreur lors de la création du lien',
|
||||
'admin.invite.deleteError': 'Erreur lors de la suppression du lien',
|
||||
'admin.tabs.settings': 'Paramètres',
|
||||
'admin.allowRegistration': 'Autoriser les inscriptions',
|
||||
'admin.allowRegistrationHint': 'Les nouveaux utilisateurs peuvent s\'inscrire eux-mêmes',
|
||||
|
||||
@@ -274,6 +274,24 @@ const nl: Record<string, string> = {
|
||||
'admin.toast.createError': 'Gebruiker aanmaken mislukt',
|
||||
'admin.toast.fieldsRequired': 'Gebruikersnaam, e-mail en wachtwoord zijn verplicht',
|
||||
'admin.createUser': 'Gebruiker aanmaken',
|
||||
'admin.invite.title': 'Uitnodigingslinks',
|
||||
'admin.invite.subtitle': 'Eenmalige registratielinks aanmaken',
|
||||
'admin.invite.create': 'Link aanmaken',
|
||||
'admin.invite.createAndCopy': 'Aanmaken en kopiëren',
|
||||
'admin.invite.empty': 'Nog geen uitnodigingslinks aangemaakt',
|
||||
'admin.invite.maxUses': 'Max. gebruik',
|
||||
'admin.invite.expiry': 'Verloopt na',
|
||||
'admin.invite.uses': 'gebruikt',
|
||||
'admin.invite.expiresAt': 'verloopt op',
|
||||
'admin.invite.createdBy': 'door',
|
||||
'admin.invite.active': 'Actief',
|
||||
'admin.invite.expired': 'Verlopen',
|
||||
'admin.invite.usedUp': 'Opgebruikt',
|
||||
'admin.invite.copied': 'Uitnodigingslink gekopieerd',
|
||||
'admin.invite.copyLink': 'Link kopiëren',
|
||||
'admin.invite.deleted': 'Uitnodigingslink verwijderd',
|
||||
'admin.invite.createError': 'Fout bij aanmaken van link',
|
||||
'admin.invite.deleteError': 'Fout bij verwijderen van link',
|
||||
'admin.tabs.settings': 'Instellingen',
|
||||
'admin.allowRegistration': 'Registratie toestaan',
|
||||
'admin.allowRegistrationHint': 'Nieuwe gebruikers kunnen zichzelf registreren',
|
||||
|
||||
@@ -274,6 +274,24 @@ const ru: Record<string, string> = {
|
||||
'admin.toast.createError': 'Ошибка создания пользователя',
|
||||
'admin.toast.fieldsRequired': 'Имя пользователя, эл. почта и пароль обязательны',
|
||||
'admin.createUser': 'Создать пользователя',
|
||||
'admin.invite.title': 'Ссылки-приглашения',
|
||||
'admin.invite.subtitle': 'Создание одноразовых ссылок для регистрации',
|
||||
'admin.invite.create': 'Создать ссылку',
|
||||
'admin.invite.createAndCopy': 'Создать и скопировать',
|
||||
'admin.invite.empty': 'Ссылки-приглашения ещё не созданы',
|
||||
'admin.invite.maxUses': 'Макс. использований',
|
||||
'admin.invite.expiry': 'Действует',
|
||||
'admin.invite.uses': 'использовано',
|
||||
'admin.invite.expiresAt': 'истекает',
|
||||
'admin.invite.createdBy': 'от',
|
||||
'admin.invite.active': 'Активна',
|
||||
'admin.invite.expired': 'Истекла',
|
||||
'admin.invite.usedUp': 'Исчерпана',
|
||||
'admin.invite.copied': 'Ссылка-приглашение скопирована',
|
||||
'admin.invite.copyLink': 'Копировать ссылку',
|
||||
'admin.invite.deleted': 'Ссылка-приглашение удалена',
|
||||
'admin.invite.createError': 'Ошибка при создании ссылки',
|
||||
'admin.invite.deleteError': 'Ошибка при удалении ссылки',
|
||||
'admin.tabs.settings': 'Настройки',
|
||||
'admin.allowRegistration': 'Разрешить регистрацию',
|
||||
'admin.allowRegistrationHint': 'Новые пользователи могут регистрироваться самостоятельно',
|
||||
|
||||
@@ -274,6 +274,24 @@ const zh: Record<string, string> = {
|
||||
'admin.toast.createError': '创建用户失败',
|
||||
'admin.toast.fieldsRequired': '用户名、邮箱和密码为必填项',
|
||||
'admin.createUser': '创建用户',
|
||||
'admin.invite.title': '邀请链接',
|
||||
'admin.invite.subtitle': '创建一次性注册链接',
|
||||
'admin.invite.create': '创建链接',
|
||||
'admin.invite.createAndCopy': '创建并复制',
|
||||
'admin.invite.empty': '尚未创建邀请链接',
|
||||
'admin.invite.maxUses': '最大使用次数',
|
||||
'admin.invite.expiry': '有效期',
|
||||
'admin.invite.uses': '已使用',
|
||||
'admin.invite.expiresAt': '过期时间',
|
||||
'admin.invite.createdBy': '由',
|
||||
'admin.invite.active': '有效',
|
||||
'admin.invite.expired': '已过期',
|
||||
'admin.invite.usedUp': '已用完',
|
||||
'admin.invite.copied': '邀请链接已复制',
|
||||
'admin.invite.copyLink': '复制链接',
|
||||
'admin.invite.deleted': '邀请链接已删除',
|
||||
'admin.invite.createError': '创建链接失败',
|
||||
'admin.invite.deleteError': '删除链接失败',
|
||||
'admin.tabs.settings': '设置',
|
||||
'admin.allowRegistration': '允许注册',
|
||||
'admin.allowRegistrationHint': '新用户可以自行注册',
|
||||
|
||||
Reference in New Issue
Block a user