mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Add metadata to FastAPI endpoints for OpenAPI.
operation_ids give us better function names when generating the typescript API from the openapi.json. BaseModel.Config.title does the same for type names. Response models (or 204 status codes) need to be explicit or the API will be declared as returning any.
This commit is contained in:
parent
4053356e13
commit
b7439cbd17
@ -18,6 +18,9 @@ class FrozenCombatJs(BaseModel):
|
||||
target_positions: list[LeafletPoint] | None
|
||||
footprint: list[LeafletPoly] | None
|
||||
|
||||
class Config:
|
||||
title = "FrozenCombat"
|
||||
|
||||
@staticmethod
|
||||
def for_combat(combat: FrozenCombat, theater: ConflictTheater) -> FrozenCombatJs:
|
||||
if isinstance(combat, AirCombat):
|
||||
|
||||
@ -20,6 +20,9 @@ class ControlPointJs(BaseModel):
|
||||
destination: LeafletPoint | None
|
||||
sidc: str
|
||||
|
||||
class Config:
|
||||
title = "ControlPoint"
|
||||
|
||||
@staticmethod
|
||||
def for_control_point(control_point: ControlPoint) -> ControlPointJs:
|
||||
destination = None
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
from dcs import Point
|
||||
from dcs.mapping import LatLng
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi import APIRouter, Body, Depends, HTTPException, status
|
||||
|
||||
from game import Game
|
||||
from .models import ControlPointJs
|
||||
@ -12,14 +12,18 @@ from ...sim import GameUpdateEvents
|
||||
router: APIRouter = APIRouter(prefix="/control-points")
|
||||
|
||||
|
||||
@router.get("/")
|
||||
@router.get(
|
||||
"/", operation_id="list_control_points", response_model=list[ControlPointJs]
|
||||
)
|
||||
def list_control_points(
|
||||
game: Game = Depends(GameContext.require),
|
||||
) -> list[ControlPointJs]:
|
||||
return ControlPointJs.all_in_game(game)
|
||||
|
||||
|
||||
@router.get("/{cp_id}")
|
||||
@router.get(
|
||||
"/{cp_id}", operation_id="get_control_point_by_id", response_model=ControlPointJs
|
||||
)
|
||||
def get_control_point(
|
||||
cp_id: int, game: Game = Depends(GameContext.require)
|
||||
) -> ControlPointJs:
|
||||
@ -32,7 +36,11 @@ def get_control_point(
|
||||
return ControlPointJs.for_control_point(cp)
|
||||
|
||||
|
||||
@router.get("/{cp_id}/destination-in-range")
|
||||
@router.get(
|
||||
"/{cp_id}/destination-in-range",
|
||||
operation_id="control_point_destination_in_range",
|
||||
response_model=bool,
|
||||
)
|
||||
def destination_in_range(
|
||||
cp_id: int, lat: float, lng: float, game: Game = Depends(GameContext.require)
|
||||
) -> bool:
|
||||
@ -47,9 +55,15 @@ def destination_in_range(
|
||||
return cp.destination_in_range(point)
|
||||
|
||||
|
||||
@router.put("/{cp_id}/destination")
|
||||
@router.put(
|
||||
"/{cp_id}/destination",
|
||||
operation_id="set_control_point_destination",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
)
|
||||
def set_destination(
|
||||
cp_id: int, destination: LeafletPoint, game: Game = Depends(GameContext.require)
|
||||
cp_id: int,
|
||||
destination: LeafletPoint = Body(..., title="destination"),
|
||||
game: Game = Depends(GameContext.require),
|
||||
) -> None:
|
||||
cp = game.theater.find_control_point_by_id(cp_id)
|
||||
if cp is None:
|
||||
@ -77,7 +91,11 @@ def set_destination(
|
||||
EventStream.put_nowait(GameUpdateEvents().update_control_point(cp))
|
||||
|
||||
|
||||
@router.put("/{cp_id}/cancel-travel")
|
||||
@router.put(
|
||||
"/{cp_id}/cancel-travel",
|
||||
operation_id="clear_control_point_destination",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
)
|
||||
def cancel_travel(cp_id: int, game: Game = Depends(GameContext.require)) -> None:
|
||||
cp = game.theater.find_control_point_by_id(cp_id)
|
||||
if cp is None:
|
||||
|
||||
@ -16,6 +16,9 @@ class HoldZonesJs(BaseModel):
|
||||
permissible_zones: list[LeafletPoly] = Field(alias="permissibleZones")
|
||||
preferred_lines: list[LeafletPoly] = Field(alias="preferredLines")
|
||||
|
||||
class Config:
|
||||
title = "HoldZones"
|
||||
|
||||
@classmethod
|
||||
def empty(cls) -> HoldZonesJs:
|
||||
return HoldZonesJs(
|
||||
@ -62,6 +65,9 @@ class IpZonesJs(BaseModel):
|
||||
permissibleZone: LeafletPoly = Field(alias="permissibleZone")
|
||||
safeZones: list[LeafletPoly] = Field(alias="safeZones")
|
||||
|
||||
class Config:
|
||||
title = "IpZones"
|
||||
|
||||
@classmethod
|
||||
def empty(cls) -> IpZonesJs:
|
||||
return IpZonesJs(homeBubble=[], ipBubble=[], permissibleZone=[], safeZones=[])
|
||||
@ -89,6 +95,9 @@ class JoinZonesJs(BaseModel):
|
||||
permissible_zones: list[LeafletPoly] = Field(alias="permissibleZones")
|
||||
preferred_lines: list[LeafletPoly] = Field(alias="preferredLines")
|
||||
|
||||
class Config:
|
||||
title = "JoinZones"
|
||||
|
||||
@classmethod
|
||||
def empty(cls) -> JoinZonesJs:
|
||||
return JoinZonesJs(
|
||||
|
||||
@ -9,19 +9,25 @@ from .models import HoldZonesJs, IpZonesJs, JoinZonesJs
|
||||
router: APIRouter = APIRouter(prefix="/debug/waypoint-geometries")
|
||||
|
||||
|
||||
@router.get("/hold/{flight_id}")
|
||||
@router.get(
|
||||
"/hold/{flight_id}", operation_id="get_debug_hold_zones", response_model=HoldZonesJs
|
||||
)
|
||||
def hold_zones(
|
||||
flight_id: UUID, game: Game = Depends(GameContext.require)
|
||||
) -> HoldZonesJs:
|
||||
return HoldZonesJs.for_flight(game.db.flights.get(flight_id), game)
|
||||
|
||||
|
||||
@router.get("/ip/{flight_id}")
|
||||
@router.get(
|
||||
"/ip/{flight_id}", operation_id="get_debug_ip_zones", response_model=IpZonesJs
|
||||
)
|
||||
def ip_zones(flight_id: UUID, game: Game = Depends(GameContext.require)) -> IpZonesJs:
|
||||
return IpZonesJs.for_flight(game.db.flights.get(flight_id), game)
|
||||
|
||||
|
||||
@router.get("/join/{flight_id}")
|
||||
@router.get(
|
||||
"/join/{flight_id}", operation_id="get_debug_join_zones", response_model=JoinZonesJs
|
||||
)
|
||||
def join_zones(
|
||||
flight_id: UUID, game: Game = Depends(GameContext.require)
|
||||
) -> JoinZonesJs:
|
||||
|
||||
@ -22,6 +22,9 @@ class FlightJs(BaseModel):
|
||||
sidc: str
|
||||
waypoints: list[FlightWaypointJs] | None
|
||||
|
||||
class Config:
|
||||
title = "Flight"
|
||||
|
||||
@staticmethod
|
||||
def for_flight(flight: Flight, with_waypoints: bool) -> FlightJs:
|
||||
# Don't provide a location for aircraft that aren't in the air. Later we can
|
||||
|
||||
@ -12,14 +12,14 @@ from game.server.leaflet import LeafletPoly, ShapelyUtil
|
||||
router: APIRouter = APIRouter(prefix="/flights")
|
||||
|
||||
|
||||
@router.get("/")
|
||||
@router.get("/", operation_id="list_flights", response_model=list[FlightJs])
|
||||
def list_flights(
|
||||
with_waypoints: bool = False, game: Game = Depends(GameContext.require)
|
||||
) -> list[FlightJs]:
|
||||
return FlightJs.all_in_game(game, with_waypoints)
|
||||
|
||||
|
||||
@router.get("/{flight_id}")
|
||||
@router.get("/{flight_id}", operation_id="get_flight_by_id", response_model=FlightJs)
|
||||
def get_flight(
|
||||
flight_id: UUID,
|
||||
with_waypoints: bool = False,
|
||||
@ -29,7 +29,11 @@ def get_flight(
|
||||
return FlightJs.for_flight(flight, with_waypoints)
|
||||
|
||||
|
||||
@router.get("/{flight_id}/commit-boundary")
|
||||
@router.get(
|
||||
"/{flight_id}/commit-boundary",
|
||||
operation_id="get_commit_boundary_for_flight",
|
||||
response_model=LeafletPoly,
|
||||
)
|
||||
def commit_boundary(
|
||||
flight_id: UUID, game: Game = Depends(GameContext.require)
|
||||
) -> LeafletPoly:
|
||||
|
||||
@ -17,6 +17,9 @@ class FrontLineJs(BaseModel):
|
||||
id: UUID
|
||||
extents: list[LeafletPoint]
|
||||
|
||||
class Config:
|
||||
title = "FrontLine"
|
||||
|
||||
@staticmethod
|
||||
def for_front_line(front_line: FrontLine) -> FrontLineJs:
|
||||
a = front_line.position.point_from_heading(
|
||||
|
||||
@ -9,12 +9,14 @@ from ..dependencies import GameContext
|
||||
router: APIRouter = APIRouter(prefix="/front-lines")
|
||||
|
||||
|
||||
@router.get("/")
|
||||
@router.get("/", operation_id="list_front_lines", response_model=list[FrontLineJs])
|
||||
def list_front_lines(game: Game = Depends(GameContext.require)) -> list[FrontLineJs]:
|
||||
return FrontLineJs.all_in_game(game)
|
||||
|
||||
|
||||
@router.get("/{front_line_id}")
|
||||
@router.get(
|
||||
"/{front_line_id}", operation_id="get_front_line_by_id", response_model=FrontLineJs
|
||||
)
|
||||
def get_front_line(
|
||||
front_line_id: UUID, game: Game = Depends(GameContext.require)
|
||||
) -> FrontLineJs:
|
||||
|
||||
@ -23,6 +23,9 @@ class GameJs(BaseModel):
|
||||
flights: list[FlightJs]
|
||||
map_center: LeafletPoint
|
||||
|
||||
class Config:
|
||||
title = "Game"
|
||||
|
||||
@staticmethod
|
||||
def from_game(game: Game) -> GameJs:
|
||||
return GameJs(
|
||||
|
||||
@ -7,7 +7,7 @@ from .models import GameJs
|
||||
router: APIRouter = APIRouter(prefix="/game")
|
||||
|
||||
|
||||
@router.get("/")
|
||||
@router.get("/", operation_id="get_game_state", response_model=GameJs)
|
||||
def game_state(game: Game | None = Depends(GameContext.get)) -> GameJs | None:
|
||||
if game is None:
|
||||
return None
|
||||
|
||||
@ -19,6 +19,8 @@ class LeafletPoint(BaseModel):
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
title = "LatLng"
|
||||
|
||||
|
||||
class ShapelyUtil:
|
||||
@staticmethod
|
||||
|
||||
@ -12,11 +12,17 @@ class MapZonesJs(BaseModel):
|
||||
exclusion: list[LeafletPoly]
|
||||
sea: list[LeafletPoly]
|
||||
|
||||
class Config:
|
||||
title = "MapZones"
|
||||
|
||||
|
||||
class UnculledZoneJs(BaseModel):
|
||||
position: LeafletPoint
|
||||
radius: float
|
||||
|
||||
class Config:
|
||||
title = "UnculledZone"
|
||||
|
||||
|
||||
class ThreatZonesJs(BaseModel):
|
||||
full: list[LeafletPoly]
|
||||
@ -24,6 +30,9 @@ class ThreatZonesJs(BaseModel):
|
||||
air_defenses: list[LeafletPoly]
|
||||
radar_sams: list[LeafletPoly]
|
||||
|
||||
class Config:
|
||||
title = "ThreatZones"
|
||||
|
||||
@classmethod
|
||||
def from_zones(cls, zones: ThreatZones, theater: ConflictTheater) -> ThreatZonesJs:
|
||||
return ThreatZonesJs(
|
||||
@ -37,3 +46,6 @@ class ThreatZonesJs(BaseModel):
|
||||
class ThreatZoneContainerJs(BaseModel):
|
||||
blue: ThreatZonesJs
|
||||
red: ThreatZonesJs
|
||||
|
||||
class Config:
|
||||
title = "ThreatZoneContainer"
|
||||
|
||||
@ -8,7 +8,7 @@ from ..leaflet import ShapelyUtil
|
||||
router: APIRouter = APIRouter(prefix="/map-zones")
|
||||
|
||||
|
||||
@router.get("/terrain")
|
||||
@router.get("/terrain", operation_id="get_terrain_zones", response_model=MapZonesJs)
|
||||
def get_terrain(game: Game = Depends(GameContext.require)) -> MapZonesJs:
|
||||
zones = game.theater.landmap
|
||||
if zones is None:
|
||||
@ -21,7 +21,9 @@ def get_terrain(game: Game = Depends(GameContext.require)) -> MapZonesJs:
|
||||
)
|
||||
|
||||
|
||||
@router.get("/unculled")
|
||||
@router.get(
|
||||
"/unculled", operation_id="list_unculled_zones", response_model=list[UnculledZoneJs]
|
||||
)
|
||||
def get_unculled_zones(
|
||||
game: Game = Depends(GameContext.require),
|
||||
) -> list[UnculledZoneJs]:
|
||||
@ -33,7 +35,9 @@ def get_unculled_zones(
|
||||
]
|
||||
|
||||
|
||||
@router.get("/threats")
|
||||
@router.get(
|
||||
"/threats", operation_id="get_threat_zones", response_model=ThreatZoneContainerJs
|
||||
)
|
||||
def get_threat_zones(
|
||||
game: Game = Depends(GameContext.require),
|
||||
) -> ThreatZoneContainerJs:
|
||||
|
||||
@ -8,3 +8,6 @@ from game.server.leaflet import LeafletPoly
|
||||
class NavMeshPolyJs(BaseModel):
|
||||
poly: LeafletPoly
|
||||
threatened: bool
|
||||
|
||||
class Config:
|
||||
title = "NavMeshPoly"
|
||||
|
||||
@ -8,7 +8,7 @@ from ..leaflet import ShapelyUtil
|
||||
router: APIRouter = APIRouter(prefix="/navmesh")
|
||||
|
||||
|
||||
@router.get("/", response_model=list[NavMeshPolyJs])
|
||||
@router.get("/", operation_id="get_navmesh", response_model=list[NavMeshPolyJs])
|
||||
def get(
|
||||
for_player: bool, game: Game = Depends(GameContext.require)
|
||||
) -> list[NavMeshPolyJs]:
|
||||
|
||||
@ -8,7 +8,11 @@ from ..dependencies import GameContext, QtCallbacks, QtContext
|
||||
router: APIRouter = APIRouter(prefix="/qt")
|
||||
|
||||
|
||||
@router.post("/create-package/front-line/{front_line_id}")
|
||||
@router.post(
|
||||
"/create-package/front-line/{front_line_id}",
|
||||
operation_id="open_new_front_line_package_dialog",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
)
|
||||
def new_front_line_package(
|
||||
front_line_id: UUID,
|
||||
game: Game = Depends(GameContext.require),
|
||||
@ -17,7 +21,11 @@ def new_front_line_package(
|
||||
qt.create_new_package(game.db.front_lines.get(front_line_id))
|
||||
|
||||
|
||||
@router.post("/create-package/tgo/{tgo_id}")
|
||||
@router.post(
|
||||
"/create-package/tgo/{tgo_id}",
|
||||
operation_id="open_new_tgo_package_dialog",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
)
|
||||
def new_tgo_package(
|
||||
tgo_id: UUID,
|
||||
game: Game = Depends(GameContext.require),
|
||||
@ -26,7 +34,11 @@ def new_tgo_package(
|
||||
qt.create_new_package(game.db.tgos.get(tgo_id))
|
||||
|
||||
|
||||
@router.post("/info/tgo/{tgo_id}")
|
||||
@router.post(
|
||||
"/info/tgo/{tgo_id}",
|
||||
operation_id="open_tgo_info_dialog",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
)
|
||||
def show_tgo_info(
|
||||
tgo_id: UUID,
|
||||
game: Game = Depends(GameContext.require),
|
||||
@ -35,7 +47,11 @@ def show_tgo_info(
|
||||
qt.show_tgo_info(game.db.tgos.get(tgo_id))
|
||||
|
||||
|
||||
@router.post("/create-package/control-point/{cp_id}")
|
||||
@router.post(
|
||||
"/create-package/control-point/{cp_id}",
|
||||
operation_id="open_new_control_point_package_dialog",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
)
|
||||
def new_cp_package(
|
||||
cp_id: int,
|
||||
game: Game = Depends(GameContext.require),
|
||||
@ -50,7 +66,11 @@ def new_cp_package(
|
||||
qt.create_new_package(cp)
|
||||
|
||||
|
||||
@router.post("/info/control-point/{cp_id}")
|
||||
@router.post(
|
||||
"/info/control-point/{cp_id}",
|
||||
operation_id="open_control_point_info_dialog",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
)
|
||||
def show_control_point_info(
|
||||
cp_id: int,
|
||||
game: Game = Depends(GameContext.require),
|
||||
|
||||
@ -68,6 +68,9 @@ class SupplyRouteJs(BaseModel):
|
||||
blue: bool
|
||||
active_transports: list[str]
|
||||
|
||||
class Config:
|
||||
title = "SupplyRoute"
|
||||
|
||||
@staticmethod
|
||||
def for_link(
|
||||
game: Game, a: ControlPoint, b: ControlPoint, points: list[Point], sea: bool
|
||||
|
||||
@ -7,7 +7,7 @@ from ..dependencies import GameContext
|
||||
router: APIRouter = APIRouter(prefix="/supply-routes")
|
||||
|
||||
|
||||
@router.get("/")
|
||||
@router.get("/", operation_id="list_supply_routes", response_model=list[SupplyRouteJs])
|
||||
def list_supply_routes(
|
||||
game: Game = Depends(GameContext.require),
|
||||
) -> list[SupplyRouteJs]:
|
||||
|
||||
@ -25,6 +25,9 @@ class TgoJs(BaseModel):
|
||||
dead: bool # TODO: Event stream
|
||||
sidc: str # TODO: Event stream
|
||||
|
||||
class Config:
|
||||
title = "Tgo"
|
||||
|
||||
@staticmethod
|
||||
def for_tgo(tgo: TheaterGroundObject) -> TgoJs:
|
||||
if not tgo.might_have_aa:
|
||||
|
||||
@ -9,11 +9,11 @@ from ..dependencies import GameContext
|
||||
router: APIRouter = APIRouter(prefix="/tgos")
|
||||
|
||||
|
||||
@router.get("/")
|
||||
@router.get("/", operation_id="list_tgos", response_model=list[TgoJs])
|
||||
def list_tgos(game: Game = Depends(GameContext.require)) -> list[TgoJs]:
|
||||
return TgoJs.all_in_game(game)
|
||||
|
||||
|
||||
@router.get("/{tgo_id}")
|
||||
@router.get("/{tgo_id}", operation_id="get_tgo_by_id", response_model=TgoJs)
|
||||
def get_tgo(tgo_id: UUID, game: Game = Depends(GameContext.require)) -> TgoJs:
|
||||
return TgoJs.for_tgo(game.db.tgos.get(tgo_id))
|
||||
|
||||
@ -34,6 +34,9 @@ class FlightWaypointJs(BaseModel):
|
||||
include_in_path: bool
|
||||
timing: str
|
||||
|
||||
class Config:
|
||||
title = "Waypoint"
|
||||
|
||||
@staticmethod
|
||||
def for_waypoint(
|
||||
waypoint: FlightWaypoint, flight: Flight, waypoint_idx: int
|
||||
|
||||
@ -34,14 +34,22 @@ def waypoints_for_flight(flight: Flight) -> list[FlightWaypointJs]:
|
||||
]
|
||||
|
||||
|
||||
@router.get("/{flight_id}", response_model=list[FlightWaypointJs])
|
||||
@router.get(
|
||||
"/{flight_id}",
|
||||
operation_id="list_all_waypoints_for_flight",
|
||||
response_model=list[FlightWaypointJs],
|
||||
)
|
||||
def all_waypoints_for_flight(
|
||||
flight_id: UUID, game: Game = Depends(GameContext.require)
|
||||
) -> list[FlightWaypointJs]:
|
||||
return waypoints_for_flight(game.db.flights.get(flight_id))
|
||||
|
||||
|
||||
@router.post("/{flight_id}/{waypoint_idx}/position")
|
||||
@router.post(
|
||||
"/{flight_id}/{waypoint_idx}/position",
|
||||
operation_id="set_waypoint_position",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
)
|
||||
def set_position(
|
||||
flight_id: UUID,
|
||||
waypoint_idx: int,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user