-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.py
291 lines (252 loc) Β· 10.5 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# IMPORTS
from datetime import datetime
import json, time
from fastapi import FastAPI, Request, status, Depends, Response
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.middleware import Middleware
from auth import VerifyToken
from threading import Thread # Not used yet
# Templates
from fastapi.templating import Jinja2Templates
from fastapi.staticfiles import StaticFiles
from fastapi.openapi.docs import get_swagger_ui_html
from globals import (
token_auth_scheme,
config,
redis_client,
tags_metadata,
WHITELISTED_IPS,
append_request_log,
append_denied_log,
)
from sql import selectQuery, insertQuery
# Import all the endpoints for each table
from surftimer.ck_latestrecords import router as ck_latestrecords_router
from surftimer.ck_maptier import router as ck_maptier_router
from surftimer.ck_playerrank import router as ck_playerrank_router
from surftimer.ck_playeroptions2 import router as ck_playeroptions2_router
from surftimer.ck_bonus import router as ck_bonus_router
from surftimer.ck_checkpoints import router as ck_checkpoints_router
from surftimer.ck_playertemp import router as ck_playertemp_router
from surftimer.ck_prinfo import router as ck_prinfo_router
from surftimer.ck_replays import router as ck_replays_router
from surftimer.ck_spawnlocations import router as ck_spawnlocations_router
from surftimer.ck_zones import router as ck_zones_router
from surftimer.points import router as points_calculation
from surftimer.refactored import router as refactored_router
class IPValidatorMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
# Get client IP
ip = str(request.client.host)
# Check if IP is allowed
if ip not in WHITELISTED_IPS:
append_denied_log(request)
data = {"message": "Not Allowed", "ip": ip}
return JSONResponse(status_code=status.HTTP_400_BAD_REQUEST, content=data)
append_request_log(request)
# Proceed if IP is allowed
return await call_next(request)
# Swagger UI configuration - https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/
swagger_config = {
"displayOperationId": False, # Show operationId on the UI
"defaultModelsExpandDepth": 1, # The default expansion depth for models (set to -1 completely hide the models)
"defaultModelExpandDepth": 2,
"defaultModelRendering": "example",
"deepLinking": True, # Enables deep linking for tags and operations
"useUnsafeMarkdown": True,
"displayRequestDuration": True,
"filter": True,
"showExtensions": True,
"syntaxHighlight.theme": "arta",
"docExpansion": "none",
"pluginLoadType": "chain",
"tagsSorter": "alpha",
}
app = FastAPI(
title="SurfTimer API",
description="""by [`tslashd`](https://github.com/tslashd)""",
version="0.0.0",
debug=True,
swagger_ui_parameters=swagger_config,
middleware=[Middleware(IPValidatorMiddleware)],
openapi_tags=tags_metadata,
)
# Attach the routes
app.include_router(ck_latestrecords_router)
app.include_router(ck_maptier_router)
app.include_router(ck_playerrank_router)
app.include_router(ck_playeroptions2_router)
app.include_router(ck_bonus_router)
app.include_router(ck_checkpoints_router)
app.include_router(ck_playertemp_router)
app.include_router(ck_prinfo_router)
app.include_router(ck_replays_router)
app.include_router(ck_spawnlocations_router)
app.include_router(ck_zones_router)
app.include_router(points_calculation)
app.include_router(refactored_router)
@app.get("/docs2", include_in_schema=False)
async def custom_swagger_ui_html_cdn():
return get_swagger_ui_html(
openapi_url=app.openapi_url,
title=f"{app.title} - Swagger UI",
# swagger_ui_dark.css CDN link
swagger_css_url="https://cdn.jsdelivr.net/gh/Itz-fork/Fastapi-Swagger-UI-Dark/assets/swagger_ui_dark.min.css",
swagger_ui_parameters=swagger_config,
)
@app.get("/", include_in_schema=False)
async def home():
data = {"message": "Suuuuh duuuud"}
return JSONResponse(status_code=status.HTTP_200_OK, content=data)
# SurfTimer-Mapchooser queries
@app.get(
"/surftimer/mapchooser",
name="Mapchooser & Nominations & RTV",
tags=["Mapchooser"],
include_in_schema=False,
)
async def mapchooser(
request: Request,
type: int,
server_tier: str = None,
tier_min: int = None,
tier_max: int = None,
tier: int = None,
steamid: str = None,
style: int = None,
):
"""All queries for `st-mapchooser, st-rtv, st-nominations` are contained here with types for the different ones.\n
Above mentioned plugins need to be reworked to use API Calls, below is actual improvement from this implementation:\n
```fix
Old (1220 maps):
===== [Nominations] BuildMapMenu took 12.726562s
===== [Nominations] BuildTierMenus took 12.379882s
New (1220 maps):
===== [Nominations] Build ALL menus took 0.015625s
```"""
tic = time.perf_counter()
json_data = []
# Check if data is cached in Redis
cache_key = f"mapchooser:{type}_{tier_min}_{tier_max}_{tier}_{steamid}_{style}"
cached_data = redis_client.get(cache_key)
if cached_data:
# Return cached data
# print(json.loads(cached_data))
print(f"[Redis] Loaded '{cache_key}' ({time.perf_counter() - tic:0.4f}s)")
return JSONResponse(
status_code=status.HTTP_200_OK, content=json.loads(cached_data)
)
# This needs to be dynamic depending on tickrates
db = "surftimer_test"
switch_case = {
# sql_SelectMapList - Mapchooser/Nominations
1: f"""SELECT {db}.ck_zones.mapname, tier as maptier, count({db}.ck_zones.zonetype = 3) as stages, bonus as bonuses
FROM {db}.ck_zones
INNER JOIN {db}.ck_maptier on {db}.ck_zones.mapname = {db}.ck_maptier.mapname
LEFT JOIN (
SELECT mapname as map_2, MAX({db}.ck_zones.zonegroup) as bonus
FROM {db}.ck_zones
GROUP BY mapname
) as a on {db}.ck_zones.mapname = a.map_2
WHERE (zonegroup = 0 AND (zonetype = 1 or zonetype = 5 or zonetype = 3))
GROUP BY mapname, tier, bonus
ORDER BY mapname ASC""",
# sql_SelectMapListRange - Mapchooser/Nominations
2: f"""SELECT {db}.ck_zones.mapname, tier as maptier, count({db}.ck_zones.zonetype = 3) as stages, bonus as bonuses
FROM {db}.ck_zones
INNER JOIN {db}.ck_maptier on {db}.ck_zones.mapname = {db}.ck_maptier.mapname
LEFT JOIN (
SELECT mapname as map_2, MAX({db}.ck_zones.zonegroup) as bonus
FROM {db}.ck_zones
GROUP BY mapname
) as a on {db}.ck_zones.mapname = a.map_2
WHERE (zonegroup = 0 AND (zonetype = 1 or zonetype = 5 or zonetype = 3))
AND tier >= {tier_min} AND tier <= {tier_max}
GROUP BY mapname, tier, bonus
ORDER BY mapname ASC""",
# sql_SelectMapListSpecific - Mapchooser/Nominations
3: f"""SELECT {db}.ck_zones.mapname, tier as maptier, count({db}.ck_zones.zonetype = 3) as stages, bonus as bonuses
FROM {db}.ck_zones
INNER JOIN {db}.ck_maptier on {db}.ck_zones.mapname = {db}.ck_maptier.mapname
LEFT JOIN (
SELECT mapname as map_2, MAX({db}.ck_zones.zonegroup) as bonus
FROM {db}.ck_zones
GROUP BY mapname
) as a on {db}.ck_zones.mapname = a.map_2
WHERE (zonegroup = 0 AND (zonetype = 1 or zonetype = 5 or zonetype = 3))
AND tier = {tier}
GROUP BY mapname, tier, bonus
ORDER BY mapname ASC""",
# sql_SelectIncompleteMapList - Nominations
4: f"""SELECT mapname
FROM {db}.ck_maptier
WHERE tier > 0
AND mapname
NOT IN (
SELECT mapname FROM {db}.ck_playertimes WHERE steamid = '{steamid}' AND style = {style}) ORDER BY tier ASC, mapname ASC""",
# sql_SelectRank - Mapchooser/RockTheVote
5: f"""SELECT COUNT(*) AS playerrank
FROM {db}.ck_playerrank
WHERE style = 0
AND points >= (SELECT points FROM {db}.ck_playerrank WHERE steamid = '{steamid}' AND style = 0)""",
# sql_SelectPoints - Mapchooser/RockTheVote
6: f"""SELECT points AS playerpoints
FROM {db}.ck_playerrank
WHERE steamid = '{steamid}'
AND style = 0""",
}
query = switch_case.get(type)
if query is None:
return "Invalid query number."
if type == 2 and (tier_min is None or tier_max is None):
return "Tier min and max values are required for query 2."
if type == 3 and tier is None:
return "Tier value is required for query 3."
if type == 4 and (steamid is None or style is None):
return "SteamID and Style values are required for query 4."
if type == 5 and (steamid is None):
return "SteamID value is required for this query."
if type == 6 and (steamid is None):
return "SteamID value is required for this query."
xquery = selectQuery(query)
for result in xquery:
json_data.append(result)
print(
f"Q_Type: {type} | T_Min: {tier_min} | T_Max: {tier_max} | Tier: {tier} | SteamID: {steamid} | Style: {style}"
)
# Local storage of data
# filename = 'surftimer/mapchooser.json'
# # create_json_file(json_data, filename)
# with open(filename, 'w') as file:
# json.dump(json_data, file, indent=4, separators=(',', ': '))
toc = time.perf_counter()
print(f"Execution time {toc - tic:0.4f}")
# Cache the data in Redis
redis_client.set(
cache_key,
json.dumps(json_data),
ex=config["REDIS"]["EXPIRY"],
)
return json_data
# new code π
@app.get(
"/api/private",
tags=["Private"],
name="Test Authentication Tokens",
include_in_schema=False,
)
async def private(
response: Response, token: str = Depends(token_auth_scheme)
): # π updated code
"""A valid access token is required to access this route"""
result = VerifyToken(token.credentials).verify() # π updated code
# π new code
if result.get("status"):
response.status_code = status.HTTP_400_BAD_REQUEST
return result
# π new code
dt = datetime.fromtimestamp(int(result["exp"]))
formatted_datetime = dt.strftime("%H:%M:%S %d-%m-%Y")
print("expiry:", formatted_datetime)
return result