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