mirror of
https://github.com/wowlikon/LiB.git
synced 2026-02-03 20:31:09 +00:00
191 lines
5.7 KiB
Python
191 lines
5.7 KiB
Python
"""Основной модуль"""
|
|
|
|
import asyncio, sys, traceback
|
|
from contextlib import asynccontextmanager
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from time import perf_counter
|
|
from uuid import uuid4
|
|
|
|
from alembic import command
|
|
from alembic.config import Config
|
|
from fastapi import FastAPI, Depends, Request, Response, status, HTTPException
|
|
from fastapi.staticfiles import StaticFiles
|
|
from fastapi.responses import JSONResponse
|
|
from ollama import Client, ResponseError
|
|
from sqlmodel import Session
|
|
|
|
from library_service.auth import run_seeds
|
|
from library_service.routers import api_router
|
|
from library_service.routers.misc import unknown
|
|
from library_service.services.captcha import limiter, cleanup_task, require_captcha
|
|
from library_service.settings import (
|
|
LOGGING_CONFIG,
|
|
engine,
|
|
get_app,
|
|
get_logger,
|
|
OLLAMA_URL,
|
|
)
|
|
|
|
|
|
SKIP_LOGGING_PATHS = frozenset({"/favicon.ico", "/favicon.svg"})
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(_):
|
|
"""Жизненный цикл сервиса"""
|
|
logger = get_logger()
|
|
logger.info("[+] Initializing database...")
|
|
|
|
try:
|
|
with engine.begin() as connection:
|
|
alembic_cfg = Config("alembic.ini")
|
|
alembic_cfg.attributes["configure_logging"] = False
|
|
alembic_cfg.attributes["connection"] = connection
|
|
command.upgrade(alembic_cfg, "head")
|
|
except Exception as e:
|
|
logger.error(f"[-] Migration failed: {e}")
|
|
raise e
|
|
|
|
logger.info("[+] Running seeds...")
|
|
try:
|
|
with Session(engine) as session:
|
|
run_seeds(session)
|
|
logger.info("[+] Database setup completed.")
|
|
except Exception as e:
|
|
logger.error(f"[-] Seeding failed: {e}")
|
|
|
|
logger.info("[+] Loading ollama models...")
|
|
ollama_client = Client(host=OLLAMA_URL)
|
|
try:
|
|
ollama_client.pull("mxbai-embed-large")
|
|
ollama_client.pull("llama3.2")
|
|
except ResponseError as e:
|
|
logger.error(f"[-] Failed to pull models {e}")
|
|
asyncio.create_task(cleanup_task())
|
|
logger.info("[+] Starting application...")
|
|
yield # Обработка запросов
|
|
logger.info("[+] Application shutdown")
|
|
|
|
|
|
app = get_app(lifespan)
|
|
|
|
|
|
@app.exception_handler(status.HTTP_404_NOT_FOUND)
|
|
async def custom_not_found_handler(request: Request, exc: HTTPException):
|
|
path = request.url.path
|
|
|
|
if path.startswith("/api"):
|
|
return JSONResponse(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
content={"detail": "API endpoint not found", "path": path},
|
|
)
|
|
|
|
return await unknown(request, app)
|
|
|
|
|
|
@app.middleware("http")
|
|
async def catch_exceptions_middleware(request: Request, call_next):
|
|
"""Middleware для подробного json-описания Internal error"""
|
|
try:
|
|
return await call_next(request)
|
|
except Exception as exc:
|
|
exc_type, exc_value, exc_tb = sys.exc_info()
|
|
logger = get_logger()
|
|
logger.exception(exc)
|
|
|
|
return JSONResponse(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
content={
|
|
"message": str(exc),
|
|
"type": exc_type.__name__ if exc_type else "Unknown",
|
|
"path": str(request.url),
|
|
"method": request.method,
|
|
},
|
|
)
|
|
|
|
|
|
@app.middleware("http")
|
|
async def log_requests(request: Request, call_next):
|
|
"""Middleware для логирования HTTP-запросов"""
|
|
path = request.url.path
|
|
if path.startswith("/static") or path in SKIP_LOGGING_PATHS:
|
|
return await call_next(request)
|
|
|
|
logger = get_logger()
|
|
request_id = uuid4().hex[:8]
|
|
timestamp = datetime.now().isoformat()
|
|
method = request.method
|
|
url = str(request.url)
|
|
user_agent = request.headers.get("user-agent", "Unknown")
|
|
client_ip = request.client.host if request.client else None
|
|
|
|
start_time = perf_counter()
|
|
|
|
try:
|
|
logger.debug(
|
|
f"[{request_id}] Starting: {method} {url}",
|
|
extra={"request_id": request_id, "user_agent": user_agent},
|
|
)
|
|
|
|
response: Response = await call_next(request)
|
|
process_time = perf_counter() - start_time
|
|
|
|
logger.info(
|
|
f"[{request_id}] {method} {url} - {response.status_code} - {process_time:.4f}s",
|
|
extra={
|
|
"request_id": request_id,
|
|
"timestamp": timestamp,
|
|
"method": method,
|
|
"url": url,
|
|
"status": response.status_code,
|
|
"process_time": process_time,
|
|
"client_ip": client_ip,
|
|
"user_agent": user_agent,
|
|
},
|
|
)
|
|
return response
|
|
|
|
except Exception as e:
|
|
process_time = perf_counter() - start_time
|
|
logger.error(
|
|
f"[{request_id}] {method} {url} - Error: {e} - {process_time:.4f}s",
|
|
extra={
|
|
"request_id": request_id,
|
|
"timestamp": timestamp,
|
|
"method": method,
|
|
"url": url,
|
|
"error": str(e),
|
|
"process_time": process_time,
|
|
"client_ip": client_ip,
|
|
"user_agent": user_agent,
|
|
},
|
|
exc_info=True,
|
|
)
|
|
return Response(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
content="Internal Server Error",
|
|
)
|
|
|
|
|
|
# Подключение маршрутов
|
|
app.include_router(api_router)
|
|
app.mount(
|
|
"/static",
|
|
StaticFiles(directory=Path(__file__).parent / "static"),
|
|
name="static",
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
|
|
uvicorn.run(
|
|
"library_service.main:app",
|
|
host="0.0.0.0",
|
|
port=8000,
|
|
forwarded_allow_ips="*",
|
|
log_config=LOGGING_CONFIG,
|
|
access_log=False,
|
|
)
|