From 6c2c37ff065e8f911e56fc4d958b5fb0582b1bd1 Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Thu, 5 Feb 2026 17:56:13 -0500 Subject: [PATCH] fix(standardization): enforce V3 String-Only ID mapping and harden account isolation - Added 'account_id_random' to persistent property list to fix local search isolation. - Standardized search body and helpers to support mandatory x-account-id headers. - Refactored LiveQuery to use synchronous dependency tracking ($derived.by) for reliable search updates. - Broadened server-side search to handle inclusive OR logic on the client, preventing disappearing results. - Updated task list with IDAA Recovery Meeting testing status. --- TODO.md | 15 ++++ src/lib/ae_api/api_get__crud_obj_li_v3.ts | 4 + src/lib/ae_events/ae_events__event.ts | 78 ++++++------------- .../idaa/(idaa)/recovery_meetings/+layout.ts | 2 +- .../ae_idaa_comp__event_obj_li_wrapper.svelte | 25 +++--- 5 files changed, 55 insertions(+), 69 deletions(-) diff --git a/TODO.md b/TODO.md index b1f0e5c1..142e9d2a 100644 --- a/TODO.md +++ b/TODO.md @@ -19,6 +19,21 @@ This is a list of tasks to be completed before the next event/show/conference. - [x] **Session Search:** Modernized logic, fixed layout bugs and icon exports. (Completed 2026-01-27) - [x] **Exhibit Search:** Standardized Exhibitor and Lead Tracking reactive search. (Completed 2026-01-28) +## Urgent Tasks (Feb 5, 2026) + +1. **IDAA Module Verification:** + - [ ] **Bulletin Board (BB):** Verify Post/Comment create/edit; test email notifications for staff and original posters; verify anonymous toggle; test file attachments on Posts; optimize inline image display (collapse/expand). + - [ ] **Recovery Meetings:** Verify all search filters; audit full editing workflow; verify special code/copy buttons for Zoom/Jitsi. + - [ ] **View and Edit:** View and Edit of an IDAA Recovery Meeting still needs to be fully tested (70% done). + +2. **Events - Presentation Management:** + - [ ] **Basics:** Ensure all core presentation management features are fully functional. + - [ ] **Uploads:** Verify direct file uploads to Presenter, Session, Location, and Event. + +3. **Events - Badges:** + - [ ] **Rendering:** Verify Badge Template pulling and correct rendering. + - [ ] **Editing:** Ensure basic fields (name, affiliations, badge type) are editable. + ## Urgent Tasks (Feb 4, 2026) 1. **IDAA Module Verification:** 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 c4a8042f..c34f817e 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 @@ -97,6 +97,7 @@ interface GetAeObjLiV3Params { offset?: number; order_by_li?: Record | Record[] | null; delay_ms?: number; + headers?: key_val; log_lvl?: number; } @@ -112,6 +113,7 @@ export async function get_ae_obj_li_v3({ offset = 0, order_by_li = null, delay_ms = 0, + headers = {}, log_lvl = 0 }: GetAeObjLiV3Params) { // 1. Build V3 Endpoint @@ -135,12 +137,14 @@ export async function get_ae_obj_li_v3({ console.log('*** get_ae_obj_li_v3 ***'); console.log('Endpoint:', endpoint); console.log('Params:', params); + console.log('Headers:', headers); } return await get_object({ api_cfg, endpoint, params, + headers, log_lvl }); } diff --git a/src/lib/ae_events/ae_events__event.ts b/src/lib/ae_events/ae_events__event.ts index a311c54c..b3e813ee 100644 --- a/src/lib/ae_events/ae_events__event.ts +++ b/src/lib/ae_events/ae_events__event.ts @@ -455,11 +455,9 @@ export async function update_ae_obj__event({ * Unified Search for Events (V3 API) * * STRATEGY: Hybrid Search/Filter - * 1. Server-side (V3 Search): Used for high-level filtering (Account, Type, String Search). - * This reduces the data payload significantly. - * 2. Client-side (Filter Layer): Used for complex logic that the backend Search API - * may not handle natively yet, such as inclusive 'OR' logic for Physical/Virtual - * meetings or specific person ID matching. + * 1. Server-side (V3 Search): Used for text search (qry_str) to reduce payload. + * 2. Client-side (Filter Layer): Handles all other filters (Type, Location, Person) + * to ensure correct inclusive OR logic and stable ID matching. */ export async function search__event({ api_cfg, @@ -502,50 +500,30 @@ export async function search__event({ let result_li: ae_Event[] | null = null; - // Use V3 Search if ANY filter is active to ensure we query the full database - const has_active_filters = (qry_str && qry_str.trim().length > 0) || - qry_physical === true || - qry_virtual === true || - (qry_type && qry_type !== 'all' && qry_type !== '') || - qry_person_id || - qry_conference !== null; - - if (has_active_filters) { - // Option A: Active Search (Server-side filtering) + if (qry_str && qry_str.trim().length > 0) { + // Option A: Active Text Search const search_query: any = { and: [] }; const params: key_val = {}; - if (qry_str && qry_str.trim().length > 0) { - // Use default_qry_str for searching as requested - // Using 'like' with wildcards and populating params['lk_qry'] to match working event_session logic - search_query.and.push({ field: 'default_qry_str', op: 'like', value: `%${qry_str.trim()}%` }); - params['lk_qry'] = { 'default_qry_str': qry_str.trim() }; + search_query.and.push({ field: 'default_qry_str', op: 'like', value: `%${qry_str.trim()}%` }); + params['lk_qry'] = { 'default_qry_str': qry_str.trim() }; + + if (for_obj_id) { + // V3 Standard: Use random string ID for body filters. + // The API resolves this to the integer column automatically. + search_query.and.push({ field: `${for_obj_type}_id_random`, op: 'eq', value: for_obj_id }); } // NOTE: We do NOT push 'physical' and 'virtual' to the server-side query here. - // The V3 Search API uses AND logic, which would exclude meetings that are - // only physical or only virtual. We let the Client-side Filter handle this below. - - if (qry_type && qry_type !== 'all' && qry_type !== '') { - search_query.and.push({ field: 'type', op: 'eq', value: qry_type }); - } - - if (for_obj_id) { - search_query.and.push({ field: `${for_obj_type}_id`, op: 'eq', value: for_obj_id }); - } - - if (enabled === 'enabled') search_query.and.push({ field: 'enable', op: 'eq', value: 1 }); - else if (enabled === 'not_enabled') search_query.and.push({ field: 'enable', op: 'eq', value: 0 }); - - if (hidden === 'hidden') search_query.and.push({ field: 'hide', op: 'eq', value: 1 }); - else if (hidden === 'not_hidden') search_query.and.push({ field: 'hide', op: 'eq', value: 0 }); + // The V3 Search API uses AND logic for the body, which would exclude + // meetings that are only physical or only virtual if both filters are active. + // We handle this in the Client-side Filter Layer below for correct OR logic. result_li = await api.search_ae_obj_v3({ api_cfg, obj_type: 'event', - // Inject header context for Auth but keep body context for Filtering headers: { 'x-account-id': for_obj_id }, search_query, params, @@ -558,8 +536,7 @@ export async function search__event({ log_lvl }); } else { - // Option B: List All (No filters active) - // Fallback to standard list retrieval to ensure we get results when the search bar is empty. + // Option B: List All result_li = await api.get_ae_obj_li_v3({ api_cfg, obj_type: 'event', @@ -571,18 +548,18 @@ export async function search__event({ limit, offset, order_by_li, + headers: { 'x-account-id': for_obj_id }, log_lvl }); } - // Handle potential V3 API envelope { data: [], meta: ... } or direct array + // Handle potential V3 API envelope let valid_result_li: ae_Event[] = []; if (Array.isArray(result_li)) { valid_result_li = result_li; } else if (result_li && typeof result_li === 'object' && Array.isArray((result_li as any).data)) { valid_result_li = (result_li as any).data; } else { - // If null, undefined, or unknown format, return empty list to prevent iteration errors return []; } @@ -601,19 +578,11 @@ export async function search__event({ }); } - /** - * Client-side Filter Layer - * - * WHY: The V3 Search API defaults to 'AND' logic for its top-level filters. - * Some UI requirements (like showing meetings that are EITHER physical OR virtual) - * are more reliably handled here after the broad server-side fetch. - * This also ensures that complex person-id matching across multiple legacy fields - * remains consistent without requiring massive backend search indices. - */ + // Client-side Filter Layer const filtered_obj_li = processed_obj_li.filter((ev: any) => { // Handle conference filter if (qry_conference != null) { - const ev_conf = ev.conference === true || ev.conference === 1 || ev.conference === '1'; + const ev_conf = ev.conference == true; if (ev_conf !== !!qry_conference) return false; } @@ -624,8 +593,8 @@ export async function search__event({ // Location Filtering (Inclusive OR logic) if (qry_physical === true || qry_virtual === true) { - const ev_physical = ev.physical === true || ev.physical === 1 || ev.physical === '1'; - const ev_virtual = ev.virtual === true || ev.virtual === 1 || ev.virtual === '1'; + const ev_physical = ev.physical == true; + const ev_virtual = ev.virtual == true; let match = false; if (qry_physical === true && ev_physical) match = true; @@ -650,7 +619,7 @@ export async function search__event({ }); if (log_lvl) { - console.log(`Filter results (V3 Search): Input=${processed_obj_li.length}, Output=${filtered_obj_li.length}`); + console.log(`Filter results (Hybrid): Input=${processed_obj_li.length}, Output=${filtered_obj_li.length}`); } return filtered_obj_li.slice(0, limit); @@ -797,6 +766,7 @@ export const properties_to_save = [ 'event_id', 'code', 'account_id', + 'account_id_random', 'conference', 'type', 'name', diff --git a/src/routes/idaa/(idaa)/recovery_meetings/+layout.ts b/src/routes/idaa/(idaa)/recovery_meetings/+layout.ts index 391950ad..b5fe276a 100644 --- a/src/routes/idaa/(idaa)/recovery_meetings/+layout.ts +++ b/src/routes/idaa/(idaa)/recovery_meetings/+layout.ts @@ -42,7 +42,7 @@ export async function load({ params, parent }) { qry_conference: false, // IDAA Recovery Meetings are not standard conferences enabled: 'enabled', hidden: 'not_hidden', - limit: 199, + limit: 499, order_by_li: { priority: 'DESC', sort: 'DESC', diff --git a/src/routes/idaa/(idaa)/recovery_meetings/ae_idaa_comp__event_obj_li_wrapper.svelte b/src/routes/idaa/(idaa)/recovery_meetings/ae_idaa_comp__event_obj_li_wrapper.svelte index cb82351c..83a98539 100644 --- a/src/routes/idaa/(idaa)/recovery_meetings/ae_idaa_comp__event_obj_li_wrapper.svelte +++ b/src/routes/idaa/(idaa)/recovery_meetings/ae_idaa_comp__event_obj_li_wrapper.svelte @@ -42,18 +42,15 @@ /** * Stable LiveQuery Pattern (Aether UI V3) * - * WHY: We wrap liveQuery in $derived to ensure that Svelte recreates the - * Dexie observable whenever the input props (event_id_random_li) change. - * - * TWO SCENARIOS: - * 1. Specific IDs: If event_id_random_li is provided (from the Search Orchestrator), - * we use bulkGet for high-performance targeted retrieval. - * 2. Fallback: If no IDs are provided, we perform a broad search for the account. + * WHY: We use $derived.by to synchronously track the 'event_id_random_li' prop. + * This ensures that whenever the parent search logic updates the ID list, + * Svelte recreates this Dexie observable and triggers a fresh UI render. */ - let lq__event_obj_li = $derived( - liveQuery(async () => { - const ids = event_id_random_li; - + let lq__event_obj_li = $derived.by(() => { + // SVELTE 5 REACTIVITY: Track IDs synchronously + const ids = event_id_random_li; + + return liveQuery(async () => { // SCENARIO 1: Specific IDs provided (from Search Fast Path or API) if (Array.isArray(ids)) { if (ids.length > 0) { @@ -72,7 +69,7 @@ if (log_lvl) console.log(`Wrapper LQ: Fallback search for ${link_to_type}: ${link_to_id}`); const base_query = db_events.event .where(dq__where_type_id_val) - .equals(dq__where_eq_id_val); + .equals(link_to_id); if (order_by == 'name') { return await base_query @@ -87,8 +84,8 @@ } } return null; - }) - ); + }); + }); {#if $lq__event_obj_li}