# Add user profile edit page to my.irc.now ## Context The account portal at my.irc.now has no profile page. Users cannot view or edit their account details. We need a full profile page with editable display name and email, a password change link (via Keycloak), and read-only account info. ## Changes ### 1. SQL migration: `sql/002_add_display_name.sql` (new) ```sql ALTER TABLE users ADD COLUMN IF NOT EXISTS display_name TEXT; ``` Run against accounts-db manually. ### 2. `crates/common/src/auth.rs` -- add `display_name` to UserClaims Add `pub display_name: Option` field. Update the two test constructors to include `display_name: None`. ### 3. `crates/web-api/Cargo.toml` -- add chrono ```toml chrono = { version = "0.4", features = ["serde"] } ``` Needed for `created_at` timestamp formatting. ### 4. `crates/web-api/src/state.rs` -- add `http_client` Add `pub http_client: reqwest::Client` to `AppState`. Used for Keycloak admin API calls. ### 5. `crates/web-api/src/main.rs` -- wire up - Construct `reqwest::Client::new()` and add to `AppState` - Register `GET /profile` and `POST /profile/update` ### 6. `crates/web-api/src/routes/mod.rs` -- add profile module ```rust pub mod profile; ``` ### 7. `crates/web-api/src/routes/profile.rs` (new) -- handler module **GET /profile** (`index`): - Query DB for email, display_name, plan, created_at via `sqlx::query_as` - Render `ProfileTemplate` with form fields and read-only info - Accept `?saved=1` and `?error=...` query params for flash messages **POST /profile/update** (`update`): - Deserialize `ProfileForm { display_name, email }` - If email changed: call Keycloak admin API to update it (client_credentials grant using existing OIDC client ID/secret, then PUT to admin users endpoint) - Update local DB: `UPDATE users SET display_name = $1, email = $2 WHERE keycloak_sub = $3` - Refresh session claims - Redirect to `/profile?saved=1` **Keycloak admin helpers** (private functions in same file): - `get_keycloak_admin_token(state)` -- POST client_credentials grant to token endpoint - `update_keycloak_email(state, sub, email)` -- PUT to `/admin/realms/irc-now/users/{sub}` ### 8. `crates/web-api/src/routes/auth.rs` -- update callback - Add `display_name: Option` to `UserRow` - Change RETURNING clause to `RETURNING plan, stripe_customer_id, display_name` - Add `display_name: row.display_name` to claims construction ### 9. `crates/web-api/templates/profile.html` (new) Extends `base.html`. Contains: - Flash message cards for saved/error states - Form with display_name input, email input, save button - Password card with link to Keycloak account console - Read-only account card showing plan, sub, created_at ### 10. `crates/web-api/templates/base.html` -- add nav link Add `profile` between billing and log out in `.nav-r`. ### 11. `crates/web-api/static/portal.css` -- profile styles Add `.profile-field` and `.profile-label` classes for the read-only info section. ### 12. Keycloak config (manual) Enable "Service Account Roles" on `oidc-account-portal` client in Keycloak admin console. Assign `realm-management` -> `manage-users` role to the service account. ## Files ``` sql/002_add_display_name.sql -- new crates/common/src/auth.rs -- add display_name field crates/web-api/Cargo.toml -- add chrono crates/web-api/src/state.rs -- add http_client crates/web-api/src/main.rs -- wire routes + http_client crates/web-api/src/routes/mod.rs -- add profile module crates/web-api/src/routes/profile.rs -- new handler crates/web-api/src/routes/auth.rs -- update upsert RETURNING crates/web-api/templates/profile.html -- new template crates/web-api/templates/base.html -- add nav link crates/web-api/static/portal.css -- profile styles ``` ## Verification 1. `cargo build -p irc-now-web-api` compiles 2. `cargo test -p irc-now-common` passes (UserClaims tests updated) 3. Run migration against accounts-db 4. Configure Keycloak service account roles 5. Build + deploy, verify profile page loads at my.irc.now/profile 6. Test display name save, email change, password link