From 8b50a85620d30299c896a2d67a4d35094c4fdaf3 Mon Sep 17 00:00:00 2001 From: Ferdinand Date: Tue, 7 Apr 2026 13:46:37 +0200 Subject: [PATCH] feat: FastAPI backend with /analyze, /move, /preview endpoints --- server.py | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 server.py diff --git a/server.py b/server.py new file mode 100644 index 0000000..aabf883 --- /dev/null +++ b/server.py @@ -0,0 +1,96 @@ +import os +import shutil +import webbrowser +import threading +from typing import List + +from dotenv import load_dotenv +from fastapi import FastAPI, HTTPException +from fastapi.responses import FileResponse, Response +from fastapi.middleware.cors import CORSMiddleware +from pydantic import BaseModel +import uvicorn + +from analyzer import analyze_folder + +load_dotenv() + +app = FastAPI(title="Foto-Kurator") + +app.add_middleware( + CORSMiddleware, + allow_origins=["http://localhost:8000"], + allow_methods=["GET", "POST"], + allow_headers=["Content-Type"], +) + + +class AnalyzeRequest(BaseModel): + folder: str + blur_threshold: float = 100.0 + over_threshold: float = 240.0 + under_threshold: float = 30.0 + dup_threshold: int = 8 + use_ai: bool = False + + +class MoveRequest(BaseModel): + paths: List[str] + folder: str + + +@app.get("/") +def serve_frontend(): + return FileResponse("index.html") + + +@app.get("/preview") +def preview(path: str): + if not os.path.isfile(path): + raise HTTPException(status_code=404, detail="Datei nicht gefunden") + ext = os.path.splitext(path)[1].lower() + media = "image/jpeg" if ext in (".jpg", ".jpeg") else "image/png" + with open(path, "rb") as f: + return Response(content=f.read(), media_type=media) + + +@app.post("/analyze") +def analyze(req: AnalyzeRequest): + if not os.path.isdir(req.folder): + raise HTTPException(status_code=400, detail=f"Ordner nicht gefunden: {req.folder}") + api_key = os.getenv("ANTHROPIC_API_KEY") if req.use_ai else None + results = analyze_folder( + folder=req.folder, + blur_threshold=req.blur_threshold, + over_threshold=req.over_threshold, + under_threshold=req.under_threshold, + dup_threshold=req.dup_threshold, + use_ai=req.use_ai, + api_key=api_key, + ) + return {"results": results} + + +@app.post("/move") +def move_files(req: MoveRequest): + target_dir = os.path.join(req.folder, "_aussortiert") + os.makedirs(target_dir, exist_ok=True) + moved = [] + errors = [] + for path in req.paths: + try: + dest = os.path.join(target_dir, os.path.basename(path)) + shutil.move(path, dest) + moved.append(path) + except Exception as e: + errors.append({"path": path, "error": str(e)}) + return {"moved": moved, "errors": errors} + + +def open_browser(): + webbrowser.open("http://localhost:8000") + + +if __name__ == "__main__": + threading.Timer(1.0, open_browser).start() + uvicorn.run(app, host="127.0.0.1", port=8000)