hyperswitch
85b83faf - feat(observability): config API for the mappers dictionary and notification reads

Commit
32 days ago
feat(observability): config API for the mappers dictionary and notification reads The two resources whose absence is visible in the product: the mappers screen fails outright because its table was never created in the store it read from, and the notification bell has nowhere to record what a user has read. Six routes under `/alerts/config`, behind the same internal API key and answering in the same `{ "status": ... }` envelope the notifier uses. They join the definition and enablement resources under that scope rather than sitting beside the delivery routes, so the tree says one thing about itself: a route under `/config` is a row, and a route under `/chat` or `/email` is a message. `alerts_dicts` keeps history: a delete retires the live row rather than removing it, and the unique index is partial — one enabled row per `(name, key_)`, with superseded rows kept. A save is therefore a single `INSERT … ON CONFLICT (name, key_) WHERE is_enabled IS TRUE DO UPDATE`, spelling the index's own predicate so Postgres can infer the arbiter. Both halves are load-bearing: an upsert assuming a plain unique constraint fails outright, and one matching on `(name, key_)` without the predicate finds a retired row and brings it back carrying its old value. Diesel expresses this through `filter_target`; a bound parameter would not, since the planner cannot prove `is_enabled = $1` implies the index. `product`, `values_` and `metadata` are `json` rather than `jsonb` because the dashboard serializes them itself and the mappers screen parses some of them twice. `RawJson` carries the bytes from the wire to the column and back without building a tree: parsing into a `serde_json::Value` and serializing it again re-encodes the document, and the screen would be handed back something it did not save. That is the opposite trade from a definition, whose json columns are typed because the alert manager reads them; nothing reads a dictionary entry but the screen that wrote it. An entry's JSON is capped by `dictionary.max_entry_bytes`, since one oversized save otherwise becomes a row nothing can read back — a broken page long after the save that caused it. The refusal is `IR_08`: `IR_05` through `IR_07` already name conditions the definition and enablement routes report. The watermark is per user, but the internal API key names the service, not a person, so the routes read `X-User-Name` and nothing authenticates it. Local accounts are disabled in both environments, so every request arrives with no name, the table holds one shared row, and dictionary saves fall to the column's default. Keeping the name on the request makes that a data fact rather than a schema one — the day the portal authenticates, rows appear per person with no route and no migration to change. A header rather than a path segment: the empty name every caller sends today has no spelling as a path segment, and a user name is an email address wherever there is one, which a path would write into every access log. A read that finds nothing is a `200` with `status: "absent"`, not a `404`. The mappers screen and the bell both have a defined behaviour for "nothing saved yet", and making that an HTTP error would mean the alert manager has to treat an error response as normal — the habit that hides a real one. `404` keeps the meaning the routes beside these give it: a path naming something this service does not have. `diesel_models` gains `diesel/uuid`, which the observability schema's surrogate keys need and no table in `hyperswitch_db` has, and both tables are registered for `GetPrimaryKey` so the list query can go through `generic_filter`. Verified against a migrated local Postgres: 122 tests green across the crate, including the 13 `#[ignore]`d ones that store rows — a repeated save leaves one row rather than two, a retired entry stays as history and is not revived by the next save, and the JSON columns come back byte-identical through both the write and a fresh read. Clippy clean; `diesel_models` checks under `v1` and under `v2,kv_store`.
Committer
Parents
Loading