- Sync engine: BaseSync abstract class + 4 sync modules (users/pricing/uapi/llmage) - Checkpoint management via sync_state table - Batch processing with retry and exponential backoff - Incremental fetch from Sage DB via sqlor - UPSERT to local cache tables - API handlers: balance/accounting/users/pricing/health - Balance: cache lookup + Sage fallback - Accounting: create with idempotency, query with filters/pagination - Users: keyword search, org filter - Pricing: filter by ppid/llmid/type/status - Health: basic + readiness checks (DB connectivity) - DAPI auth: middleware + authenticate_request function - HMAC-SHA256 signature verification - Timestamp window validation - Sage downapikey table lookup - HTTP client: SageHttpClient with aiohttp - Auto DAPI signature injection - Connection pooling, retry, timeout - Router: 12 routes registered - Module init: load_sageapi() wires everything to ServerEnv
153 lines
5.4 KiB
Python
153 lines
5.4 KiB
Python
"""SageAPI module initialization.
|
|
|
|
Registers all public functions to ServerEnv so they are accessible
|
|
from dspy scripts and other modules via the global environment.
|
|
Also sets up route registration and database event bindings.
|
|
"""
|
|
|
|
from appPublic.log import debug, info
|
|
from sqlor.dbpools import DBPools
|
|
from ahserver.serverenv import ServerEnv
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Auth
|
|
# ---------------------------------------------------------------------------
|
|
from .middleware.dapi_auth import authenticate_request, DapiAuthMiddleware
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Sync
|
|
# ---------------------------------------------------------------------------
|
|
from .sync.base_sync import BaseSync, run_all_syncs
|
|
from .sync.user_sync import UserSync
|
|
from .sync.pricing_sync import PricingSync
|
|
from .sync.uapi_sync import UapiSync
|
|
from .sync.llmage_sync import LlmageSync
|
|
|
|
# Module-level convenience functions for sync
|
|
async def sync_users() -> str:
|
|
"""Convenience: run user sync."""
|
|
syncer = UserSync()
|
|
return await syncer.sync()
|
|
|
|
async def sync_pricing() -> str:
|
|
"""Convenience: run pricing sync."""
|
|
syncer = PricingSync()
|
|
return await syncer.sync()
|
|
|
|
async def sync_uapi() -> str:
|
|
"""Convenience: run uapi sync."""
|
|
syncer = UapiSync()
|
|
return await syncer.sync()
|
|
|
|
async def sync_llmage() -> str:
|
|
"""Convenience: run llmage sync."""
|
|
syncer = LlmageSync()
|
|
return await syncer.sync()
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Cache
|
|
# ---------------------------------------------------------------------------
|
|
from .cache.cache_manager import CacheManager
|
|
|
|
# Global cache instance (per-process)
|
|
_cache_manager = CacheManager(max_entries=10000, default_ttl=300)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# API
|
|
# ---------------------------------------------------------------------------
|
|
from .api.balance import get_customer_balance, update_customer_balance
|
|
from .api.accounting import create_accounting_record, query_accounting_records
|
|
from .api.users import query_users, get_user_by_id
|
|
from .api.pricing import query_pricing, get_pricing_by_llmid
|
|
from .api.health import health_check, readiness_check
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Router
|
|
# ---------------------------------------------------------------------------
|
|
from .router import Router, setup_routes
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Utils
|
|
# ---------------------------------------------------------------------------
|
|
from .utils.http_client import SageHttpClient
|
|
from .utils.crypto import encrypt_payload, decrypt_payload
|
|
|
|
|
|
def _bind_sageapi_events(dbpools: DBPools, dbname: str) -> None:
|
|
"""Bind database events to SageAPI cache invalidation handlers."""
|
|
bindings = [
|
|
(f'{dbname}:sync_state:c:after', _cache_manager.invalidate_sync_state),
|
|
(f'{dbname}:sync_state:u:after', _cache_manager.invalidate_sync_state),
|
|
(f'{dbname}:sync_state:d:after', _cache_manager.invalidate_sync_state),
|
|
(f'{dbname}:accounting_records:c:after', _cache_manager.invalidate_accounting),
|
|
(f'{dbname}:accounting_records:u:after', _cache_manager.invalidate_accounting),
|
|
(f'{dbname}:accounting_records:d:after', _cache_manager.invalidate_accounting),
|
|
]
|
|
for event_name, handler in bindings:
|
|
try:
|
|
dbpools.bind(event_name, handler)
|
|
debug(f'SageAPI event bound: {event_name}')
|
|
except Exception as e:
|
|
debug(f'SageAPI event bind skipped: {event_name} ({e})')
|
|
|
|
|
|
def load_sageapi() -> None:
|
|
"""Register all SageAPI functions into ServerEnv.
|
|
|
|
Called by the Sage server during module loading phase.
|
|
"""
|
|
env = ServerEnv()
|
|
|
|
# Auth
|
|
env.authenticate_request = authenticate_request
|
|
env.DapiAuthMiddleware = DapiAuthMiddleware
|
|
|
|
# Sync
|
|
env.sync_users = sync_users
|
|
env.sync_pricing = sync_pricing
|
|
env.sync_uapi = sync_uapi
|
|
env.sync_llmage = sync_llmage
|
|
env.run_all_syncs = run_all_syncs
|
|
env.BaseSync = BaseSync
|
|
env.UserSync = UserSync
|
|
env.PricingSync = PricingSync
|
|
env.UapiSync = UapiSync
|
|
env.LlmageSync = LlmageSync
|
|
|
|
# Cache
|
|
env.cache_manager = _cache_manager
|
|
|
|
# API
|
|
env.get_customer_balance = get_customer_balance
|
|
env.update_customer_balance = update_customer_balance
|
|
env.create_accounting_record = create_accounting_record
|
|
env.query_accounting_records = query_accounting_records
|
|
env.query_users = query_users
|
|
env.get_user_by_id = get_user_by_id
|
|
env.query_pricing = query_pricing
|
|
env.get_pricing_by_llmid = get_pricing_by_llmid
|
|
env.health_check = health_check
|
|
env.readiness_check = readiness_check
|
|
|
|
# Router
|
|
router = Router()
|
|
setup_routes(router)
|
|
env.sageapi_router = router
|
|
info(f'SageAPI: {len(router.get_routes())} routes registered')
|
|
|
|
# Utils
|
|
env.SageHttpClient = SageHttpClient
|
|
env.encrypt_payload = encrypt_payload
|
|
env.decrypt_payload = decrypt_payload
|
|
|
|
# Bind database events
|
|
dbpools = DBPools()
|
|
dbname = env.get_module_dbname('sageapi')
|
|
if dbname:
|
|
_bind_sageapi_events(dbpools, dbname)
|
|
info(f'SageAPI: event listeners bound for database: {dbname}')
|
|
else:
|
|
debug('SageAPI: event listeners skipped (no database configured)')
|
|
|
|
info('SageAPI module loaded successfully')
|