gitscope
← marzneshin / v0.7.3 / app/marzneshin.py
https://dev.kronoz.win/r/marzneshin.git
app/marzneshin.py · Python
Blame Raw
  1import logging
  2from contextlib import asynccontextmanager
  3from typing import AsyncGenerator
  4
  5from apscheduler.schedulers.asyncio import AsyncIOScheduler
  6from fastapi import FastAPI, Request, status
  7from fastapi.encoders import jsonable_encoder
  8from fastapi.exceptions import RequestValidationError
  9from fastapi.middleware.cors import CORSMiddleware
 10from fastapi.responses import HTMLResponse, JSONResponse
 11from fastapi_pagination import add_pagination
 12from starlette.staticfiles import StaticFiles
 13from uvicorn import Config, Server
 14
 15from app.config.env import (
 16    DEBUG,
 17    DOCS,
 18    HOME_PAGE_TEMPLATE,
 19    UVICORN_HOST,
 20    UVICORN_PORT,
 21    UVICORN_SSL_CERTFILE,
 22    UVICORN_SSL_KEYFILE,
 23    UVICORN_UDS,
 24    DASHBOARD_PATH,
 25    TASKS_RECORD_USER_USAGES_INTERVAL,
 26    TASKS_REVIEW_USERS_INTERVAL,
 27    TASKS_EXPIRE_DAYS_REACHED_INTERVAL,
 28    TASKS_RESET_USER_DATA_USAGE,
 29)
 30from app.templates import render_template
 31from . import __version__
 32from .routes import api_router
 33from .tasks import (
 34    nodes_startup,
 35    record_user_usages,
 36    reset_user_data_usage,
 37    review_users,
 38    expire_days_reached,
 39)
 40from .webhooks import webhooks_router
 41
 42logger = logging.getLogger(__name__)
 43
 44
 45@asynccontextmanager
 46async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
 47    await nodes_startup()
 48    yield
 49    scheduler.shutdown()
 50
 51
 52app = FastAPI(
 53    title="MarzneshinAPI",
 54    description="Unified GUI Censorship Resistant Solution Powered by Xray",
 55    version=__version__,
 56    lifespan=lifespan,
 57    docs_url="/docs" if DOCS else None,
 58    redoc_url="/redoc" if DOCS else None,
 59)
 60
 61app.webhooks.include_router(webhooks_router)
 62
 63app.include_router(api_router)
 64add_pagination(app)
 65
 66
 67@app.get("/", response_class=HTMLResponse)
 68def home_page():
 69    return render_template(HOME_PAGE_TEMPLATE)
 70
 71
 72app.add_middleware(
 73    CORSMiddleware,
 74    allow_origins=["*"],
 75    allow_credentials=True,
 76    allow_methods=["*"],
 77    allow_headers=["*"],
 78)
 79
 80scheduler = AsyncIOScheduler(timezone="UTC")
 81scheduler.add_job(
 82    record_user_usages,
 83    "interval",
 84    coalesce=True,
 85    seconds=TASKS_RECORD_USER_USAGES_INTERVAL,
 86)
 87scheduler.add_job(
 88    review_users,
 89    "interval",
 90    seconds=TASKS_REVIEW_USERS_INTERVAL,
 91    coalesce=True,
 92    max_instances=1,
 93)
 94scheduler.add_job(
 95    expire_days_reached,
 96    "interval",
 97    seconds=TASKS_EXPIRE_DAYS_REACHED_INTERVAL,
 98    coalesce=True,
 99    max_instances=1,
100)
101scheduler.add_job(
102    reset_user_data_usage,
103    "interval",
104    seconds=TASKS_RESET_USER_DATA_USAGE,
105    coalesce=True,
106)
107
108
109@app.exception_handler(RequestValidationError)
110def validation_exception_handler(
111    request: Request, exc: RequestValidationError
112):
113    details = {}
114    for error in exc.errors():
115        details[error["loc"][-1]] = error.get("msg")
116    return JSONResponse(
117        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
118        content=jsonable_encoder({"detail": details}),
119    )
120
121
122async def main():
123    if not DEBUG:
124        app.mount(
125            DASHBOARD_PATH,
126            StaticFiles(directory="dashboard/dist", html=True),
127            name="dashboard",
128        )
129        app.mount(
130            "/static/",
131            StaticFiles(directory="dashboard/dist/static"),
132            name="static",
133        )
134        app.mount(
135            "/locales/",
136            StaticFiles(directory="dashboard/dist/locales"),
137            name="locales",
138        )
139    scheduler.start()
140    cfg = Config(
141        app=app,
142        host=UVICORN_HOST,
143        port=UVICORN_PORT,
144        uds=(None if DEBUG else UVICORN_UDS),
145        ssl_certfile=UVICORN_SSL_CERTFILE,
146        ssl_keyfile=UVICORN_SSL_KEYFILE,
147        workers=1,
148        reload=DEBUG,
149        log_level=logging.DEBUG if DEBUG else logging.INFO,
150    )
151    server = Server(cfg)
152    await server.serve()