"""BAN YARO — Gesundheitsdokumente (Befunde, Röntgen, Rezepte …)""" import os import uuid from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form from typing import Optional from database import db from auth import get_current_user router = APIRouter() MEDIA_DIR = os.getenv("MEDIA_DIR", "/data/media") ALLOWED_EXTENSIONS = {".jpg", ".jpeg", ".png", ".webp", ".pdf"} MAX_SIZE_BYTES = 10 * 1024 * 1024 # 10 MB ERLAUBTE_TYPEN = {"blutbild", "roentgen", "rezept", "impfausweis", "sonstiges"} def _check_dog_owner(conn, dog_id: int, user_id: int): dog = conn.execute( "SELECT id FROM dogs WHERE id=? AND user_id=?", (dog_id, user_id) ).fetchone() if not dog: raise HTTPException(404, "Hund nicht gefunden.") return dog # ------------------------------------------------------------------ # GET /api/health-docs?dog_id=... # ------------------------------------------------------------------ @router.get("") async def list_docs(dog_id: int, user=Depends(get_current_user)): with db() as conn: _check_dog_owner(conn, dog_id, user["id"]) rows = conn.execute( """SELECT hd.*, t.name AS vet_name FROM health_documents hd LEFT JOIN tieraerzte t ON t.id = hd.vet_id WHERE hd.dog_id=? ORDER BY hd.created_at DESC""", (dog_id,) ).fetchall() return [dict(r) for r in rows] # ------------------------------------------------------------------ # POST /api/health-docs/upload (multipart/form-data) # ------------------------------------------------------------------ @router.post("/upload", status_code=201) async def upload_doc( dog_id: int = Form(...), typ: str = Form(...), titel: str = Form(...), beschreibung: Optional[str] = Form(None), datum: Optional[str] = Form(None), vet_id: Optional[int] = Form(None), file: UploadFile = File(...), user=Depends(get_current_user), ): if typ not in ERLAUBTE_TYPEN: raise HTTPException(400, f"Unbekannter Typ. Erlaubt: {', '.join(sorted(ERLAUBTE_TYPEN))}") ext = os.path.splitext(file.filename or "")[1].lower() if not ext: ext = ".jpg" if ext not in ALLOWED_EXTENSIONS: raise HTTPException(400, "Nur JPG, PNG, WebP und PDF erlaubt.") content = await file.read() if len(content) > MAX_SIZE_BYTES: raise HTTPException(413, "Datei zu groß. Maximal 10 MB erlaubt.") with db() as conn: _check_dog_owner(conn, dog_id, user["id"]) if vet_id: vet = conn.execute("SELECT id FROM tieraerzte WHERE id=?", (vet_id,)).fetchone() if not vet: vet_id = None # Datei speichern dog_dir = os.path.join(MEDIA_DIR, "health_docs", str(dog_id)) os.makedirs(dog_dir, exist_ok=True) filename = f"{uuid.uuid4().hex}{ext}" filepath = os.path.join(dog_dir, filename) with open(filepath, "wb") as f: f.write(content) file_url = f"/media/health_docs/{dog_id}/{filename}" file_type = "pdf" if ext == ".pdf" else ext.lstrip(".") with db() as conn: conn.execute( """INSERT INTO health_documents (dog_id, user_id, typ, titel, beschreibung, file_path, file_type, datum, vet_id) VALUES (?,?,?,?,?,?,?,?,?)""", (dog_id, user["id"], typ, titel.strip(), beschreibung, file_url, file_type, datum or None, vet_id) ) row = conn.execute( """SELECT hd.*, t.name AS vet_name FROM health_documents hd LEFT JOIN tieraerzte t ON t.id = hd.vet_id WHERE hd.id = last_insert_rowid()""" ).fetchone() return dict(row) # ------------------------------------------------------------------ # DELETE /api/health-docs/{id} # ------------------------------------------------------------------ @router.delete("/{doc_id}", status_code=204) async def delete_doc(doc_id: int, user=Depends(get_current_user)): with db() as conn: row = conn.execute( "SELECT * FROM health_documents WHERE id=? AND user_id=?", (doc_id, user["id"]) ).fetchone() if not row: raise HTTPException(404, "Dokument nicht gefunden.") # Datei löschen — file_path ist z.B. /media/health_docs/42/abc123.pdf file_path = row["file_path"] if file_path: # /media/... → MEDIA_DIR/... rel = file_path.lstrip("/") if rel.startswith("media/"): rel = rel[len("media/"):] abs_path = os.path.join(MEDIA_DIR, rel) if os.path.isfile(abs_path): try: os.remove(abs_path) except OSError: pass conn.execute("DELETE FROM health_documents WHERE id=?", (doc_id,)) return None