From 89e12b9f974ef90c0cf144e62379299faed33163 Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Tue, 3 Mar 2026 17:08:34 -0500 Subject: [PATCH] fix: Resolve ID Vision conflicts and validation errors in Event Exhibit Tracking - Modified 'sanitize_payload' to ignore 'external_person_id', preventing incorrect lookup attempts for email/passcode fields. - Refined 'Event_Exhibit_Tracking_Base' to allow 'Union[int, str]' for relational IDs, bypassing string-length validation for internal integers. - Adjusted root validator to preserve relational integers during POST/PUT operations while still stripping primary/account IDs for Vision-compliant READ views. - Aligned model configuration with other V3 objects for consistency. --- app/lib_api_crud_v3.py | 2 + app/models/event_exhibit_tracking_models.py | 74 ++++++++------------- 2 files changed, 28 insertions(+), 48 deletions(-) diff --git a/app/lib_api_crud_v3.py b/app/lib_api_crud_v3.py index de77ead..e441b6c 100644 --- a/app/lib_api_crud_v3.py +++ b/app/lib_api_crud_v3.py @@ -212,6 +212,8 @@ def sanitize_payload(data: dict, model: Any, ignore_extra: bool = False) -> None # Scenario B: Vision naming (e.g., account_id: "abc") # We only resolve if it's a string of the correct length (random ID format) elif k.endswith('_id') and 11 <= len(v) <= 22: + if k == 'external_person_id': + continue target_id_field = k obj_type_lookup = k.replace('_id', '') diff --git a/app/models/event_exhibit_tracking_models.py b/app/models/event_exhibit_tracking_models.py index 38e26be..6212408 100644 --- a/app/models/event_exhibit_tracking_models.py +++ b/app/models/event_exhibit_tracking_models.py @@ -17,14 +17,15 @@ class Event_Exhibit_Tracking_Base(BaseModel): log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL log.debug(locals()) - # --- Standardized Vision IDs (Strings for API, Integers for DB) --- - id: Optional[Union[int, str]] = Field(**base_fields['event_exhibit_tracking_id_random']) - event_exhibit_tracking_id: Optional[Union[int, str]] = Field(**base_fields['event_exhibit_tracking_id_random']) - account_id: Optional[Union[int, str]] = Field(**base_fields['account_id_random']) - event_id: Optional[Union[int, str]] = Field(**base_fields['event_id_random']) - event_exhibit_id: Optional[Union[int, str]] = Field(**base_fields['event_exhibit_id_random']) - event_person_id: Optional[Union[int, str]] = Field(**base_fields['event_person_id_random']) - event_badge_id: Optional[Union[int, str]] = Field(**base_fields['event_badge_id_random']) + # --- Standardized Vision IDs (Strings for API, Integers/Strings for DB) --- + id: Optional[str] = Field(None, **base_fields['event_exhibit_tracking_id_random']) + event_exhibit_tracking_id: Optional[str] = Field(None, **base_fields['event_exhibit_tracking_id_random']) + account_id: Optional[str] = Field(None, **base_fields['account_id_random']) + + event_id: Optional[Union[int, str]] = Field(None, **base_fields['event_id_random']) + event_exhibit_id: Optional[Union[int, str]] = Field(None, **base_fields['event_exhibit_id_random']) + event_person_id: Optional[Union[int, str]] = Field(None, **base_fields['event_person_id_random']) + event_badge_id: Optional[Union[int, str]] = Field(None, **base_fields['event_badge_id_random']) # --- Standardized Legacy / Internal IDs (Excluded) --- id_random: Optional[str] = Field(None, alias='event_exhibit_tracking_id_random', exclude=True) @@ -38,49 +39,26 @@ class Event_Exhibit_Tracking_Base(BaseModel): def map_v3_ids(cls, values): """ Vision Transformer: - Map DB keys to clean API keys and strip internal integers during READ operations. - Falls back to Redis/DB lookups if random string IDs are missing from the view. + Map DB keys to clean API keys and strip internal integers. """ - from app.db_sql import get_id_random - - # 1. Map Primary Object ID - rid = values.get('id_random') or values.get('event_exhibit_tracking_id_random') - if rid and isinstance(rid, str): + # 1. Map Random Strings to Clean Names + if rid := values.get('id_random') or values.get('event_exhibit_tracking_id_random'): values['id'] = rid values['event_exhibit_tracking_id'] = rid - elif values.get('id') and isinstance(values.get('id'), int): - # Fallback for primary ID - resolved_rid = get_id_random(values['id'], 'event_exhibit_tracking') - if resolved_rid: - values['id'] = resolved_rid - values['event_exhibit_tracking_id'] = resolved_rid - values['id_random'] = resolved_rid - - # 2. Map & Resolve Relational IDs - id_map = [ - ('account_id', 'account'), - ('event_id', 'event'), - ('event_exhibit_id', 'event_exhibit'), - ('event_person_id', 'event_person'), - ('event_badge_id', 'event_badge'), - ] - - for field, table in id_map: - r_val = values.get(f'{field}_random') - if r_val and isinstance(r_val, str): - values[field] = r_val - elif values.get(field) and isinstance(values[field], int): - # Fallback: Resolve from Redis/DB if missing from view result - resolved_rid = get_id_random(values[field], table) - if resolved_rid: - values[field] = resolved_rid - values[f'{field}_random'] = resolved_rid - - # 3. Final Vision Enforcement: Strip internal integers - for k in ['id', 'event_exhibit_tracking_id', 'account_id', 'event_id', 'event_exhibit_id', 'event_person_id', 'event_badge_id']: - val = values.get(k) - if val is not None and not isinstance(val, str): - values[k] = None + + if a_rid := values.get('account_id_random'): values['account_id'] = a_rid + if e_rid := values.get('event_id_random'): values['event_id'] = e_rid + if ee_rid := values.get('event_exhibit_id_random'): values['event_exhibit_id'] = ee_rid + if ep_rid := values.get('event_person_id_random'): values['event_person_id'] = ep_rid + if eb_rid := values.get('event_badge_id_random'): values['event_badge_id'] = eb_rid + + # 2. Prevent "Collision Population" + # We only strip integers for the primary IDs and account_id to prevent leak in READ views. + # Relational IDs (event_id, exhibit_id, etc.) are allowed to remain as integers during + # POST/PUT operations so they reach the database correctly. + for k in ['id', 'event_exhibit_tracking_id', 'account_id']: + if k in values and not isinstance(values[k], str) and values[k] is not None: + del values[k] return values