"""BAN YARO — Foto-Challenge der Woche""" import os import uuid import logging from datetime import date, timedelta from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form from pydantic import BaseModel from typing import Optional from database import db from auth import get_current_user, get_current_user_optional from media_utils import convert_media, generate_preview logger = logging.getLogger(__name__) router = APIRouter() MEDIA_DIR = os.getenv("MEDIA_DIR", "/data/media") CHALLENGE_DIR = os.path.join(MEDIA_DIR, "challenges") _CHALLENGE_THEMEN = [ "Bestes Schnüffel-Foto 👃", "Action-Aufnahme 🏃", "Schlafendes Tier 😴", "Gassi im Regen 🌧️", "Hundeblick in die Kamera 👀", "Spielzeit mit Freunden 🐕", "Herbstspaziergang 🍂", "Beste Sprung-Aufnahme 🦘", "Hund am Wasser 🌊", "Erstes Mal im Schnee ❄️", "Genuss-Moment 🦴", "Versteckt im Gebüsch 🌿", "Tierisches Selfie 🤳", "Hund & Kind 👶", "Hund & Katze zusammen 🐱", "Der beste Buddel-Moment 🐾", "Freude beim Apportieren 🎾", "Hund in seiner Lieblingshöhle 🛋️", "Sonnenuntergangs-Gassi 🌅", "Hundebegegnung auf dem Spaziergang 🐕🐕", "Ausdrucksstarker Hundeblick 😍", "Hund im Herbstlaub 🍁", "Welpenfoto 🍼", "Seniorenhund im Porträt 👴", "Lustigste Schlafposition 💤", "Hund trägt etwas 🎀", "Hund + Besitzer Spiegelfoto 🪞", "Hund auf Abenteuer 🏕️", "Beste Lauf-Action 💨", "Hund im Café ☕", ] def _current_week_monday() -> str: today = date.today() monday = today - timedelta(days=today.weekday()) return monday.isoformat() def _current_week_sunday() -> str: monday = date.fromisoformat(_current_week_monday()) return (monday + timedelta(days=6)).isoformat() def _ensure_current_challenge(conn) -> int: """Stellt sicher dass eine Challenge für die aktuelle Woche existiert. Gibt die ID zurück.""" monday = _current_week_monday() sunday = _current_week_sunday() existing = conn.execute( "SELECT id FROM foto_challenge WHERE start_date = ?", (monday,) ).fetchone() if existing: return existing["id"] # Thema aus Rotation wählen (Wochennummer % Anzahl Themen) week_num = date.today().isocalendar()[1] thema = _CHALLENGE_THEMEN[week_num % len(_CHALLENGE_THEMEN)] cur = conn.execute( "INSERT INTO foto_challenge (thema, beschreibung, start_date, end_date, created_by) " "VALUES (?, ?, ?, ?, NULL)", (thema, f"Diese Woche: {thema}", monday, sunday) ) return cur.lastrowid # ------------------------------------------------------------------ # GET /api/challenges/current # ------------------------------------------------------------------ @router.get("/current") async def get_current_challenge(user=Depends(get_current_user_optional)): with db() as conn: challenge_id = _ensure_current_challenge(conn) challenge = conn.execute( "SELECT * FROM foto_challenge WHERE id = ?", (challenge_id,) ).fetchone() submissions = conn.execute(""" SELECT cs.id, cs.user_id, cs.dog_id, cs.foto_url, cs.caption, cs.votes, cs.created_at, u.name AS user_name, u.avatar_url, d.name AS dog_name, d.foto_url AS dog_foto_url FROM challenge_submissions cs LEFT JOIN users u ON u.id = cs.user_id LEFT JOIN dogs d ON d.id = cs.dog_id WHERE cs.challenge_id = ? ORDER BY cs.votes DESC, cs.created_at ASC """, (challenge_id,)).fetchall() my_submission = None my_votes = set() if user: mine = conn.execute( "SELECT id FROM challenge_submissions WHERE challenge_id=? AND user_id=?", (challenge_id, user["id"]) ).fetchone() if mine: my_submission = mine["id"] voted_rows = conn.execute( "SELECT cv.submission_id FROM challenge_votes cv " "JOIN challenge_submissions cs ON cs.id = cv.submission_id " "WHERE cv.user_id = ? AND cs.challenge_id = ?", (user["id"], challenge_id) ).fetchall() my_votes = {r["submission_id"] for r in voted_rows} # Countdown bis Sonntag end = date.fromisoformat(challenge["end_date"]) days_left = (end - date.today()).days + 1 result_subs = [] for s in submissions: sd = dict(s) sd["i_voted"] = (sd["id"] in my_votes) if user else False result_subs.append(sd) return { "challenge": dict(challenge), "submissions": result_subs, "my_submission_id": my_submission, "days_left": max(0, days_left), } # ------------------------------------------------------------------ # POST /api/challenges/{id}/submit # ------------------------------------------------------------------ @router.post("/{challenge_id}/submit", status_code=201) async def submit_photo( challenge_id: int, caption: Optional[str] = Form(None), dog_id: Optional[int] = Form(None), foto: UploadFile = File(...), user=Depends(get_current_user), ): with db() as conn: challenge = conn.execute( "SELECT * FROM foto_challenge WHERE id = ?", (challenge_id,) ).fetchone() if not challenge: raise HTTPException(404, "Challenge nicht gefunden.") today = date.today().isoformat() if today > challenge["end_date"]: raise HTTPException(400, "Die Challenge ist bereits beendet.") # Doppelt-Check existing = conn.execute( "SELECT id FROM challenge_submissions WHERE challenge_id=? AND user_id=?", (challenge_id, user["id"]) ).fetchone() if existing: raise HTTPException(409, "Du hast bereits ein Foto eingereicht.") # Foto speichern os.makedirs(CHALLENGE_DIR, exist_ok=True) orig_filename = foto.filename or "foto.jpg" ext = os.path.splitext(orig_filename)[1] or ".jpg" base = uuid.uuid4().hex raw = await foto.read() # HEIC→JPEG Konvertierung falls nötig try: converted, out_ext = convert_media(raw, orig_filename) except Exception: converted, out_ext = raw, ext save_filename = f"{base}{out_ext}" save_path = os.path.join(CHALLENGE_DIR, save_filename) with open(save_path, "wb") as f: f.write(converted) foto_url = f"/media/challenges/{save_filename}" # Preview try: preview = generate_preview(converted, out_ext) if preview: prev_path = os.path.join(CHALLENGE_DIR, f"{base}_preview.webp") with open(prev_path, "wb") as f: f.write(preview) except Exception: pass with db() as conn: cur = conn.execute( "INSERT INTO challenge_submissions (challenge_id, user_id, dog_id, foto_url, caption) " "VALUES (?, ?, ?, ?, ?)", (challenge_id, user["id"], dog_id, foto_url, caption) ) row = conn.execute(""" SELECT cs.*, u.name AS user_name, d.name AS dog_name FROM challenge_submissions cs LEFT JOIN users u ON u.id = cs.user_id LEFT JOIN dogs d ON d.id = cs.dog_id WHERE cs.id = ? """, (cur.lastrowid,)).fetchone() return dict(row) # ------------------------------------------------------------------ # POST /api/challenges/submissions/{id}/vote — Toggle-Vote # ------------------------------------------------------------------ @router.post("/submissions/{submission_id}/vote") async def vote_submission(submission_id: int, user=Depends(get_current_user)): with db() as conn: sub = conn.execute( "SELECT * FROM challenge_submissions WHERE id = ?", (submission_id,) ).fetchone() if not sub: raise HTTPException(404, "Einreichung nicht gefunden.") if sub["user_id"] == user["id"]: raise HTTPException(400, "Du kannst nicht für dein eigenes Foto abstimmen.") existing = conn.execute( "SELECT id FROM challenge_votes WHERE submission_id=? AND user_id=?", (submission_id, user["id"]) ).fetchone() if existing: # Toggle: Vote entfernen conn.execute( "DELETE FROM challenge_votes WHERE submission_id=? AND user_id=?", (submission_id, user["id"]) ) conn.execute( "UPDATE challenge_submissions SET votes = MAX(0, votes - 1) WHERE id=?", (submission_id,) ) voted = False else: conn.execute( "INSERT INTO challenge_votes (submission_id, user_id) VALUES (?, ?)", (submission_id, user["id"]) ) conn.execute( "UPDATE challenge_submissions SET votes = votes + 1 WHERE id=?", (submission_id,) ) voted = True votes = conn.execute( "SELECT votes FROM challenge_submissions WHERE id=?", (submission_id,) ).fetchone()["votes"] return {"voted": voted, "votes": votes} # ------------------------------------------------------------------ # GET /api/challenges/winners — letzte 4 Gewinner # ------------------------------------------------------------------ @router.get("/winners") async def get_winners(): with db() as conn: # Vergangene Challenges (ohne aktuelle Woche) monday = _current_week_monday() challenges = conn.execute( "SELECT id, thema, start_date, end_date FROM foto_challenge " "WHERE end_date < ? ORDER BY end_date DESC LIMIT 4", (monday,) ).fetchall() winners = [] for ch in challenges: winner = conn.execute(""" SELECT cs.id, cs.user_id, cs.foto_url, cs.caption, cs.votes, u.name AS user_name, u.avatar_url, d.name AS dog_name FROM challenge_submissions cs LEFT JOIN users u ON u.id = cs.user_id LEFT JOIN dogs d ON d.id = cs.dog_id WHERE cs.challenge_id = ? ORDER BY cs.votes DESC, cs.created_at ASC LIMIT 1 """, (ch["id"],)).fetchone() winners.append({ "challenge": dict(ch), "winner": dict(winner) if winner else None, }) return winners