Files
api/base.py

180 lines
4.9 KiB
Python
Raw Normal View History

2024-08-10 22:49:00 -04:00
import importlib
2025-01-24 19:26:07 -05:00
import sys
2025-01-24 19:26:07 -05:00
sys.path.insert(0, ".")
2024-08-10 22:49:00 -04:00
import logging
2024-10-02 20:54:34 -04:00
import asyncio
2025-12-18 07:27:37 -05:00
from contextlib import asynccontextmanager
2024-08-11 13:55:11 -04:00
from typing import Any
from fastapi import FastAPI, Request
2024-08-10 22:49:00 -04:00
from fastapi.middleware.cors import CORSMiddleware
2025-10-03 11:34:48 -04:00
from scalar_fastapi import get_scalar_api_reference
from lyric_search.sources import redis_cache
2024-10-02 20:54:34 -04:00
2025-08-11 15:06:58 -04:00
logging.basicConfig(level=logging.INFO)
2025-08-15 13:31:15 -04:00
logging.getLogger("aiosqlite").setLevel(logging.WARNING)
2025-08-11 15:06:58 -04:00
logging.getLogger("httpx").setLevel(logging.WARNING)
2025-12-18 07:27:37 -05:00
logging.getLogger("python_multipart.multipart").setLevel(logging.WARNING)
logging.getLogger("streamrip").setLevel(logging.WARNING)
logging.getLogger("utils.sr_wrapper").setLevel(logging.WARNING)
2024-08-10 22:49:00 -04:00
logger = logging.getLogger()
2025-04-26 19:47:12 -04:00
2024-10-02 20:54:34 -04:00
loop = asyncio.get_event_loop()
2025-12-18 07:27:37 -05:00
# Pre-import endpoint modules so we can wire up lifespan
constants = importlib.import_module("constants").Constants()
# Will be set after app creation
_routes: dict = {}
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Lifespan context manager for startup/shutdown events."""
# Startup
uvicorn_access_logger = logging.getLogger("uvicorn.access")
uvicorn_access_logger.disabled = True
# Start Radio playlists
if "radio" in _routes and hasattr(_routes["radio"], "on_start"):
await _routes["radio"].on_start()
# Start endpoint background tasks
if "trip" in _routes and hasattr(_routes["trip"], "startup"):
await _routes["trip"].startup()
if "lighting" in _routes and hasattr(_routes["lighting"], "startup"):
await _routes["lighting"].startup()
logger.info("Application startup complete")
yield
# Shutdown
if "lighting" in _routes and hasattr(_routes["lighting"], "shutdown"):
await _routes["lighting"].shutdown()
if "trip" in _routes and hasattr(_routes["trip"], "shutdown"):
await _routes["trip"].shutdown()
logger.info("Application shutdown complete")
app = FastAPI(
title="codey.lol API",
version="1.0",
contact={"name": "codey"},
redirect_slashes=False,
loop=loop,
2025-12-18 07:27:37 -05:00
docs_url=None, # Disabled - using Scalar at /docs instead
redoc_url="/redoc",
lifespan=lifespan,
)
2024-10-02 20:54:34 -04:00
2024-08-14 22:43:20 -04:00
util = importlib.import_module("util").Utilities(app, constants)
2025-07-01 11:38:38 -04:00
origins = [
"https://codey.lol",
"https://old.codey.lol",
"https://api.codey.lol",
"https://status.boatson.boats",
2025-07-01 11:38:38 -04:00
"https://_new.codey.lol",
"http://localhost:4321",
"https://local.codey.lol:4321",
2025-07-01 11:38:38 -04:00
]
2024-08-10 22:49:00 -04:00
app.add_middleware(
CORSMiddleware, # type: ignore
allow_origins=origins,
allow_credentials=True,
2025-08-09 07:48:07 -04:00
allow_methods=["POST", "GET", "HEAD", "OPTIONS"],
allow_headers=["*"],
) # type: ignore
2024-08-10 22:49:00 -04:00
2025-10-07 12:07:45 -04:00
2025-12-18 07:27:37 -05:00
# Scalar API documentation at /docs (replaces default Swagger UI)
@app.get("/docs", include_in_schema=False)
2025-10-03 11:34:48 -04:00
def scalar_docs():
2025-10-07 12:07:45 -04:00
return get_scalar_api_reference(openapi_url="/openapi.json", title="codey.lol API")
2025-08-09 07:48:07 -04:00
2024-08-10 22:49:00 -04:00
"""
Blacklisted routes
"""
2025-01-29 16:03:33 -05:00
@app.get("/", include_in_schema=False)
2024-08-10 22:49:00 -04:00
def disallow_get():
return util.get_blocked_response()
2025-01-29 16:03:33 -05:00
@app.head("/", include_in_schema=False)
2025-01-23 13:02:03 -05:00
def base_head():
return
2025-01-29 16:03:33 -05:00
@app.get("/{path}", include_in_schema=False)
2025-02-16 08:50:53 -05:00
def disallow_get_any(request: Request, var: Any = None):
path = request.path_params["path"]
2025-12-18 07:27:37 -05:00
allowed_paths = ["widget", "misc/no", "docs", "redoc", "openapi.json"]
2025-10-07 12:07:45 -04:00
logging.info(
f"Checking path: {path}, allowed: {path in allowed_paths or path.split('/', maxsplit=1)[0] in allowed_paths}"
)
2025-05-01 15:54:27 -04:00
if not (
isinstance(path, str)
2025-10-03 11:34:48 -04:00
and (path.split("/", maxsplit=1)[0] in allowed_paths or path in allowed_paths)
2025-05-01 15:54:27 -04:00
):
2025-10-03 11:34:48 -04:00
logging.error(f"BLOCKED path: {path}")
return util.get_blocked_response()
else:
logging.info("OK, %s", path)
2024-08-10 22:49:00 -04:00
2025-01-29 16:03:33 -05:00
@app.post("/", include_in_schema=False)
2024-08-10 22:49:00 -04:00
def disallow_base_post():
return util.get_blocked_response()
2024-08-10 22:49:00 -04:00
"""
End Blacklisted Routes
"""
"""
Actionable Routes
"""
2025-02-05 20:23:06 -05:00
2025-12-18 07:27:37 -05:00
_routes.update({
"randmsg": importlib.import_module("endpoints.rand_msg").RandMsg(
app, util, constants
),
"lyrics": importlib.import_module("endpoints.lyric_search").LyricSearch(
app, util, constants
),
"yt": importlib.import_module("endpoints.yt").YT(app, util, constants),
2025-04-26 19:47:12 -04:00
"radio": importlib.import_module("endpoints.radio").Radio(
app, util, constants, loop
),
2025-05-17 08:07:38 -04:00
"meme": importlib.import_module("endpoints.meme").Meme(app, util, constants),
2025-08-09 07:48:07 -04:00
"trip": importlib.import_module("endpoints.rip").RIP(app, util, constants),
"auth": importlib.import_module("endpoints.auth").Auth(app),
2025-10-02 10:45:30 -04:00
"lighting": importlib.import_module("endpoints.lighting").Lighting(
app, util, constants
),
2025-12-18 07:27:37 -05:00
})
2025-02-18 13:59:17 -05:00
# Misc endpoint depends on radio endpoint instance
2025-12-18 07:27:37 -05:00
radio_endpoint = _routes.get("radio")
2025-02-18 13:59:17 -05:00
if radio_endpoint:
2025-12-18 07:27:37 -05:00
_routes["misc"] = importlib.import_module("endpoints.misc").Misc(
app, util, constants, radio_endpoint
)
2024-08-13 19:21:48 -04:00
2025-01-19 07:01:07 -05:00
"""
End Actionable Routes
"""
2024-08-10 22:49:00 -04:00
"""
2025-01-19 07:01:07 -05:00
Startup
"""
2025-03-19 11:18:44 -04:00
2025-02-14 16:07:24 -05:00
redis = redis_cache.RedisCache()
loop.create_task(redis.create_index())