abbe7a18fc
- 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
101 lines
3.4 KiB
Python
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,
|
|
}
|