from __future__ import annotations from typing import Literal from sqlalchemy.orm import Session from govoplan_core.auth import ApiPrincipal from govoplan_core.api.v1.schemas import DeltaDeletedItem from govoplan_core.core.change_sequence import ( ChangeSequenceEntry, ) from govoplan_files.backend.change_tracking import ( FILES_CONNECTOR_CREDENTIALS_COLLECTION, FILES_CONNECTOR_POLICIES_COLLECTION, FILES_CONNECTOR_PROFILES_COLLECTION, FILES_CONNECTOR_SPACES_COLLECTION, ) from govoplan_files.backend.schemas import ( FileConnectorCredentialResponse, FileConnectorSettingsDeltaResponse, FileConnectorPolicyResponse, FileConnectorProfileResponse, ) from govoplan_files.backend.db.models import FileConnectorSpace from govoplan_files.backend.storage.connector_credential_store import ( ConnectorCredential, ) from govoplan_files.backend.storage.connector_profiles import ConnectorProfile from govoplan_files.backend.storage.connector_policy_store import ( connector_policy_response, ) from govoplan_files.backend.route_support import ( FILES_CONNECTOR_CREDENTIAL_RESOURCE, FILES_CONNECTOR_PROFILE_RESOURCE, FILES_CONNECTOR_SPACE_RESOURCE, _can_read_disabled_connector_profiles, _connector_deleted_item, _connector_space_response, _ensure_campaign_file_access, _file_connector_settings_response_watermark, _file_connector_settings_watermark, _visible_connector_credentials, _visible_connector_profiles, _visible_connector_spaces, ) def _full_file_connector_settings_delta_response( session: Session, principal: ApiPrincipal, *, scope_type: str, scope_id: str | None, provider: str | None, campaign_id: str | None, include_disabled: bool, include_inactive: bool, owner_type: Literal["user", "group"] | None, owner_id: str | None, ) -> FileConnectorSettingsDeltaResponse: if campaign_id: _ensure_campaign_file_access(session, principal, campaign_id) profiles = _visible_connector_profiles( session, principal, provider=provider, campaign_id=campaign_id, include_disabled=include_disabled and _can_read_disabled_connector_profiles(principal), include_admin_scopes=_can_read_disabled_connector_profiles(principal), include_effective_policy=False, ) credentials = _visible_connector_credentials( session, principal, provider=provider, include_disabled=include_disabled, ) spaces = _visible_connector_spaces( session, principal, owner_type=owner_type, owner_id=owner_id, include_inactive=include_inactive, ) return FileConnectorSettingsDeltaResponse( profiles=[ FileConnectorProfileResponse(**profile.to_response()) for profile in profiles ], credentials=[ FileConnectorCredentialResponse(**credential.to_response()) for credential in credentials ], spaces=[_connector_space_response(space) for space in spaces], policy=FileConnectorPolicyResponse( **connector_policy_response( session, tenant_id=principal.tenant_id, scope_type=scope_type, scope_id=scope_id, ) ), changed_sections=["profiles", "credentials", "spaces", "policy"], deleted=[], watermark=_file_connector_settings_watermark( session, tenant_id=principal.tenant_id ), has_more=False, full=True, ) def _changed_file_connector_setting_ids( entries: list[ChangeSequenceEntry], ) -> tuple[set[str], set[str], set[str], bool]: changed_profile_ids = { entry.resource_id for entry in entries if entry.collection == FILES_CONNECTOR_PROFILES_COLLECTION and entry.resource_type == FILES_CONNECTOR_PROFILE_RESOURCE } changed_credential_ids = { entry.resource_id for entry in entries if entry.collection == FILES_CONNECTOR_CREDENTIALS_COLLECTION and entry.resource_type == FILES_CONNECTOR_CREDENTIAL_RESOURCE } changed_space_ids = { entry.resource_id for entry in entries if entry.collection == FILES_CONNECTOR_SPACES_COLLECTION and entry.resource_type == FILES_CONNECTOR_SPACE_RESOURCE } policy_changed = any( entry.collection == FILES_CONNECTOR_POLICIES_COLLECTION for entry in entries ) return ( changed_profile_ids, changed_credential_ids, changed_space_ids, policy_changed, ) def _file_connector_settings_changed_sections( *, changed_profile_ids: set[str], changed_credential_ids: set[str], changed_space_ids: set[str], policy_changed: bool, profiles: list[ConnectorProfile], ) -> list[str]: changed_sections = [] if changed_profile_ids or any( profile.credential_profile_id and profile.credential_profile_id in changed_credential_ids for profile in profiles ): changed_sections.append("profiles") if changed_credential_ids: changed_sections.append("credentials") if changed_space_ids: changed_sections.append("spaces") if policy_changed: changed_sections.append("policy") return changed_sections def _file_connector_settings_deleted_items( entries: list[ChangeSequenceEntry], *, visible_profiles: dict[str, ConnectorProfile], visible_credentials: dict[str, ConnectorCredential], visible_spaces: dict[str, FileConnectorSpace], ) -> list[DeltaDeletedItem]: return [ _connector_deleted_item(entry) for entry in entries if ( entry.resource_type == FILES_CONNECTOR_PROFILE_RESOURCE and entry.resource_id not in visible_profiles ) or ( entry.resource_type == FILES_CONNECTOR_CREDENTIAL_RESOURCE and entry.resource_id not in visible_credentials ) or ( entry.resource_type == FILES_CONNECTOR_SPACE_RESOURCE and entry.resource_id not in visible_spaces ) ] def _incremental_file_connector_settings_delta_response( session: Session, principal: ApiPrincipal, *, entries: list[ChangeSequenceEntry], has_more: bool, scope_type: str, scope_id: str | None, provider: str | None, campaign_id: str | None, include_disabled: bool, include_inactive: bool, owner_type: Literal["user", "group"] | None, owner_id: str | None, ) -> FileConnectorSettingsDeltaResponse: changed_profile_ids, changed_credential_ids, changed_space_ids, policy_changed = ( _changed_file_connector_setting_ids(entries) ) profiles = _visible_connector_profiles( session, principal, provider=provider, campaign_id=campaign_id, include_disabled=include_disabled and _can_read_disabled_connector_profiles(principal), include_admin_scopes=_can_read_disabled_connector_profiles(principal), include_effective_policy=False, ) visible_profiles = { profile.id: profile for profile in profiles if profile.id in changed_profile_ids or ( profile.credential_profile_id and profile.credential_profile_id in changed_credential_ids ) } credentials = _visible_connector_credentials( session, principal, provider=provider, include_disabled=include_disabled, ) visible_credentials = { credential.id: credential for credential in credentials if credential.id in changed_credential_ids } spaces = _visible_connector_spaces( session, principal, owner_type=owner_type, owner_id=owner_id, include_inactive=include_inactive, ) visible_spaces = { space.id: space for space in spaces if space.id in changed_space_ids } return FileConnectorSettingsDeltaResponse( profiles=[ FileConnectorProfileResponse(**profile.to_response()) for profile in visible_profiles.values() ], credentials=[ FileConnectorCredentialResponse(**credential.to_response()) for credential in visible_credentials.values() ], spaces=[_connector_space_response(space) for space in visible_spaces.values()], policy=FileConnectorPolicyResponse( **connector_policy_response( session, tenant_id=principal.tenant_id, scope_type=scope_type, scope_id=scope_id, ) ) if policy_changed else None, changed_sections=_file_connector_settings_changed_sections( changed_profile_ids=changed_profile_ids, changed_credential_ids=changed_credential_ids, changed_space_ids=changed_space_ids, policy_changed=policy_changed, profiles=profiles, ), deleted=_file_connector_settings_deleted_items( entries, visible_profiles=visible_profiles, visible_credentials=visible_credentials, visible_spaces=visible_spaces, ), watermark=_file_connector_settings_response_watermark( session, tenant_id=principal.tenant_id, entries=entries, has_more=has_more ), has_more=has_more, full=False, )