feat: add operational Dashboard
This commit is contained in:
1 parent
4a1959ecd6
commit
b8b741ccee
23 files changed
+1278
-59
No files matched your search
@@ -0,0 +1 @@
|
||||
"""Permission-aware operational Dashboard read model."""
|
||||
@@ -0,0 +1,14 @@
|
||||
"""Dependency construction for the Dashboard composition service."""
|
||||
|
||||
from app.dashboard.service import DashboardService
|
||||
from app.inventory.dependencies import get_inventory_service
|
||||
from app.locations.dependencies import get_location_map_service
|
||||
from app.repairs.dependencies import get_repair_center_service
|
||||
|
||||
|
||||
def get_dashboard_service() -> DashboardService:
|
||||
return DashboardService(
|
||||
inventory_service=get_inventory_service(),
|
||||
repair_center_service=get_repair_center_service(),
|
||||
location_service=get_location_map_service(),
|
||||
)
|
||||
@@ -0,0 +1,32 @@
|
||||
"""Typed contracts for the operational Dashboard overview."""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.inventory.models import InventoryFleetSummary
|
||||
from app.locations.models import LocationSummary
|
||||
from app.repairs.models import ActiveRepairSummary
|
||||
|
||||
|
||||
class DashboardAccess(BaseModel):
|
||||
can_view_miners: bool = False
|
||||
can_view_repairs: bool = False
|
||||
can_view_locations: bool = False
|
||||
|
||||
|
||||
class DashboardOverviewData(BaseModel):
|
||||
fleet: InventoryFleetSummary | None = None
|
||||
repairs: ActiveRepairSummary | None = None
|
||||
locations: LocationSummary | None = None
|
||||
generated_at: datetime
|
||||
|
||||
|
||||
class DashboardOverviewMeta(BaseModel):
|
||||
available_sections: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class DashboardOverviewResponse(BaseModel):
|
||||
data: DashboardOverviewData
|
||||
meta: DashboardOverviewMeta = Field(default_factory=DashboardOverviewMeta)
|
||||
error: None = None
|
||||
@@ -0,0 +1,40 @@
|
||||
"""HTTP boundary for the operational Dashboard overview."""
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends, Response
|
||||
|
||||
from app.dashboard.dependencies import get_dashboard_service
|
||||
from app.dashboard.models import (
|
||||
DashboardAccess,
|
||||
DashboardOverviewResponse,
|
||||
)
|
||||
from app.dashboard.service import DashboardService
|
||||
from app.identity import require_permission, user_has_permission
|
||||
|
||||
|
||||
router = APIRouter(prefix="/api/dashboard", tags=["dashboard"])
|
||||
DashboardUser = Annotated[dict, Depends(require_permission("dashboard.view"))]
|
||||
DashboardServiceDependency = Annotated[
|
||||
DashboardService,
|
||||
Depends(get_dashboard_service),
|
||||
]
|
||||
|
||||
|
||||
@router.get("/overview", response_model=DashboardOverviewResponse)
|
||||
def get_dashboard_overview(
|
||||
current_user: DashboardUser,
|
||||
service: DashboardServiceDependency,
|
||||
response: Response,
|
||||
) -> DashboardOverviewResponse:
|
||||
response.headers["Cache-Control"] = "no-store"
|
||||
return service.get_overview(
|
||||
DashboardAccess(
|
||||
can_view_miners=user_has_permission(current_user, "miners.view"),
|
||||
can_view_repairs=user_has_permission(current_user, "repairs.view"),
|
||||
can_view_locations=user_has_permission(
|
||||
current_user,
|
||||
"locations.view",
|
||||
),
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,64 @@
|
||||
"""Composition service for permission-aware Dashboard summaries."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from app.dashboard.models import (
|
||||
DashboardAccess,
|
||||
DashboardOverviewData,
|
||||
DashboardOverviewMeta,
|
||||
DashboardOverviewResponse,
|
||||
)
|
||||
from app.inventory.service import InventoryService
|
||||
from app.locations.service import LocationMapService
|
||||
from app.repairs.center_service import RepairCenterService
|
||||
|
||||
|
||||
class DashboardService:
|
||||
"""Coordinate domain-owned summaries without introducing new rules."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
inventory_service: InventoryService,
|
||||
repair_center_service: RepairCenterService,
|
||||
location_service: LocationMapService,
|
||||
):
|
||||
self.inventory_service = inventory_service
|
||||
self.repair_center_service = repair_center_service
|
||||
self.location_service = location_service
|
||||
|
||||
def get_overview(self, access: DashboardAccess) -> DashboardOverviewResponse:
|
||||
fleet = (
|
||||
self.inventory_service.summarize_fleet()
|
||||
if access.can_view_miners
|
||||
else None
|
||||
)
|
||||
repairs = (
|
||||
self.repair_center_service.summarize_active_repairs()
|
||||
if access.can_view_repairs
|
||||
else None
|
||||
)
|
||||
locations = (
|
||||
self.location_service.summarize_locations(
|
||||
include_miner_counts=access.can_view_miners
|
||||
)
|
||||
if access.can_view_locations
|
||||
else None
|
||||
)
|
||||
available_sections = [
|
||||
name
|
||||
for name, value in (
|
||||
("fleet", fleet),
|
||||
("repairs", repairs),
|
||||
("locations", locations),
|
||||
)
|
||||
if value is not None
|
||||
]
|
||||
return DashboardOverviewResponse(
|
||||
data=DashboardOverviewData(
|
||||
fleet=fleet,
|
||||
repairs=repairs,
|
||||
locations=locations,
|
||||
generated_at=datetime.now(timezone.utc),
|
||||
),
|
||||
meta=DashboardOverviewMeta(available_sections=available_sections),
|
||||
)
|
||||
Reference in new issue
Block a user