182 lines
7.3 KiB
Python
182 lines
7.3 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from govoplan_core.core.access import CAPABILITY_AUTH_PERMISSION_EVALUATOR, CAPABILITY_AUTH_PRINCIPAL_RESOLVER
|
|
from govoplan_core.core.module_guards import drop_table_retirement_provider, persistent_table_uninstall_guard
|
|
from govoplan_core.core.modules import (
|
|
FrontendModule,
|
|
MigrationSpec,
|
|
ModuleContext,
|
|
ModuleInterfaceProvider,
|
|
ModuleInterfaceRequirement,
|
|
ModuleManifest,
|
|
NavItem,
|
|
PermissionDefinition,
|
|
RoleTemplate,
|
|
)
|
|
from govoplan_core.db.base import Base
|
|
from govoplan_files.backend.change_tracking import register_files_change_tracking
|
|
from govoplan_files.backend.db import models as file_models # noqa: F401 - populate Files ORM metadata
|
|
|
|
register_files_change_tracking()
|
|
|
|
|
|
def _permission(scope: str, label: str, description: str) -> PermissionDefinition:
|
|
module_id, resource, action = scope.split(":", 2)
|
|
return PermissionDefinition(
|
|
scope=scope,
|
|
label=label,
|
|
description=description,
|
|
category="Files",
|
|
level="tenant",
|
|
module_id=module_id,
|
|
resource=resource,
|
|
action=action,
|
|
)
|
|
|
|
|
|
PERMISSIONS = (
|
|
_permission("files:file:read", "View files", "List, search and preview managed files."),
|
|
_permission("files:file:download", "Download files", "Download managed files and generated archives."),
|
|
_permission("files:file:upload", "Upload files", "Upload new managed file versions."),
|
|
_permission("files:file:organize", "Organize files", "Create folders, rename, move or copy managed files."),
|
|
_permission("files:file:share", "Share files", "Grant or revoke managed file shares."),
|
|
_permission("files:file:delete", "Delete files", "Delete or hide managed files and folders where policy allows it."),
|
|
_permission("files:file:admin", "Administer file spaces", "Administer all file spaces in the tenant."),
|
|
)
|
|
|
|
ROLE_TEMPLATES = (
|
|
RoleTemplate(
|
|
slug="file_manager",
|
|
name="File manager",
|
|
description="Manage tenant file spaces without campaign delivery rights.",
|
|
permissions=(
|
|
"files:file:read",
|
|
"files:file:download",
|
|
"files:file:upload",
|
|
"files:file:organize",
|
|
"files:file:share",
|
|
"files:file:delete",
|
|
),
|
|
),
|
|
RoleTemplate(
|
|
slug="file_viewer",
|
|
name="File viewer",
|
|
description="Read and download permitted files.",
|
|
permissions=("files:file:read", "files:file:download"),
|
|
),
|
|
)
|
|
|
|
|
|
def _tenant_summary(session, tenant_id: str) -> dict[str, int]:
|
|
from govoplan_files.backend.db.models import FileAsset, FileConnectorCredential, FileConnectorPolicy, FileConnectorProfile, FileConnectorSpace
|
|
|
|
return {
|
|
"files": session.query(FileAsset).filter(FileAsset.tenant_id == tenant_id).count(),
|
|
"connector_credentials": session.query(FileConnectorCredential).filter(FileConnectorCredential.tenant_id == tenant_id).count(),
|
|
"connector_policies": session.query(FileConnectorPolicy).filter(FileConnectorPolicy.tenant_id == tenant_id).count(),
|
|
"connector_profiles": session.query(FileConnectorProfile).filter(FileConnectorProfile.tenant_id == tenant_id).count(),
|
|
"connector_spaces": session.query(FileConnectorSpace).filter(FileConnectorSpace.tenant_id == tenant_id).count(),
|
|
}
|
|
|
|
|
|
def _veto_group_delete(session, tenant_id: str, group_id: str) -> None:
|
|
from govoplan_files.backend.db.models import FileAsset, FileConnectorSpace, FileFolder, FileShare
|
|
|
|
owned_asset_count = session.query(FileAsset).filter(FileAsset.tenant_id == tenant_id, FileAsset.owner_group_id == group_id).count()
|
|
owned_folder_count = session.query(FileFolder).filter(FileFolder.tenant_id == tenant_id, FileFolder.owner_group_id == group_id).count()
|
|
owned_connector_space_count = session.query(FileConnectorSpace).filter(
|
|
FileConnectorSpace.tenant_id == tenant_id,
|
|
FileConnectorSpace.owner_group_id == group_id,
|
|
).count()
|
|
shared_asset_count = session.query(FileShare).filter(
|
|
FileShare.tenant_id == tenant_id,
|
|
FileShare.target_type == "group",
|
|
FileShare.target_id == group_id,
|
|
).count()
|
|
if owned_asset_count or owned_folder_count or owned_connector_space_count or shared_asset_count:
|
|
raise ValueError("Cannot remove the group while it owns files, folders, connector spaces or file shares.")
|
|
|
|
|
|
def _files_router(context: ModuleContext):
|
|
from govoplan_files.backend.runtime import configure_runtime
|
|
|
|
configure_runtime(registry=context.registry, settings=context.settings)
|
|
from govoplan_files.backend.router import router
|
|
|
|
return router
|
|
|
|
|
|
manifest = ModuleManifest(
|
|
id="files",
|
|
name="Files",
|
|
version="0.1.6",
|
|
required_capabilities=(CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR),
|
|
optional_dependencies=("campaigns",),
|
|
provides_interfaces=(
|
|
ModuleInterfaceProvider(name="files.campaign_attachments", version="0.1.6"),
|
|
),
|
|
requires_interfaces=(
|
|
ModuleInterfaceRequirement(
|
|
name="campaigns.access",
|
|
version_min="0.1.0",
|
|
version_max_exclusive="0.2.0",
|
|
optional=True,
|
|
),
|
|
),
|
|
permissions=PERMISSIONS,
|
|
route_factory=_files_router,
|
|
role_templates=ROLE_TEMPLATES,
|
|
tenant_summary_providers=(_tenant_summary,),
|
|
delete_veto_providers={"group": (_veto_group_delete,)},
|
|
nav_items=(NavItem(path="/files", label="Files", icon="folder", required_any=("files:file:read",), order=40),),
|
|
frontend=FrontendModule(
|
|
module_id="files",
|
|
package_name="@govoplan/files-webui",
|
|
nav_items=(NavItem(path="/files", label="Files", icon="folder", required_any=("files:file:read",), order=40),),
|
|
),
|
|
migration_spec=MigrationSpec(
|
|
module_id="files",
|
|
metadata=Base.metadata,
|
|
script_location=str(Path(__file__).with_name("migrations") / "versions"),
|
|
retirement_supported=True,
|
|
retirement_provider=drop_table_retirement_provider(
|
|
file_models.FileBlob,
|
|
file_models.FileFolder,
|
|
file_models.FileAsset,
|
|
file_models.FileVersion,
|
|
file_models.FileShare,
|
|
file_models.FileConnectorCredential,
|
|
file_models.FileConnectorPolicy,
|
|
file_models.FileConnectorProfile,
|
|
file_models.FileConnectorSpace,
|
|
file_models.CampaignAttachmentUse,
|
|
label="Files",
|
|
),
|
|
retirement_notes="Destructive retirement drops files-owned database tables after the installer captures a database snapshot.",
|
|
),
|
|
uninstall_guard_providers=(
|
|
persistent_table_uninstall_guard(
|
|
file_models.FileBlob,
|
|
file_models.FileFolder,
|
|
file_models.FileAsset,
|
|
file_models.FileVersion,
|
|
file_models.FileShare,
|
|
file_models.FileConnectorCredential,
|
|
file_models.FileConnectorPolicy,
|
|
file_models.FileConnectorProfile,
|
|
file_models.FileConnectorSpace,
|
|
file_models.CampaignAttachmentUse,
|
|
label="Files",
|
|
),
|
|
),
|
|
capability_factories={
|
|
"files.campaign_attachments": lambda context: __import__("govoplan_files.backend.capabilities", fromlist=["campaign_capability"]).campaign_capability(context),
|
|
},
|
|
)
|
|
|
|
|
|
def get_manifest() -> ModuleManifest:
|
|
return manifest
|