Sprint 11: Freunde & Chat + Phosphor-Icon-Vollmigration
- 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
This commit is contained in:
parent
96bd57f0ad
commit
097295c628
44 changed files with 9980 additions and 300 deletions
185
backend/routes/movies.py
Normal file
185
backend/routes/movies.py
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
"""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"]}
|
||||
Loading…
Add table
Add a link
Reference in a new issue