Release v0.1.5
This commit is contained in:
360
src/govoplan_admin/backend/api/v1/schemas.py
Normal file
360
src/govoplan_admin/backend/api/v1/schemas.py
Normal file
@@ -0,0 +1,360 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any, Literal
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
|
||||
|
||||
RETENTION_DAY_KEYS = (
|
||||
"raw_campaign_json_retention_days",
|
||||
"generated_eml_retention_days",
|
||||
"stored_report_detail_retention_days",
|
||||
"mock_mailbox_retention_days",
|
||||
"audit_detail_retention_days",
|
||||
)
|
||||
|
||||
|
||||
RETENTION_POLICY_FIELD_KEYS = (
|
||||
"store_raw_campaign_json",
|
||||
*RETENTION_DAY_KEYS,
|
||||
"audit_detail_level",
|
||||
)
|
||||
|
||||
|
||||
def default_allow_lower_level_limits() -> dict[str, bool]:
|
||||
return {key: True for key in RETENTION_POLICY_FIELD_KEYS}
|
||||
|
||||
|
||||
def normalize_allow_lower_level_limits(value: Any, *, fill_defaults: bool) -> dict[str, bool] | None:
|
||||
if value in (None, ""):
|
||||
return default_allow_lower_level_limits() if fill_defaults else None
|
||||
if not isinstance(value, dict):
|
||||
raise ValueError("allow_lower_level_limits must be an object")
|
||||
normalized = default_allow_lower_level_limits() if fill_defaults else {}
|
||||
for key, allowed in value.items():
|
||||
clean_key = str(key)
|
||||
if clean_key not in RETENTION_POLICY_FIELD_KEYS:
|
||||
raise ValueError(f"Unknown retention policy field: {clean_key}")
|
||||
normalized[clean_key] = bool(allowed)
|
||||
return normalized
|
||||
|
||||
|
||||
class PrivacyRetentionPolicyItem(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
store_raw_campaign_json: bool = True
|
||||
raw_campaign_json_retention_days: int | None = Field(default=None, ge=0)
|
||||
generated_eml_retention_days: int | None = Field(default=None, ge=0)
|
||||
stored_report_detail_retention_days: int | None = Field(default=None, ge=0)
|
||||
mock_mailbox_retention_days: int | None = Field(default=None, ge=0)
|
||||
audit_detail_retention_days: int | None = Field(default=None, ge=0)
|
||||
audit_detail_level: Literal["full", "redacted", "minimal"] = "full"
|
||||
allow_lower_level_limits: dict[str, bool] = Field(default_factory=default_allow_lower_level_limits)
|
||||
|
||||
@field_validator("allow_lower_level_limits", mode="before")
|
||||
@classmethod
|
||||
def _normalize_allow_lower_level_limits(cls, value: Any) -> Any:
|
||||
return normalize_allow_lower_level_limits(value, fill_defaults=True)
|
||||
|
||||
|
||||
class MaintenanceModeItem(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
enabled: bool = False
|
||||
message: str | None = Field(default=None, max_length=500)
|
||||
|
||||
|
||||
class AdminOverviewResponse(BaseModel):
|
||||
active_tenant_id: str
|
||||
active_tenant_name: str
|
||||
tenant_count: int | None = None
|
||||
system_account_count: int | None = None
|
||||
system_group_template_count: int | None = None
|
||||
system_role_template_count: int | None = None
|
||||
user_count: int
|
||||
active_user_count: int
|
||||
group_count: int
|
||||
role_count: int
|
||||
active_api_key_count: int
|
||||
capabilities: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class SystemSettingsItem(BaseModel):
|
||||
default_locale: str = "en"
|
||||
allow_tenant_custom_groups: bool = True
|
||||
allow_tenant_custom_roles: bool = True
|
||||
allow_tenant_api_keys: bool = True
|
||||
privacy_retention_policy: PrivacyRetentionPolicyItem = Field(default_factory=PrivacyRetentionPolicyItem)
|
||||
maintenance_mode: MaintenanceModeItem = Field(default_factory=MaintenanceModeItem)
|
||||
settings: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class SystemSettingsUpdateRequest(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
default_locale: str = Field(min_length=1, max_length=20)
|
||||
allow_tenant_custom_groups: bool
|
||||
allow_tenant_custom_roles: bool
|
||||
allow_tenant_api_keys: bool
|
||||
privacy_retention_policy: PrivacyRetentionPolicyItem | None = None
|
||||
maintenance_mode: MaintenanceModeItem | None = None
|
||||
|
||||
|
||||
class ModuleCatalogItem(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
version: str
|
||||
installed: bool = True
|
||||
current_enabled: bool = False
|
||||
desired_enabled: bool = False
|
||||
protected: bool = False
|
||||
restart_required: bool = False
|
||||
dependencies: list[str] = Field(default_factory=list)
|
||||
optional_dependencies: list[str] = Field(default_factory=list)
|
||||
dependents: list[str] = Field(default_factory=list)
|
||||
permissions_count: int = 0
|
||||
role_templates_count: int = 0
|
||||
route_contributed: bool = False
|
||||
nav_count: int = 0
|
||||
frontend_package: str | None = None
|
||||
migration_module_id: str | None = None
|
||||
migration_script_location: str | None = None
|
||||
runtime_toggle_supported: bool = False
|
||||
install_uninstall_supported: bool = False
|
||||
|
||||
|
||||
class ModuleCatalogResponse(BaseModel):
|
||||
modules: list[ModuleCatalogItem]
|
||||
current_enabled: list[str]
|
||||
desired_enabled: list[str]
|
||||
configured_enabled: list[str]
|
||||
protected_modules: list[str]
|
||||
restart_required: bool = False
|
||||
runtime_toggle_supported: bool = False
|
||||
install_uninstall_supported: bool = False
|
||||
install_plan_supported: bool = False
|
||||
package_mutation_supported: bool = False
|
||||
maintenance_mode: MaintenanceModeItem = Field(default_factory=MaintenanceModeItem)
|
||||
notes: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class ModuleStateUpdateRequest(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
enabled_modules: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class ModuleInstallPlanItem(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
module_id: str = Field(min_length=1, max_length=120)
|
||||
action: Literal["install", "uninstall"]
|
||||
python_package: str | None = Field(default=None, max_length=200)
|
||||
python_ref: str | None = Field(default=None, max_length=1000)
|
||||
webui_package: str | None = Field(default=None, max_length=200)
|
||||
webui_ref: str | None = Field(default=None, max_length=1000)
|
||||
destroy_data: bool = False
|
||||
status: Literal["planned", "applied", "blocked"] = "planned"
|
||||
notes: str | None = Field(default=None, max_length=1000)
|
||||
|
||||
|
||||
class ModuleInstallPreflightIssue(BaseModel):
|
||||
severity: Literal["blocker", "warning", "info"]
|
||||
code: str
|
||||
message: str
|
||||
module_id: str | None = None
|
||||
|
||||
|
||||
class ModuleInstallChecklistItem(BaseModel):
|
||||
id: str
|
||||
label: str
|
||||
status: Literal["done", "pending", "blocked", "warning"]
|
||||
detail: str | None = None
|
||||
|
||||
|
||||
class ModuleInstallPreflightResponse(BaseModel):
|
||||
allowed: bool
|
||||
maintenance_mode: bool
|
||||
frontend_rebuild_required: bool
|
||||
restart_required: bool
|
||||
commands: list[str] = Field(default_factory=list)
|
||||
rollback_commands: list[str] = Field(default_factory=list)
|
||||
issues: list[ModuleInstallPreflightIssue] = Field(default_factory=list)
|
||||
checklist: list[ModuleInstallChecklistItem] = Field(default_factory=list)
|
||||
|
||||
|
||||
class ModuleInstallPlanResponse(BaseModel):
|
||||
items: list[ModuleInstallPlanItem] = Field(default_factory=list)
|
||||
updated_at: str | None = None
|
||||
commands: list[str] = Field(default_factory=list)
|
||||
maintenance_mode: MaintenanceModeItem = Field(default_factory=MaintenanceModeItem)
|
||||
preflight: ModuleInstallPreflightResponse | None = None
|
||||
notes: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class ModuleInstallerLockStatus(BaseModel):
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
locked: bool = False
|
||||
path: str
|
||||
|
||||
|
||||
class ModuleInstallerDaemonStatus(BaseModel):
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
running: bool = False
|
||||
status: str = "unknown"
|
||||
path: str
|
||||
|
||||
|
||||
class ModuleInstallerRunSummary(BaseModel):
|
||||
run_id: str
|
||||
status: str
|
||||
started_at: str | None = None
|
||||
finished_at: str | None = None
|
||||
rollback_status: str | None = None
|
||||
supervisor_status: str | None = None
|
||||
error: str | None = None
|
||||
record_path: str
|
||||
commands_count: int = 0
|
||||
planned_modules: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class ModuleInstallerRunListResponse(BaseModel):
|
||||
runs: list[ModuleInstallerRunSummary] = Field(default_factory=list)
|
||||
lock: ModuleInstallerLockStatus
|
||||
|
||||
|
||||
class ModuleInstallerRequestOptions(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
build_webui: bool = False
|
||||
migrate_database: bool = True
|
||||
webui_root: str | None = Field(default=None, max_length=1000)
|
||||
npm_bin: str | None = Field(default=None, max_length=500)
|
||||
database_backup_command: str | None = Field(default=None, max_length=4000)
|
||||
database_restore_command: str | None = Field(default=None, max_length=4000)
|
||||
database_restore_check_command: str | None = Field(default=None, max_length=4000)
|
||||
activate_installed_modules: bool = True
|
||||
remove_uninstalled_modules_from_desired: bool = True
|
||||
restart_commands: list[str] = Field(default_factory=list)
|
||||
health_urls: list[str] = Field(default_factory=list)
|
||||
health_timeout_seconds: float = Field(default=60.0, ge=1, le=3600)
|
||||
health_interval_seconds: float = Field(default=2.0, ge=0.2, le=120)
|
||||
|
||||
|
||||
class ModuleInstallerRequestItem(BaseModel):
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
request_id: str
|
||||
status: str
|
||||
created_at: str
|
||||
requested_by: str | None = None
|
||||
started_at: str | None = None
|
||||
finished_at: str | None = None
|
||||
cancelled_at: str | None = None
|
||||
cancelled_by: str | None = None
|
||||
retry_of: str | None = None
|
||||
error: str | None = None
|
||||
record_path: str | None = None
|
||||
options: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class ModuleInstallerRequestListResponse(BaseModel):
|
||||
requests: list[ModuleInstallerRequestItem] = Field(default_factory=list)
|
||||
daemon: ModuleInstallerDaemonStatus
|
||||
|
||||
|
||||
class ModuleInstallerRequestCreateRequest(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
options: ModuleInstallerRequestOptions = Field(default_factory=ModuleInstallerRequestOptions)
|
||||
|
||||
|
||||
class ModulePackageCatalogItem(BaseModel):
|
||||
module_id: str
|
||||
name: str
|
||||
description: str | None = None
|
||||
version: str | None = None
|
||||
action: Literal["install", "uninstall"] = "install"
|
||||
python_package: str | None = None
|
||||
python_ref: str | None = None
|
||||
webui_package: str | None = None
|
||||
webui_ref: str | None = None
|
||||
license_features: list[str] = Field(default_factory=list)
|
||||
license_allowed: bool = True
|
||||
license_enforced: bool = False
|
||||
license_missing_features: list[str] = Field(default_factory=list)
|
||||
license_reason: str | None = None
|
||||
notes: str | None = None
|
||||
tags: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class ModulePackageCatalogResponse(BaseModel):
|
||||
modules: list[ModulePackageCatalogItem] = Field(default_factory=list)
|
||||
configured: bool = False
|
||||
valid: bool = True
|
||||
path: str | None = None
|
||||
source: str | None = None
|
||||
source_type: str | None = None
|
||||
channel: str | None = None
|
||||
sequence: int | None = None
|
||||
generated_at: str | None = None
|
||||
expires_at: str | None = None
|
||||
signed: bool = False
|
||||
trusted: bool = False
|
||||
key_id: str | None = None
|
||||
warnings: list[str] = Field(default_factory=list)
|
||||
error: str | None = None
|
||||
|
||||
|
||||
class ModuleInstallPlanUpdateRequest(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
items: list[ModuleInstallPlanItem] = Field(default_factory=list)
|
||||
|
||||
|
||||
class GovernanceAssignment(BaseModel):
|
||||
tenant_id: str
|
||||
mode: Literal["available", "required"] = "available"
|
||||
|
||||
|
||||
class GovernanceTemplateItem(BaseModel):
|
||||
id: str
|
||||
kind: Literal["group", "role"]
|
||||
slug: str
|
||||
name: str
|
||||
description: str | None = None
|
||||
permissions: list[str] = Field(default_factory=list)
|
||||
effective_permission_count: int = 0
|
||||
is_active: bool = True
|
||||
assignments: list[GovernanceAssignment] = Field(default_factory=list)
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
class GovernanceTemplateListResponse(BaseModel):
|
||||
templates: list[GovernanceTemplateItem]
|
||||
|
||||
|
||||
class GovernanceTemplateCreateRequest(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
kind: Literal["group", "role"]
|
||||
slug: str
|
||||
name: str = Field(min_length=1, max_length=255)
|
||||
description: str | None = None
|
||||
permissions: list[str] = Field(default_factory=list)
|
||||
is_active: bool = True
|
||||
assignments: list[GovernanceAssignment] = Field(default_factory=list)
|
||||
|
||||
|
||||
class GovernanceTemplateUpdateRequest(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
name: str = Field(min_length=1, max_length=255)
|
||||
description: str | None = None
|
||||
permissions: list[str] = Field(default_factory=list)
|
||||
is_active: bool = True
|
||||
assignments: list[GovernanceAssignment] = Field(default_factory=list)
|
||||
Reference in New Issue
Block a user