577dd1ca7e
- Rewrite CLAUDE.md to match current 8-service architecture (was 11, 5 deleted) - Remove all as-any casts in OrderDetail.tsx (9 casts → 0) - Add cad_parsed_objects/cad_part_materials to OrderItem interface - Rename require_admin → require_global_admin across 6 router files (22 calls) - Remove EXPORT_GLB_PRODUCTION enum + generate_gltf_production_task (dead code) - Remove worker-thumbnail from ALLOWED_SERVICES, replace Flamenco link - Delete obsolete PLAN.md (1455 lines) and PLAN_REFACTOR.md (1174 lines) - Fix digit-only USD prim names with p_ prefix Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
153 lines
4.7 KiB
Python
153 lines
4.7 KiB
Python
"""Dashboard widget configuration endpoints."""
|
|
import logging
|
|
import uuid
|
|
from typing import Any
|
|
|
|
from fastapi import APIRouter, Depends
|
|
from pydantic import BaseModel
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.database import get_db
|
|
from app.domains.admin.dashboard_service import (
|
|
get_user_dashboard_config,
|
|
upsert_user_dashboard_config,
|
|
upsert_tenant_default,
|
|
)
|
|
from app.utils.auth import get_current_user, require_global_admin
|
|
from app.models.user import User
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(prefix="/dashboard", tags=["dashboard"])
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Schemas
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class WidgetPosition(BaseModel):
|
|
col: int
|
|
row: int
|
|
w: int
|
|
h: int
|
|
|
|
|
|
class WidgetConfig(BaseModel):
|
|
widget_type: str
|
|
position: WidgetPosition
|
|
config: dict[str, Any] | None = None
|
|
|
|
|
|
class DashboardConfigPayload(BaseModel):
|
|
widgets: list[WidgetConfig]
|
|
|
|
|
|
class DashboardConfigResponse(BaseModel):
|
|
widgets: list[WidgetConfig]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def _to_response(widgets: list[dict]) -> DashboardConfigResponse:
|
|
parsed = []
|
|
for w in widgets:
|
|
pos = w.get("position", {})
|
|
parsed.append(
|
|
WidgetConfig(
|
|
widget_type=w.get("widget_type", ""),
|
|
position=WidgetPosition(
|
|
col=pos.get("col", 0),
|
|
row=pos.get("row", 0),
|
|
w=pos.get("w", 1),
|
|
h=pos.get("h", 1),
|
|
),
|
|
config=w.get("config"),
|
|
)
|
|
)
|
|
return DashboardConfigResponse(widgets=parsed)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Endpoints
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@router.get("/config", response_model=DashboardConfigResponse)
|
|
async def get_config(
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
) -> DashboardConfigResponse:
|
|
"""Load the current user's dashboard widget config (with fallback cascade)."""
|
|
widgets = await get_user_dashboard_config(
|
|
db=db,
|
|
user_id=current_user.id,
|
|
tenant_id=current_user.tenant_id,
|
|
role=current_user.role.value,
|
|
)
|
|
return _to_response(widgets)
|
|
|
|
|
|
@router.put("/config", response_model=DashboardConfigResponse)
|
|
async def update_config(
|
|
payload: DashboardConfigPayload,
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
) -> DashboardConfigResponse:
|
|
"""Save or update the current user's dashboard widget config."""
|
|
widgets_raw = [w.model_dump() for w in payload.widgets]
|
|
config = await upsert_user_dashboard_config(
|
|
db=db,
|
|
user_id=current_user.id,
|
|
tenant_id=current_user.tenant_id,
|
|
widgets=widgets_raw,
|
|
)
|
|
return _to_response(list(config.widgets))
|
|
|
|
|
|
@router.get("/tenant-default", response_model=DashboardConfigResponse)
|
|
async def get_tenant_default(
|
|
current_user: User = Depends(require_global_admin),
|
|
db: AsyncSession = Depends(get_db),
|
|
) -> DashboardConfigResponse:
|
|
"""Load the tenant-default dashboard widget config (admin only)."""
|
|
from sqlalchemy import select
|
|
from app.domains.admin.models import DashboardConfig
|
|
|
|
if current_user.tenant_id is None:
|
|
return DashboardConfigResponse(widgets=[])
|
|
|
|
result = await db.execute(
|
|
select(DashboardConfig).where(
|
|
DashboardConfig.tenant_id == current_user.tenant_id,
|
|
DashboardConfig.is_tenant_default.is_(True),
|
|
)
|
|
)
|
|
config = result.scalar_one_or_none()
|
|
if config is None:
|
|
return DashboardConfigResponse(widgets=[])
|
|
return _to_response(list(config.widgets))
|
|
|
|
|
|
@router.put("/tenant-default", response_model=DashboardConfigResponse)
|
|
async def update_tenant_default(
|
|
payload: DashboardConfigPayload,
|
|
current_user: User = Depends(require_global_admin),
|
|
db: AsyncSession = Depends(get_db),
|
|
) -> DashboardConfigResponse:
|
|
"""Set the tenant-default widget config (admin only)."""
|
|
if current_user.tenant_id is None:
|
|
from fastapi import HTTPException, status
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Admin user has no tenant_id assigned.",
|
|
)
|
|
|
|
widgets_raw = [w.model_dump() for w in payload.widgets]
|
|
config = await upsert_tenant_default(
|
|
db=db,
|
|
tenant_id=current_user.tenant_id,
|
|
widgets=widgets_raw,
|
|
)
|
|
return _to_response(list(config.widgets))
|