Files
leocrm/app/schemas/workspace.py
T

114 lines
3.1 KiB
Python
Raw Normal View History

"""Schemas for Workspace API endpoints."""
from __future__ import annotations
from typing import Literal
from pydantic import BaseModel, Field, field_validator, model_validator
class WorkspaceBase(BaseModel):
name: str
icon: str = "LayoutGrid"
description: str | None = None
is_default: bool = False
is_active: bool = True
class WorkspaceCreate(WorkspaceBase):
pass
class WorkspaceUpdate(BaseModel):
name: str | None = None
icon: str | None = None
description: str | None = None
is_default: bool | None = None
is_active: bool | None = None
class WorkspaceResponse(WorkspaceBase):
id: str
created_by: str | None = None
created_at: str | None = None
updated_at: str | None = None
model_config = {"from_attributes": True}
# ─── N1: Workspace-Scope-Registry (Phase N) ───────────────────
class ScopeOption(BaseModel):
"""Ein auswählbarer Wert einer Scope-Dimension."""
value: str
label: str
class ScopeValueSource(BaseModel):
"""Deklariert, woher der Editor wählbare Werte lädt.
Security (fail-closed): nur interne ``/api/v1/...``-Pfade sind
erlaubt — keine absoluten URLs, keine protokoll-relativen Pfade,
keine externen Hosts (SSRF-sicher per Konstruktion).
"""
endpoint: str
items_path: str = "items"
value_key: str = "id"
label_key: str = "name"
@field_validator("endpoint")
@classmethod
def _must_be_internal_api_path(cls, v: str) -> str:
if not v.startswith("/api/v1/"):
raise ValueError(
"value_source endpoint must be an internal /api/v1/ path"
)
return v
class WorkspaceScopeDimension(BaseModel):
"""Eine filterbare Dimension eines Moduls im Workspace (N1).
``multiselect``/``select`` brauchen zwingend ``options`` oder
``value_source`` (fail-closed gegen leere Filter-UI); ``toggle``
ist ein reiner Schalter mit ``default``.
"""
key: str = Field(..., min_length=1, max_length=100)
label: str = Field(..., min_length=1, max_length=200)
control: Literal["multiselect", "select", "toggle"]
options: list[ScopeOption] = Field(default_factory=list)
value_source: ScopeValueSource | None = None
default: bool | str | None = None
@model_validator(mode="after")
def _options_or_source_required(self) -> WorkspaceScopeDimension:
if self.control in ("multiselect", "select"):
if not self.options and self.value_source is None:
raise ValueError(
f"scope dimension '{self.key}': control '{self.control}' "
"requires options or value_source"
)
return self
class WorkspaceModuleScopes(BaseModel):
"""Scope-Contribution eines Plugins für ein Modul."""
module_key: str = Field(..., min_length=1, max_length=100)
dimensions: list[WorkspaceScopeDimension] = Field(..., min_length=1)
class WorkspaceModuleResponse(BaseModel):
id: str
workspace_id: str
module_key: str
is_visible: bool
menu_order: int
config: dict = {}
model_config = {"from_attributes": True}