DKB_ARMKA/api/src/app.py

232 lines
7.1 KiB
Python

import os
import smbclient
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from datetime import datetime, timezone
from src.core.PDFWorker.PDFTempPoller import PDFPollWorker
from .core.config import get_settings
# from .api.router import files_router
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from src.core.nats_callback import build_file_handler
from fastapi.exceptions import HTTPException, StarletteHTTPException
from src.api.router import le_router, files_router, cg_router, rf_office_router, osv_router, fsl_router
from miniopy_async import Minio
from src.core.config import settings
from src.core.clients import S3Storage, PollWorker
from src.core.async_cache import AsyncInMemoryCache
# from src.core.logger import setup_logger
from nats.aio.client import Client as NATS
import base64
import nkeys
from loguru import logger
import os
from fastapi.staticfiles import StaticFiles
from starlette.middleware.gzip import GZipMiddleware
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
from src.core.exception_handlers import register_exception_handlers
settings = get_settings()
VERSION = "1.0.0"
UP_TIME = datetime.now(timezone.utc)
async def sub_cb(msg):
return None
protected_middleware_available = False
if not settings.DEBUG:
try:
from raisa_fastapi_protected_api import ( # pyright: ignore[reportMissingImports]
AuthorizationMiddleware,
OpenEndpoint,
ProtectedOAuthSettings,
ProtectedRolesSettings,
ProtectedSettings,
SearchType,
)
protected_middleware_available = True
except ImportError:
protected_middleware_available = False
def _normalize_root_path(value: str) -> str:
if not value:
return ""
value = value.strip()
if value == "/":
return ""
return "/" + value.strip("/")
_APP_ROOT_PATH = _normalize_root_path(settings.ROOT_PATH)
@asynccontextmanager
async def lifespan(app):
app.state.nats_subs = []
app.state.storage = None
app.state.nats = None
app.state.poller = None
app.state.pdf_poller = None
s3_client = None
smb_registered = False
try:
minio = settings.OPENBAO.MINIO
if minio is not None:
s3_client = Minio(
minio.MINIO_URL,
access_key=minio.MINIO_ADMIN_LOGIN,
secret_key=minio.MINIO_ADMIN_PASSWORD,
secure=minio.MINIO_SECURE,
)
if not await s3_client.bucket_exists(minio.MINIO_BUCKET):
raise RuntimeError(f"Бакет MinIO {minio.MINIO_BUCKET!r} не создан")
app.state.storage = S3Storage(s3_client, minio.MINIO_BUCKET)
logger.info("MinIO подключён, бакет: {}", minio.MINIO_BUCKET)
smb = settings.OPENBAO.SMB
if smb is not None:
smbclient.register_session(smb.SMB_HOST, smb.SMB_LOGIN, smb.SMB_PASSWORD,
port=smb.SMB_PORT)
smb_registered = True
logger.info("SMB подключён")
nats_settings = settings.OPENBAO.NATS
if nats_settings is not None:
nc = NATS()
app.state.nats = nc
if not settings.DEBUG:
keypair = nkeys.from_seed(nats_settings.NATS_SEED.encode("utf-8"))
def user_jwt_cb():
return nats_settings.NATS_JWT.encode("utf-8")
def signature_cb(nonce):
return base64.b64encode(keypair.sign(nonce.encode("utf-8")))
await nc.connect(nats_settings.NATS_URL, user_jwt_cb=user_jwt_cb,
signature_cb=signature_cb, max_reconnect_attempts=2)
logger.info("NATS подключён")
missing = [name for name in ("MINIO", "NATS", "SMB")
if getattr(settings.OPENBAO, name) is None]
if missing:
logger.info("Распознавание файлов отключено: не настроены {}", ", ".join(missing))
else:
cache = AsyncInMemoryCache()
app.state.poller = PollWorker(nc, cache, app.state.storage)
await app.state.poller.start()
app.state.pdf_poller = PDFPollWorker(cache, app.state.storage)
await app.state.pdf_poller.start()
logger.info("Приложение запущено")
yield
finally:
for worker in (app.state.poller, app.state.pdf_poller):
if worker is not None:
await worker.stop()
if not settings.DEBUG:
for sub in app.state.nats_subs:
await sub.unsubscribe()
if app.state.nats is not None:
await app.state.nats.close()
if s3_client is not None:
await s3_client.close_session()
if smb_registered:
smbclient.reset_connection_cache()
logger.info("Приложение закрывается")
app = FastAPI(
title="Fast API",
version=VERSION,
root_path=settings.ROOT_PATH,
lifespan=lifespan
)
@app.get(
"/healthcheck",
status_code=200
)
async def healthcheck():
return {
"uptime": UP_TIME,
"version": VERSION
}
app.include_router(le_router)
app.include_router(files_router)
app.include_router(cg_router)
app.include_router(rf_office_router)
app.include_router(osv_router)
app.include_router(fsl_router)
register_exception_handlers(app)
app.add_middleware(GZipMiddleware, minimum_size=1000, compresslevel=1)
app.add_middleware(ProxyHeadersMiddleware, trusted_hosts=["*"])
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
#Создание каталога для web приложения
os.makedirs(os.path.join('web'), exist_ok=True)
#Подключаем все ендпоинты и роуты до маунта статических файлов
# app.include_router(files_router)
#Подключение собранного web приложения
def sep_static(path, stat_dir):
sep_path = path.split("/")
index = sep_path.index(stat_dir)
return "/".join(sep_path[index:])
class SPAStaticFiles(StaticFiles):
async def get_response(self, path: str, scope):
try:
return await super().get_response(path, scope)
except (HTTPException, StarletteHTTPException) as ex:
if ex.status_code == 404:
if "images" in path:
return await super().get_response(sep_static(path, "images"), scope)
if "static" in path:
return await super().get_response(sep_static(path, "static"), scope)
return await super().get_response("index.html", scope)
else:
raise ex
app.mount("/", SPAStaticFiles(directory="web", html=True), name="web")
app.mount("/", StaticFiles(directory="web", html=True), name="web")