init
This commit is contained in:
0
transport/backend/app/routers/__init__.py
Normal file
0
transport/backend/app/routers/__init__.py
Normal file
61
transport/backend/app/routers/events.py
Normal file
61
transport/backend/app/routers/events.py
Normal file
@@ -0,0 +1,61 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from sqlalchemy import select, desc
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.database import get_db
|
||||
from app.models import Event
|
||||
from app.schemas import EventResponse
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("", response_model=list[EventResponse])
|
||||
async def get_events(
|
||||
type: Optional[str] = None,
|
||||
vehicle_id: Optional[int] = None,
|
||||
from_time: Optional[datetime] = Query(None, alias="from"),
|
||||
to_time: Optional[datetime] = Query(None, alias="to"),
|
||||
limit: int = Query(100, le=1000),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Получить список событий"""
|
||||
query = select(Event)
|
||||
|
||||
if type:
|
||||
query = query.where(Event.type == type)
|
||||
if vehicle_id:
|
||||
query = query.where(Event.vehicle_id == vehicle_id)
|
||||
if from_time:
|
||||
query = query.where(Event.timestamp >= from_time)
|
||||
if to_time:
|
||||
query = query.where(Event.timestamp <= to_time)
|
||||
|
||||
query = query.order_by(desc(Event.timestamp)).limit(limit)
|
||||
|
||||
result = await db.execute(query)
|
||||
events = result.scalars().all()
|
||||
|
||||
return events
|
||||
|
||||
|
||||
@router.get("/vehicles/{vehicle_id}/events", response_model=list[EventResponse])
|
||||
async def get_vehicle_events(
|
||||
vehicle_id: int,
|
||||
type: Optional[str] = None,
|
||||
limit: int = Query(100, le=1000),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Получить события конкретного транспортного средства"""
|
||||
query = select(Event).where(Event.vehicle_id == vehicle_id)
|
||||
|
||||
if type:
|
||||
query = query.where(Event.type == type)
|
||||
|
||||
query = query.order_by(desc(Event.timestamp)).limit(limit)
|
||||
|
||||
result = await db.execute(query)
|
||||
events = result.scalars().all()
|
||||
|
||||
return events
|
||||
103
transport/backend/app/routers/positions.py
Normal file
103
transport/backend/app/routers/positions.py
Normal file
@@ -0,0 +1,103 @@
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy import select, desc, and_
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.database import get_db
|
||||
from app.models import Vehicle, Position
|
||||
from app.schemas import PositionResponse, PositionIngest
|
||||
from app.services.websocket_manager import manager
|
||||
from app.services.event_detector import detect_events
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/vehicles/{vehicle_id}/positions", response_model=list[PositionResponse])
|
||||
async def get_vehicle_positions(
|
||||
vehicle_id: int,
|
||||
from_time: Optional[datetime] = Query(None, alias="from"),
|
||||
to_time: Optional[datetime] = Query(None, alias="to"),
|
||||
limit: int = Query(1000, le=10000),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Получить историю позиций транспортного средства"""
|
||||
# Check vehicle exists
|
||||
result = await db.execute(select(Vehicle).where(Vehicle.id == vehicle_id))
|
||||
if not result.scalar_one_or_none():
|
||||
raise HTTPException(status_code=404, detail="Vehicle not found")
|
||||
|
||||
# Build query
|
||||
query = select(Position).where(Position.vehicle_id == vehicle_id)
|
||||
|
||||
if from_time:
|
||||
query = query.where(Position.timestamp >= from_time)
|
||||
if to_time:
|
||||
query = query.where(Position.timestamp <= to_time)
|
||||
|
||||
query = query.order_by(desc(Position.timestamp)).limit(limit)
|
||||
|
||||
result = await db.execute(query)
|
||||
positions = result.scalars().all()
|
||||
|
||||
return positions
|
||||
|
||||
|
||||
@router.get("/vehicles/{vehicle_id}/last-position", response_model=Optional[PositionResponse])
|
||||
async def get_last_position(vehicle_id: int, db: AsyncSession = Depends(get_db)):
|
||||
"""Получить последнюю позицию транспортного средства"""
|
||||
result = await db.execute(
|
||||
select(Position)
|
||||
.where(Position.vehicle_id == vehicle_id)
|
||||
.order_by(desc(Position.timestamp))
|
||||
.limit(1)
|
||||
)
|
||||
position = result.scalar_one_or_none()
|
||||
|
||||
if not position:
|
||||
return None
|
||||
|
||||
return position
|
||||
|
||||
|
||||
@router.post("/ingest/position", response_model=PositionResponse, status_code=201)
|
||||
async def ingest_position(position: PositionIngest, db: AsyncSession = Depends(get_db)):
|
||||
"""Принять новую позицию от трекера/симулятора"""
|
||||
# Check vehicle exists
|
||||
result = await db.execute(select(Vehicle).where(Vehicle.id == position.vehicle_id))
|
||||
vehicle = result.scalar_one_or_none()
|
||||
|
||||
if not vehicle:
|
||||
raise HTTPException(status_code=404, detail="Vehicle not found")
|
||||
|
||||
# Get previous position for event detection
|
||||
prev_result = await db.execute(
|
||||
select(Position)
|
||||
.where(Position.vehicle_id == position.vehicle_id)
|
||||
.order_by(desc(Position.timestamp))
|
||||
.limit(1)
|
||||
)
|
||||
prev_position = prev_result.scalar_one_or_none()
|
||||
|
||||
# Create new position
|
||||
db_position = Position(
|
||||
vehicle_id=position.vehicle_id,
|
||||
timestamp=position.timestamp or datetime.utcnow(),
|
||||
lat=position.lat,
|
||||
lon=position.lon,
|
||||
speed=position.speed,
|
||||
heading=position.heading
|
||||
)
|
||||
db.add(db_position)
|
||||
await db.commit()
|
||||
await db.refresh(db_position)
|
||||
|
||||
# Detect events
|
||||
events = await detect_events(db, vehicle, db_position, prev_position)
|
||||
|
||||
# Broadcast to WebSocket clients
|
||||
await manager.broadcast_position(db_position)
|
||||
for event in events:
|
||||
await manager.broadcast_event(event)
|
||||
|
||||
return db_position
|
||||
139
transport/backend/app/routers/vehicles.py
Normal file
139
transport/backend/app/routers/vehicles.py
Normal file
@@ -0,0 +1,139 @@
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy import select, desc
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.database import get_db
|
||||
from app.models import Vehicle, Position
|
||||
from app.schemas import VehicleCreate, VehicleUpdate, VehicleResponse, VehicleWithPosition
|
||||
from app.schemas.vehicle import LastPosition
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def get_vehicle_status(last_position: Optional[Position], now: datetime) -> str:
|
||||
if not last_position:
|
||||
return "offline"
|
||||
|
||||
time_diff = now - last_position.timestamp
|
||||
if time_diff > timedelta(minutes=5):
|
||||
return "offline"
|
||||
elif last_position.speed < 2:
|
||||
return "stopped"
|
||||
else:
|
||||
return "moving"
|
||||
|
||||
|
||||
@router.get("", response_model=list[VehicleWithPosition])
|
||||
async def get_vehicles(db: AsyncSession = Depends(get_db)):
|
||||
"""Получить список всех транспортных средств с последними позициями"""
|
||||
result = await db.execute(select(Vehicle))
|
||||
vehicles = result.scalars().all()
|
||||
|
||||
response = []
|
||||
now = datetime.utcnow()
|
||||
|
||||
for vehicle in vehicles:
|
||||
# Get last position
|
||||
pos_result = await db.execute(
|
||||
select(Position)
|
||||
.where(Position.vehicle_id == vehicle.id)
|
||||
.order_by(desc(Position.timestamp))
|
||||
.limit(1)
|
||||
)
|
||||
last_pos = pos_result.scalar_one_or_none()
|
||||
|
||||
vehicle_data = VehicleWithPosition(
|
||||
id=vehicle.id,
|
||||
name=vehicle.name,
|
||||
type=vehicle.type,
|
||||
created_at=vehicle.created_at,
|
||||
last_position=LastPosition(
|
||||
lat=last_pos.lat,
|
||||
lon=last_pos.lon,
|
||||
speed=last_pos.speed,
|
||||
heading=last_pos.heading,
|
||||
timestamp=last_pos.timestamp
|
||||
) if last_pos else None,
|
||||
status=get_vehicle_status(last_pos, now)
|
||||
)
|
||||
response.append(vehicle_data)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
@router.get("/{vehicle_id}", response_model=VehicleWithPosition)
|
||||
async def get_vehicle(vehicle_id: int, db: AsyncSession = Depends(get_db)):
|
||||
"""Получить информацию о транспортном средстве"""
|
||||
result = await db.execute(select(Vehicle).where(Vehicle.id == vehicle_id))
|
||||
vehicle = result.scalar_one_or_none()
|
||||
|
||||
if not vehicle:
|
||||
raise HTTPException(status_code=404, detail="Vehicle not found")
|
||||
|
||||
# Get last position
|
||||
pos_result = await db.execute(
|
||||
select(Position)
|
||||
.where(Position.vehicle_id == vehicle.id)
|
||||
.order_by(desc(Position.timestamp))
|
||||
.limit(1)
|
||||
)
|
||||
last_pos = pos_result.scalar_one_or_none()
|
||||
now = datetime.utcnow()
|
||||
|
||||
return VehicleWithPosition(
|
||||
id=vehicle.id,
|
||||
name=vehicle.name,
|
||||
type=vehicle.type,
|
||||
created_at=vehicle.created_at,
|
||||
last_position=LastPosition(
|
||||
lat=last_pos.lat,
|
||||
lon=last_pos.lon,
|
||||
speed=last_pos.speed,
|
||||
heading=last_pos.heading,
|
||||
timestamp=last_pos.timestamp
|
||||
) if last_pos else None,
|
||||
status=get_vehicle_status(last_pos, now)
|
||||
)
|
||||
|
||||
|
||||
@router.post("", response_model=VehicleResponse, status_code=201)
|
||||
async def create_vehicle(vehicle: VehicleCreate, db: AsyncSession = Depends(get_db)):
|
||||
"""Создать новое транспортное средство"""
|
||||
db_vehicle = Vehicle(**vehicle.model_dump())
|
||||
db.add(db_vehicle)
|
||||
await db.commit()
|
||||
await db.refresh(db_vehicle)
|
||||
return db_vehicle
|
||||
|
||||
|
||||
@router.put("/{vehicle_id}", response_model=VehicleResponse)
|
||||
async def update_vehicle(vehicle_id: int, vehicle: VehicleUpdate, db: AsyncSession = Depends(get_db)):
|
||||
"""Обновить транспортное средство"""
|
||||
result = await db.execute(select(Vehicle).where(Vehicle.id == vehicle_id))
|
||||
db_vehicle = result.scalar_one_or_none()
|
||||
|
||||
if not db_vehicle:
|
||||
raise HTTPException(status_code=404, detail="Vehicle not found")
|
||||
|
||||
update_data = vehicle.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(db_vehicle, field, value)
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(db_vehicle)
|
||||
return db_vehicle
|
||||
|
||||
|
||||
@router.delete("/{vehicle_id}", status_code=204)
|
||||
async def delete_vehicle(vehicle_id: int, db: AsyncSession = Depends(get_db)):
|
||||
"""Удалить транспортное средство"""
|
||||
result = await db.execute(select(Vehicle).where(Vehicle.id == vehicle_id))
|
||||
db_vehicle = result.scalar_one_or_none()
|
||||
|
||||
if not db_vehicle:
|
||||
raise HTTPException(status_code=404, detail="Vehicle not found")
|
||||
|
||||
await db.delete(db_vehicle)
|
||||
await db.commit()
|
||||
Reference in New Issue
Block a user