from __future__ import annotations from fastapi import APIRouter, Depends, Query from sqlalchemy import or_, select from sqlalchemy.orm import Session from app.api.deps import get_current_user, get_db from app.models.booking import Booking from app.models.data_quality import DataQualityIssue from app.models.vehicle import Vehicle from app.schemas import CurrentUser, SearchResponse, SearchResultItem router = APIRouter(prefix="/api/v1/search", tags=["search"]) # Static application sections. Manager-only sections are filtered by role, mirroring the # same nav visibility rule Layout.tsx applies -- search must never surface a destination # the current role can't actually reach. _SECTIONS: list[dict] = [ { "label": "Overview", "detail": "Operations dashboard", "link": "/dashboard", "terms": ["overview", "dashboard", "readiness"], }, { "label": "Fleet", "detail": "Vehicle registry", "link": "/vehicles", "terms": ["fleet", "vehicle", "vehicles"], }, { "label": "Bookings", "detail": "Rental bookings", "link": "/bookings", "terms": ["booking", "bookings", "rental"], }, { "label": "Data quality", "detail": "Quality workbench", "link": "/data-quality", "terms": ["quality", "data quality", "issues"], "role": "operations_manager", }, { "label": "Knowledge", "detail": "Procedure assistant", "link": "/knowledge", "terms": ["knowledge", "procedures"], }, { "label": "Integrations", "detail": "Automation and integration status", "link": "/automation", "terms": ["automation", "integrations", "systems", "n8n"], "role": "operations_manager", }, { "label": "Audit trail", "detail": "Audit history", "link": "/audit", "terms": ["audit", "history"], "role": "operations_manager", }, ] @router.get("", response_model=SearchResponse) def search( q: str = Query(min_length=1, max_length=100), db: Session = Depends(get_db), user: CurrentUser = Depends(get_current_user), ) -> SearchResponse: query = q.strip() normalized = query.lower() results: list[SearchResultItem] = [] for section in _SECTIONS: role = section.get("role") if role and user.role != role: continue terms: list[str] = section["terms"] if any(term in normalized or normalized in term for term in terms): results.append( SearchResultItem( type="section", label=section["label"], detail=section["detail"], link=section["link"], ) ) like = f"%{query}%" for v in db.scalars( select(Vehicle) .where( or_( Vehicle.public_ref.ilike(like), Vehicle.make.ilike(like), Vehicle.model.ilike(like), Vehicle.registration_number.ilike(like), Vehicle.location.ilike(like), ) ) .order_by(Vehicle.public_ref) .limit(5) ).all(): results.append( SearchResultItem( type="vehicle", label=v.public_ref, detail=f"{v.make} {v.model} ยท {v.location}", link=f"/vehicles/{v.public_ref}", ) ) for b in db.scalars( select(Booking).where(Booking.public_ref.ilike(like)).order_by(Booking.starts_at.desc()).limit(5) ).all(): results.append( SearchResultItem( type="booking", label=b.public_ref, detail=b.status, link=f"/bookings/{b.public_ref}", ) ) # No customer detail route exists in this proof of concept, so customers are # deliberately never returned here -- there is nowhere useful to send the user. if user.role == "operations_manager": for i in db.scalars( select(DataQualityIssue) .where(DataQualityIssue.public_ref.ilike(like)) .order_by(DataQualityIssue.detected_at.desc()) .limit(5) ).all(): results.append( SearchResultItem( type="data_quality_issue", label=i.public_ref, detail=i.rule_type.replace("_", " "), link=f"/data-quality/{i.public_ref}", ) ) return SearchResponse(query=query, results=results[:10])