88
99from core import Plugin , DEFAULT_TAG , Side , DataObjectFactory , utils , Status
1010from datetime import datetime , timedelta , timezone
11- from fastapi import FastAPI , APIRouter , Form
11+ from fastapi import FastAPI , APIRouter , Form , Query
12+ from fastapi .openapi .docs import get_swagger_ui_html , get_redoc_html
13+ from fastapi .openapi .utils import get_openapi
1214from plugins .creditsystem .squadron import Squadron
1315from plugins .userstats .filter import StatisticsFilter , PeriodFilter
1416from psycopg .rows import dict_row
1517from services .bot import DCSServerBot
1618from typing import Optional , Any
1719from uvicorn import Config
1820
21+ from . import __version__
22+
1923app : Optional [FastAPI ] = None
2024
2125
@@ -36,18 +40,52 @@ def __init__(self, bot: DCSServerBot):
3640 self .router = APIRouter ()
3741 self .router .add_api_route (prefix + "/servers" , self .servers , methods = ["GET" ])
3842 self .router .add_api_route (prefix + "/squadrons" , self .squadrons , methods = ["GET" ])
39- self .router .add_api_route (prefix + "/topkills" , self .topkills , methods = ["GET" , "POST" ])
40- self .router .add_api_route (prefix + "/topkdr" , self .topkdr , methods = ["GET" , "POST" ])
41- self .router .add_api_route (prefix + "/trueskill" , self .trueskill , methods = ["GET" , "POST" ])
43+ self .router .add_api_route (prefix + "/topkills" , self .topkills , methods = ["GET" ])
44+ self .router .add_api_route (prefix + "/topkdr" , self .topkdr , methods = ["GET" ,])
45+ self .router .add_api_route (prefix + "/trueskill" , self .trueskill , methods = ["GET" ])
4246 self .router .add_api_route (prefix + "/getuser" , self .getuser , methods = ["POST" ])
4347 self .router .add_api_route (prefix + "/missilepk" , self .missilepk , methods = ["POST" ])
4448 self .router .add_api_route (prefix + "/stats" , self .stats , methods = ["POST" ])
45- self .router .add_api_route (prefix + "/highscore" , self .highscore , methods = ["GET" , "POST" ])
49+ self .router .add_api_route (prefix + "/highscore" , self .highscore , methods = ["GET" ])
4650 self .router .add_api_route (prefix + "/credits" , self .credits , methods = ["POST" ])
4751 self .router .add_api_route (prefix + "/traps" , self .traps , methods = ["POST" ])
4852 self .router .add_api_route (prefix + "/squadron_members" , self .squadron_members , methods = ["POST" ])
4953 self .router .add_api_route (prefix + "/squadron_credits" , self .squadron_credits , methods = ["POST" ])
5054 self .router .add_api_route (prefix + "/linkme" , self .linkme , methods = ["POST" ])
55+
56+ # add debug endpoints
57+ if cfg .get ('debug' , False ):
58+ self .log .warning ("RestAPI: Debug is enabled, you might expose your API functions!" )
59+
60+ # Enable OpenAPI schema
61+ app .add_api_route ("/openapi.json" ,
62+ lambda : get_openapi (
63+ title = "DCSServerBot REST API" ,
64+ version = __version__ ,
65+ description = "REST functions to be used for DCSServerBot." ,
66+ routes = app .routes ,
67+ ),
68+ include_in_schema = False
69+ )
70+
71+ # Enable Swagger UI
72+ app .add_api_route ("/docs" ,
73+ lambda : get_swagger_ui_html (
74+ openapi_url = "/openapi.json" ,
75+ title = "DCSServerBot REST API - Swagger UI" ,
76+ ),
77+ include_in_schema = False
78+ )
79+
80+ # Enable ReDoc
81+ app .add_api_route ("/redoc" ,
82+ lambda : get_redoc_html (
83+ openapi_url = "/openapi.json" ,
84+ title = "DCSServerBot REST API - ReDoc" ,
85+ ),
86+ include_in_schema = False
87+ )
88+
5189 self .app = app
5290 self .config = Config (app = self .app , host = cfg ['listen' ], port = cfg ['port' ], log_level = logging .ERROR ,
5391 use_colors = False )
@@ -130,55 +168,56 @@ async def squadrons(self):
130168 })
131169 return squadrons
132170
133- async def topkills (self , limit : int = Form (default = 10 )):
171+ async def topkills (self , limit : int = Query (default = 10 )):
134172 async with self .apool .connection () as conn :
135173 async with conn .cursor (row_factory = dict_row ) as cursor :
136174 await cursor .execute ("""
137- SELECT p.name AS "fullNickname", SUM(pvp) AS "AAkills", SUM(deaths) AS "deaths",
138- CASE WHEN SUM(deaths) = 0 THEN SUM(pvp) ELSE SUM(pvp)/SUM(deaths::DECIMAL) END AS "AAKDR"
175+ SELECT p.name AS "nick", DATE_TRUNC('second', p.last_seen) AS "date",
176+ SUM(pvp) AS "AAkills", SUM(deaths) AS "deaths",
177+ CASE WHEN SUM(deaths) = 0 THEN SUM(pvp) ELSE SUM(pvp)/SUM(deaths::DECIMAL) END AS "AAKDR"
139178 FROM statistics s, players p
140179 WHERE s.player_ucid = p.ucid
141180 AND hop_on > (now() AT TIME ZONE 'utc') - interval '1 month'
142- GROUP BY 1 ORDER BY 2 DESC LIMIT {limit}
181+ GROUP BY 1, 2 ORDER BY 3 DESC LIMIT {limit}
143182 """ .format (limit = limit if limit else 10 ))
144183 return await cursor .fetchall ()
145184
146- async def topkdr (self , limit : int = Form (default = 10 )):
185+ async def topkdr (self , limit : int = Query (default = 10 )):
147186 async with self .apool .connection () as conn :
148187 async with conn .cursor (row_factory = dict_row ) as cursor :
149188 await cursor .execute ("""
150- SELECT p.name AS "fullNickname", SUM(pvp) AS "AAkills", SUM(deaths) AS "deaths",
151- CASE WHEN SUM(deaths) = 0 THEN SUM(pvp) ELSE SUM(pvp)/SUM(deaths::DECIMAL) END AS "AAKDR"
189+ SELECT p.name AS "nick", DATE_TRUNC('second', p.last_seen) AS "date",
190+ SUM(pvp) AS "AAkills", SUM(deaths) AS "deaths",
191+ CASE WHEN SUM(deaths) = 0 THEN SUM(pvp) ELSE SUM(pvp)/SUM(deaths::DECIMAL) END AS "AAKDR"
152192 FROM statistics s, players p
153193 WHERE s.player_ucid = p.ucid
154194 AND hop_on > (now() AT TIME ZONE 'utc') - interval '1 month'
155- GROUP BY 1 ORDER BY 4 DESC LIMIT {limit}
195+ GROUP BY 1, 2 ORDER BY 5 DESC LIMIT {limit}
156196 """ .format (limit = limit if limit else 10 ))
157197 return await cursor .fetchall ()
158198
159- async def trueskill (self , limit : int = Form (default = 10 )):
199+ async def trueskill (self , limit : int = Query (default = 10 )):
160200 async with self .apool .connection () as conn :
161201 async with conn .cursor (row_factory = dict_row ) as cursor :
162202 await cursor .execute ("""
163- SELECT
164- p.name AS "fullNickname", SUM(pvp) AS "AAkills", SUM(deaths) AS "deaths",
165- t.skill_mu AS "TrueSkill"
203+ SELECT p.name AS "nick", DATE_TRUNC('second', p.last_seen) AS "date",
204+ SUM(pvp) AS "AAkills", SUM(deaths) AS "deaths", t.skill_mu AS "TrueSkill"
166205 FROM statistics s, players p, trueskill t
167206 WHERE s.player_ucid = p.ucid
207+ AND t.player_ucid = p.ucid
168208 AND hop_on > (now() AT TIME ZONE 'utc') - interval '1 month'
169- GROUP BY 1,4 ORDER BY 4 DESC LIMIT {limit}
209+ GROUP BY 1, 2, 5 ORDER BY 5 DESC LIMIT {limit}
170210 """ .format (limit = limit if limit else 10 ))
171211 return await cursor .fetchall ()
172212
173- async def highscore (self , server_name : str = Form (default = None ), period : str = Form (default = 'all' ),
174- limit : int = Form (default = 10 )):
213+ async def highscore (self , server_name : str = Query (default = None ), period : str = Query (default = 'all' ),
214+ limit : int = Query (default = 10 )):
175215 highscore = {}
176216 flt = StatisticsFilter .detect (self .bot , period ) or PeriodFilter (period )
177217 async with self .apool .connection () as conn :
178218 async with conn .cursor (row_factory = dict_row ) as cursor :
179219 sql = """
180- SELECT p.name AS nick,
181- DATE_TRUNC('second', p.last_seen) AS "date",
220+ SELECT p.name AS nick, DATE_TRUNC('second', p.last_seen) AS "date",
182221 ROUND(SUM(EXTRACT(EPOCH FROM(COALESCE(s.hop_off, NOW() AT TIME ZONE 'UTC') - s.hop_on)))) AS playtime
183222 FROM statistics s,
184223 players p,
@@ -209,8 +248,7 @@ async def highscore(self, server_name: str = Form(default=None), period: str = F
209248
210249 for kill_type in sql_parts .keys ():
211250 sql = f"""
212- SELECT p.name AS nick,
213- DATE_TRUNC('second', p.last_seen) AS "date",
251+ SELECT p.name AS nick, DATE_TRUNC('second', p.last_seen) AS "date",
214252 { sql_parts [kill_type ]} AS value
215253 FROM players p, statistics s, missions m
216254 WHERE s.player_ucid = p.ucid AND s.mission_id = m.id
@@ -472,7 +510,7 @@ async def create_token() -> str:
472510async def setup (bot : DCSServerBot ):
473511 global app
474512
475- app = FastAPI (docs_url = None , redoc_url = None )
513+ app = FastAPI (docs_url = None , redoc_url = None , openapi_url = None )
476514 restapi = RestAPI (bot )
477515 await bot .add_cog (restapi )
478516 app .include_router (restapi .router )
0 commit comments