Files
lauadminandClaude Opus 5.5 b8f2fe6b6e Translate UI and user-facing messages to Ukrainian (#5)
- All frontend pages, labels, notices and errors; html lang=uk, uk-UA money format
- Brand "Assistant System" in the top bar and page title
- Backend error details returned to the UI (auth, orders, receipts,
  cash registers, Checkbox/CRM/NP errors) and CLI output
- Tests updated for the new messages

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 15:27:39 +03:00

107 lines
3.6 KiB
Python

"""Точка входа FastAPI."""
from __future__ import annotations
import time
import uuid
from collections.abc import AsyncGenerator, Awaitable, Callable
from contextlib import asynccontextmanager
import structlog
from fastapi import FastAPI, Request, Response
from fastapi.middleware.cors import CORSMiddleware
from starlette.middleware.base import BaseHTTPMiddleware
from app.api.v1.router import api_router
from app.core.config import settings
from app.core.logging import configure_logging, get_logger
from app.db.session import engine
from app.services.task_queue import close_task_queue
log = get_logger(__name__)
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
configure_logging()
log.info("app_starting", environment=settings.environment, version=app.version)
yield
await close_task_queue()
await engine.dispose()
log.info("app_stopped")
class RequestContextMiddleware(BaseHTTPMiddleware):
"""Присваивает каждому запросу идентификатор и пишет структурный access-лог.
request_id возвращается заголовком `X-Request-ID`: по нему оператор,
столкнувшийся с ошибкой, находится в логах за одну команду.
"""
async def dispatch(
self, request: Request, call_next: Callable[[Request], Awaitable[Response]]
) -> Response:
request_id = request.headers.get("x-request-id") or uuid.uuid4().hex
structlog.contextvars.clear_contextvars()
structlog.contextvars.bind_contextvars(request_id=request_id)
started = time.perf_counter()
try:
response = await call_next(request)
except Exception:
log.exception(
"request_failed",
method=request.method,
path=request.url.path,
duration_ms=round((time.perf_counter() - started) * 1000, 2),
)
raise
duration_ms = round((time.perf_counter() - started) * 1000, 2)
# Health-check'и опрашиваются постоянно и засоряют лог.
if not request.url.path.startswith("/api/v1/health"):
log.info(
"request",
method=request.method,
path=request.url.path,
status=response.status_code,
duration_ms=duration_ms,
)
response.headers["X-Request-ID"] = request_id
return response
def create_app() -> FastAPI:
configure_logging()
app = FastAPI(
title=settings.project_name,
version="0.1.0",
description="Фіскалізація замовлень через Checkbox",
lifespan=lifespan,
# В проде интерактивная документация закрыта: схема API — лишняя
# подсказка для того, кто ищет незащищённый эндпоинт.
docs_url=None if settings.is_production else "/docs",
redoc_url=None if settings.is_production else "/redoc",
openapi_url=None if settings.is_production else "/openapi.json",
)
app.add_middleware(RequestContextMiddleware)
if settings.cors_origins:
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
expose_headers=["X-Request-ID"],
)
app.include_router(api_router, prefix="/api/v1")
return app
app = create_app()