Spaces:
Running
Running
| """SuperAdmin service for platform API credential management.""" | |
| from __future__ import annotations | |
| import structlog | |
| from fastapi import HTTPException | |
| from redis.asyncio import Redis | |
| from sqlalchemy.ext.asyncio import AsyncSession | |
| from app.core.crypto.credential_crypto import encrypt_secret, mask_secret | |
| from app.models.user import User | |
| from app.repositories.audit_repo import AuditRepository | |
| from app.repositories.platform_api_credential_repo import PlatformApiCredentialRepository | |
| from app.services.platform_api_registry import ( | |
| PLATFORM_API_META, | |
| PROVIDER_DEFINITIONS, | |
| build_credential_views, | |
| build_provider_views, | |
| invalidate_credential_cache, | |
| is_provider_scope, | |
| provider_scope_id, | |
| ) | |
| logger = structlog.get_logger(__name__) | |
| class PlatformApiService: | |
| """Manage optional retailer API keys with audit logging.""" | |
| def __init__(self, db: AsyncSession, redis: Redis | None = None) -> None: | |
| self._db = db | |
| self._redis = redis | |
| self._repo = PlatformApiCredentialRepository(db) | |
| self._audit = AuditRepository(db) | |
| async def list_credentials(self) -> dict: | |
| """Return provider-scoped masked credential views.""" | |
| rows = await self._repo.list_all() | |
| return build_provider_views(rows) | |
| async def upsert_provider_credential( | |
| self, | |
| actor: User, | |
| *, | |
| provider_id: str, | |
| api_key: str, | |
| api_secret: str | None, | |
| is_enabled: bool, | |
| ) -> dict: | |
| """Create or update a shared provider API key.""" | |
| if provider_id not in PROVIDER_DEFINITIONS: | |
| raise HTTPException(400, f"Unknown provider: {provider_id}") | |
| if not api_key.strip(): | |
| raise HTTPException(400, "API key is required") | |
| scope_id = provider_scope_id(provider_id) | |
| await self._repo.upsert( | |
| platform_id=scope_id, | |
| provider=provider_id, | |
| api_key_encrypted=encrypt_secret(api_key.strip()), | |
| api_secret_encrypted=encrypt_secret(api_secret.strip()) if api_secret else None, | |
| is_enabled=is_enabled, | |
| updated_by_id=actor.id, | |
| updated_by_email=actor.email, | |
| ) | |
| await self._audit.log( | |
| actor_id=actor.id, | |
| actor_email=actor.email, | |
| action="upsert_platform_api", | |
| target_type="platform_api", | |
| target_id=scope_id, | |
| details={ | |
| "provider": provider_id, | |
| "scope": "provider", | |
| "is_enabled": is_enabled, | |
| "api_key_hint": mask_secret(api_key.strip()), | |
| }, | |
| ) | |
| await invalidate_credential_cache(self._redis, scope_id) | |
| logger.info("Provider API credential saved", provider_id=provider_id) | |
| return build_provider_views(await self._repo.list_all()) | |
| async def upsert_credential( | |
| self, | |
| actor: User, | |
| *, | |
| platform_id: str, | |
| provider: str, | |
| api_key: str, | |
| api_secret: str | None, | |
| is_enabled: bool, | |
| ) -> dict: | |
| """Create or update encrypted platform API credential (legacy per-platform).""" | |
| if is_provider_scope(platform_id): | |
| return await self.upsert_provider_credential( | |
| actor, | |
| provider_id=provider, | |
| api_key=api_key, | |
| api_secret=api_secret, | |
| is_enabled=is_enabled, | |
| ) | |
| meta = PLATFORM_API_META.get(platform_id) | |
| if not meta: | |
| raise HTTPException(400, f"Unknown platform: {platform_id}") | |
| if provider not in meta["providers"]: | |
| raise HTTPException( | |
| 400, | |
| f"Provider '{provider}' not allowed for {platform_id}. " | |
| f"Allowed: {', '.join(meta['providers']) or 'none'}", | |
| ) | |
| if not api_key.strip(): | |
| raise HTTPException(400, "API key is required") | |
| await self._repo.upsert( | |
| platform_id=platform_id, | |
| provider=provider, | |
| api_key_encrypted=encrypt_secret(api_key.strip()), | |
| api_secret_encrypted=encrypt_secret(api_secret.strip()) if api_secret else None, | |
| is_enabled=is_enabled, | |
| updated_by_id=actor.id, | |
| updated_by_email=actor.email, | |
| ) | |
| await self._audit.log( | |
| actor_id=actor.id, | |
| actor_email=actor.email, | |
| action="upsert_platform_api", | |
| target_type="platform_api", | |
| target_id=platform_id, | |
| details={ | |
| "provider": provider, | |
| "is_enabled": is_enabled, | |
| "api_key_hint": mask_secret(api_key.strip()), | |
| }, | |
| ) | |
| await invalidate_credential_cache(self._redis, platform_id) | |
| logger.info("Platform API credential saved", platform_id=platform_id, provider=provider) | |
| return build_provider_views(await self._repo.list_all()) | |
| async def disable_credential(self, actor: User, scope_or_platform_id: str) -> dict: | |
| """Disable SuperAdmin credential (falls back to env/free logic).""" | |
| row = await self._repo.get_by_platform(scope_or_platform_id) | |
| if not row: | |
| raise HTTPException(404, "No credential configured") | |
| row.is_enabled = False | |
| row.updated_by_id = actor.id | |
| row.updated_by_email = actor.email | |
| await self._db.flush() | |
| await self._audit.log( | |
| actor_id=actor.id, | |
| actor_email=actor.email, | |
| action="disable_platform_api", | |
| target_type="platform_api", | |
| target_id=scope_or_platform_id, | |
| details={"provider": row.provider}, | |
| ) | |
| await invalidate_credential_cache(self._redis, scope_or_platform_id) | |
| return build_provider_views(await self._repo.list_all()) | |
| async def delete_credential(self, actor: User, scope_or_platform_id: str) -> dict: | |
| """Remove SuperAdmin credential entirely.""" | |
| deleted = await self._repo.delete(scope_or_platform_id) | |
| if not deleted: | |
| raise HTTPException(404, "No credential configured") | |
| await self._audit.log( | |
| actor_id=actor.id, | |
| actor_email=actor.email, | |
| action="delete_platform_api", | |
| target_type="platform_api", | |
| target_id=scope_or_platform_id, | |
| details={}, | |
| ) | |
| await invalidate_credential_cache(self._redis, scope_or_platform_id) | |
| return build_provider_views(await self._repo.list_all()) | |