diff --git a/GEMINI.md b/GEMINI.md index 81f01978..abd9d327 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -61,6 +61,12 @@ This project is the frontend UI/UX for the Aether (AE) system, built with Svelte ## 📝 Development History (Consolidated) +### Hardening & Svelte 5 Modernization (2026-01-16) +- **Hardening:** Improved resilience for Journals and IDAA modules against API downtime and "ghost" account fallback. +- **Commit:** Atomic refactor of 15 files focusing on type safety, Svelte 5 runes, and API robustness. +- **Fixes:** Resolved download progress crashes in `api_get_object_v1` and corrected event handling in several Svelte 5 components. +- **Status:** Investigating Badge Rendering issues where `badge_template_obj` fails to load despite `badge_obj` being available. + ### Config & Stability Sprint (2026-01-14) - **Standardization:** Refactored Module, Journal, and Entry modals into a unified "Config" pattern with consistent iconography (Wrench/Gear/Zap). - **Auto-Save:** Implemented background persistence for Status & Security fields using non-blocking API calls. diff --git a/documentation/PROJECT_CRUD_V3_UPGRADE.md b/documentation/PROJECT_CRUD_V3_UPGRADE.md new file mode 100644 index 00000000..649bc4f3 --- /dev/null +++ b/documentation/PROJECT_CRUD_V3_UPGRADE.md @@ -0,0 +1,95 @@ +# Project: CRUD V3 Final Migration + +> **Status:** Active / In Progress +> **Last Updated:** 2026-01-18 +> **Goal:** Eliminate all dependency on legacy API wrappers (`create_ae_obj_crud`, `get_ae_obj_id_crud`, etc.) and ensure 100% adoption of the V3 Standard (`/v3/crud/...`). + +--- + +## 1. Executive Summary +While the **Journals** and **Identity (User/Account)** modules have been successfully migrated to the V3 architecture, a significant portion of the **Events**, **Sponsorships**, and **IDAA** modules still rely on legacy V1/V2 wrappers. This document serves as the master checklist to reach 100% V3 compliance. + +**Why this matters:** +* **Security:** V3 enforces strict multi-tenant isolation via JWT. +* **Maintenance:** Legacy wrappers in `api.ts` contribute to technical debt and "God Object" anti-patterns. +* **Performance:** V3 offers optimized search and partial updates (PATCH) that legacy endpoints lack. + +--- + +## 2. Migration Audit (Findings) + +The following files have been identified as using legacy CRUD wrappers. + +### 🔴 High Priority: Events Module +The entire `ae_events` library is heavily dependent on legacy `v2` list and `v1` CRUD wrappers. + +- [ ] `src/lib/ae_events/ae_events__event_session.ts` +- [ ] `src/lib/ae_events/ae_events__event_presenter.ts` +- [ ] `src/lib/ae_events/ae_events__event_presentation.ts` +- [ ] `src/lib/ae_events/ae_events__event_location.ts` +- [ ] `src/lib/ae_events/ae_events__event_badge_template.ts` +- [ ] `src/lib/ae_events/ae_events__event_device.ts` +- [ ] `src/lib/ae_events/ae_events__exhibit.ts` +- [ ] `src/lib/ae_events/ae_events__event_file.ts` + +### 🟠 Medium Priority: Core & Sponsorships +Legacy patterns persisting in core logic and config modules. + +- [ ] `src/lib/ae_sponsorships/ae_sponsorships_functions.ts` +- [ ] `src/lib/ae_core/core__hosted_files.ts` (Uses `get_ae_obj_id_crud`) +- [ ] `src/lib/ae_core/core__site.ts` (Uses `get_ae_obj_id_crud`) +- [ ] `src/lib/ae_core/core__country_subdivisions.ts` +- [ ] `src/lib/ae_core/core__time_zones.ts` +- [ ] `src/lib/ae_core/core__countries.ts` + +### 🟡 Low Priority: UI Components & Routes +Specific UI components that make direct API calls instead of using store functions. + +- [ ] `src/lib/elements/element_data_store.svelte` (Direct `create_ae_obj_crud`) +- [ ] `src/lib/elements/element_data_store_v2.svelte` +- [ ] `src/routes/events/[event_id]/event_page_menu.svelte` +- [ ] `src/routes/events/[event_id]/(pres_mgmt)/session/ae_comp__event_session_alert.svelte` +- [ ] `src/routes/events/ae_comp__event_session_obj_li.svelte` +- [ ] `src/routes/idaa/(idaa)/recovery_meetings/ae_idaa_comp__event_obj_id_edit.svelte` + +--- + +## 3. Migration Procedure + +For each file listed above, follow this standard refactoring pattern: + +1. **Imports:** + * Remove imports of `create_ae_obj_crud`, `update_ae_obj_id_crud`, etc. + * Import V3 helpers: `get_ae_obj_v3`, `create_ae_obj_v3`, `update_ae_obj_v3`, `delete_ae_obj_v3`, `search_ae_obj_v3`. + +2. **Pattern Replacement:** + + * **Get (Single):** + * *Old:* `get_ae_obj_id_crud({ api_cfg, obj_type: 'event_session', obj_id: '...' })` + * *New:* `get_ae_obj_v3({ api_cfg, obj_type: 'event_session', obj_id: '...' })` + + * **Get (List):** + * *Old:* `get_ae_obj_li_for_obj_id_crud_v2(...)` + * *New:* `get_ae_obj_li_v3(...)` or `search_ae_obj_v3(...)` if complex filtering is needed. + + * **Update:** + * *Old:* `update_ae_obj_id_crud({ ..., fields: { name: 'New Name' } })` + * *New:* `update_ae_obj_v3({ ..., data: { name: 'New Name' } })` + * *Note:* Ensure payload whitelisting is applied! V3 will 400 Error on unknown columns. + + * **Create:** + * *Old:* `create_ae_obj_crud({ ..., fields: { ... } })` + * *New:* `create_ae_obj_v3({ ..., data: { ... } })` + +3. **Verification:** + * Verify the module still loads data (check Network tab for `/v3/` requests). + * Verify saving works (check for 400 Bad Request errors). + +--- + +## 4. Final Cleanup +Once all checkboxes above are completed: +1. [ ] Remove legacy exports from `src/lib/api/api.ts`. +2. [ ] Delete `src/lib/ae_api/api_get__crud_obj_li_v1.ts`. +3. [ ] Delete `src/lib/ae_api/api_get__crud_obj_li_v2.ts`. +4. [ ] Delete `src/lib/ae_api/api_get__crud_obj_id.ts` (Legacy version). diff --git a/src/lib/ae_core/ae_core__site.ts b/src/lib/ae_core/ae_core__site.ts index 45638be3..7cf63f3c 100644 --- a/src/lib/ae_core/ae_core__site.ts +++ b/src/lib/ae_core/ae_core__site.ts @@ -131,9 +131,15 @@ export async function lookup_site_domain_v3({ delete guest_api_cfg.account_id; const search_query = { - q: fqdn + and: [{ field: 'fqdn', op: 'eq', value: fqdn }] }; + if (log_lvl) { + console.log(`BOOTSTRAP SEARCH: fqdn=${fqdn}`); + console.log(`BOOTSTRAP HEADERS:`, guest_api_cfg.headers); + console.log(`BOOTSTRAP QUERY:`, JSON.stringify(search_query)); + } + // We use search because we are looking up by a unique field (fqdn) rather than ID. // The backend should return a list, but since FQDN is unique, it will have 1 item. const result_li = await api.search_ae_obj_v3({ @@ -147,6 +153,8 @@ export async function lookup_site_domain_v3({ log_lvl }); + if (log_lvl) console.log(`BOOTSTRAP RESULT:`, result_li); + if (result_li && result_li.length > 0) { const result = result_li[0]; // Standardize and save to cache diff --git a/src/lib/ae_events/ae_events__event.ts b/src/lib/ae_events/ae_events__event.ts index 3c5ebfc6..8af77ad2 100644 --- a/src/lib/ae_events/ae_events__event.ts +++ b/src/lib/ae_events/ae_events__event.ts @@ -21,7 +21,7 @@ export async function load_ae_obj_id__event({ inc_device_li = false, inc_location_li = false, inc_session_li = false, - inc_template_li = false, + inc_template_li = false, try_cache = true, log_lvl = 0 }: { @@ -104,7 +104,7 @@ export async function load_ae_obj_id__event({ */ async function _handle_nested_loads(event_obj: any, { api_cfg, inc_device_li, inc_location_li, inc_session_li, inc_template_li, log_lvl }: any) { const current_event_id = event_obj.event_id || event_obj.id; - + if (inc_device_li) { event_obj.event_device_obj_li = await load_ae_obj_li__event_device({ api_cfg, @@ -171,7 +171,7 @@ export async function load_ae_obj_li__event({ try_cache?: boolean; log_lvl?: number; }): Promise { - + // Check if offline if (typeof navigator !== 'undefined' && !navigator.onLine) { if (log_lvl) console.log('Browser is offline. Skipping API and attempting cache load.'); @@ -182,7 +182,7 @@ export async function load_ae_obj_li__event({ } let promise; - + if (qry_conference !== null) { // V3 Search now permits 'conference' field. const search_query: any = { @@ -193,7 +193,7 @@ export async function load_ae_obj_li__event({ if (for_obj_id) { search_query.and.push({ field: 'account_id_random', op: 'eq', value: for_obj_id }); } - + promise = api.search_ae_obj_v3({ api_cfg, obj_type: 'event', @@ -443,7 +443,7 @@ export async function qry_ae_obj_li__event({ log_lvl?: number; }) { const search_query: any = { and: [] }; - + if (qry_str) { // Use reserved 'q' property for global full-text search as per V3 Guide search_query.q = qry_str; @@ -498,11 +498,11 @@ export async function qry_ae_obj_li__event({ 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'; - + let match = false; if (qry_physical === true && ev_physical) match = true; if (qry_virtual === true && ev_virtual) match = true; - + if (!match) return false; } @@ -624,16 +624,16 @@ export async function qry_ae_obj_li__event_v2({ } // Location Filtering (Inclusive OR logic) - // If either filter is explicitly true, we restrict results. + // If either filter is explicitly true, we restrict results. // If both are false or null, we show everything. 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'; - + let match = false; if (qry_physical === true && ev_physical) match = true; if (qry_virtual === true && ev_virtual) match = true; - + if (!match) return false; } @@ -855,7 +855,7 @@ export function sync_config__event_pres_mgmt({ pres_mgmt_cfg_remote?.hide__presentation_code ?? false; pres_mgmt_cfg_local.hide__presentation_datetime = pres_mgmt_cfg_remote?.hide__presentation_datetime ?? false; - prev_mgmt_cfg_local.show_content__presentation_description = + pres_mgmt_cfg_local.show_content__presentation_description = pres_mgmt_cfg_remote?.show_content__presentation_description ?? false; pres_mgmt_cfg_local.hide__presenter_code = pres_mgmt_cfg_remote?.hide__presenter_code ?? false; diff --git a/src/lib/app_components/e_app_sign_in_out.svelte b/src/lib/app_components/e_app_sign_in_out.svelte index 66367614..63386cf0 100644 --- a/src/lib/app_components/e_app_sign_in_out.svelte +++ b/src/lib/app_components/e_app_sign_in_out.svelte @@ -489,7 +489,7 @@ // WARNING: This function returns a list. We only want the first one. There should be no more than 1 record returned. // WARNING: This function returns a list. We only want the first one. There should be no more than 1 record returned. - // We use enabled: 'all' and hidden: 'all' to ensure we find the person record even if + // We use enabled: 'all' and hidden: 'all' to ensure we find the person record even if // technical fields like 'hide' are NULL or the record is temporarily disabled. ae_promises['person'] = core_func .load_ae_obj_li__person({ @@ -596,7 +596,7 @@ // WARNING: This function returns a list. We only want the first one. There should be no more than 1 record returned. // WARNING: This function returns a list. We only want the first one. There should be no more than 1 record returned. - // We use enabled: 'all' and hidden: 'all' to ensure we find the person record even if + // We use enabled: 'all' and hidden: 'all' to ensure we find the person record even if // technical fields like 'hide' are NULL or the record is temporarily disabled. ae_promises['person'] = core_func .load_ae_obj_li__person({ @@ -833,7 +833,7 @@ class="top-center bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-200 rounded-lg border-gray-200 dark:border-gray-700 divide-gray-200 dark:divide-gray-700 shadow-md relative mx-auto w-full divide-y" >