from fastapi import APIRouter, Depends, HTTPException, Path, Query, Response, status from typing import List, Optional import json import logging from app.lib_general_v3 import AccountContext, get_account_context from app.methods.lookup_methods import get_lookup_list_v3, resolve_lookup_v3 from app.methods.site_methods import load_site_obj from app.models.response_models import Resp_Body_Base, mk_resp from app.object_definitions.lookups import lu_obj_li log = logging.getLogger(__name__) router = APIRouter() @router.get("/{lu_type}/list", response_model=Resp_Body_Base) async def get_v3_lookup_list( lu_type: str = Path(..., min_length=2, max_length=50), for_type: Optional[str] = Query(None, min_length=2, max_length=50), for_id: Optional[int] = Query(None), site_id: Optional[str] = Query(None, min_length=8, max_length=22), include_disabled: bool = Query(False), only_priority: bool = Query(False), account_ctx: AccountContext = Depends(get_account_context), response: Response = Response ): """ Returns a hierarchical, ranked, and deduplicated list of lookup records. Supports Object/Account overrides, negative shadowing, and Site Whitelist policies. """ v3_key = f"lu_v3_{lu_type}" if v3_key not in lu_obj_li: return mk_resp(data=False, status_code=400, response=response, status_message=f"Lookup type '{lu_type}' not supported in V3.") # Phase 2: Whitelist Policy Injection whitelist = None if site_id: if site_obj := load_site_obj(site_id=site_id, model_as_dict=True): # Check if this site belongs to the current account context # NOTE: site_obj.get('account_id') returns the RANDOM string ID in V3 models if site_obj.get('account_id') == account_ctx.account_id_random: cfg = site_obj.get('cfg_json') if isinstance(cfg, str): try: cfg = json.loads(cfg) except: cfg = {} if isinstance(cfg, dict): policy = cfg.get('lookup_policy', {}) whitelist = policy.get(lu_type) else: return mk_resp(data=False, status_code=403, response=response, status_message="Site does not belong to the authorized account.") results = get_lookup_list_v3( lu_type=lu_type, account_ctx=account_ctx, for_type=for_type, for_id=for_id, include_disabled=include_disabled, whitelist=whitelist, only_priority=only_priority ) if not results and not include_disabled: return mk_resp(data=[], status_code=200, response=response, status_message="No active records found.") return mk_resp(data=results) @router.get("/{lu_type}/resolve", response_model=Resp_Body_Base) async def resolve_v3_lookup( lu_type: str = Path(..., min_length=2, max_length=50), q: str = Query(..., min_length=1, description="The code, group, or identity to resolve."), site_id: Optional[str] = Query(None, min_length=8, max_length=22), account_ctx: AccountContext = Depends(get_account_context), response: Response = Response ): """ Resolves an identity string to the highest-priority hierarchical match. """ v3_key = f"lu_v3_{lu_type}" if v3_key not in lu_obj_li: return mk_resp(data=False, status_code=400, response=response, status_message=f"Lookup type '{lu_type}' not supported in V3.") # TODO: Add whitelist support for resolve if needed. # For now, resolve uses the full ranked list. identity_fields = lu_obj_li[v3_key].get("searchable_fields", ["group"]) result = resolve_lookup_v3( lu_type=lu_type, query=q, account_ctx=account_ctx, identity_fields=identity_fields ) if not result: return mk_resp(data=None, status_code=404, response=response, status_message=f"Could not resolve '{q}' for lookup '{lu_type}'.") return mk_resp(data=result)