From 33d42ed85b84e5488d9170f89a6522ab0aeb5bca Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Fri, 2 Jan 2026 18:27:12 -0500 Subject: [PATCH] Saving first versions to use the new CRUD V3 endpoints. --- ...Aether_API_CRUD_V3_beta_recommendations.md | 31 +++ src/lib/ae_journals/ae_journals__journal.ts | 240 +++++++++--------- .../ae_journals/ae_journals__journal_entry.ts | 144 +++++------ 3 files changed, 212 insertions(+), 203 deletions(-) create mode 100644 documentation/Aether_API_CRUD_V3_beta_recommendations.md diff --git a/documentation/Aether_API_CRUD_V3_beta_recommendations.md b/documentation/Aether_API_CRUD_V3_beta_recommendations.md new file mode 100644 index 00000000..80a53f0d --- /dev/null +++ b/documentation/Aether_API_CRUD_V3_beta_recommendations.md @@ -0,0 +1,31 @@ +# Aether API CRUD V3 Beta Recommendations + +Following the initial migration of the Journals module to the V3 CRUD endpoints, the following architectural recommendations are proposed for the FastAPI backend to improve developer experience and frontend efficiency. + +--- + +## 1. "View" Selection (`use_alt_tbl` Replacement) +In V2, the `use_alt_tbl` flag was frequently used to fetch "rich" objects (e.g., including joined data like `person_name`). +- **Recommendation:** Implement a `response_view` (or `view`) query parameter for both GET list and POST search endpoints. +- **Example:** `GET /v3/crud/journal/?view=enriched` +- **Goal:** Allow the client to request a heavier model with joined fields only when necessary, keeping the default response lightweight. + +## 2. Hybrid Filtering for Search +Currently, the `/search` endpoint requires a full JSON body even for simple standard filters like `enabled` or `hidden`. +- **Recommendation:** Update the `POST .../search` endpoint to accept standard query parameters that automatically append `AND` conditions to the logic defined in the body. +- **Example:** `POST /v3/crud/journal/search?enabled=true` +- **Goal:** Simplifies frontend code for common toggles while still allowing complex logic in the POST body. + +## 3. Standardized Full-Text Search Field +The current migration uses a field named `default_qry_str` with a `match` operator for text search. +- **Recommendation:** Implement a reserved top-level property in the Search Pydantic model (e.g., `_search_` or `query_string`) specifically for global text search. +- **Goal:** Decouples the frontend from knowing specific database column names used for full-text indexing. The backend can then decide which columns (name, description, tags, etc.) to include in the search. + +## 4. Explicit "Parent" Filtering in Search +Filtering by parent (e.g., Account or Site) is a primary use case but currently requires manual injection into the `and` array. +- **Recommendation:** Expose `for_obj_type` and `for_obj_id` as top-level arguments in the Search API. +- **Goal:** Standardizes how parent context is passed, allowing the backend to more easily perform ownership and permission validation before executing the search. + +--- +**Date:** 2026-01-02 +**Status:** Beta Feedback diff --git a/src/lib/ae_journals/ae_journals__journal.ts b/src/lib/ae_journals/ae_journals__journal.ts index af1f9c51..96417c44 100644 --- a/src/lib/ae_journals/ae_journals__journal.ts +++ b/src/lib/ae_journals/ae_journals__journal.ts @@ -140,7 +140,7 @@ export async function load_ae_obj_id__journal({ return ae_promises.load__journal_obj; } -// Updated 2025-03-15 +// Updated 2026-01-02 export async function load_ae_obj_li__journal({ api_cfg, for_obj_type = 'account', @@ -182,77 +182,93 @@ export async function load_ae_obj_li__journal({ ); } - const params_json: key_val = {}; + let promise; if (qry_person_id) { - const qry_param = { - type: 'AND', - field: 'person_id_random', - operator: '=', - value: qry_person_id + const search_query: any = { + and: [{ field: 'person_id_random', op: 'eq', value: qry_person_id }] }; - params_json['qry'].push(qry_param); - } - if (log_lvl) { - console.log('params_json:', params_json); - } + if (for_obj_id) { + search_query.and.push({ + field: `${for_obj_type}_id_random`, + op: 'eq', + value: for_obj_id + }); + } - ae_promises.load__journal_obj_li = await api - .get_ae_obj_li_for_obj_id_crud_v2({ - api_cfg: api_cfg, + // Add enabled/hidden filters to search query as V3 search is explicit + if (enabled === 'enabled') { + search_query.and.push({ field: 'enabled', op: 'eq', value: true }); + } else if (enabled === 'not_enabled') { + search_query.and.push({ field: 'enabled', op: 'eq', value: false }); + } + + if (hidden === 'hidden') { + search_query.and.push({ field: 'hidden', op: 'eq', value: true }); + } else if (hidden === 'not_hidden') { + search_query.and.push({ field: 'hidden', op: 'eq', value: false }); + } + + if (log_lvl) { + console.log('Using search_ae_obj_v3 with query:', search_query); + } + + promise = api.search_ae_obj_v3({ + api_cfg, obj_type: 'journal', - for_obj_type: for_obj_type, - for_obj_id: for_obj_id, - use_alt_tbl: false, - use_alt_mdl: false, - use_alt_exp: false, - enabled: enabled, - hidden: hidden, - order_by_li: order_by_li, - limit: limit, - offset: offset, - params_json: params_json, - params: params, - log_lvl: log_lvl - }) - .then(async function (journal_obj_li_get_result) { - if (journal_obj_li_get_result) { - if (try_cache) { - // Process the results first - const processed_obj_li = await process_ae_obj__journal_props({ - obj_li: journal_obj_li_get_result, - log_lvl: log_lvl - }); - if (log_lvl) { - console.log('Processed object list:', processed_obj_li); - } - // Save the updated results list to the database - if (log_lvl) { - console.log('Saving to DB...'); - } - await db_save_ae_obj_li__ae_obj({ - db_instance: db_journals, - table_name: 'journal', - obj_li: processed_obj_li, - properties_to_save: properties_to_save, - log_lvl: log_lvl - }); - if (log_lvl) { - console.log('DB save completed.'); - } - - // db_save_ae_obj_li__journal({ - // obj_type: 'journal', - // obj_li: journal_obj_li_get_result, - // log_lvl: log_lvl - // }); - } - return journal_obj_li_get_result; - } else { - return []; - } + search_query, + order_by_li, + limit, + offset, + log_lvl }); + } else { + promise = api.get_ae_obj_li_v3({ + api_cfg, + obj_type: 'journal', + for_obj_type, + for_obj_id, + enabled, + hidden, + limit, + offset, + order_by_li, + log_lvl + }); + } + + ae_promises.load__journal_obj_li = await promise.then(async function (journal_obj_li_get_result) { + if (journal_obj_li_get_result) { + if (try_cache) { + // Process the results first + const processed_obj_li = await process_ae_obj__journal_props({ + obj_li: journal_obj_li_get_result, + log_lvl: log_lvl + }); + if (log_lvl) { + console.log('Processed object list:', processed_obj_li); + } + // Save the updated results list to the database + if (log_lvl) { + console.log('Saving to DB...'); + } + await db_save_ae_obj_li__ae_obj({ + db_instance: db_journals, + table_name: 'journal', + obj_li: processed_obj_li, + properties_to_save: properties_to_save, + log_lvl: log_lvl + }); + if (log_lvl) { + console.log('DB save completed.'); + } + } + return journal_obj_li_get_result; + } else { + return []; + } + }); if (log_lvl) { console.log('ae_promises.load__journal_obj_li:', ae_promises.load__journal_obj_li); @@ -505,8 +521,8 @@ export async function update_ae_obj__journal({ } } -// This new function is using CRUD v2. This should allow for more flexibility in the queries. -// Updated 2025-03-15 +// This new function is using CRUD V3 Search. +// Updated 2026-01-02 export async function qry__journal({ api_cfg, journal_id, @@ -540,49 +556,42 @@ export async function qry__journal({ ); } - // let enabled: string = (params.qry__enabled ?? 'enabled'); // all, disabled, enabled - // let hidden: string = (params.qry__hidden ?? 'not_hidden'); // all, hidden, not_hidden - // let limit: number = (params.qry__limit ?? 25); // 99 - // let offset: number = (params.qry__offset ?? 0); // 0 - - const params_json: key_val = {}; - - // if (qry_str && qry_str.length > 2) { - // params_json['ft_qry'] = {}; - // params_json['ft_qry']['default_qry_str'] = qry_str; - // } - - params_json['qry'] = []; + const search_query: any = { and: [] }; if (qry_files === true) { - const qry_param = { - type: 'AND', - field: 'file_count_all', - operator: '>', - value: 0 - }; - params_json['qry'].push(qry_param); + search_query.and.push({ field: 'file_count_all', op: 'gt', value: 0 }); } else if (qry_files === false) { - const qry_param = { - type: 'AND', - field: 'file_count_all', - operator: 'IS', - value: null - }; - params_json['qry'].push(qry_param); + search_query.and.push({ field: 'file_count_all', op: 'is', value: null }); } if (qry_start_datetime) { - const qry_param = { - type: 'AND', - field: 'start_datetime', - operator: '>', - value: qry_start_datetime - }; - params_json['qry'].push(qry_param); + search_query.and.push({ field: 'start_datetime', op: 'gt', value: qry_start_datetime }); } - const order_by_li = { + // if (qry_str && qry_str.length > 2) { + // // V3 Search full text handling would be added here if supported by backend via specific field or op + // } + + // Add for_obj_id context (Account ID) + if (journal_id) { + // Assuming journal_id here is actually the account_id as per original usage context + search_query.and.push({ field: 'account_id_random', op: 'eq', value: journal_id }); + } + + // Add enabled/hidden filters + if (enabled === 'enabled') { + search_query.and.push({ field: 'enabled', op: 'eq', value: true }); + } else if (enabled === 'not_enabled') { + search_query.and.push({ field: 'enabled', op: 'eq', value: false }); + } + + if (hidden === 'hidden') { + search_query.and.push({ field: 'hidden', op: 'eq', value: true }); + } else if (hidden === 'not_hidden') { + search_query.and.push({ field: 'hidden', op: 'eq', value: false }); + } + + const order_by_li: Record = { priority: 'DESC', sort: 'DESC', start_datetime: 'ASC', @@ -592,22 +601,14 @@ export async function qry__journal({ }; ae_promises.load__journal_obj_li = await api - .get_ae_obj_li_for_obj_id_crud_v2({ + .search_ae_obj_v3({ api_cfg: api_cfg, obj_type: 'journal', - for_obj_type: 'account', - for_obj_id: journal_id, - use_alt_tbl: true, // NOTE: We want to use the alt table for journal searching - use_alt_mdl: false, - use_alt_exp: false, - enabled: enabled, - hidden: hidden, - order_by_li: order_by_li, - limit: limit, - offset: offset, - params_json: params_json, - params: params, - log_lvl: log_lvl + search_query, + order_by_li, + limit, + offset, + log_lvl }) .then(async function (journal_obj_li_get_result) { if (journal_obj_li_get_result) { @@ -635,11 +636,6 @@ export async function qry__journal({ console.log('DB save completed.'); } } - - // db_save_ae_obj_li__journal({ - // obj_type: 'journal', - // obj_li: journal_obj_li_get_result - // }); return journal_obj_li_get_result; } else { return []; @@ -919,7 +915,7 @@ async function _process_generic_props>({ for (const key in processed_obj) { if (key.endsWith('_random')) { const newKey = key.slice(0, -7); // Remove '_random' suffix - processed_obj[newKey] = processed_obj[key]; + (processed_obj as any)[newKey] = processed_obj[key]; } } // Ensure 'id' is set from '[obj_type]_id_random' diff --git a/src/lib/ae_journals/ae_journals__journal_entry.ts b/src/lib/ae_journals/ae_journals__journal_entry.ts index 2f8953d7..f28fd38b 100644 --- a/src/lib/ae_journals/ae_journals__journal_entry.ts +++ b/src/lib/ae_journals/ae_journals__journal_entry.ts @@ -82,7 +82,7 @@ export async function load_ae_obj_id__journal_entry({ return ae_promises.load__journal_entry_obj; } -// Updated 2025-03-15 +// Updated 2026-01-02 export async function load_ae_obj_li__journal_entry({ api_cfg, for_obj_type = 'journal', @@ -120,33 +120,37 @@ export async function load_ae_obj_li__journal_entry({ ); } - // let enabled: string = (params.qry__enabled ?? 'enabled'); // all, disabled, enabled - // let hidden: string = (params.qry__hidden ?? 'not_hidden'); // all, hidden, not_hidden - // let limit: number = (params.qry__limit ?? 99); // 99 - // let offset: number = (params.qry__offset ?? 0); // 0 + let promise; - const params_json: key_val = {}; - - // console('params_json:', params_json); - - ae_promises.load__journal_entry_obj_li = await api - .get_ae_obj_li_for_obj_id_crud_v2({ - api_cfg: api_cfg, + if (for_obj_type === 'journal' && for_obj_id) { + promise = api.get_nested_obj_li_v3({ + api_cfg, + parent_type: 'journal', + parent_id: for_obj_id, + child_type: 'journal_entry', + limit, + offset, + enabled, + hidden, + order_by_li, + log_lvl + }); + } else { + promise = api.get_ae_obj_li_v3({ + api_cfg, obj_type: 'journal_entry', - for_obj_type: for_obj_type, - for_obj_id: for_obj_id, - use_alt_tbl: false, - use_alt_mdl: false, - use_alt_exp: false, - enabled: enabled, - hidden: hidden, - order_by_li: order_by_li, - limit: limit, - offset: offset, - params_json: params_json, - params: params, - log_lvl: log_lvl - }) + for_obj_type, + for_obj_id, + enabled, + hidden, + limit, + offset, + order_by_li, + log_lvl + }); + } + + ae_promises.load__journal_entry_obj_li = await promise .then(async function (journal_entry_obj_li_get_result) { if (journal_entry_obj_li_get_result) { if (try_cache) { @@ -172,12 +176,6 @@ export async function load_ae_obj_li__journal_entry({ if (log_lvl) { console.log('DB save completed.'); } - - // await db_save_ae_obj_li__journal_entry({ - // obj_type: 'journal_entry', - // obj_li: journal_entry_obj_li_get_result, - // log_lvl: log_lvl - // }); } return journal_entry_obj_li_get_result; } else { @@ -340,8 +338,8 @@ export async function delete_ae_obj_id__journal_entry({ return ae_promises.delete__journal_entry_obj; } -// This new function is using CRUD v2. This should allow for more flexibility in the queries. -// Updated 2025-06-04 +// This new function is using CRUD V3 Search. +// Updated 2026-01-02 export async function qry__journal_entry({ api_cfg, journal_id, @@ -381,71 +379,55 @@ export async function qry__journal_entry({ try_cache?: boolean; log_lvl?: number; }) { - log_lvl = 1; + // log_lvl = 1; if (log_lvl) { console.log(`*** qry__journal_entry() *** journal_id=${journal_id}`); } - const params_json: key_val = {}; - - params_json['qry'] = []; + const search_query: any = { and: [] }; if (qry_str) { console.log('qry_str:', qry_str); - // let qry_param = - // { - // type: "AND", - // field: "qry_str", - // operator: "LIKE", - // value: qry_str - // }; - const qry_param = { - type: '', - field: 'default_qry_str', // default is really just the standard FT string in the DB - operator: 'MATCH', - value: qry_str - }; - params_json['qry'].push(qry_param); + // Using 'match' as per previous code's intent + search_query.and.push({ field: 'default_qry_str', op: 'match', value: qry_str }); } if (qry_created_on) { - const qry_param = { - type: 'AND', - field: 'created_on', - operator: '>', - value: qry_created_on - }; - params_json['qry'].push(qry_param); + search_query.and.push({ field: 'created_on', op: 'gt', value: qry_created_on }); } if (qry_priority) { console.log('qry_priority:', qry_priority); - const qry_param = { - type: 'AND', - field: 'priority', - operator: '=', - value: qry_priority - }; - params_json['qry'].push(qry_param); + search_query.and.push({ field: 'priority', op: 'eq', value: qry_priority }); + } + + // Add context + if (journal_id) { + search_query.and.push({ field: 'journal_id_random', op: 'eq', value: journal_id }); + } + + // Add enabled/hidden filters + if (enabled === 'enabled') { + search_query.and.push({ field: 'enabled', op: 'eq', value: true }); + } else if (enabled === 'not_enabled') { + search_query.and.push({ field: 'enabled', op: 'eq', value: false }); + } + + if (hidden === 'hidden') { + search_query.and.push({ field: 'hidden', op: 'eq', value: true }); + } else if (hidden === 'not_hidden') { + search_query.and.push({ field: 'hidden', op: 'eq', value: false }); } ae_promises.load__journal_entry_obj_li = await api - .get_ae_obj_li_for_obj_id_crud_v2({ + .search_ae_obj_v3({ api_cfg: api_cfg, obj_type: 'journal_entry', - for_obj_type: 'journal', - for_obj_id: journal_id, - use_alt_tbl: true, // NOTE: We want to use the alt table for journal entry searching??? - use_alt_mdl: false, - use_alt_exp: false, - enabled: enabled, - hidden: hidden, - order_by_li: order_by_li, - limit: limit, - offset: offset, - params_json: params_json, - params: params, - log_lvl: log_lvl + search_query, + order_by_li, + limit, + offset, + log_lvl }) .then(function (journal_entry_obj_li_get_result) { if (journal_entry_obj_li_get_result) { @@ -935,7 +917,7 @@ async function _process_generic_props>({ for (const key in processed_obj) { if (key.endsWith('_random')) { const newKey = key.slice(0, -7); // Remove '_random' suffix - processed_obj[newKey] = processed_obj[key]; + (processed_obj as any)[newKey] = processed_obj[key]; } } // Ensure 'id' is set from '[obj_type]_id_random'