feat(db/m7-user-mgmt): migration 0024 + self-service + SMTP queries (U3)

Adds users.email (optional, lowercase-unique via partial index),
smtp_config singleton, and password_resets tokens. Plus the
queries U3-T2 / T3 / T4 use:

- ChangeUserPassword: self-service password change. HTTP layer
  verifies the current password before this fires.
- UpdateUserProfile: set display_name + email together.
- RegenerateApiToken: invalidate old API token.
- GetUserByEmail: case-insensitive lookup for forgot-password.
- GetSMTPConfig + UpdateSMTPConfig: admin SMTP settings CRUD.
- CreatePasswordReset / GetPasswordReset / UsePasswordReset
  (:execrows for atomic claim) / DeleteExpiredPasswordResets
  (cron-style cleanup, not yet wired).

Email column is nullable; users without email have admin-reset
as their only password-recovery path. The lower() unique index
allows many NULLs and prevents case-insensitive duplicates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-07 12:34:44 -04:00
parent 777e32ca24
commit a509ec538b
9 changed files with 411 additions and 8 deletions
+22
View File
@@ -340,6 +340,14 @@ type LidarrRequest struct {
UpdatedAt pgtype.Timestamptz
}
type PasswordReset struct {
Token string
UserID pgtype.UUID
CreatedAt pgtype.Timestamptz
ExpiresAt pgtype.Timestamptz
UsedAt pgtype.Timestamptz
}
type PlayEvent struct {
ID pgtype.UUID
UserID pgtype.UUID
@@ -446,6 +454,19 @@ type SkipEvent struct {
PositionMs int32
}
type SmtpConfig struct {
ID bool
Enabled bool
Host string
Port int32
Username string
Password string
FromAddress string
FromName string
UseTls bool
UpdatedAt pgtype.Timestamptz
}
type SystemPlaylistRun struct {
UserID pgtype.UUID
LastRunAt pgtype.Timestamptz
@@ -492,6 +513,7 @@ type User struct {
ListenbrainzEnabled bool
DisplayName *string
AutoApproveRequests bool
Email *string
}
type UserInvite struct {