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.
This commit is contained in:
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user