From 3b300ab3b45616254a0401a1a44e4404e93a52b0 Mon Sep 17 00:00:00 2001 From: Gregor Klevze Date: Sat, 21 Mar 2026 09:16:11 +0100 Subject: [PATCH] Add Qdrant vectorstore service, gateway API-key middleware, docs and .gitignore --- README.md | 2 +- docker-compose.yml | 1 + gateway/main.py | 18 +++++++++++++++++- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9ee2d69..2b9a4f2 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ docker compose up -d --build ## Health ```bash -curl https://vision.klevze.net/health +curl -H "X-API-Key: " https://vision.klevze.net/health ``` ## Universal analyze (ALL) diff --git a/docker-compose.yml b/docker-compose.yml index 21b789b..0ed05ff 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,7 @@ services: - BLIP_URL=http://blip:8000 - YOLO_URL=http://yolo:8000 - QDRANT_SVC_URL=http://qdrant-svc:8000 + - API_KEY=please-change-me - VISION_TIMEOUT=300 - MAX_IMAGE_BYTES=52428800 depends_on: diff --git a/gateway/main.py b/gateway/main.py index 6f4cc52..cffac96 100644 --- a/gateway/main.py +++ b/gateway/main.py @@ -5,7 +5,9 @@ import asyncio from typing import Any, Dict, Optional import httpx -from fastapi import FastAPI, HTTPException, UploadFile, File, Form +from fastapi import FastAPI, HTTPException, UploadFile, File, Form, Request +from fastapi.responses import JSONResponse +from starlette.middleware.base import BaseHTTPMiddleware from pydantic import BaseModel, Field CLIP_URL = os.getenv("CLIP_URL", "http://clip:8000") @@ -14,7 +16,21 @@ YOLO_URL = os.getenv("YOLO_URL", "http://yolo:8000") QDRANT_SVC_URL = os.getenv("QDRANT_SVC_URL", "http://qdrant-svc:8000") VISION_TIMEOUT = float(os.getenv("VISION_TIMEOUT", "20")) +# API key (set via env var `API_KEY`). If not set, gateway will reject requests. +API_KEY = os.getenv("API_KEY") + +class APIKeyMiddleware(BaseHTTPMiddleware): + async def dispatch(self, request: Request, call_next): + # allow health and docs endpoints without API key + if request.url.path in ("/health", "/openapi.json", "/docs", "/redoc"): + return await call_next(request) + key = request.headers.get("x-api-key") or request.headers.get("X-API-Key") + if not API_KEY or key != API_KEY: + return JSONResponse(status_code=401, content={"detail": "Unauthorized"}) + return await call_next(request) + app = FastAPI(title="Skinbase Vision Gateway", version="1.0.0") +app.add_middleware(APIKeyMiddleware) class ClipRequest(BaseModel):