diff --git a/app/models/hosted_file_models.py b/app/models/hosted_file_models.py index c20de4a..af5c3dc 100644 --- a/app/models/hosted_file_models.py +++ b/app/models/hosted_file_models.py @@ -67,21 +67,24 @@ class Hosted_File_Base(BaseModel): def map_v3_ids(cls, values): """ Vision Transformer: - Map DB keys to clean API keys and strip internal integers. + Map DB keys to clean API keys and strip internal integers during READ operations. + During CREATE (POST) operations, we ensure resolved integers are preserved. """ - # 1. Capture the random ID string + # 1. Map Random Strings to Clean Names rid = values.get('id_random') or values.get('hosted_file_id_random') - - # 2. Map Random Strings to Clean Names for the Frontend - # We always want the string version in 'id' and 'hosted_file_id' for the API response - if rid: + if rid and isinstance(rid, str): values['id'] = rid values['hosted_file_id'] = rid - if a_rid := values.get('account_id_random'): - # If we have a random account ID string, use it for the Vision API - values['account_id'] = a_rid + if a_rid := values.get('account_id_random'): values['account_id'] = a_rid + # 2. Prevent "Collision Population" or leakage of integers during API responses + for k in ['id', 'hosted_file_id', 'account_id']: + val = values.get(k) + if val is not None and not isinstance(val, str): + if values.get(f'{k}_random') or (k=='id' and values.get('id_random')): + del values[k] + return values class Config: diff --git a/app/models/journal_models.py b/app/models/journal_models.py index 20f9f96..0a28769 100644 --- a/app/models/journal_models.py +++ b/app/models/journal_models.py @@ -16,12 +16,12 @@ class Journal_Base(BaseModel): log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL log.debug(locals()) - # --- Standardized Vision IDs (Strings) --- - id: Optional[str] = Field(None, **base_fields['journal_id_random']) - journal_id: Optional[str] = Field(None, **base_fields['journal_id_random']) - account_id: Optional[str] = Field(None, **base_fields['account_id_random']) - person_id: Optional[str] = Field(None, **base_fields['person_id_random']) - user_id: Optional[str] = Field(None, **base_fields['user_id_random']) + # --- Standardized Vision IDs (Strings for API, Integers for DB) --- + id: Optional[Union[int, str]] = Field(None, **base_fields['journal_id_random']) + journal_id: Optional[Union[int, str]] = Field(None, **base_fields['journal_id_random']) + account_id: Optional[Union[int, str]] = Field(None, **base_fields['account_id_random']) + person_id: Optional[Union[int, str]] = Field(None, **base_fields['person_id_random']) + user_id: Optional[Union[int, str]] = Field(None, **base_fields['user_id_random']) external_id: Optional[str] # ID generated by or for external systems (should be stable and not change) import_id: Optional[str] # Used for import purposes to track the source of the data @@ -132,24 +132,25 @@ class Journal_Base(BaseModel): def map_v3_ids(cls, values): """ Vision Transformer: - Map DB keys to clean API keys and strip internal integers. + Map DB keys to clean API keys and strip internal integers during READ operations. + During CREATE (POST) operations, we ensure resolved integers are preserved. """ # 1. Map Random Strings to Clean Names - if rid := values.get('id_random') or values.get('journal_id_random'): + rid = values.get('id_random') or values.get('journal_id_random') + if rid and isinstance(rid, str): values['id'] = rid values['journal_id'] = rid - if a_rid := values.get('account_id_random'): - values['account_id'] = a_rid - if p_rid := values.get('person_id_random'): - values['person_id'] = p_rid - if u_rid := values.get('user_id_random'): - values['user_id'] = u_rid + if a_rid := values.get('account_id_random'): values['account_id'] = a_rid + if p_rid := values.get('person_id_random'): values['person_id'] = p_rid + if u_rid := values.get('user_id_random'): values['user_id'] = u_rid - # 2. Prevent "Collision Population" + # 2. Prevent "Collision Population" or leakage of integers during API responses for k in ['id', 'journal_id', 'account_id', 'person_id', 'user_id']: - if k in values and not isinstance(values[k], str): - del values[k] + val = values.get(k) + if val is not None and not isinstance(val, str): + if values.get(f'{k}_random') or (k=='id' and values.get('id_random')): + del values[k] return values diff --git a/app/models/page_models.py b/app/models/page_models.py index c872c21..6855103 100644 --- a/app/models/page_models.py +++ b/app/models/page_models.py @@ -14,19 +14,11 @@ class Page_Base(BaseModel): log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL log.debug(locals()) - # --- Standardized Vision IDs (Strings) --- - id: Optional[str] = Field(None, **base_fields['page_id_random']) - page_id: Optional[str] = Field(None, **base_fields['page_id_random']) - account_id: Optional[str] = Field(None, **base_fields['account_id_random']) - site_id: Optional[str] = Field(None, **base_fields['site_id_random']) - - # page_id_random: Optional[str] = Field( - # **base_fields['page_id_random'], - # alias = 'page_id_random', - # ) - # id: Optional[int] = Field( - # alias = 'page_id' - # ) + # --- Standardized Vision IDs (Strings for API, Integers for DB) --- + id: Optional[Union[int, str]] = Field(None, **base_fields['page_id_random']) + page_id: Optional[Union[int, str]] = Field(None, **base_fields['page_id_random']) + account_id: Optional[Union[int, str]] = Field(None, **base_fields['account_id_random']) + site_id: Optional[Union[int, str]] = Field(None, **base_fields['site_id_random']) code: Optional[str] name: Optional[str] @@ -69,22 +61,24 @@ class Page_Base(BaseModel): def map_v3_ids(cls, values): """ Vision Transformer: - Map DB keys to clean API keys and strip internal integers. + Map DB keys to clean API keys and strip internal integers during READ operations. + During CREATE (POST) operations, we ensure resolved integers are preserved. """ # 1. Map Random Strings to Clean Names - if rid := values.get('id_random') or values.get('page_id_random'): + rid = values.get('id_random') or values.get('page_id_random') + if rid and isinstance(rid, str): values['id'] = rid values['page_id'] = rid - if a_rid := values.get('account_id_random'): - values['account_id'] = a_rid - if s_rid := values.get('site_id_random'): - values['site_id'] = s_rid + if a_rid := values.get('account_id_random'): values['account_id'] = a_rid + if s_rid := values.get('site_id_random'): values['site_id'] = s_rid - # 2. Prevent "Collision Population" + # 2. Prevent "Collision Population" or leakage of integers during API responses for k in ['id', 'account_id', 'site_id']: - if k in values and not isinstance(values[k], str): - del values[k] + val = values.get(k) + if val is not None and not isinstance(val, str): + if values.get(f'{k}_random') or (k=='id' and values.get('id_random')): + del values[k] return values diff --git a/app/models/post_models.py b/app/models/post_models.py index b9edeba..389ccb9 100644 --- a/app/models/post_models.py +++ b/app/models/post_models.py @@ -16,12 +16,12 @@ class Post_Base(BaseModel): log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL log.debug(locals()) - # --- Standardized Vision IDs (Strings) --- - id: Optional[str] = Field(None, **base_fields['post_id_random']) - post_id: Optional[str] = Field(None, **base_fields['post_id_random']) - account_id: Optional[str] = Field(None, **base_fields['account_id_random']) - person_id: Optional[str] = Field(None, **base_fields['person_id_random']) - user_id: Optional[str] = Field(None, **base_fields['user_id_random']) + # --- Standardized Vision IDs (Strings for API, Integers for DB) --- + id: Optional[Union[int, str]] = Field(None, **base_fields['post_id_random']) + post_id: Optional[Union[int, str]] = Field(None, **base_fields['post_id_random']) + account_id: Optional[Union[int, str]] = Field(None, **base_fields['account_id_random']) + person_id: Optional[Union[int, str]] = Field(None, **base_fields['person_id_random']) + user_id: Optional[Union[int, str]] = Field(None, **base_fields['user_id_random']) external_person_id: Optional[str] # Person ID generated by external system (should be stable and not change) @@ -93,24 +93,25 @@ class Post_Base(BaseModel): def map_v3_ids(cls, values): """ Vision Transformer: - Map DB keys to clean API keys and strip internal integers. + Map DB keys to clean API keys and strip internal integers during READ operations. + During CREATE (POST) operations, we ensure resolved integers are preserved. """ # 1. Map Random Strings to Clean Names - if rid := values.get('id_random') or values.get('post_id_random'): + rid = values.get('id_random') or values.get('post_id_random') + if rid and isinstance(rid, str): values['id'] = rid values['post_id'] = rid - if a_rid := values.get('account_id_random'): - values['account_id'] = a_rid - if p_rid := values.get('person_id_random'): - values['person_id'] = p_rid - if u_rid := values.get('user_id_random'): - values['user_id'] = u_rid + if a_rid := values.get('account_id_random'): values['account_id'] = a_rid + if p_rid := values.get('person_id_random'): values['person_id'] = p_rid + if u_rid := values.get('user_id_random'): values['user_id'] = u_rid - # 2. Prevent "Collision Population" + # 2. Prevent "Collision Population" or leakage of integers during API responses for k in ['id', 'post_id', 'account_id', 'person_id', 'user_id']: - if k in values and not isinstance(values[k], str): - del values[k] + val = values.get(k) + if val is not None and not isinstance(val, str): + if values.get(f'{k}_random') or (k=='id' and values.get('id_random')): + del values[k] return values