---
module: profile.profile
title: Profile
tab: Architect
order: 30
audience: dev
---

How the **Profile** tab is built. It is a self-service editor over a single `users` row, served entirely by **hiveops-auth**, with one synchronous cross-service call to **hiveops-mgmt** to resolve the display institution. There is **no Kafka, no executor, and no record-store** in this feature — it is a plain request/response CRUD over a relational table plus a jsonb column.

## Services involved
| Service | Role in this feature |
|---|---|
| **hiveops-profile** (frontend) | Svelte SPA `ProfileInfo.svelte`; port 5184; `profile.bcos.cloud`. Holds state in the `userInfo` store, does client-side avatar crop/size-check. |
| **hiveops-auth** (backend, owner) | Port 8082 / prod host 8001. Owns `UserProfileController` + `UserProfileService`, the `users` table, and DB `hiveiq_auth`. Source of truth for all editable fields. |
| **hiveops-mgmt** | Called synchronously at response-build time to turn the user's email domain into a company/institution display name. Optional — degrades to `null`. |

See [platform.service-ownership] — user identity/profile is an **auth-owned** domain; mgmt owns institution/company mapping.

## Data flow (read)
1. SPA calls `GET /auth/api/users/me` with `Authorization: Bearer <jwt>`.
2. `UserProfileController.getMe` resolves the caller from `@AuthenticationPrincipal` (email) → `userRepository.findByEmail`.
3. `buildResponse` derives the email **domain** and calls `MgmtServiceClient.getCompanyName(domain)` → HTTP `GET {mgmt}/api/internal/customers/domain/{domain}/institution-keys` with header `X-Internal-Secret`. It reads `companyName` from the JSON body; on any failure it returns `null` (graceful degrade).
4. `UserMeResponse.from(user, companyName)` maps entity → DTO. **The `institution` field in the response is the mgmt companyName, not the `users.institution` column.**

## Data flow (write)
1. SPA `PUT /auth/api/users/me` with `{ name, phone, jobTitle, avatarData }`.
2. `UserProfileService.updateProfile` loads the user by email, applies **only non-null** fields (`name`, `phone`, `jobTitle`, `avatarData`), and `save()`s.
3. Controller re-runs `buildResponse` (another mgmt call) and returns the fresh `UserMeResponse`; the SPA writes it back into the `userInfo` store.

There are **no Kafka topics** produced or consumed by this feature (contrast with device/incident flows in [platform.kafka]). Avatar bytes travel inline as a base64 data URL in the request/response body — there is no object store or CDN in the path.

## DB tables (all in `hiveiq_auth`)
| Table | Access | Columns this feature touches |
|---|---|---|
| `users` | read + write | `email` (lookup key), `name`, `phone`, `job_title`, `avatar_data` (TEXT), `role`, `mfa_enabled`, `fleet_task_approver`, `institution` (present but **not** surfaced in the /me response), `preferences` (jsonb) |

- Editable profile columns were added in Flyway migration `V10__add_profile_fields.sql` (`phone`, `job_title`, `institution`, `avatar_data`).
- `avatar_data` is unbounded `TEXT` holding a `data:image/jpeg;base64,...` string (client crops to 200×200, JPEG q0.85).
- `preferences` is a single `jsonb` column (theme, notifications, display, session timeout) — edited by the **sibling** Appearance/Display/Notification tabs via `/auth/api/users/me/preferences`, not by this Profile tab.

See [platform.data-architecture] for the per-service DB ownership model.

## Key API endpoints (this feature)
| Method | Path | Handler | Purpose |
|---|---|---|---|
| GET | `/auth/api/users/me` | `UserProfileController.getMe` | Load profile + resolved institution |
| PUT | `/auth/api/users/me` | `UserProfileController.updateMe` → `UserProfileService.updateProfile` | Partial update of name/phone/jobTitle/avatar |

(Adjacent, not this tab: `GET|PUT /auth/api/users/me/preferences`, `POST /auth/api/change-password`.)

## Design notes / coupling
- **Auth → mgmt is a hard synchronous dependency for the institution label only.** Every `GET`/`PUT` of `/users/me` makes a REST call to mgmt. It fails soft (institution → null) but adds latency and couples auth to mgmt availability. Consider caching the domain→company mapping if this hot path grows.
- Identity is always the JWT subject email; there is no path to edit another user's profile through this controller, so no ownership/authorization branching is needed.
