feat(manager-core,picloud): accept email on admin create + patch

The /admins create/patch endpoints now plumb email through to the
repo so the dashboard's invite + edit forms aren't silently dropping
it on the floor. Discovered during smoke testing — the database
column existed and was exposed in the response DTO, but neither
the request DTO nor the repo's create() accepted it.

CreateAdminRequest gains optional email; PatchAdminRequest gains
email with JSON Merge Patch semantics:
  absent     → don't change
  null       → clear (write NULL)
  "<string>" → set to that value

The tri-state needs Option<Option<String>> with a tiny custom
deserializer; serde collapses absent and null otherwise.

normalize_email() trims, treats blanks as None, and rejects
obviously bogus values (no '@', >254 chars) with a 422. Real
email verification is a future concern.

Repo trait gains an email parameter on create() and a new
update_email() method. The unique-violation branch in create now
inspects constraint() to distinguish duplicate username from
duplicate email.

Integration test exercises create-with-email, PATCH null clears,
PATCH value sets, PATCH without email key no-ops on email.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-05-27 19:27:52 +02:00
parent 39a6df2bfe
commit 0c9f11558a
5 changed files with 163 additions and 12 deletions

View File

@@ -53,7 +53,7 @@ async fn boot(pool: PgPool) -> Seeded {
let hash = hash_password("owner-pw").expect("hash");
let owner = auth
.users
.create("owner", &hash, InstanceRole::Owner)
.create("owner", &hash, InstanceRole::Owner, None)
.await
.expect("seed owner");
@@ -119,7 +119,7 @@ async fn seed_user(
) -> AdminUserId {
let repo = PostgresAdminUserRepository::new(pool.clone());
let hash = hash_password(password).expect("hash");
repo.create(username, &hash, role)
repo.create(username, &hash, role, None)
.await
.expect("seed user")
.id