gitscope
← marzneshin / v0.3.0-alpha3 / app/marzneshin.py
https://dev.kronoz.win/r/marzneshin.git
app/marzneshin.py · Python
Blame Raw
  1import asyncio
  2import logging
  3from datetime import datetime as dt
  4from datetime import timedelta as td
  5
  6from apscheduler.schedulers.asyncio import AsyncIOScheduler
  7from fastapi import FastAPI, Request, status
  8from fastapi.encoders import jsonable_encoder
  9from fastapi.exceptions import RequestValidationError
 10from fastapi.middleware.cors import CORSMiddleware
 11from fastapi.responses import HTMLResponse, JSONResponse
 12from fastapi_pagination import add_pagination
 13from starlette.staticfiles import StaticFiles
 14from uvicorn import Config, Server
 15
 16import config
 17from app.templates import render_template
 18from config 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)
 28
 29from . import __version__, telegram
 30from .routes import api_router
 31from .tasks import (
 32    delete_expired_reminders,
 33    nodes_startup,
 34    record_realtime_bandwidth,
 35    record_user_usages,
 36    reset_user_data_usage,
 37    review_users,
 38    send_notifications,
 39)
 40
 41logger = logging.getLogger(__name__)
 42dashboard_path = "/dashboard/"
 43
 44app = FastAPI(
 45    title="MarzneshinAPI",
 46    description="Unified GUI Censorship Resistant Solution Powered by Xray",
 47    version=__version__,
 48    docs_url="/docs" if DOCS else None,
 49    redoc_url="/redoc" if DOCS else None,
 50)
 51
 52app.include_router(api_router)
 53add_pagination(app)
 54
 55
 56@app.get("/", response_class=HTMLResponse)
 57def home_page():
 58    return render_template(HOME_PAGE_TEMPLATE)
 59
 60
 61app.add_middleware(
 62    CORSMiddleware,
 63    allow_origins=["*"],
 64    allow_credentials=True,
 65    allow_methods=["*"],
 66    allow_headers=["*"],
 67)
 68
 69scheduler = AsyncIOScheduler(timezone="UTC")
 70scheduler.add_job(record_user_usages, "interval", coalesce=True, seconds=30)
 71scheduler.add_job(review_users, "interval", seconds=600, coalesce=True, max_instances=1)
 72scheduler.add_job(reset_user_data_usage, "interval", coalesce=True, hours=1)
 73scheduler.add_job(
 74    record_realtime_bandwidth, "interval", seconds=1, coalesce=True, max_instances=1
 75)
 76
 77if config.WEBHOOK_ADDRESS:
 78    scheduler.add_job(send_notifications, "interval", seconds=30, replace_existing=True)
 79    scheduler.add_job(
 80        delete_expired_reminders,
 81        "interval",
 82        hours=2,
 83        start_date=dt.utcnow() + td(minutes=1),
 84    )
 85
 86
 87@app.on_event("startup")
 88async def on_start():
 89    await nodes_startup()
 90
 91
 92@app.on_event("shutdown")
 93async def on_shutdown():
 94    scheduler.shutdown()
 95    logger.info("Sending pending notifications before shutdown...")
 96    await send_notifications()
 97
 98
 99@app.exception_handler(RequestValidationError)
100def validation_exception_handler(request: Request, exc: RequestValidationError):
101    details = {}
102    for error in exc.errors():
103        details[error["loc"][-1]] = error.get("msg")
104    return JSONResponse(
105        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
106        content=jsonable_encoder({"detail": details}),
107    )
108
109
110async def main():
111    if not DEBUG:
112        app.mount(
113            "/dashboard/",
114            StaticFiles(directory="dashboard/dist", html=True),
115            name="dashboard",
116        )
117    scheduler.start()
118    asyncio.create_task(telegram.start_bot())
119    cfg = Config(
120        app=app,
121        host=UVICORN_HOST,
122        port=UVICORN_PORT,
123        uds=(None if DEBUG else UVICORN_UDS),
124        ssl_certfile=UVICORN_SSL_CERTFILE,
125        ssl_keyfile=UVICORN_SSL_KEYFILE,
126        workers=1,
127        reload=DEBUG,
128        log_level=logging.DEBUG if DEBUG else logging.INFO,
129    )
130    server = Server(cfg)
131    await server.serve()