from __future__ import annotations from pathlib import Path from govoplan_calendar.backend.db import models as calendar_models # noqa: F401 - populate Calendar ORM metadata from govoplan_core.core.module_guards import drop_table_retirement_provider, persistent_table_uninstall_guard from govoplan_core.core.modules import FrontendModule, MigrationSpec, ModuleContext, ModuleManifest, NavItem, PermissionDefinition, RoleTemplate from govoplan_core.db.base import Base 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="Calendar", level="tenant", module_id=module_id, resource=resource, action=action, ) PERMISSIONS = ( _permission("calendar:calendar:read", "View calendars", "List tenant calendar collections and metadata."), _permission("calendar:calendar:write", "Manage calendars", "Create and edit tenant calendar collections."), _permission("calendar:calendar:admin", "Administer calendars", "Delete calendars and manage tenant-level calendar settings."), _permission("calendar:event:read", "View calendar events", "List and inspect calendar events."), _permission("calendar:event:write", "Manage calendar events", "Create and edit calendar events."), _permission("calendar:event:delete", "Delete calendar events", "Delete or cancel calendar events where policy allows it."), _permission("calendar:event:import", "Import iCalendar events", "Import VEVENT data from iCalendar sources."), _permission("calendar:event:export", "Export iCalendar events", "Export events as text/calendar VEVENT data."), _permission("calendar:availability:read", "Read availability", "Read free/busy and availability data for integrations."), ) ROLE_TEMPLATES = ( RoleTemplate( slug="calendar_manager", name="Calendar manager", description="Manage tenant calendars and events.", permissions=( "calendar:calendar:read", "calendar:calendar:write", "calendar:event:read", "calendar:event:write", "calendar:event:delete", "calendar:event:import", "calendar:event:export", "calendar:availability:read", ), ), RoleTemplate( slug="calendar_viewer", name="Calendar viewer", description="Read calendars, events, exports, and availability.", permissions=( "calendar:calendar:read", "calendar:event:read", "calendar:event:export", "calendar:availability:read", ), ), ) def _tenant_summary(session, tenant_id: str) -> dict[str, int]: from govoplan_calendar.backend.db.models import CalendarCollection, CalendarEvent, CalendarSyncCredential, CalendarSyncSource return { "calendars": session.query(CalendarCollection).filter(CalendarCollection.tenant_id == tenant_id, CalendarCollection.deleted_at.is_(None)).count(), "calendar_events": session.query(CalendarEvent).filter(CalendarEvent.tenant_id == tenant_id, CalendarEvent.deleted_at.is_(None)).count(), "calendar_sync_sources": session.query(CalendarSyncSource).filter(CalendarSyncSource.tenant_id == tenant_id, CalendarSyncSource.deleted_at.is_(None)).count(), "calendar_sync_credentials": session.query(CalendarSyncCredential).filter(CalendarSyncCredential.tenant_id == tenant_id, CalendarSyncCredential.deleted_at.is_(None)).count(), } def _calendar_router(context: ModuleContext): from govoplan_calendar.backend.runtime import configure_runtime configure_runtime(registry=context.registry, settings=context.settings) from govoplan_calendar.backend.router import router return router manifest = ModuleManifest( id="calendar", name="Calendar", version="0.1.6", dependencies=("access",), optional_dependencies=("mail", "tasks", "scheduling", "appointments", "workflow", "notifications", "dms", "connectors"), permissions=PERMISSIONS, route_factory=_calendar_router, role_templates=ROLE_TEMPLATES, tenant_summary_providers=(_tenant_summary,), nav_items=(NavItem(path="/calendar", label="Calendar", icon="calendar", required_any=("calendar:event:read",), order=55),), frontend=FrontendModule( module_id="calendar", package_name="@govoplan/calendar-webui", nav_items=(NavItem(path="/calendar", label="Calendar", icon="calendar", required_any=("calendar:event:read",), order=55),), ), migration_spec=MigrationSpec( module_id="calendar", metadata=Base.metadata, script_location=str(Path(__file__).with_name("migrations") / "versions"), retirement_supported=True, retirement_provider=drop_table_retirement_provider( calendar_models.CalendarCollection, calendar_models.CalendarEvent, calendar_models.CalendarSyncCredential, calendar_models.CalendarSyncSource, label="Calendar", ), retirement_notes="Destructive retirement drops calendar-owned database tables after the installer captures a database snapshot.", ), uninstall_guard_providers=( persistent_table_uninstall_guard( calendar_models.CalendarCollection, calendar_models.CalendarEvent, calendar_models.CalendarSyncCredential, calendar_models.CalendarSyncSource, label="Calendar", ), ), ) def get_manifest() -> ModuleManifest: return manifest