Files
bvandeusen a509ec538b 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>
2026-05-07 12:34:44 -04:00

23 lines
812 B
SQL

-- name: CreatePasswordReset :exec
INSERT INTO password_resets (token, user_id, expires_at)
VALUES ($1, $2, $3);
-- name: GetPasswordReset :one
SELECT * FROM password_resets WHERE token = $1;
-- name: UsePasswordReset :execrows
-- Atomic claim: marks the token used only if currently unused
-- and not expired. Returns rows-affected so the caller can
-- distinguish "successful claim" (rows=1) from "already used /
-- expired / nonexistent" (rows=0).
UPDATE password_resets
SET used_at = now()
WHERE token = $1
AND used_at IS NULL
AND expires_at > now();
-- name: DeleteExpiredPasswordResets :exec
-- Cleanup: drop tokens that have been expired more than 7 days.
-- Not wired in U3; useful to have for a future cron sweep.
DELETE FROM password_resets WHERE expires_at < now() - interval '7 days';