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

How the **Notifications** preference feature is built. It is a thin, self-scoped CRUD over a JSON column — no async pipeline, no Kafka, no executor→topic→record-store pattern.

## Services involved
- **hiveops-profile** (frontend only) — Svelte SPA at `profile.bcos.cloud`. No backend module; `hiveops-profile/backend` does not exist. Component: `frontend/src/components/Profile/NotificationSettings.svelte`, data layer `frontend/src/lib/api.ts` (`profileAPI`).
- **hiveops-auth** — the sole backend of record. Port 8082, NGINX `/api/auth/`, DB `hiveiq_auth`. Owns the `users` table and the embedded preferences JSON.
- The frontend also talks to `hiveops-messaging` and `hiveops-mgmt` for other Profile screens, but the Notifications tab uses **only** the auth service.

See [platform.service-ownership] — user identity + preferences are auth's domain; no other service writes them.

## Data flow (synchronous, request/response)
```
NotificationSettings.svelte
  └─ profileAPI.getPreferences()  →  GET  /auth/api/users/me/preferences
  └─ profileAPI.updatePreferences() → PUT /auth/api/users/me/preferences
        (baseURL = window.__APP_CONFIG__.apiUrl → NGINX gateway → hiveiq-auth)
        Authorization: Bearer <JWT from localStorage 'token'>

hiveops-auth
  UserProfileController (@RequestMapping "/auth/api/users/me")
    └─ UserProfileService.getPreferences(email) / updatePreferences(email, req)
         └─ UserRepository.findByEmail(principal)  →  User.preferences (JSON)
              └─ userRepository.save(user)  →  UPDATE users SET preferences = ?::jsonb
```
- **No Kafka topics** are produced or consumed by this feature. See [platform.kafka] — this module is intentionally absent from the event bus.
- **No async executor / record-store.** Read and write are both in-request, `@Transactional` on the write.

## DB tables read/written
- **`users`** (schema `hiveiq_auth`) — read and written.
  - Column **`preferences`** — `JSONB NOT NULL DEFAULT '{}'` (added by Flyway `V9__add_user_preferences.sql`).
  - Hibernate mapping: `@JdbcTypeCode(SqlTypes.JSON)` + `@Column(columnDefinition = "jsonb")` on `User.preferences`, deserialized to the `UserPreferences` POJO.
- No join tables, no per-preference rows. All notification fields are keys inside the one JSON document. See [platform.data-architecture] for the JSONB-column-vs-table tradeoff.

## The preferences document (notification subset)
`UserPreferences` (POJO, not an `@Entity`) — notification-relevant fields with defaults:
| Field | Type | Default |
|---|---|---|
| `emailNotificationsEnabled` | boolean | `true` |
| `pushNotificationsEnabled` | boolean | `true` |
| `notifyOnCritical` | boolean | `true` |
| `notifyOnHigh` | boolean | `true` |
| `notifyOnAssignment` | boolean | `false` |
| `notifyOnStatusChange` | boolean | `false` |
| `notificationQuietStart` | String `HH:MM` \| null | `null` |
| `notificationQuietEnd` | String `HH:MM` \| null | `null` |

The same document also carries display/appearance/security prefs (itemsPerPage, dateFormat, timeFormat, timezone, themeMode, tableDensity, sessionTimeoutMinutes) — one blob, one column, shared across all Profile tabs.

## Key API endpoints
| Method | Path | Purpose | Auth |
|---|---|---|---|
| GET | `/auth/api/users/me/preferences` | Read caller's full prefs doc | authenticated (self) |
| PUT | `/auth/api/users/me/preferences` | Merge-update caller's prefs | authenticated (self) |
| GET | `/auth/api/users/me` | Caller's profile (not prefs) | authenticated (self) |

## Merge semantics (important for correctness)
`updatePreferences` applies each incoming field only when non-null (`if (req.getX() != null) prefs.setX(...)`), so partial PUTs are safe — the Notifications tab sends only its own fields and leaves display/appearance/security untouched. **Consequence:** a `null` for a quiet-hours field is a no-op, so quiet hours cannot be cleared through this endpoint once set (see Internal tab).

## What is NOT here (by design / by gap)
- No producer to any notification/delivery topic; no consumer reads these flags. Delivery wiring (email/push, severity gating, quiet-hours enforcement) does not exist in the codebase yet — the screen is a stored-preference surface awaiting a consumer.
- No admin/cross-user editing path; identity is pinned to the JWT principal.

Cross-links: [platform.data-architecture] · [platform.kafka] · [platform.service-ownership]
