Feature: Hundeernährungs-Feature — Kalorien-Rechner, Futter-Guide, Giftliste, KI-Berater (SW by-v698)
This commit is contained in:
parent
b1d9fb4f54
commit
6e4bf25581
7 changed files with 838 additions and 8 deletions
|
|
@ -6,9 +6,10 @@ import os
|
|||
import html
|
||||
import logging
|
||||
from collections import deque
|
||||
import httpx
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.responses import FileResponse, JSONResponse
|
||||
from fastapi.responses import FileResponse, JSONResponse, Response
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
from fastapi.middleware.gzip import GZipMiddleware
|
||||
from brotli_asgi import BrotliMiddleware
|
||||
|
|
@ -43,10 +44,43 @@ logger = logging.getLogger(__name__)
|
|||
# ------------------------------------------------------------------
|
||||
# Startup / Shutdown
|
||||
# ------------------------------------------------------------------
|
||||
def _backfill_image_sizes():
|
||||
"""Füllt img_width/img_height für alle diary_media-Bilder ohne Maße nach."""
|
||||
import io
|
||||
from database import db
|
||||
from media_utils import get_image_size
|
||||
MEDIA_DIR = os.getenv("MEDIA_DIR", "/data/media")
|
||||
with db() as conn:
|
||||
rows = conn.execute(
|
||||
"SELECT id, url FROM diary_media WHERE media_type='image' AND img_width IS NULL"
|
||||
).fetchall()
|
||||
if not rows:
|
||||
return
|
||||
logger.info("Backfill Bildmaße: %d Einträge...", len(rows))
|
||||
updated = 0
|
||||
for row in rows:
|
||||
# url ist z.B. /media/diary/xxx.jpg → Pfad: MEDIA_DIR/diary/xxx.jpg
|
||||
rel = row["url"].removeprefix("/media/")
|
||||
path = os.path.join(MEDIA_DIR, rel)
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
data = f.read()
|
||||
size = get_image_size(data)
|
||||
if size:
|
||||
with db() as conn:
|
||||
conn.execute(
|
||||
"UPDATE diary_media SET img_width=?, img_height=? WHERE id=?",
|
||||
(size[0], size[1], row["id"])
|
||||
)
|
||||
updated += 1
|
||||
except Exception:
|
||||
pass
|
||||
logger.info("Backfill Bildmaße abgeschlossen: %d/%d aktualisiert.", updated, len(rows))
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
logger.info("Ban Yaro startet...")
|
||||
init_db()
|
||||
_backfill_image_sizes()
|
||||
from routes.movies import seed_movies
|
||||
seed_movies()
|
||||
logger.info(f"KI-Modus: {ki.KI_MODE}")
|
||||
|
|
@ -76,7 +110,7 @@ class SecurityHeadersMiddleware(BaseHTTPMiddleware):
|
|||
response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
|
||||
response.headers["Content-Security-Policy"] = (
|
||||
"default-src 'self'; "
|
||||
"script-src 'self' 'unsafe-inline' 'unsafe-eval'; "
|
||||
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://umami.motocamp.de; "
|
||||
"style-src 'self' 'unsafe-inline'; "
|
||||
"img-src 'self' data: blob: https:; "
|
||||
"connect-src 'self' https:; "
|
||||
|
|
@ -198,6 +232,7 @@ from routes.adoption import router as adoption_router
|
|||
from routes.health_docs import router as health_docs_router
|
||||
from routes.passport import router as passport_router
|
||||
from routes.playdate import router as playdate_router
|
||||
from routes.ernaehrung import router as ernaehrung_router
|
||||
|
||||
app.include_router(auth_router, prefix="/api/auth", tags=["Auth"])
|
||||
app.include_router(dogs_router, prefix="/api/dogs", tags=["Hunde"])
|
||||
|
|
@ -256,6 +291,7 @@ app.include_router(adoption_router, prefix="/api/adoption", ta
|
|||
app.include_router(health_docs_router, prefix="/api/health-docs", tags=["Gesundheitsdokumente"])
|
||||
app.include_router(passport_router, prefix="/api/passport", tags=["Hundepass"])
|
||||
app.include_router(playdate_router, prefix="/api/playdate", tags=["Playdate"])
|
||||
app.include_router(ernaehrung_router, prefix="/api/dogs", tags=["Ernährung"])
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
|
@ -285,6 +321,27 @@ MEDIA_DIR = os.getenv("MEDIA_DIR", "/data/media")
|
|||
os.makedirs(MEDIA_DIR, exist_ok=True)
|
||||
app.mount("/media", StaticFiles(directory=MEDIA_DIR), name="media")
|
||||
|
||||
@app.get("/stats/script.js")
|
||||
async def umami_script_proxy():
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
r = await client.get("https://umami.motocamp.de/script.js")
|
||||
return Response(content=r.content, media_type="application/javascript",
|
||||
headers={"Cache-Control": "public, max-age=86400"})
|
||||
|
||||
@app.post("/stats/api/send")
|
||||
async def umami_send_proxy(request: Request):
|
||||
body = await request.body()
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
r = await client.post(
|
||||
"https://umami.motocamp.de/api/send",
|
||||
content=body,
|
||||
headers={"Content-Type": "application/json",
|
||||
"User-Agent": request.headers.get("user-agent", "")},
|
||||
)
|
||||
return Response(content=r.content, status_code=r.status_code,
|
||||
media_type="application/json")
|
||||
|
||||
|
||||
@app.get("/robots.txt")
|
||||
async def robots():
|
||||
return FileResponse(f"{STATIC_DIR}/robots.txt", media_type="text/plain")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue