Files
leocrm/app/routes/dashboard.py
T
Agent Zero abbe7a18fc fix(audit): P0-P3 audit fixes — 838 ruff errors → 0, 30 F821 bugs fixed, 118 files changed
- P0: hooks.py 3-tuple fix, trigger_dispatcher Contract, contacts/plugin unregister_actions_by_owner
- P0: 5 test files — check_permission mocks removed, hardcoded DB credential → env var
- P1: attachment_service DmsFile via Contract helper, restore_registry/history_hooks dedup
- P1: mail/plugin restore unregister, mcp_client datetime.now(UTC), saved_views/filters patterns
- P1: ProtectedRoute fail-closed, 13 test assertion fixes (bcrypt, DB-URLs, SECRET_KEYs)
- P2: deprecated notifications → post_system_message (3 files), forgejo Base, report_generator lazy import
- P2: webhooks permissions, deps.py/roles.py plugin perms removed, import_export default
- P2: address/tags/entity_links patterns removed, worker.py Contract-Umgehungen fixed
- P2: 28 frontend TODOs (hardcoded constants, deprecated notification API)
- P3: dead code, duplicates, deprecated imports, private attr, __import__ inline
- P3: 8 frontend TODOs (LucideIcons, inline styles, XSS, i18n)
- ruff: 838 → 0 (612 auto-fix + 246 manual + 27 F821 regression fix)
- F821: 30 → 0 (AutomationDefinition, DmsFile, user_id, Path, Any, String)
- Contract-Umgehungen: 2 neue gefunden (worker.py:169, worker.py:280) und gefixt
2026-08-16 01:17:18 +02:00

101 lines
3.4 KiB
Python

"""Dashboard routes — list available widgets from active plugins (Task 5.25)."""
from __future__ import annotations
import uuid
from fastapi import APIRouter, Depends
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.db import get_db
from app.core.visibility import apply_visibility_filter
from app.deps import require_permission
from app.models.contact import Contact
from app.plugins.registry import get_registry
router = APIRouter(prefix="/api/v1/dashboard", tags=["dashboard"])
@router.get("/widgets")
async def list_dashboard_widgets(
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(require_permission("dashboard:read")),
):
"""List all available dashboard widgets from active plugins.
Returns a flat list of widget definitions contributed by active plugins,
sorted by their order field. Each widget includes the contributing plugin name.
"""
registry = get_registry()
manifests = await registry.get_active_manifests(db)
widgets: list[dict] = []
for manifest in manifests:
for widget in manifest.get("dashboard_widgets", []):
widget_copy = dict(widget)
widget_copy["plugin_name"] = manifest["name"]
widgets.append(widget_copy)
# Sort by order field
widgets.sort(key=lambda w: w.get("order", 100))
return {"items": widgets, "total": len(widgets)}
@router.get("/counts")
async def get_dashboard_counts(
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(require_permission("dashboard:read")),
):
"""Get dashboard count statistics filtered by user visibility.
Returns counts for contacts and companies that the current user
is allowed to see based on ownership and sharing permissions.
"""
tenant_id = uuid.UUID(current_user["tenant_id"])
user_id = uuid.UUID(current_user["user_id"])
is_system_admin = current_user.get("is_system_admin", False)
# Contact count with visibility filter
contact_query = select(func.count(Contact.id)).where(
Contact.tenant_id == tenant_id,
Contact.deleted_at.is_(None),
)
contact_query = await apply_visibility_filter(
db, contact_query, "contact", Contact, user_id, tenant_id, is_system_admin
)
contact_result = await db.execute(contact_query)
contact_count = contact_result.scalar() or 0
# Company count (Contact.type == 'company') with visibility filter
company_query = select(func.count(Contact.id)).where(
Contact.tenant_id == tenant_id,
Contact.deleted_at.is_(None),
Contact.type == "company",
)
company_query = await apply_visibility_filter(
db, company_query, "contact", Contact, user_id, tenant_id, is_system_admin
)
company_result = await db.execute(company_query)
company_count = company_result.scalar() or 0
# Person count (Contact.type == 'person') with visibility filter
person_query = select(func.count(Contact.id)).where(
Contact.tenant_id == tenant_id,
Contact.deleted_at.is_(None),
Contact.type == "person",
)
person_query = await apply_visibility_filter(
db, person_query, "contact", Contact, user_id, tenant_id, is_system_admin
)
person_result = await db.execute(person_query)
person_count = person_result.scalar() or 0
return {
"contacts": contact_count,
"companies": company_count,
"persons": person_count,
"total": contact_count,
}