# Роуты для просмотра автомобилей и агрегированной статистики. from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy import func, select from sqlalchemy.orm import selectinload from ..deps import get_persistence from ...storage.db import PersistenceService from ...storage.models import Car, Image from ...storage.schemas import CarRead router = APIRouter() @router.get("/cars") def list_cars( page: int = Query(1, ge=1), per_page: int = Query(20, ge=1, le=100), brand: str | None = None, model: str | None = None, year_min: int | None = None, year_max: int | None = None, is_sold: bool | None = None, persistence: PersistenceService = Depends(get_persistence), ): # Список автомобилей с пагинацией и фильтрами with persistence.session_scope() as session: query = select(Car) count_query = select(func.count(Car.id)) if brand: escaped_brand = brand.replace("%", r"\%").replace("_", r"\_") cond = Car.brand.ilike(f"%{escaped_brand}%", escape="\\") query = query.where(cond) count_query = count_query.where(cond) if model: escaped_model = model.replace("%", r"\%").replace("_", r"\_") cond = Car.model.ilike(f"%{escaped_model}%", escape="\\") query = query.where(cond) count_query = count_query.where(cond) if year_min is not None: query = query.where(Car.year >= year_min) count_query = count_query.where(Car.year >= year_min) if year_max is not None: query = query.where(Car.year <= year_max) count_query = count_query.where(Car.year <= year_max) if is_sold is not None: query = query.where(Car.is_sold == is_sold) count_query = count_query.where(Car.is_sold == is_sold) total = session.execute(count_query).scalar() or 0 offset = (page - 1) * per_page cars = session.execute( query.options(selectinload(Car.images)).order_by(Car.last_seen_at.desc()).offset(offset).limit(per_page) ).scalars().all() return { "total": total, "page": page, "per_page": per_page, "pages": (total + per_page - 1) // per_page if per_page else 0, "items": [CarRead.model_validate(car).model_dump(mode="json") for car in cars], } @router.get("/cars/{car_id}") def get_car( car_id: int, persistence: PersistenceService = Depends(get_persistence), ): # Детальная информация об автомобиле с изображениями with persistence.session_scope() as session: car = session.execute( select(Car).options(selectinload(Car.images)).where(Car.id == car_id) ).scalars().first() if car is None: raise HTTPException(status_code=404, detail="Car not found") return CarRead.model_validate(car).model_dump(mode="json") @router.get("/cars/by-origin/{origin_id}") def get_car_by_origin( origin_id: str, persistence: PersistenceService = Depends(get_persistence), ): # Поиск автомобиля по origin_id with persistence.session_scope() as session: car = session.execute( select(Car).options(selectinload(Car.images)).where(Car.origin_id == origin_id) ).scalars().first() if car is None: raise HTTPException(status_code=404, detail="Car not found") return CarRead.model_validate(car).model_dump(mode="json") @router.get("/stats") def get_stats(persistence: PersistenceService = Depends(get_persistence)): # Общая статистика по БД with persistence.session_scope() as session: total_cars = session.execute(select(func.count(Car.id))).scalar() or 0 total_images = session.execute(select(func.count(Image.id))).scalar() or 0 brands = session.execute( select(Car.brand, func.count(Car.id)) .group_by(Car.brand) .order_by(func.count(Car.id).desc()) .limit(20) ).all() return { "total_cars": total_cars, "total_images": total_images, "top_brands": [{"brand": b, "count": c} for b, c in brands], }