Release v0.1.2
This commit is contained in:
34
src/govoplan_files/backend/capabilities.py
Normal file
34
src/govoplan_files/backend/capabilities.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from govoplan_core.core.modules import ModuleContext
|
||||
from govoplan_files.backend.runtime import configure_runtime
|
||||
from govoplan_files.backend.storage.campaign_attachments import (
|
||||
annotate_built_messages_with_managed_files,
|
||||
managed_match_payloads,
|
||||
prepared_campaign_snapshot,
|
||||
public_attachment_summary_payload,
|
||||
)
|
||||
from govoplan_files.backend.storage.campaign_usage import record_campaign_attachment_uses_for_jobs
|
||||
from govoplan_files.backend.storage.files import current_version_and_blob
|
||||
|
||||
|
||||
class FilesCampaignCapability:
|
||||
prepared_campaign_snapshot = staticmethod(prepared_campaign_snapshot)
|
||||
managed_match_payloads = staticmethod(managed_match_payloads)
|
||||
public_attachment_summary_payload = staticmethod(public_attachment_summary_payload)
|
||||
annotate_built_messages_with_managed_files = staticmethod(annotate_built_messages_with_managed_files)
|
||||
record_campaign_attachment_uses_for_jobs = staticmethod(record_campaign_attachment_uses_for_jobs)
|
||||
current_version_and_blob = staticmethod(current_version_and_blob)
|
||||
|
||||
@staticmethod
|
||||
def mark_job_attachment_uses_sent(session: Any, job: Any) -> None:
|
||||
from govoplan_files.backend.storage.campaign_usage import mark_job_attachment_uses_sent
|
||||
|
||||
mark_job_attachment_uses_sent(session, job)
|
||||
|
||||
|
||||
def campaign_capability(context: ModuleContext) -> FilesCampaignCapability:
|
||||
configure_runtime(settings=context.settings)
|
||||
return FilesCampaignCapability()
|
||||
@@ -83,6 +83,9 @@ manifest = ModuleManifest(
|
||||
metadata=Base.metadata,
|
||||
script_location=str(Path(__file__).with_name("migrations") / "versions"),
|
||||
),
|
||||
capability_factories={
|
||||
"files.campaign_attachments": lambda context: __import__("govoplan_files.backend.capabilities", fromlist=["campaign_capability"]).campaign_capability(context),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ from sqlalchemy import or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from govoplan_core.auth.dependencies import ApiPrincipal, has_scope, require_scope
|
||||
from govoplan_core.core.optional import reraise_unless_missing_package
|
||||
from govoplan_files.backend.schemas import (
|
||||
ArchiveRequest,
|
||||
BulkDeleteRequest,
|
||||
@@ -41,7 +42,6 @@ from govoplan_files.backend.schemas import (
|
||||
_conflict_resolutions,
|
||||
)
|
||||
from govoplan_core.db.models import Group, UserGroupMembership
|
||||
from govoplan_campaign.backend.db.models import Campaign, CampaignShare
|
||||
from govoplan_files.backend.db.models import FileAsset, FileFolder, FileShare
|
||||
from govoplan_core.db.session import get_session
|
||||
from govoplan_files.backend.runtime import settings
|
||||
@@ -66,6 +66,14 @@ from govoplan_files.backend.storage.transfers import rename_selection, transfer_
|
||||
router = APIRouter(prefix="/files", tags=["files"])
|
||||
|
||||
|
||||
def _campaign_models():
|
||||
try:
|
||||
from govoplan_campaign.backend.db.models import Campaign, CampaignShare
|
||||
except ModuleNotFoundError as exc:
|
||||
reraise_unless_missing_package(exc, "govoplan_campaign")
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Campaign module is not installed") from exc
|
||||
return Campaign, CampaignShare
|
||||
|
||||
|
||||
def _is_admin(principal: ApiPrincipal) -> bool:
|
||||
return has_scope(principal, "files:file:admin")
|
||||
@@ -105,6 +113,7 @@ def _ensure_campaign_file_access(session: Session, principal: ApiPrincipal, camp
|
||||
return
|
||||
if not has_scope(principal, "campaigns:campaign:read"):
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Missing scope: campaign:read")
|
||||
Campaign, CampaignShare = _campaign_models()
|
||||
campaign = session.get(Campaign, campaign_id)
|
||||
if not campaign or campaign.tenant_id != principal.tenant_id:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Campaign not found")
|
||||
|
||||
@@ -4,8 +4,6 @@ from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Iterable, Protocol
|
||||
|
||||
import boto3
|
||||
|
||||
from govoplan_files.backend.runtime import settings
|
||||
|
||||
|
||||
@@ -94,6 +92,10 @@ class S3StorageBackend:
|
||||
|
||||
@property
|
||||
def client(self):
|
||||
try:
|
||||
import boto3
|
||||
except ModuleNotFoundError as exc:
|
||||
raise StorageBackendError("boto3 is required for the S3 storage backend") from exc
|
||||
return boto3.client(
|
||||
"s3",
|
||||
endpoint_url=self.endpoint_url,
|
||||
|
||||
@@ -2,15 +2,17 @@ from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
from pathlib import PurePosixPath
|
||||
from typing import Iterable
|
||||
from typing import TYPE_CHECKING, Iterable
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from govoplan_campaign.backend.db.models import CampaignJob
|
||||
from govoplan_files.backend.db.models import CampaignAttachmentUse, FileAsset, FileBlob, FileVersion
|
||||
from govoplan_files.backend.storage.common import utcnow
|
||||
from govoplan_files.backend.storage.files import current_version_and_blob, list_assets_for_user
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from govoplan_campaign.backend.db.models import CampaignJob
|
||||
|
||||
|
||||
def _candidate_match_keys(raw_match: str) -> set[str]:
|
||||
cleaned = raw_match.replace("\\", "/").strip().strip("/")
|
||||
|
||||
@@ -10,7 +10,7 @@ from sqlalchemy import or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from govoplan_core.db.models import Group, Tenant, User
|
||||
from govoplan_campaign.backend.db.models import Campaign
|
||||
from govoplan_core.core.optional import reraise_unless_missing_package
|
||||
from govoplan_files.backend.db.models import CampaignAttachmentUse, FileAsset, FileBlob, FileShare, FileVersion
|
||||
from govoplan_files.backend.runtime import settings
|
||||
from govoplan_files.backend.storage.access import ensure_owner_access, user_group_ids
|
||||
@@ -19,6 +19,15 @@ from govoplan_files.backend.storage.common import FileConflictResolution, FileSt
|
||||
from govoplan_files.backend.storage.paths import filename_from_path, join_folder_filename, normalize_folder, normalize_logical_path, safe_storage_component
|
||||
|
||||
|
||||
def _campaign_model():
|
||||
try:
|
||||
from govoplan_campaign.backend.db.models import Campaign
|
||||
except ModuleNotFoundError as exc:
|
||||
reraise_unless_missing_package(exc, "govoplan_campaign")
|
||||
raise FileStorageError("Campaign module is not installed") from exc
|
||||
return Campaign
|
||||
|
||||
|
||||
def _asset_query_for_owner(session: Session, *, tenant_id: str, owner_type: str, owner_id: str):
|
||||
query = session.query(FileAsset).filter(FileAsset.tenant_id == tenant_id, FileAsset.owner_type == owner_type)
|
||||
if owner_type == "user":
|
||||
@@ -296,6 +305,7 @@ def share_file(
|
||||
if target_id != tenant_id or not tenant or not tenant.is_active:
|
||||
raise FileStorageError("Tenant not found")
|
||||
if target_type == "campaign":
|
||||
Campaign = _campaign_model()
|
||||
campaign = session.get(Campaign, target_id)
|
||||
if not campaign or campaign.tenant_id != tenant_id:
|
||||
raise FileStorageError("Campaign not found")
|
||||
|
||||
Reference in New Issue
Block a user