from __future__ import annotations import json from fastapi import APIRouter, Depends from sqlalchemy.orm import Session from govoplan_core.auth import ApiPrincipal, require_any_scope from govoplan_files.backend.change_tracking import ( FILES_CONNECTOR_PROFILES_COLLECTION, ) from govoplan_files.backend.schemas import ( FileConnectorProfileResponse, FileConnectorProfileUpdateRequest, ) from govoplan_core.db.session import get_session from govoplan_files.backend.storage.common import FileStorageError from govoplan_files.backend.storage.connector_credential_deletion import ( delete_connector_profile_row, ) from govoplan_files.backend.storage.connector_deployment import ( connector_effective_endpoint_url, ) from govoplan_files.backend.storage.connector_profile_store import ( connector_profile_from_row, get_connector_profile_row, update_connector_profile_row, ) from govoplan_files.backend.storage.connector_policy import ( ConnectorPolicyDenied, ) from govoplan_files.backend.route_support import ( FILES_CONNECTOR_PROFILE_RESOURCE, _can_read_disabled_connector_profiles, _connector_policy_error, _credential_row_for_profile, _ensure_connector_configuration_allowed, _ensure_connector_local_policy_allowed, _http_error, _record_connector_settings_change, _require_connector_profile_write, _visible_connector_profile, ) router = APIRouter(prefix="/files", tags=["files"]) @router.get( "/connectors/profiles/{profile_id}", response_model=FileConnectorProfileResponse ) def get_connector_profile( profile_id: str, campaign_id: str | None = None, include_disabled: bool = False, session: Session = Depends(get_session), principal: ApiPrincipal = Depends( require_any_scope( "files:file:read", "files:file:upload", "files:file:download", "files:file:admin", "system:settings:read", "admin:settings:read", ) ), ): try: profile = _visible_connector_profile( session, principal, profile_id, campaign_id=campaign_id, include_disabled=include_disabled and _can_read_disabled_connector_profiles(principal), include_effective_policy=False, ) except (OSError, ValueError, json.JSONDecodeError) as exc: raise _http_error(exc) from exc return FileConnectorProfileResponse(**profile.to_response()) @router.patch( "/connectors/profiles/{profile_id}", response_model=FileConnectorProfileResponse ) def update_connector_profile( profile_id: str, payload: FileConnectorProfileUpdateRequest, session: Session = Depends(get_session), principal: ApiPrincipal = Depends( require_any_scope( "files:file:admin", "system:settings:write", "admin:settings:write" ) ), ): try: row = get_connector_profile_row( session, tenant_id=principal.tenant_id, profile_id=profile_id, include_disabled=True, ) except FileStorageError as exc: raise _http_error(exc, not_found=True) from exc _require_connector_profile_write(principal, row.scope_type) credentials = payload.credentials try: credential_profile_id = ( payload.credential_profile_id if payload.credential_profile_id is not None else row.credential_profile_id ) provider = payload.provider if payload.provider is not None else row.provider credential_row = _credential_row_for_profile( session, principal, credential_profile_id=credential_profile_id, provider=provider, profile_id=row.id, scope_type=row.scope_type, scope_id=row.scope_id, include_disabled=True, ) _ensure_connector_configuration_allowed( session, principal, connector_id=row.id, credential_id=credential_profile_id, provider=provider, endpoint_url=connector_effective_endpoint_url( provider=provider, endpoint_url=payload.endpoint_url if payload.endpoint_url is not None else row.endpoint_url, metadata=payload.metadata if payload.metadata is not None else row.metadata_, ), base_path=payload.base_path if payload.base_path is not None else row.base_path, scope_type=row.scope_type, scope_id=row.scope_id, operation="configure", ) if payload.policy is not None: _ensure_connector_local_policy_allowed( session, principal, scope_type=row.scope_type, scope_id=row.scope_id, policy=payload.policy, ) update_connector_profile_row( session, row, user_id=principal.user.id, label=payload.label, provider=payload.provider, endpoint_url=payload.endpoint_url, base_path=payload.base_path, enabled=payload.enabled, credential_profile_id=payload.credential_profile_id, credential_mode=payload.credential_mode, username=credentials.username if credentials else None, password=credentials.password if credentials else None, token=credentials.token if credentials else None, password_env=credentials.password_env if credentials else None, token_env=credentials.token_env if credentials else None, secret_ref=credentials.secret_ref if credentials else None, clear_password=payload.clear_password, clear_token=payload.clear_token, capabilities=payload.capabilities, policy=payload.policy, metadata=payload.metadata, ) _record_connector_settings_change( session, collection=FILES_CONNECTOR_PROFILES_COLLECTION, resource_type=FILES_CONNECTOR_PROFILE_RESOURCE, resource_id=row.id, operation="updated", principal=principal, tenant_id=row.tenant_id, payload={ "scope_type": row.scope_type, "scope_id": row.scope_id, "provider": row.provider, }, ) session.commit() session.refresh(row) return FileConnectorProfileResponse( **connector_profile_from_row( row, credential_row=credential_row ).to_response() ) except ConnectorPolicyDenied as exc: session.rollback() raise _connector_policy_error(exc) from exc except (FileStorageError, ValueError, json.JSONDecodeError) as exc: session.rollback() raise _http_error(exc) from exc @router.delete( "/connectors/profiles/{profile_id}", response_model=FileConnectorProfileResponse ) def deactivate_connector_profile( profile_id: str, session: Session = Depends(get_session), principal: ApiPrincipal = Depends( require_any_scope( "files:file:admin", "system:settings:write", "admin:settings:write" ) ), ): try: row = get_connector_profile_row( session, tenant_id=principal.tenant_id, profile_id=profile_id, include_disabled=True, ) except FileStorageError as exc: raise _http_error(exc, not_found=True) from exc _require_connector_profile_write(principal, row.scope_type) try: changed = delete_connector_profile_row( session, row, deletion_reason="api_delete", user_id=principal.user.id, api_key_id=principal.api_key.id if principal.api_key else None, ) if changed: _record_connector_settings_change( session, collection=FILES_CONNECTOR_PROFILES_COLLECTION, resource_type=FILES_CONNECTOR_PROFILE_RESOURCE, resource_id=row.id, operation="deleted", principal=principal, tenant_id=row.tenant_id, payload={ "scope_type": row.scope_type, "scope_id": row.scope_id, "provider": row.provider, }, ) session.commit() session.refresh(row) return FileConnectorProfileResponse( **connector_profile_from_row(row).to_response() ) except FileStorageError as exc: session.rollback() raise _http_error(exc) from exc except Exception: session.rollback() raise