from fastapi import APIRouter, Depends, HTTPException, status from fastapi.encoders import jsonable_encoder from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.api.deps import get_current_telegram_user, log_audit from app.db.session import get_session from app.models.car import Car, ServiceVisit, VehicleAccess from app.models.user import User from app.schemas.service_center import ( VehicleAccessGrant, VehicleAccessRead, VehicleCreate, VehicleRead, VehicleUpdate, ) from app.schemas.user import UserRead from app.services.vehicle_identity import normalize_license_plate, validate_vin router = APIRouter(tags=["my"]) @router.get("/me", response_model=UserRead) async def me(current_user: User = Depends(get_current_telegram_user)) -> User: return current_user def vehicle_data(payload: VehicleCreate | VehicleUpdate, *, partial: bool = False) -> dict: raw = payload.model_dump(exclude_unset=partial) data = { key: value for key, value in raw.items() if key not in {"license_plate", "license_plate_country", "vin"} } if "license_plate" in raw: data["license_plate_display"] = raw["license_plate"] data["license_plate_normalized"] = normalize_license_plate(raw["license_plate"]) data["plate_number"] = raw["license_plate"] if "license_plate_country" in raw: data["license_plate_country"] = ( raw["license_plate_country"].upper() if raw["license_plate_country"] else None ) if "vin" in raw: data["vin_normalized"] = validate_vin(raw["vin"]) data["vin"] = raw["vin"] return data @router.get("/my/vehicles", response_model=list[VehicleRead]) async def my_vehicles( session: AsyncSession = Depends(get_session), current_user: User = Depends(get_current_telegram_user), ) -> list[Car]: result = await session.execute( select(Car) .join(VehicleAccess, VehicleAccess.vehicle_id == Car.id) .where(VehicleAccess.user_id == current_user.id, VehicleAccess.status == "active") .order_by(Car.created_at.desc()) ) return list(result.scalars()) @router.post("/my/vehicles", response_model=VehicleRead, status_code=status.HTTP_201_CREATED) async def create_vehicle( payload: VehicleCreate, session: AsyncSession = Depends(get_session), current_user: User = Depends(get_current_telegram_user), ) -> Car: car = Car(**vehicle_data(payload), owner_id=current_user.id) session.add(car) await session.flush() session.add(VehicleAccess(vehicle_id=car.id, user_id=current_user.id, role="owner", status="active")) await log_audit(session, actor=current_user, action="vehicle.create", target_type="vehicle", target_id=car.id) await session.commit() await session.refresh(car) return car @router.patch("/my/vehicles/{vehicle_id}", response_model=VehicleRead) async def update_vehicle( vehicle_id: int, payload: VehicleUpdate, session: AsyncSession = Depends(get_session), current_user: User = Depends(get_current_telegram_user), ) -> Car: car = await session.get(Car, vehicle_id) if car is None: raise HTTPException(status_code=404, detail="Vehicle not found") if car.owner_id != current_user.id: raise HTTPException(status_code=403, detail="Forbidden") for field, value in vehicle_data(payload, partial=True).items(): setattr(car, field, value) await log_audit(session, actor=current_user, action="vehicle.update", target_type="vehicle", target_id=car.id) await session.commit() await session.refresh(car) return car @router.get("/my/vehicles/{vehicle_id}/service-history") async def vehicle_service_history( vehicle_id: int, session: AsyncSession = Depends(get_session), current_user: User = Depends(get_current_telegram_user), ) -> dict: car = await session.get(Car, vehicle_id) if car is None: raise HTTPException(status_code=404, detail="Vehicle not found") if car.owner_id != current_user.id: raise HTTPException(status_code=403, detail="Forbidden") result = await session.execute( select(ServiceVisit) .where(ServiceVisit.vehicle_id == vehicle_id) .order_by(ServiceVisit.visit_date.desc()) ) visits = list(result.scalars()) return {"vehicle_id": vehicle_id, "service_visits": jsonable_encoder(visits)} @router.post("/my/vehicles/{vehicle_id}/grant-service-access", response_model=VehicleAccessRead) async def grant_vehicle_access( vehicle_id: int, payload: VehicleAccessGrant, session: AsyncSession = Depends(get_session), current_user: User = Depends(get_current_telegram_user), ) -> VehicleAccess: car = await session.get(Car, vehicle_id) if car is None: raise HTTPException(status_code=404, detail="Vehicle not found") if car.owner_id != current_user.id: raise HTTPException(status_code=403, detail="Forbidden") if not payload.user_id: raise HTTPException(status_code=400, detail="user_id is required for access grants") result = await session.execute( select(VehicleAccess).where( VehicleAccess.vehicle_id == vehicle_id, VehicleAccess.user_id == payload.user_id, VehicleAccess.role == payload.role, ) ) access = result.scalar_one_or_none() if access is None: access = VehicleAccess(vehicle_id=vehicle_id, user_id=payload.user_id, role=payload.role, status="active") session.add(access) else: access.status = "active" access.revoked_at = None await log_audit( session, actor=current_user, action="vehicle_access.grant", target_type="vehicle", target_id=vehicle_id, metadata={"granted_user_id": payload.user_id, "role": payload.role}, ) await session.commit() await session.refresh(access) return access