import asyncio import json from fastapi import APIRouter, Depends, Query from auth.dependencies import get_current_user from auth.models import TokenPayload from devices import service as devices_service from users import service as users_service from crm import service as crm_service from melodies import service as melodies_service router = APIRouter(prefix="/api/search", tags=["search"]) LIMIT = 5 def _truncate(s: str, n: int = 48) -> str: if not s: return "" return s if len(s) <= n else s[:n - 1] + "…" def _search_devices(q: str) -> list[dict]: try: results = devices_service.list_devices(search=q) except Exception: return [] out = [] for d in results[:LIMIT]: label = d.device_name or d.serial_number or d.device_id or d.id sublabel = d.serial_number if d.device_name else None out.append({ "type": "device", "id": d.id, "label": _truncate(label), "sublabel": _truncate(sublabel) if sublabel else None, "url": f"/devices/{d.id}", }) return out def _search_users(q: str) -> list[dict]: try: results = users_service.list_users(search=q) except Exception: return [] out = [] for u in results[:LIMIT]: label = u.display_name or u.email or u.id sublabel = u.email if u.display_name else None out.append({ "type": "user", "id": u.id, "label": _truncate(label), "sublabel": _truncate(sublabel) if sublabel else None, "url": f"/users/{u.id}", }) return out def _search_customers(q: str) -> list[dict]: try: results = crm_service.list_customers(search=q) except Exception: return [] out = [] for c in results[:LIMIT]: name_parts = [c.name, c.surname] label = " ".join(p for p in name_parts if p) or c.organization or c.id sublabel_parts = [] if c.organization and (c.name or c.surname): sublabel_parts.append(c.organization) if c.location: if c.location.city: sublabel_parts.append(c.location.city) if c.location.country: sublabel_parts.append(c.location.country) out.append({ "type": "customer", "id": c.id, "label": _truncate(label), "sublabel": _truncate(" · ".join(sublabel_parts)) if sublabel_parts else None, "url": f"/crm/customers/{c.id}", }) return out def _search_products(q: str) -> list[dict]: try: results = crm_service.list_products(search=q) except Exception: return [] out = [] for p in results[:LIMIT]: sublabel_parts = [] if p.category: sublabel_parts.append(p.category.value.replace("_", " ").title()) if p.sku: sublabel_parts.append(p.sku) out.append({ "type": "product", "id": p.id, "label": _truncate(p.name or p.id), "sublabel": _truncate(" · ".join(sublabel_parts)) if sublabel_parts else None, "url": f"/crm/products/{p.id}", }) return out async def _search_melodies(q: str) -> list[dict]: try: results = await melodies_service.list_melodies(search=q) except Exception: return [] out = [] for m in results[:LIMIT]: try: name_dict = json.loads(m.information.name) if m.information.name else {} label = name_dict.get("en") or name_dict.get("gr") or next(iter(name_dict.values()), None) or m.id except Exception: label = m.information.name or m.id sublabel_parts = [] if m.pid: sublabel_parts.append(m.pid) if m.information.melodyTone: sublabel_parts.append(m.information.melodyTone.value.title()) if m.information.totalActiveBells: sublabel_parts.append(f"{m.information.totalActiveBells} bells") out.append({ "type": "melody", "id": m.id, "label": _truncate(label), "sublabel": _truncate(" · ".join(sublabel_parts)) if sublabel_parts else None, "url": f"/melodies/{m.id}", }) return out @router.get("") async def global_search( q: str = Query(..., min_length=1, max_length=100), _user: TokenPayload = Depends(get_current_user), ): q = q.strip() if not q: return {"results": []} # Run sync searches in a thread pool, melody search is already async loop = asyncio.get_event_loop() devices_fut = loop.run_in_executor(None, _search_devices, q) users_fut = loop.run_in_executor(None, _search_users, q) customers_fut = loop.run_in_executor(None, _search_customers, q) products_fut = loop.run_in_executor(None, _search_products, q) melodies_task = _search_melodies(q) devices, users, customers, products, melodies = await asyncio.gather( devices_fut, users_fut, customers_fut, products_fut, melodies_task ) results = [] for group in (devices, users, customers, products, melodies): results.extend(group) return {"results": results}