""" Endpoint de recherche globale rapide — utilisé par la barre Cmd+K """ from fastapi import APIRouter, Query from ..database import db router = APIRouter(prefix="/api/search", tags=["search"]) IP_RE = r"^(\d{1,3}\.){0,3}\d{1,3}$" @router.get("/quick") async def quick_search(q: str = Query(..., min_length=1, max_length=100)): """ Recherche unifiée sur IPs, JA4, ASN, hosts. Retourne jusqu'à 5 résultats par catégorie. """ q = q.strip() pattern = f"%{q}%" results = [] # ── IPs ────────────────────────────────────────────────────────────────── ip_rows = db.query( """ SELECT replaceRegexpAll(toString(src_ip), '^::ffff:', '') AS clean_ip, count() AS hits, max(detected_at) AS last_seen, any(threat_level) AS threat_level FROM ml_detected_anomalies WHERE ilike(toString(src_ip), %(p)s) AND detected_at >= now() - INTERVAL 24 HOUR GROUP BY clean_ip ORDER BY hits DESC """, {"p": pattern}, ) for r in ip_rows.result_rows: ip = str(r[0]) results.append({ "type": "ip", "value": ip, "label": ip, "meta": f"{r[1]} détections · {r[3]}", "url": f"/detections/ip/{ip}", "investigation_url": f"/investigation/{ip}", }) # ── JA4 fingerprints ───────────────────────────────────────────────────── ja4_rows = db.query( """ SELECT ja4, count() AS hits, uniq(src_ip) AS unique_ips FROM ml_detected_anomalies WHERE ilike(ja4, %(p)s) AND ja4 != '' AND detected_at >= now() - INTERVAL 24 HOUR GROUP BY ja4 ORDER BY hits DESC """, {"p": pattern}, ) for r in ja4_rows.result_rows: results.append({ "type": "ja4", "value": str(r[0]), "label": str(r[0]), "meta": f"{r[1]} détections · {r[2]} IPs", "url": f"/investigation/ja4/{r[0]}", }) # ── Hosts ───────────────────────────────────────────────────────────────── host_rows = db.query( """ SELECT host, count() AS hits, uniq(src_ip) AS unique_ips FROM ml_detected_anomalies WHERE ilike(host, %(p)s) AND host != '' AND detected_at >= now() - INTERVAL 24 HOUR GROUP BY host ORDER BY hits DESC """, {"p": pattern}, ) for r in host_rows.result_rows: results.append({ "type": "host", "value": str(r[0]), "label": str(r[0]), "meta": f"{r[1]} hits · {r[2]} IPs", "url": f"/detections?search={r[0]}", }) # ── ASN ─────────────────────────────────────────────────────────────────── asn_rows = db.query( """ SELECT asn_org, asn_number, count() AS hits, uniq(src_ip) AS unique_ips FROM ml_detected_anomalies WHERE (ilike(asn_org, %(p)s) OR ilike(asn_number, %(p)s)) AND asn_org != '' AND asn_number != '' AND detected_at >= now() - INTERVAL 24 HOUR GROUP BY asn_org, asn_number ORDER BY hits DESC """, {"p": pattern}, ) for r in asn_rows.result_rows: results.append({ "type": "asn", "value": str(r[1]), "label": f"AS{r[1]} — {r[0]}", "meta": f"{r[2]} hits · {r[3]} IPs", "url": f"/detections?asn={r[1]}", }) return {"query": q, "results": results}