diff --git a/documentation/GUIDE__AE_API_V3_for_Frontend.md b/documentation/GUIDE__AE_API_V3_for_Frontend.md index 4b9b46a..d0fcbbc 100644 --- a/documentation/GUIDE__AE_API_V3_for_Frontend.md +++ b/documentation/GUIDE__AE_API_V3_for_Frontend.md @@ -72,35 +72,117 @@ Modify data in the system. ## 4. V3 Uniform Lookup System -The V3 Lookup system provides a hierarchical, deduplicated interface for standardized tables (Countries, Timezones, etc.). It supports global defaults, account overrides, and site-specific whitelisting. +The V3 Lookup system provides a hierarchical, deduplicated interface for standardized reference tables (Countries, Timezones, etc.). It supports global defaults, account-level overrides, and object-level overrides, with optional site-specific whitelisting. + +### How the hierarchy works + +Each lookup table (`lu_v3_country`, `lu_v3_time_zone`, etc.) can hold multiple rows for the same logical item at different scopes: + +| Scope | `account_id` | `for_type` / `for_id` | Wins over | +|---|---|---|---| +| Global default | `NULL` | `NULL` / `NULL` | nothing | +| Account override | set | `NULL` / `NULL` | Global default | +| Object override | set | set | Account override + Global default | + +The API uses `ROW_NUMBER() PARTITION BY group` to collapse all rows for the same item down to the single highest-priority winner before returning results. **`group` is the identity key** — it is what makes two rows "the same item competing for priority." + +> [!IMPORTANT] +> **The `group` field is not a display label.** It is the deduplication key. Each lookup type uses a different natural key for `group`: +> +> | Lookup type | `group` value | Example | +> |---|---|---| +> | `country` | ISO alpha-2 code | `"US"`, `"CA"`, `"GB"` | +> | `country_subdivision` | subdivision code | `"US-NY"`, `"CA-ON"` | +> | `time_zone` | IANA timezone name | `"America/New_York"`, `"US/Eastern"` | +> +> For `time_zone`, `group` and `name` must always be identical — there is no concept of "override all US timezones as a group." Each timezone is its own identity. ### A. List Lookups -Retrieve a ranked and filtered list of lookup items. + +Retrieve the deduplicated, ranked list for a lookup type. + * **Endpoint:** `GET /v3/lookup/{lu_type}/list` * **Available Types:** `country`, `country_subdivision`, `time_zone` * **Parameters:** - * `site_id` (Optional): Random ID of the site to apply a **Whitelist Policy**. - * `only_priority` (Optional): Set to `true` to return only high-priority items (e.g., common time zones). - * `for_type` / `for_id` (Optional): Context for object-specific overrides. - * `include_disabled` (Optional): Set to `true` to see shadowed/disabled records. + * `site_id` (Optional): Random ID of the site — applies a **Whitelist Policy** (see §C). + * `only_priority` (Optional): `true` returns only `priority=1` items (e.g., common time zones). + * `for_type` / `for_id` (Optional): Object context — activates object-level override matching. + * `include_disabled` (Optional): `true` includes shadowed/disabled records (useful for admin views). + +**Frontend keying:** Always key Svelte `{#each}` blocks on `group`, not `id` or `name`. `group` is guaranteed unique in the response. Keying on `id` will break if an account override wins (different `id`, same logical item). ### B. Resolve Identity -Resolves a string (code, group, or name) to a single record. + +Resolves a string to a single lookup record. + * **Endpoint:** `GET /v3/lookup/{lu_type}/resolve?q=VALUE` -* **Usage:** Use this when you have an external code (e.g., ISO "US") and need the full Aether record. +* **Usage:** Use when you have an external code (e.g., ISO `"US"`) and need the full Aether record. Scans `name`, `group`, and other identity fields. ### C. Site Whitelist Policy -To limit lookups for a specific site, add a `lookup_policy` to the `site.cfg_json` field. -**Schema:** + +To restrict which lookup items appear for a specific site, add a `lookup_policy` to `site.cfg_json`: + ```json { "lookup_policy": { "country": ["US", "CA", "GB"], - "time_zone": ["America/New_York"] + "time_zone": ["America/New_York", "US/Eastern"] } } ``` -*Note: Whitelist values must match the `group` field in the database.* + +> **Whitelist values must match the `group` field** — i.e., the natural key for that type (ISO code for country, IANA name for time zone). Using a display name will silently return no results for that item. + +### D. Adding and managing client overrides + +When a client needs a customized label or wants to hide/reorder lookup items, create override records rather than modifying global defaults. + +**Rules:** +1. **Never modify global default rows** (`account_id = NULL`). Those are shared across all accounts. Any change there affects every client. +2. **Set `group` to the exact same value as the global default row** for the item you are overriding. If `group` doesn't match, the override creates a new item instead of replacing the existing one. +3. **Set `account_id`** to the client's account ID. Leave `for_type` / `for_id` null unless the override is specific to a single object (e.g., one site). + +**Example — rename "US/Eastern" for one account:** + +```sql +INSERT INTO lu_v3_time_zone + (account_id, name, name_override, `group`, enable, priority, sort) +VALUES + (42, 'US/Eastern', 'Eastern Time (Client Label)', 'US/Eastern', 1, 1, 50); +``` + +The `name_override` field is the display label the frontend should prefer when set. `group = 'US/Eastern'` ensures this row competes with — and wins over — the global default in the `PARTITION BY group` deduplication. + +**To disable an item for one account** (hide it from their dropdowns): + +```sql +INSERT INTO lu_v3_time_zone + (account_id, name, `group`, enable) +VALUES + (42, 'US/Samoa', 'US/Samoa', 0); +``` + +Setting `enable = 0` on an account-scoped row shadows the global default for that account only. + +**To remove a client override** (revert to global default): + +Simply delete the row where `account_id = ` and `group = ''`. The global default row is unaffected and immediately resumes winning. + +### E. Adding new global lookup items + +When seeding new lookup data (e.g., adding timezones in bulk): + +1. Set `group = name` for every row (for `time_zone`). This is a hard invariant — if `group` is set to a regional label like `"United States"` instead of the timezone name, the entire group collapses to a single winner and all but one entry disappear from the API response. +2. Set `account_id = NULL` and `for_type = NULL` / `for_id = NULL` for global defaults. +3. After seeding, verify with: + ```sql + -- Should return 0 rows; any result means multiple items will collapse into one + SELECT `group`, COUNT(*) AS cnt + FROM lu_v3_time_zone + WHERE account_id IS NULL + GROUP BY `group` + HAVING cnt > 1; + ``` ---