gitscope
← marzneshin / v0.6.1 / app/marzneshin.py
https://dev.kronoz.win/r/marzneshin.git
app/marzneshin.py · Python
Blame Raw
  1import asyncio
  2import logging
  3from contextlib import asynccontextmanager
  4from datetime import datetime as dt
  5from datetime import timedelta as td
  6from typing import AsyncGenerator
  7
  8from apscheduler.schedulers.asyncio import AsyncIOScheduler
  9from fastapi import FastAPI, Request, status
 10from fastapi.encoders import jsonable_encoder
 11from fastapi.exceptions import RequestValidationError
 12from fastapi.middleware.cors import CORSMiddleware
 13from fastapi.responses import HTMLResponse, JSONResponse
 14from fastapi_pagination import add_pagination
 15from starlette.staticfiles import StaticFiles
 16from uvicorn import Config, Server
 17
 18from app.config.env import (
 19    DEBUG,
 20    DOCS,
 21    HOME_PAGE_TEMPLATE,
 22    UVICORN_HOST,
 23    UVICORN_PORT,
 24    UVICORN_SSL_CERTFILE,
 25    UVICORN_SSL_KEYFILE,
 26    UVICORN_UDS,
 27    DASHBOARD_PATH,
 28    WEBHOOK_ADDRESS,
 29)
 30from app.templates import render_template
 31from . import __version__, telegram
 32from .routes import api_router
 33from .tasks import (
 34    delete_expired_reminders,
 35    nodes_startup,
 36    record_user_usages,
 37    reset_user_data_usage,
 38    review_users,
 39    send_notifications,
 40)
 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    logger.info("Sending pending notifications before shutdown...")
 51    await send_notifications()
 52
 53
 54app = FastAPI(
 55    title="MarzneshinAPI",
 56    description="Unified GUI Censorship Resistant Solution Powered by Xray",
 57    version=__version__,
 58    lifespan=lifespan,
 59    docs_url="/docs" if DOCS else None,
 60    redoc_url="/redoc" if DOCS else None,
 61)
 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(record_user_usages, "interval", coalesce=True, seconds=30)
 82scheduler.add_job(
 83    review_users, "interval", seconds=30, coalesce=True, max_instances=1
 84)
 85scheduler.add_job(reset_user_data_usage, "interval", coalesce=True, hours=1)
 86
 87if WEBHOOK_ADDRESS:
 88    scheduler.add_job(
 89        send_notifications, "interval", seconds=30, replace_existing=True
 90    )
 91    scheduler.add_job(
 92        delete_expired_reminders,
 93        "interval",
 94        hours=2,
 95        start_date=dt.utcnow() + td(minutes=1),
 96    )
 97
 98
 99@app.exception_handler(RequestValidationError)
100def validation_exception_handler(
101    request: Request, exc: RequestValidationError
102):
103    details = {}
104    for error in exc.errors():
105        details[error["loc"][-1]] = error.get("msg")
106    return JSONResponse(
107        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
108        content=jsonable_encoder({"detail": details}),
109    )
110
111
112async def main():
113    if not DEBUG:
114        app.mount(
115            DASHBOARD_PATH,
116            StaticFiles(directory="dashboard/dist", html=True),
117            name="dashboard",
118        )
119        app.mount(
120            "/static/",
121            StaticFiles(directory="dashboard/dist/static"),
122            name="static",
123        )
124        app.mount(
125            "/locales/",
126            StaticFiles(directory="dashboard/dist/locales"),
127            name="locales",
128        )
129    scheduler.start()
130    asyncio.create_task(telegram.start_bot())
131    cfg = Config(
132        app=app,
133        host=UVICORN_HOST,
134        port=UVICORN_PORT,
135        uds=(None if DEBUG else UVICORN_UDS),
136        ssl_certfile=UVICORN_SSL_CERTFILE,
137        ssl_keyfile=UVICORN_SSL_KEYFILE,
138        workers=1,
139        reload=DEBUG,
140        log_level=logging.DEBUG if DEBUG else logging.INFO,
141    )
142    server = Server(cfg)
143    await server.serve()