import re from auth import require_auth from database import events_collection from fastapi import APIRouter, Depends, HTTPException, Query from models.api import FilterOptionsResponse, PaginatedEventResponse router = APIRouter(dependencies=[Depends(require_auth)]) @router.get("/events", response_model=PaginatedEventResponse) def list_events( service: str | None = None, actor: str | None = None, operation: str | None = None, result: str | None = None, start: str | None = None, end: str | None = None, search: str | None = None, page: int = Query(default=1, ge=1), page_size: int = Query(default=50, ge=1, le=500), ): filters = [] if service: filters.append({"service": service}) if actor: actor_safe = re.escape(actor) filters.append( { "$or": [ {"actor_display": {"$regex": actor_safe, "$options": "i"}}, {"actor_upn": {"$regex": actor_safe, "$options": "i"}}, {"actor.user.userPrincipalName": {"$regex": actor_safe, "$options": "i"}}, {"actor.user.id": actor}, ] } ) if operation: filters.append({"operation": {"$regex": re.escape(operation), "$options": "i"}}) if result: filters.append({"result": {"$regex": re.escape(result), "$options": "i"}}) if start or end: time_filter = {} if start: time_filter["$gte"] = start if end: time_filter["$lte"] = end filters.append({"timestamp": time_filter}) if search: search_safe = re.escape(search) filters.append( { "$or": [ {"raw_text": {"$regex": search_safe, "$options": "i"}}, {"display_summary": {"$regex": search_safe, "$options": "i"}}, {"actor_display": {"$regex": search_safe, "$options": "i"}}, {"target_displays": {"$elemMatch": {"$regex": search_safe, "$options": "i"}}}, {"operation": {"$regex": search_safe, "$options": "i"}}, ] } ) query = {"$and": filters} if filters else {} safe_page_size = max(1, min(page_size, 500)) safe_page = max(1, page) skip = (safe_page - 1) * safe_page_size try: total = events_collection.count_documents(query) cursor = events_collection.find(query).sort("timestamp", -1).skip(skip).limit(safe_page_size) events = list(cursor) except Exception as exc: raise HTTPException(status_code=500, detail=f"Failed to query events: {exc}") from exc for e in events: e["_id"] = str(e["_id"]) return { "items": events, "total": total, "page": safe_page, "page_size": safe_page_size, } @router.get("/filter-options", response_model=FilterOptionsResponse) def filter_options(limit: int = Query(default=200, ge=1, le=1000)): """ Provide distinct values for UI filters (best-effort, capped). """ safe_limit = max(1, min(limit, 1000)) try: services = sorted(events_collection.distinct("service"))[:safe_limit] operations = sorted(events_collection.distinct("operation"))[:safe_limit] results = sorted([r for r in events_collection.distinct("result") if r])[:safe_limit] actors = sorted([a for a in events_collection.distinct("actor_display") if a])[:safe_limit] actor_upns = sorted([a for a in events_collection.distinct("actor_upn") if a])[:safe_limit] devices = sorted([a for a in events_collection.distinct("target_displays") if isinstance(a, str)])[:safe_limit] except Exception as exc: raise HTTPException(status_code=500, detail=f"Failed to load filter options: {exc}") from exc return { "services": services, "operations": operations, "results": results, "actors": actors, "actor_upns": actor_upns, "devices": devices, }