Mechanic's work place
Some checks failed
ci / test (push) Has been cancelled

This commit is contained in:
VPN SaaS Dev
2026-05-16 10:04:56 +09:00
parent fec9635079
commit 83ad880b9d
39 changed files with 2951 additions and 74 deletions

View File

@@ -1,6 +1,12 @@
from fastapi import FastAPI
from contextlib import asynccontextmanager
from time import monotonic
from uuid import uuid4
from fastapi import Depends, FastAPI, Request, Response
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession
from app.api import (
admin,
@@ -16,10 +22,54 @@ from app.api import (
service_visits,
sto_booking,
users,
work_orders,
)
from app.core.config import settings
from app.db.session import get_session
from app.services.rate_limit import get_redis_client
app = FastAPI(title="Drivers Bot API", version="0.1.0")
@asynccontextmanager
async def lifespan(app: FastAPI):
settings.validate_production_settings()
yield
app = FastAPI(title="Drivers Bot API", version="0.1.0", lifespan=lifespan)
REQUEST_COUNT = 0
REQUEST_ERRORS = 0
REQUEST_DURATION_TOTAL = 0.0
@app.middleware("http")
async def production_headers_and_metrics(request: Request, call_next):
global REQUEST_COUNT, REQUEST_DURATION_TOTAL, REQUEST_ERRORS
request_id = request.headers.get("X-Request-ID") or str(uuid4())
start = monotonic()
try:
response = await call_next(request)
except Exception:
REQUEST_ERRORS += 1
raise
duration = monotonic() - start
REQUEST_COUNT += 1
REQUEST_DURATION_TOTAL += duration
response.headers["X-Request-ID"] = request_id
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
response.headers["X-Frame-Options"] = "DENY"
if settings.is_production:
response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
response.headers["Content-Security-Policy"] = (
"default-src 'self' https://telegram.org https://*.telegram.org; "
"connect-src 'self' https://api.telegram.org; "
"img-src 'self' data: https:; "
"script-src 'self' 'unsafe-inline' https://telegram.org https://*.telegram.org; "
"style-src 'self' 'unsafe-inline'; "
"frame-ancestors 'none'"
)
return response
dev_origins = ["http://localhost:8000", "http://127.0.0.1:8000"] if not settings.is_production else []
cors_origins = settings.cors_origin_list or dev_origins
@@ -43,6 +93,7 @@ app.include_router(parser.router, prefix="/api")
app.include_router(service_centers.router, prefix="/api")
app.include_router(sto_booking.router, prefix="/api")
app.include_router(service_visits.router, prefix="/api")
app.include_router(work_orders.router, prefix="/api")
app.include_router(change_requests.router, prefix="/api")
app.include_router(admin.router, prefix="/api")
@@ -52,4 +103,41 @@ async def health() -> dict[str, str]:
return {"status": "ok"}
@app.get("/metrics")
async def metrics() -> Response:
avg = REQUEST_DURATION_TOTAL / REQUEST_COUNT if REQUEST_COUNT else 0
body = "\n".join(
[
"# TYPE carpass_requests_total counter",
f"carpass_requests_total {REQUEST_COUNT}",
"# TYPE carpass_request_errors_total counter",
f"carpass_request_errors_total {REQUEST_ERRORS}",
"# TYPE carpass_request_duration_seconds_avg gauge",
f"carpass_request_duration_seconds_avg {avg:.6f}",
"",
]
)
return Response(body, media_type="text/plain; version=0.0.4")
@app.get("/ready")
async def ready(session: AsyncSession = Depends(get_session)) -> dict[str, str]:
await session.execute(text("select 1"))
migration = "unknown"
try:
version = await session.execute(text("select version_num from alembic_version limit 1"))
migration = version.scalar_one_or_none() or "unknown"
except Exception:
migration = "not_checked"
redis_status = "disabled"
if settings.redis_url:
redis = await get_redis_client()
if redis is None:
redis_status = "client_missing"
else:
await redis.ping()
redis_status = "ok"
return {"status": "ready", "database": "ok", "redis": redis_status, "migration": migration}
app.mount("/", StaticFiles(directory="web", html=True), name="web")