From 6eb601f56d1832eece386705b4b8e908e64ef2e4 Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Fri, 2 Jan 2026 19:30:33 -0500 Subject: [PATCH] Saving changes to the Journals and API CRUD V3 fixes. --- documentation/V3_FRONTEND_API_GUIDE.md | 126 ++++++------------ src/lib/ae_api/api_get__crud_obj_li_v3.ts | 53 +++++++- src/lib/ae_api/api_post__crud_search_v3.ts | 20 ++- .../ae_journals/ae_journals__journal_entry.ts | 10 ++ src/lib/api/api.ts | 3 +- .../journals/[journal_id]/+layout.svelte | 18 +-- 6 files changed, 123 insertions(+), 107 deletions(-) diff --git a/documentation/V3_FRONTEND_API_GUIDE.md b/documentation/V3_FRONTEND_API_GUIDE.md index 54c75a86..a46aeb56 100644 --- a/documentation/V3_FRONTEND_API_GUIDE.md +++ b/documentation/V3_FRONTEND_API_GUIDE.md @@ -9,86 +9,48 @@ This guide explains how to update or create frontend functions to interact with | Feature | CRUD V2 (Legacy) | CRUD V3 (Modern) | | --- | --- | --- | | **Base Prefix** | `/v2/crud` | `/v3/crud` | -| **List Suffix** | Uses `/list` (e.g., `/v2/crud/journal/list`) | **No suffix** (e.g., `/v3/crud/journal/`) | -| **Nested Path** | Not supported in URL path | **Supported** (e.g., `/v3/crud/journal/{id}/journal_entry/`) | -| **Order By** | Passed in **Headers** (`order_by_li`) | Passed as **Query Parameter** (`order_by_li`) | -| **Complex Search**| Limited to GET `jp` (~2KB limit) | **POST `/search`** (Unlimited size) | -| **Latency Simulation**| Not standardized | `X-Delay-ms` (Header) or `delay_ms` (Query) | +| **List Suffix** | Uses `/list` | **No suffix** (e.g., `/v3/crud/journal/`) | +| **Nested Path** | Not supported in URL | **Supported** (e.g., `/v3/crud/journal/{id}/journal_entry/`) | +| **View Selection**| `tbl_alt`, `mdl_alt` | **`view` parameter** (e.g., `?view=enriched`) | +| **Complex Search**| Limited to GET `jp` | **POST `/search`** (Unlimited size + Hybrid params) | +| **Full-Text Search**| Manual column names | **Reserved `q` property** in SearchQuery | --- ## 2. Implementing V3 CRUD Functions -### A. List Top-Level Objects (GET) -Use this for simple lists or filtering by a parent via query parameters. +### A. List & Single Object (GET) +Support for view selection allows fetching richer data models when needed. ```ts -export async function get_ae_obj_li_v3({ - api_cfg, - obj_type, - for_obj_type, - for_obj_id, - enabled = 'enabled', - hidden = 'not_hidden', - limit = 100, - offset = 0, - order_by_li = null, // e.g., {"created_on": "DESC"} - delay_ms = 0 -}) { - // 1. Build V3 Endpoint (Note: No /list suffix) - const endpoint = `/v3/crud/${obj_type}/`; - - // 2. Build Query Params - const params: any = { - enabled, - hidden, - limit, - offset - }; - - if (for_obj_type) params.for_obj_type = for_obj_type; - if (for_obj_id) params.for_obj_id = for_obj_id; - if (order_by_li) params.order_by_li = JSON.stringify(order_by_li); - if (delay_ms) params.delay_ms = delay_ms; - - return await get_object({ api_cfg, endpoint, params }); +// Example: Get enriched journal details +// GET /v3/crud/journal/{id}?view=enriched +export async function get_ae_obj_v3({ api_cfg, obj_type, obj_id, view = 'default' }) { + const endpoint = `/v3/crud/${obj_type}/${obj_id}`; + return await get_object({ api_cfg, endpoint, params: { view } }); } ``` -### B. Access Nested Objects (GET) -V3 allows you to enforce parent-child relationships directly in the URL. - -```ts -// Example: Get all entries for a specific journal -// GET /v3/crud/journal/{journal_id}/journal_entry/ -export async function get_nested_obj_li_v3({ - api_cfg, - parent_type, - parent_id, - child_type, - limit = 100 -}) { - const endpoint = `/v3/crud/${parent_type}/${parent_id}/${child_type}/`; - return await get_object({ api_cfg, endpoint, params: { limit } }); -} -``` - -### C. Advanced Search (POST) -Use the new `/search` endpoint for complex logic (AND/OR, LIKE, IN) that would exceed URL length limits. +### B. Advanced & Hybrid Search (POST) +The `/search` endpoint combines the power of complex logical bodies with the simplicity of query parameters. ```ts export async function search_ae_obj_v3({ api_cfg, obj_type, - search_query, // Complex SearchQuery object - order_by_li = null + search_query, // { q: "search term", and: [...] } + enabled = 'enabled', + view = 'default', + for_obj_type, + for_obj_id }) { const endpoint = `/v3/crud/${obj_type}/search`; - const params: any = {}; - if (order_by_li) params.order_by_li = JSON.stringify(order_by_li); + // Standard filters can be passed as query params + const params: any = { enabled, view }; + if (for_obj_type) params.for_obj_type = for_obj_type; + if (for_obj_id) params.for_obj_id = for_obj_id; - // Note: search_query is sent in the BODY via POST return await post_object({ api_cfg, endpoint, @@ -96,36 +58,26 @@ export async function search_ae_obj_v3({ data: search_query }); } +``` -/** - * Example search_query usage: - * { - * "and": [ - * { "field": "enable", "op": "eq", "value": true }, - * { - * "or": [ - * { "field": "name", "op": "like", "value": "%Meeting%" }, - * { "field": "priority", "op": "gt", "value": 5 } - * ] - * } - * ] - * } - */ +### C. Standardized Global Search +Use the `q` property in your search body for a general keyword search across indexed columns. + +```json +// POST /v3/crud/journal/search +{ + "q": "Annual Meeting", + "and": [ + { "field": "enable", "op": "eq", "value": true } + ] +} ``` --- ## 3. Best Practices for V3 -1. **Stop using Headers for Sorting**: In V2, we used `headers['order_by_li']`. In V3, always use `params['order_by_li']`. -2. **Prefer `/search` for Filters**: If your query involves more than a simple `for_obj_id`, use the `POST .../search` endpoint. It is safer, more readable, and bypasses the 2083-character URL limit. -3. **Non-Blocking UI**: Use the `delay_ms` parameter during development to test how your Svelte components handle loading states and skeleton screens. -4. **Singular Nouns**: Always use singular names for `obj_type` (e.g., `journal`, not `journals`), following the backend's `obj_type_kv_li` registry. - ---- - -## 4. Special Case: Account Context -V3 strictly enforces account context. Ensure your `api_cfg` logic correctly sets the `X-Account-ID` header. - -* If a request doesn't need an account (e.g., public site data), set the `X-No-Account-ID: bypass` header. -* If using a temporary token, use the `x_no_account_id_token` query parameter. +1. **Use `view` for Rich Data**: Instead of manually joining data in separate calls, use `?view=enriched` or `?view=detail` if defined in the backend. +2. **Hybrid Search**: Use query parameters for simple toggles (enabled/hidden) and the POST body for complex logic. +3. **Global Search**: Always prefer the `q` property for text search instead of manually targeting `default_qry_str`. +4. **Singular Nouns**: Always use singular names for `obj_type` (e.g., `journal`). \ No newline at end of file diff --git a/src/lib/ae_api/api_get__crud_obj_li_v3.ts b/src/lib/ae_api/api_get__crud_obj_li_v3.ts index 1e31317e..70d5f766 100644 --- a/src/lib/ae_api/api_get__crud_obj_li_v3.ts +++ b/src/lib/ae_api/api_get__crud_obj_li_v3.ts @@ -1,6 +1,41 @@ import type { key_val } from '$lib/stores/ae_stores'; import { get_object } from './api_get_object'; +interface GetAeObjV3Params { + api_cfg: any; + obj_type: string; + obj_id: string; + view?: string; + log_lvl?: number; +} + +/** + * Get a single object by ID (V3) + */ +export async function get_ae_obj_v3({ + api_cfg, + obj_type, + obj_id, + view = 'default', + log_lvl = 0 +}: GetAeObjV3Params) { + const endpoint = `/v3/crud/${obj_type}/${obj_id}`; + const params: key_val = { view }; + + if (log_lvl) { + console.log('*** get_ae_obj_v3 ***'); + console.log('Endpoint:', endpoint); + console.log('Params:', params); + } + + return await get_object({ + api_cfg, + endpoint, + params, + log_lvl + }); +} + interface GetAeObjLiV3Params { api_cfg: any; obj_type: string; @@ -8,6 +43,7 @@ interface GetAeObjLiV3Params { for_obj_id?: string; enabled?: 'all' | 'enabled' | 'not_enabled'; hidden?: 'all' | 'hidden' | 'not_hidden'; + view?: string; limit?: number; offset?: number; order_by_li?: Record | null; @@ -22,19 +58,21 @@ export async function get_ae_obj_li_v3({ for_obj_id, enabled = 'enabled', hidden = 'not_hidden', + view = 'default', limit = 100, offset = 0, order_by_li = null, delay_ms = 0, log_lvl = 0 }: GetAeObjLiV3Params) { - // 1. Build V3 Endpoint (Note: No /list suffix) + // 1. Build V3 Endpoint const endpoint = `/v3/crud/${obj_type}/`; // 2. Build Query Params const params: key_val = { enabled, hidden, + view, limit, offset }; @@ -63,10 +101,11 @@ interface GetNestedObjLiV3Params { parent_type: string; parent_id: string; child_type: string; - limit?: number; - offset?: number; enabled?: 'all' | 'enabled' | 'not_enabled'; hidden?: 'all' | 'hidden' | 'not_hidden'; + view?: string; + limit?: number; + offset?: number; order_by_li?: Record | null; delay_ms?: number; log_lvl?: number; @@ -77,10 +116,11 @@ export async function get_nested_obj_li_v3({ parent_type, parent_id, child_type, - limit = 100, - offset = 0, enabled = 'enabled', hidden = 'not_hidden', + view = 'default', + limit = 100, + offset = 0, order_by_li = null, delay_ms = 0, log_lvl = 0 @@ -90,6 +130,7 @@ export async function get_nested_obj_li_v3({ const params: key_val = { enabled, hidden, + view, limit, offset }; @@ -109,4 +150,4 @@ export async function get_nested_obj_li_v3({ params, log_lvl }); -} +} \ No newline at end of file diff --git a/src/lib/ae_api/api_post__crud_search_v3.ts b/src/lib/ae_api/api_post__crud_search_v3.ts index 898ebe40..c4efe308 100644 --- a/src/lib/ae_api/api_post__crud_search_v3.ts +++ b/src/lib/ae_api/api_post__crud_search_v3.ts @@ -4,7 +4,12 @@ import { post_object } from './api_post_object'; interface SearchAeObjV3Params { api_cfg: any; obj_type: string; - search_query: any; // Complex SearchQuery object + search_query: any; // Complex SearchQuery object: { q: string, and: [], or: [] } + enabled?: 'all' | 'enabled' | 'not_enabled'; + hidden?: 'all' | 'hidden' | 'not_hidden'; + view?: string; + for_obj_type?: string; + for_obj_id?: string; order_by_li?: Record | null; limit?: number; offset?: number; @@ -16,6 +21,11 @@ export async function search_ae_obj_v3({ api_cfg, obj_type, search_query, + enabled = 'enabled', + hidden = 'not_hidden', + view = 'default', + for_obj_type, + for_obj_id, order_by_li = null, limit = 100, offset = 0, @@ -24,11 +34,17 @@ export async function search_ae_obj_v3({ }: SearchAeObjV3Params) { const endpoint = `/v3/crud/${obj_type}/search`; + // Hybrid search: Standard filters passed as query params const params: key_val = { + enabled, + hidden, + view, limit, offset }; + if (for_obj_type) params['for_obj_type'] = for_obj_type; + if (for_obj_id) params['for_obj_id'] = for_obj_id; if (order_by_li) params['order_by_li'] = JSON.stringify(order_by_li); if (delay_ms > 0) params['delay_ms'] = delay_ms; @@ -47,4 +63,4 @@ export async function search_ae_obj_v3({ data: search_query, log_lvl }); -} +} \ No newline at end of file diff --git a/src/lib/ae_journals/ae_journals__journal_entry.ts b/src/lib/ae_journals/ae_journals__journal_entry.ts index f28fd38b..fd92d809 100644 --- a/src/lib/ae_journals/ae_journals__journal_entry.ts +++ b/src/lib/ae_journals/ae_journals__journal_entry.ts @@ -157,6 +157,7 @@ export async function load_ae_obj_li__journal_entry({ // Process the results first const processed_obj_li = await process_ae_obj__journal_entry_props({ obj_li: journal_entry_obj_li_get_result, + journal_id: for_obj_type === 'journal' ? for_obj_id : undefined, log_lvl: log_lvl }); if (log_lvl) { @@ -240,6 +241,7 @@ export async function create_ae_obj__journal_entry({ // Process the results first const processed_obj_li = await process_ae_obj__journal_entry_props({ obj_li: [journal_entry_obj_create_result], + journal_id, log_lvl: log_lvl }); if (log_lvl) { @@ -950,9 +952,11 @@ async function _process_generic_props>({ // Updated 2025-05-09 export async function process_ae_obj__journal_entry_props({ obj_li, + journal_id, log_lvl = 0 }: { obj_li: any[]; + journal_id?: string; log_lvl?: number; }) { return _process_generic_props({ @@ -960,6 +964,12 @@ export async function process_ae_obj__journal_entry_props({ obj_type: 'journal_entry', log_lvl, specific_processor: async (obj) => { + // Inject journal_id if provided and missing + if (journal_id) { + if (!obj.journal_id) obj.journal_id = journal_id; + if (!obj.journal_id_random) obj.journal_id_random = journal_id; + } + // Content processing let content = obj.content ?? ''; let content_cleaned: null | string = null; diff --git a/src/lib/api/api.ts b/src/lib/api/api.ts index 27a68171..1f60398c 100644 --- a/src/lib/api/api.ts +++ b/src/lib/api/api.ts @@ -12,7 +12,7 @@ import { post_object } from '$lib/ae_api/api_post_object'; // Exported at the en import { get_ae_obj_id_crud } from '$lib/ae_api/api_get__crud_obj_id'; import { get_ae_obj_li_for_obj_id_crud } from '$lib/ae_api/api_get__crud_obj_li_v1'; import { get_ae_obj_li_for_obj_id_crud_v2 } from '$lib/ae_api/api_get__crud_obj_li_v2'; -import { get_ae_obj_li_v3, get_nested_obj_li_v3 } from '$lib/ae_api/api_get__crud_obj_li_v3'; +import { get_ae_obj_v3, get_ae_obj_li_v3, get_nested_obj_li_v3 } from '$lib/ae_api/api_get__crud_obj_li_v3'; import { search_ae_obj_v3 } from '$lib/ae_api/api_post__crud_search_v3'; // This new function has not been tested yet!!! @@ -916,6 +916,7 @@ const obj = { patch_object: patch_object, post_object: post_object, get_ae_obj_id_crud: get_ae_obj_id_crud, + get_ae_obj_v3: get_ae_obj_v3, get_ae_obj_li_for_obj_id_crud: get_ae_obj_li_for_obj_id_crud, get_ae_obj_li_for_obj_id_crud_v2: get_ae_obj_li_for_obj_id_crud_v2, get_ae_obj_li_v3: get_ae_obj_li_v3, diff --git a/src/routes/journals/[journal_id]/+layout.svelte b/src/routes/journals/[journal_id]/+layout.svelte index 915df1ae..eacfd504 100644 --- a/src/routes/journals/[journal_id]/+layout.svelte +++ b/src/routes/journals/[journal_id]/+layout.svelte @@ -373,24 +373,20 @@ if ($journals_loc.qry__category_code) { data_kv.category_code = $journals_loc.qry__category_code; } - $effect(() => { - if (log_lvl) { - console.log('Creating new journal entry with data_kv:', data_kv); - } - }); + if (log_lvl) { + console.log('Creating new journal entry with data_kv:', data_kv); + } journals_func .create_ae_obj__journal_entry({ api_cfg: $ae_api, - journal_id: $lq__journal_obj.journal_id, + journal_id: $lq__journal_obj?.journal_id, data_kv: data_kv, log_lvl: log_lvl }) .then((results) => { - $effect(() => { - if (log_lvl) { - console.log('New journal entry created:', results); - } - }); + if (log_lvl) { + console.log('New journal entry created:', results); + } $journals_slct.journal_entry_id = results?.journal_entry_id_random; // $journals_loc.entry.edit = true; $journals_loc.entry.edit_kv[$journals_slct.journal_entry_id] =