gitscope
← marzneshin / v0.6.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)
 26from app.templates import render_template
 27from . import __version__
 28from .routes import api_router
 29from .tasks import (
 30    nodes_startup,
 31    record_user_usages,
 32    reset_user_data_usage,
 33    review_users,
 34    expire_days_reached,
 35)
 36from .webhooks import webhooks_router
 37
 38logger = logging.getLogger(__name__)
 39
 40
 41@asynccontextmanager
 42async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
 43    await nodes_startup()
 44    yield
 45    scheduler.shutdown()
 46
 47
 48app = FastAPI(
 49    title="MarzneshinAPI",
 50    description="Unified GUI Censorship Resistant Solution Powered by Xray",
 51    version=__version__,
 52    lifespan=lifespan,
 53    docs_url="/docs" if DOCS else None,
 54    redoc_url="/redoc" if DOCS else None,
 55)
 56
 57app.webhooks.include_router(webhooks_router)
 58
 59app.include_router(api_router)
 60add_pagination(app)
 61
 62
 63@app.get("/", response_class=HTMLResponse)
 64def home_page():
 65    return render_template(HOME_PAGE_TEMPLATE)
 66
 67
 68app.add_middleware(
 69    CORSMiddleware,
 70    allow_origins=["*"],
 71    allow_credentials=True,
 72    allow_methods=["*"],
 73    allow_headers=["*"],
 74)
 75
 76scheduler = AsyncIOScheduler(timezone="UTC")
 77scheduler.add_job(record_user_usages, "interval", coalesce=True, seconds=30)
 78scheduler.add_job(
 79    review_users, "interval", seconds=30, coalesce=True, max_instances=1
 80)
 81scheduler.add_job(
 82    expire_days_reached, "interval", seconds=30, coalesce=True, max_instances=1
 83)
 84scheduler.add_job(reset_user_data_usage, "interval", coalesce=True, hours=1)
 85
 86
 87@app.exception_handler(RequestValidationError)
 88def validation_exception_handler(
 89    request: Request, exc: RequestValidationError
 90):
 91    details = {}
 92    for error in exc.errors():
 93        details[error["loc"][-1]] = error.get("msg")
 94    return JSONResponse(
 95        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
 96        content=jsonable_encoder({"detail": details}),
 97    )
 98
 99
100async def main():
101    if not DEBUG:
102        app.mount(
103            DASHBOARD_PATH,
104            StaticFiles(directory="dashboard/dist", html=True),
105            name="dashboard",
106        )
107        app.mount(
108            "/static/",
109            StaticFiles(directory="dashboard/dist/static"),
110            name="static",
111        )
112        app.mount(
113            "/locales/",
114            StaticFiles(directory="dashboard/dist/locales"),
115            name="locales",
116        )
117    scheduler.start()
118    cfg = Config(
119        app=app,
120        host=UVICORN_HOST,
121        port=UVICORN_PORT,
122        uds=(None if DEBUG else UVICORN_UDS),
123        ssl_certfile=UVICORN_SSL_CERTFILE,
124        ssl_keyfile=UVICORN_SSL_KEYFILE,
125        workers=1,
126        reload=DEBUG,
127        log_level=logging.DEBUG if DEBUG else logging.INFO,
128    )
129    server = Server(cfg)
130    await server.serve()