Add services endpoints

This commit is contained in:
Raykov-MS 2026-05-14 17:36:35 +03:00
parent e267d2d631
commit 2a3827ff91
3 changed files with 75 additions and 2 deletions

25
api/src/api/v1/admin.py Normal file
View File

@ -0,0 +1,25 @@
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import text
from sqlalchemy.exc import SQLAlchemyError
from src.api.v1.deps import require_admin
from src.db.base import engine
from src.domain.models import Users
router = APIRouter(prefix="/admin", tags=["admin"])
@router.post("/refresh-tree")
async def refresh_tree(current_user: Users = Depends(require_admin)):
"""Обновляет MV дерева статей расходов."""
try:
async with engine.connect() as conn:
# CONCURRENTLY требует autocommit-режим.
conn = await conn.execution_options(isolation_level="AUTOCOMMIT")
await conn.execute(text("REFRESH MATERIALIZED VIEW CONCURRENTLY v3.mv_expense_item_tree"))
except SQLAlchemyError as exc:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail=f"Не удалось обновить mv_expense_item_tree: {exc}",
) from exc
return {"status": "ok", "message": "mv_expense_item_tree refreshed"}

View File

@ -1,7 +1,8 @@
from fastapi import APIRouter from fastapi import APIRouter
from src.api.v1 import auth, users from src.api.v1 import auth, users, admin
api_router = APIRouter() api_router = APIRouter()
api_router.include_router(auth.router) api_router.include_router(auth.router)
api_router.include_router(users.router) api_router.include_router(users.router)
api_router.include_router(admin.router)

View File

@ -2,8 +2,10 @@ import os
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from datetime import datetime, timezone from datetime import datetime, timezone
from fastapi import FastAPI from fastapi import FastAPI, Response
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy import text
from sqlalchemy.exc import SQLAlchemyError
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from starlette.middleware.gzip import GZipMiddleware from starlette.middleware.gzip import GZipMiddleware
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
@ -69,6 +71,41 @@ async def healthcheck2():
"port": settings.PORT, "port": settings.PORT,
} }
@app.get("/healthz", status_code=200)
async def healthz():
return {"status": "ok", "uptime": UP_TIME, "version": VERSION}
@app.get("/readyz")
async def readyz(response: Response):
result = {"status": "ok", "db_status": "ok", "mv_expense_item_tree": "ok"}
try:
async with engine.connect() as conn:
await conn.execute(text("SELECT 1"))
if "postgresql" in settings.DATABASE_URL:
my_exists = (
await conn.execute(
text(
"""
SELECT 1
FROM pg_matviews
WHERE schemaname = 'v3'
AND matviewname = 'mv_expense_item_tree'
"""
)
)
).scalar_one_or_none()
if my_exists is None:
result["status"] = "fail"
result["mv_expense_item_tree"] = "missing"
response.status_code = 503
except SQLAlchemyError as exc:
result["status"] = "fail"
result["db"] = f"error: {exc.__class__.__name__}"
response.status_code = 503
return result
if not settings.DEBUG: if not settings.DEBUG:
app.add_middleware( app.add_middleware(
@ -83,6 +120,16 @@ if not settings.DEBUG:
path=settings.ROOT_PATH + "/healthcheck", path=settings.ROOT_PATH + "/healthcheck",
type_search=SearchType.ABSOLUTE, type_search=SearchType.ABSOLUTE,
), ),
OpenEndpoint(path="/healthz", type_search=SearchType.ABSOLUTE),
OpenEndpoint(
path=settings.ROOT_PATH + "/healthz",
type_search=SearchType.ABSOLUTE,
),
OpenEndpoint(path="/readyz", type_search=SearchType.ABSOLUTE),
OpenEndpoint(
path=settings.ROOT_PATH + "/readyz",
type_search=SearchType.ABSOLUTE,
),
OpenEndpoint(path="/openapi.json", type_search=SearchType.START), OpenEndpoint(path="/openapi.json", type_search=SearchType.START),
OpenEndpoint( OpenEndpoint(
path=settings.ROOT_PATH + "/openapi.json", path=settings.ROOT_PATH + "/openapi.json",