- Freundschaften (pending/accepted), Nutzersuche, Anfragen per Push - Direktnachrichten mit Polling, iMessage-Stil, Deep-Links aus Push - Alle Seiten (map, places, diary, health, dog-profile, sitting, knigge, forum, wiki, walks) vollständig auf Phosphor-Icons migriert - Wikidata-Rassen-Scraper (~833 neue Rassen, lokal gespiegelte Fotos) - TheDogAPI lokal gespiegelt (169 Rassen + Fotos) - Quiz-Result-Cards horizontal (korrekte Bildproportionen) - SW by-v89
185 lines
10 KiB
Python
185 lines
10 KiB
Python
"""BAN YARO — Hunde-Filme Routes"""
|
||
|
||
from fastapi import APIRouter, Depends, HTTPException
|
||
from pydantic import BaseModel
|
||
from typing import Optional
|
||
from datetime import datetime
|
||
from database import db
|
||
from auth import get_current_user, get_current_user_optional
|
||
|
||
router = APIRouter()
|
||
|
||
|
||
# ------------------------------------------------------------------
|
||
# Hardcoded Film-Daten
|
||
# ------------------------------------------------------------------
|
||
FILME = [
|
||
{"id": "lassie", "titel": "Lassie", "jahr": 1943, "genre": "Familie", "hund_rasse": "Collie", "stirbt_der_hund": False, "beschreibung": "Der Klassiker schlechthin. Lassie findet immer nach Hause.", "bild_emoji": "🐕", "bewertung_avg": 4.2},
|
||
{"id": "benji", "titel": "Benji", "jahr": 1974, "genre": "Familie", "hund_rasse": "Mischling", "stirbt_der_hund": False, "beschreibung": "Ein herrenloser Hund rettet Kinder aus den Händen von Entführern.", "bild_emoji": "🐾", "bewertung_avg": 4.0},
|
||
{"id": "marley-and-me", "titel": "Marley & Ich", "jahr": 2008, "genre": "Drama/Komödie", "hund_rasse": "Labrador", "stirbt_der_hund": True, "beschreibung": "Der chaotischste, aber liebste Labrador der Welt. Achtung: Taschentücher bereithalten.", "bild_emoji": "😭", "bewertung_avg": 4.5},
|
||
{"id": "hachiko", "titel": "Hachi: A Dog's Tale", "jahr": 2009, "genre": "Drama", "hund_rasse": "Akita", "stirbt_der_hund": True, "beschreibung": "Basiert auf der wahren Geschichte des treuen Akita Hachikō. Starke emotionale Wirkung.", "bild_emoji": "💔", "bewertung_avg": 4.8},
|
||
{"id": "101-dalmatiner", "titel": "101 Dalmatiner", "jahr": 1961, "genre": "Animation/Familie", "hund_rasse": "Dalmatiner", "stirbt_der_hund": False, "beschreibung": "Dalmatiner-Welpen vs. die böse Cruella de Vil. Animationsklassiker.", "bild_emoji": "🐡", "bewertung_avg": 4.3},
|
||
{"id": "beethoven", "titel": "Beethoven", "jahr": 1992, "genre": "Familie/Komödie", "hund_rasse": "Bernhardiner", "stirbt_der_hund": False, "beschreibung": "Riesiger Bernhardiner bringt Chaos ins Familienleben. Mehrere Fortsetzungen.", "bild_emoji": "🎵", "bewertung_avg": 3.8},
|
||
{"id": "rex", "titel": "Kommissar Rex", "jahr": 1994, "genre": "Krimi/Serie", "hund_rasse": "Deutscher Schäferhund", "stirbt_der_hund": False, "beschreibung": "Österreichische Krimiserie. Rex löst gemeinsam mit seinem Herrchen Verbrechen.", "bild_emoji": "🔍", "bewertung_avg": 4.1},
|
||
{"id": "old-yeller", "titel": "Old Yeller", "jahr": 1957, "genre": "Familie/Drama", "hund_rasse": "Mischling", "stirbt_der_hund": True, "beschreibung": "Amerikanischer Filmklassiker. Berühmtestes Filmende der Hundfilm-Geschichte.", "bild_emoji": "🌾", "bewertung_avg": 4.0},
|
||
{"id": "buddy", "titel": "Air Bud", "jahr": 1997, "genre": "Familie/Sport", "hund_rasse": "Golden Retriever", "stirbt_der_hund": False, "beschreibung": "Hund spielt Basketball. Klingt absurd, wurde ein Hit.", "bild_emoji": "🏀", "bewertung_avg": 3.5},
|
||
{"id": "john-wick", "titel": "John Wick", "jahr": 2014, "genre": "Action", "hund_rasse": "Beagle", "stirbt_der_hund": True, "beschreibung": "Achtung Spoiler: Der Hund stirbt am Anfang. Das löst die ganze Geschichte aus. Kontroversiell beliebt.", "bild_emoji": "💣", "bewertung_avg": 4.6},
|
||
{"id": "isle-of-dogs", "titel": "Isle of Dogs", "jahr": 2018, "genre": "Animation", "hund_rasse": "Verschiedene", "stirbt_der_hund": False, "beschreibung": "Wes Anderson Stopmotion-Meisterwerk. Alle Hunde Japans auf einer Insel verbannt.", "bild_emoji": "🏝️", "bewertung_avg": 4.4},
|
||
{"id": "eight-below", "titel": "8 Below", "jahr": 2006, "genre": "Abenteuer/Drama", "hund_rasse": "Schlittenhunde", "stirbt_der_hund": True, "beschreibung": "Basiert auf wahren Ereignissen. Schlittenhunde überleben die Antarktis. Einige nicht.", "bild_emoji": "❄️", "bewertung_avg": 4.3},
|
||
]
|
||
|
||
PROMIS = [
|
||
{"name": "Hachikō", "rasse": "Akita Inu", "bekannt_fuer": "9 Jahre lang täglich auf seinen verstorbenen Herrchen am Bahnhof Shibuya gewartet. Statue in Tokio.", "emoji": "🗿"},
|
||
{"name": "Rin Tin Tin", "rasse": "Deutscher Schäferhund", "bekannt_fuer": "Filmhund der 1920er-Jahre. Rettete Warner Bros. vor dem Bankrott. Erster Hundestar Hollywoods.", "emoji": "🎬"},
|
||
{"name": "Laika", "rasse": "Mischling", "bekannt_fuer": "Erstes Lebewesen im Weltall (Sputnik 2, 1957). Wurde zur sowjetischen Weltraumpionierin.", "emoji": "🚀"},
|
||
{"name": "Endal", "rasse": "Labrador", "bekannt_fuer": "Assistenzhund in England. Erster Hund der eine EC-Karte am Geldautomaten benutzte.", "emoji": "💳"},
|
||
{"name": "Barry", "rasse": "Bernhardiner", "bekannt_fuer": "Legendärer Rettungshund der Alpen (1800–1812). Soll 40 Menschen das Leben gerettet haben.", "emoji": "🏔️"},
|
||
{"name": "Greyfriars Bobby", "rasse": "Skye Terrier", "bekannt_fuer": "14 Jahre lang das Grab seines Herrchens in Edinburgh bewacht. Statue und Pub benannt nach ihm.", "emoji": "⛪"},
|
||
]
|
||
|
||
|
||
# ------------------------------------------------------------------
|
||
# Schemas
|
||
# ------------------------------------------------------------------
|
||
class FilmVoteRequest(BaseModel):
|
||
bewertung: int # 1–5
|
||
|
||
|
||
class HundDesMonatsVoteRequest(BaseModel):
|
||
dog_id: int
|
||
|
||
|
||
# ------------------------------------------------------------------
|
||
# GET /api/movies/filme — Film-Liste mit optionaler User-Bewertung
|
||
# ------------------------------------------------------------------
|
||
@router.get("/filme")
|
||
async def get_filme(user=Depends(get_current_user_optional)):
|
||
user_ratings = {}
|
||
community_avgs = {}
|
||
|
||
with db() as conn:
|
||
if user:
|
||
rows = conn.execute(
|
||
"SELECT film_id, bewertung FROM movie_votes WHERE user_id=?",
|
||
(user["id"],),
|
||
).fetchall()
|
||
user_ratings = {r["film_id"]: r["bewertung"] for r in rows}
|
||
|
||
avg_rows = conn.execute(
|
||
"SELECT film_id, AVG(bewertung) as avg_bew, COUNT(*) as cnt FROM movie_votes GROUP BY film_id"
|
||
).fetchall()
|
||
community_avgs = {r["film_id"]: {"avg": round(r["avg_bew"], 1), "cnt": r["cnt"]} for r in avg_rows}
|
||
|
||
result = []
|
||
for film in FILME:
|
||
f = dict(film)
|
||
f["user_rating"] = user_ratings.get(film["id"])
|
||
if film["id"] in community_avgs:
|
||
f["bewertung_avg"] = community_avgs[film["id"]]["avg"]
|
||
f["bewertung_cnt"] = community_avgs[film["id"]]["cnt"]
|
||
else:
|
||
f["bewertung_cnt"] = 0
|
||
result.append(f)
|
||
|
||
return result
|
||
|
||
|
||
# ------------------------------------------------------------------
|
||
# POST /api/movies/filme/{film_id}/vote — Bewertung abgeben (Upsert)
|
||
# ------------------------------------------------------------------
|
||
@router.post("/filme/{film_id}/vote")
|
||
async def vote_film(film_id: str, data: FilmVoteRequest, user=Depends(get_current_user)):
|
||
if not any(f["id"] == film_id for f in FILME):
|
||
raise HTTPException(404, "Film nicht gefunden.")
|
||
if data.bewertung < 1 or data.bewertung > 5:
|
||
raise HTTPException(400, "Bewertung muss zwischen 1 und 5 liegen.")
|
||
|
||
with db() as conn:
|
||
conn.execute(
|
||
"""INSERT INTO movie_votes (user_id, film_id, bewertung)
|
||
VALUES (?, ?, ?)
|
||
ON CONFLICT(user_id, film_id) DO UPDATE SET bewertung=excluded.bewertung""",
|
||
(user["id"], film_id, data.bewertung),
|
||
)
|
||
row = conn.execute(
|
||
"SELECT AVG(bewertung) as avg_bew, COUNT(*) as cnt FROM movie_votes WHERE film_id=?",
|
||
(film_id,),
|
||
).fetchone()
|
||
|
||
return {
|
||
"film_id": film_id,
|
||
"bewertung_avg": round(row["avg_bew"], 1) if row["avg_bew"] else data.bewertung,
|
||
"bewertung_cnt": row["cnt"],
|
||
"user_rating": data.bewertung,
|
||
}
|
||
|
||
|
||
# ------------------------------------------------------------------
|
||
# GET /api/movies/hund-des-monats — Top-Votes des aktuellen Monats
|
||
# ------------------------------------------------------------------
|
||
@router.get("/hund-des-monats")
|
||
async def get_hund_des_monats(user=Depends(get_current_user_optional)):
|
||
monat = datetime.now().strftime("%Y-%m")
|
||
|
||
with db() as conn:
|
||
rows = conn.execute(
|
||
"""SELECT d.id, d.name, d.rasse, d.foto_url, u.name as besitzer_name,
|
||
COUNT(v.id) as stimmen
|
||
FROM hund_des_monats_votes v
|
||
JOIN dogs d ON d.id = v.dog_id
|
||
JOIN users u ON u.id = d.user_id
|
||
WHERE v.monat = ?
|
||
GROUP BY v.dog_id
|
||
ORDER BY stimmen DESC
|
||
LIMIT 10""",
|
||
(monat,),
|
||
).fetchall()
|
||
|
||
user_vote = None
|
||
if user:
|
||
row = conn.execute(
|
||
"SELECT dog_id FROM hund_des_monats_votes WHERE user_id=? AND monat=?",
|
||
(user["id"], monat),
|
||
).fetchone()
|
||
if row:
|
||
user_vote = row["dog_id"]
|
||
|
||
return {
|
||
"monat": monat,
|
||
"top": [dict(r) for r in rows],
|
||
"user_vote": user_vote,
|
||
}
|
||
|
||
|
||
# ------------------------------------------------------------------
|
||
# POST /api/movies/hund-des-monats/vote — Abstimmen (Auth required)
|
||
# ------------------------------------------------------------------
|
||
@router.post("/hund-des-monats/vote")
|
||
async def vote_hund_des_monats(data: HundDesMonatsVoteRequest, user=Depends(get_current_user)):
|
||
monat = datetime.now().strftime("%Y-%m")
|
||
|
||
with db() as conn:
|
||
# Prüfen ob Hund existiert und entweder dem User gehört oder öffentlich ist
|
||
dog = conn.execute(
|
||
"SELECT id, user_id, is_public FROM dogs WHERE id=?",
|
||
(data.dog_id,),
|
||
).fetchone()
|
||
if not dog:
|
||
raise HTTPException(404, "Hund nicht gefunden.")
|
||
if dog["user_id"] != user["id"] and not dog["is_public"]:
|
||
raise HTTPException(403, "Dieser Hund ist nicht öffentlich.")
|
||
|
||
conn.execute(
|
||
"""INSERT INTO hund_des_monats_votes (user_id, dog_id, monat)
|
||
VALUES (?, ?, ?)
|
||
ON CONFLICT(user_id, monat) DO UPDATE SET dog_id=excluded.dog_id""",
|
||
(user["id"], data.dog_id, monat),
|
||
)
|
||
|
||
# Aktuelle Stimmenanzahl für den gewählten Hund
|
||
row = conn.execute(
|
||
"SELECT COUNT(*) as cnt FROM hund_des_monats_votes WHERE dog_id=? AND monat=?",
|
||
(data.dog_id, monat),
|
||
).fetchone()
|
||
|
||
return {"dog_id": data.dog_id, "monat": monat, "stimmen": row["cnt"]}
|