new file: .env.example
new file: .gitignore new file: Dockerfile new file: README.md new file: app.py new file: app/__init__.py new file: app/game.py new file: app/models.py new file: app/routes.py new file: app/seed.py new file: data/movies.json new file: docker-compose.yml new file: requirements.txt new file: static/css/style.css new file: static/js/game.js new file: static/js/player.js new file: templates/index.html
This commit is contained in:
39
app/__init__.py
Normal file
39
app/__init__.py
Normal file
@@ -0,0 +1,39 @@
|
||||
import os
|
||||
|
||||
from flask import Flask
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
db = SQLAlchemy()
|
||||
|
||||
|
||||
def create_app():
|
||||
app = Flask(__name__)
|
||||
|
||||
# Konfiguration aus Umgebungsvariablen (Coolify)
|
||||
app.config["SECRET_KEY"] = os.environ.get("SECRET_KEY", "dev-secret-key")
|
||||
app.config["MAX_ATTEMPTS"] = int(os.environ.get("MAX_ATTEMPTS", "10"))
|
||||
app.config["DAILY_RESET_UTC"] = int(os.environ.get("DAILY_RESET_UTC", "9"))
|
||||
|
||||
db_host = os.environ.get("DB_HOST", "localhost")
|
||||
db_port = os.environ.get("DB_PORT", "5432")
|
||||
db_user = os.environ.get("DB_USER", "filmdle")
|
||||
db_password = os.environ.get("DB_PASSWORD", "")
|
||||
db_name = os.environ.get("DB_DATABASE", "filmdle")
|
||||
|
||||
app.config["SQLALCHEMY_DATABASE_URI"] = (
|
||||
f"postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}"
|
||||
)
|
||||
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
|
||||
|
||||
db.init_app(app)
|
||||
|
||||
from . import routes # noqa: F401
|
||||
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
# Seed (idempotent) beim Start, damit die App sofort funktioniert
|
||||
from .seed import seed_all
|
||||
|
||||
seed_all()
|
||||
|
||||
return app
|
||||
202
app/game.py
Normal file
202
app/game.py
Normal file
@@ -0,0 +1,202 @@
|
||||
import hashlib
|
||||
import random
|
||||
from datetime import date, datetime, time, timedelta
|
||||
|
||||
from . import db
|
||||
from .models import Movie, Player, Session, Universe
|
||||
|
||||
# Statusklassen fuer die Zellen
|
||||
GREEN = "green"
|
||||
YELLOW = "yellow"
|
||||
RED = "red"
|
||||
|
||||
|
||||
def get_max_attempts():
|
||||
from flask import current_app
|
||||
return current_app.config.get("MAX_ATTEMPTS", 10)
|
||||
|
||||
|
||||
def _game_date(today=None):
|
||||
"""Ermittelt den 'Spieltag' basierend auf der globalen Reset-Uhrzeit (UTC)."""
|
||||
from flask import current_app
|
||||
reset_hour = current_app.config.get("DAILY_RESET_UTC", 9)
|
||||
now = today or datetime.utcnow()
|
||||
game_date = now.date()
|
||||
# Vor der Resetzeit gilt noch der Vortag
|
||||
if now.time() < time(reset_hour % 24, 0, 0):
|
||||
game_date = game_date - timedelta(days=1)
|
||||
return game_date
|
||||
|
||||
|
||||
def daily_target(game_date=None):
|
||||
"""Bestimmt deterministisch den taeglichen Ziel-Film (fuer alle gleich)."""
|
||||
game_date = game_date or _game_date()
|
||||
movies = Movie.query.order_by(Movie.id).all()
|
||||
if not movies:
|
||||
return None
|
||||
idx = int(hashlib.sha256(str(game_date).encode()).hexdigest(), 16) % len(movies)
|
||||
return movies[idx]
|
||||
|
||||
|
||||
def random_target():
|
||||
movies = Movie.query.order_by(Movie.id).all()
|
||||
if not movies:
|
||||
return None
|
||||
return random.choice(movies)
|
||||
|
||||
|
||||
def get_or_create_player(nickname):
|
||||
player = Player.query.filter_by(nickname=nickname).first()
|
||||
if player is None:
|
||||
player = Player(nickname=nickname)
|
||||
db.session.add(player)
|
||||
db.session.commit()
|
||||
return player
|
||||
|
||||
|
||||
def _series(normalized_title):
|
||||
"""Erstellt eine Normalisierung fuer Titel-Vergleiche (nur Buchstaben, klein)."""
|
||||
return "".join(ch for ch in normalized_title.lower() if ch.isalnum())
|
||||
|
||||
|
||||
def match_platforms(guess_movie, target_movie):
|
||||
guess = set(p.name for p in guess_movie.platforms)
|
||||
target = set(p.name for p in target_movie.platforms)
|
||||
if guess == target:
|
||||
return GREEN
|
||||
# gelb: mindestens eine gemeinsame Platform
|
||||
if guess & target:
|
||||
return YELLOW
|
||||
return RED
|
||||
|
||||
|
||||
def match_genres(guess_movie, target_movie):
|
||||
guess = set(g.name for g in guess_movie.genres)
|
||||
target = set(g.name for g in target_movie.genres)
|
||||
if guess == target:
|
||||
return GREEN
|
||||
if guess & target:
|
||||
return YELLOW
|
||||
return RED
|
||||
|
||||
|
||||
def match_year(guess_year, target_year):
|
||||
if guess_year == target_year:
|
||||
return GREEN, "equal"
|
||||
if guess_year < target_year:
|
||||
return YELLOW, "up" # Ziel liegt hoeher (spaeter)
|
||||
return YELLOW, "down" # Ziel liegt tiefer (frueher)
|
||||
|
||||
|
||||
def match_fsk(guess_fsk, target_fsk):
|
||||
if guess_fsk == target_fsk:
|
||||
return GREEN
|
||||
if guess_fsk is None or target_fsk is None:
|
||||
return YELLOW if guess_fsk == target_fsk else RED
|
||||
return RED
|
||||
|
||||
|
||||
def match_director(guess_director, target_director):
|
||||
if not guess_director or not target_director:
|
||||
return GREEN if guess_director == target_director else RED
|
||||
if _series(guess_director) == _series(target_director):
|
||||
return GREEN
|
||||
return RED
|
||||
|
||||
|
||||
def match_studio(guess_studio, target_studio):
|
||||
if (guess_studio or "").lower() == (target_studio or "").lower():
|
||||
return GREEN
|
||||
return RED
|
||||
|
||||
|
||||
def match_budget(guess_budget, target_budget):
|
||||
if guess_budget is None or target_budget is None:
|
||||
return RED if guess_budget != target_budget else GREEN
|
||||
if guess_budget == target_budget:
|
||||
return GREEN, "equal"
|
||||
if guess_budget < target_budget:
|
||||
return YELLOW, "up"
|
||||
return YELLOW, "down"
|
||||
|
||||
|
||||
def match_universe(guess_universe, target_universe):
|
||||
# Fuer beide None => green (kein Universum)
|
||||
if (guess_universe or "") == (target_universe or ""):
|
||||
return GREEN
|
||||
if not guess_universe or not target_universe:
|
||||
return RED
|
||||
return RED
|
||||
|
||||
|
||||
def compare(guess_movie, target_movie):
|
||||
"""Vergleicht einen Tipp mit dem Ziel-Film und liefert die 9 Zellen."""
|
||||
ys, ydir = match_year(guess_movie.year, target_movie.year)
|
||||
bs, bdir = match_budget(guess_movie.budget, target_movie.budget)
|
||||
|
||||
return {
|
||||
"title": guess_movie.title,
|
||||
"platforms": {"status": match_platforms(guess_movie, target_movie), "value": sorted(p.name for p in guess_movie.platforms)},
|
||||
"genres": {"status": match_genres(guess_movie, target_movie), "value": sorted(g.name for g in guess_movie.genres)},
|
||||
"year": {"status": ys, "value": guess_movie.year, "direction": ydir, "diff": target_movie.year - guess_movie.year},
|
||||
"fsk": {"status": match_fsk(guess_movie.fsk, target_movie.fsk), "value": guess_movie.fsk},
|
||||
"studio": {"status": match_studio(guess_movie.studio.name if guess_movie.studio else None, target_movie.studio.name if target_movie.studio else None), "value": guess_movie.studio.name if guess_movie.studio else None},
|
||||
"director": {"status": match_director(guess_movie.director, target_movie.director), "value": guess_movie.director or None},
|
||||
"budget": {"status": bs, "value": guess_movie.budget, "direction": bdir, "diff": (target_movie.budget - guess_movie.budget) if guess_movie.budget is not None and target_movie.budget is not None else None},
|
||||
"universe": {"status": match_universe(guess_movie.universe.name if guess_movie.universe else None, target_movie.universe.name if target_movie.universe else None), "value": guess_movie.universe.name if guess_movie.universe else None},
|
||||
}
|
||||
|
||||
|
||||
def is_correct(compared):
|
||||
"""Prueft ob alle Zellen gruen sind (== Ziel gefunden)."""
|
||||
return all(cell["status"] == GREEN for cell in compared.values() if cell is not compared["title"])
|
||||
|
||||
|
||||
def finish_session(session, won):
|
||||
"""Markiert eine Runde als gewonnen/verloren und aktualisiert den Str eak."""
|
||||
session.status = "won" if won else "lost"
|
||||
session.won = won
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def _streak_for(player, mode, won):
|
||||
"""Berechnet den aktuellen Str eak (Sieg bzw. Niederlage) fuer Spieler+Modus."""
|
||||
sessions = (
|
||||
Session.query
|
||||
.filter_by(player_id=player.id, mode=mode)
|
||||
.order_by(Session.id.desc())
|
||||
.all()
|
||||
)
|
||||
count = 0
|
||||
for s in sessions:
|
||||
if s.won is None:
|
||||
continue
|
||||
if s.won == won:
|
||||
count += 1
|
||||
else:
|
||||
break
|
||||
return count
|
||||
|
||||
|
||||
def get_streaks(player_id):
|
||||
"""Liefert getrennte Sieg-/Niederlagen-Str eaks fuer Tages- und Unbegrenzt-Modus."""
|
||||
player = Player.query.get(player_id)
|
||||
if player is None:
|
||||
return None
|
||||
return {
|
||||
"daily": {
|
||||
"win": _streak_for(player, "daily", True),
|
||||
"loss": _streak_for(player, "daily", False),
|
||||
},
|
||||
"unlimited": {
|
||||
"win": _streak_for(player, "unlimited", True),
|
||||
"loss": _streak_for(player, "unlimited", False),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def existing_daily_session(player_id, game_date):
|
||||
"""Gibt eine vorhandene, abgeschlossene Tages-Runde zurueck (falls vorhanden)."""
|
||||
return Session.query.filter_by(
|
||||
player_id=player_id, mode="daily", date=game_date
|
||||
).order_by(Session.id.desc()).first()
|
||||
85
app/models.py
Normal file
85
app/models.py
Normal file
@@ -0,0 +1,85 @@
|
||||
from datetime import datetime
|
||||
|
||||
from . import db
|
||||
|
||||
# ---- Referenz-/Nachschlagetabellen (feste Liste) ----
|
||||
|
||||
movie_genres = db.Table(
|
||||
"movie_genres",
|
||||
db.Column("movie_id", db.Integer, db.ForeignKey("movies.id"), primary_key=True),
|
||||
db.Column("genre_id", db.Integer, db.ForeignKey("genres.id"), primary_key=True),
|
||||
)
|
||||
|
||||
movie_platforms = db.Table(
|
||||
"movie_platforms",
|
||||
db.Column("movie_id", db.Integer, db.ForeignKey("movies.id"), primary_key=True),
|
||||
db.Column("platform_id", db.Integer, db.ForeignKey("platforms.id"), primary_key=True),
|
||||
)
|
||||
|
||||
|
||||
class Genre(db.Model):
|
||||
__tablename__ = "genres"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(60), unique=True, nullable=False)
|
||||
|
||||
|
||||
class Platform(db.Model):
|
||||
__tablename__ = "platforms"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(60), unique=True, nullable=False)
|
||||
|
||||
|
||||
class Studio(db.Model):
|
||||
__tablename__ = "studios"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(60), unique=True, nullable=False)
|
||||
|
||||
|
||||
class Universe(db.Model):
|
||||
__tablename__ = "universes"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(60), unique=True, nullable=False)
|
||||
|
||||
|
||||
# ---- Haupttabelle ----
|
||||
|
||||
class Movie(db.Model):
|
||||
__tablename__ = "movies"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
title = db.Column(db.String(200), nullable=False, index=True)
|
||||
year = db.Column(db.Integer, nullable=False)
|
||||
fsk = db.Column(db.Integer, nullable=True) # 0, 6, 12, 16, 18
|
||||
studio_id = db.Column(db.Integer, db.ForeignKey("studios.id"), nullable=True)
|
||||
director = db.Column(db.String(120), nullable=True)
|
||||
budget = db.Column(db.Integer, nullable=True) # in USD
|
||||
universe_id = db.Column(db.Integer, db.ForeignKey("universes.id"), nullable=True)
|
||||
|
||||
studio = db.relationship("Studio")
|
||||
universe = db.relationship("Universe")
|
||||
genres = db.relationship("Genre", secondary=movie_genres, lazy="joined")
|
||||
platforms = db.relationship("Platform", secondary=movie_platforms, lazy="joined")
|
||||
|
||||
|
||||
# ---- Spieler & Runden ----
|
||||
|
||||
class Player(db.Model):
|
||||
__tablename__ = "players"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
nickname = db.Column(db.String(60), unique=True, nullable=False)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
|
||||
class Session(db.Model):
|
||||
__tablename__ = "sessions"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
player_id = db.Column(db.Integer, db.ForeignKey("players.id"), nullable=False)
|
||||
mode = db.Column(db.String(20), nullable=False) # 'daily' | 'unlimited'
|
||||
target_movie_id = db.Column(db.Integer, db.ForeignKey("movies.id"), nullable=False)
|
||||
status = db.Column(db.String(20), nullable=False, default="in_progress") # won|lost|in_progress
|
||||
attempts_used = db.Column(db.Integer, nullable=False, default=0)
|
||||
won = db.Column(db.Boolean, nullable=True) # True won / False lost / None in-progress
|
||||
date = db.Column(db.Date, nullable=True) # nur fuer 'daily'
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
player = db.relationship("Player")
|
||||
target = db.relationship("Movie")
|
||||
226
app/routes.py
Normal file
226
app/routes.py
Normal file
@@ -0,0 +1,226 @@
|
||||
from flask import Blueprint, jsonify, render_template, request
|
||||
|
||||
from . import db
|
||||
from .game import (
|
||||
_game_date, compare, daily_target, existing_daily_session, finish_session,
|
||||
get_max_attempts, get_or_create_player, get_streaks, is_correct, random_target,
|
||||
)
|
||||
from .models import Movie, Player, Session
|
||||
|
||||
bp = Blueprint("main", __name__)
|
||||
|
||||
|
||||
# ---------- Seiten ----------
|
||||
|
||||
@bp.route("/")
|
||||
def index():
|
||||
return render_template("index.html", mode="daily")
|
||||
|
||||
|
||||
@bp.route("/unlimited")
|
||||
def unlimited():
|
||||
return render_template("index.html", mode="unlimited")
|
||||
|
||||
|
||||
# ---------- API ----------
|
||||
|
||||
@bp.route("/api/player", methods=["POST"])
|
||||
def api_player():
|
||||
data = request.get_json(silent=True) or {}
|
||||
nickname = (data.get("nickname") or "").strip()
|
||||
if not nickname:
|
||||
return jsonify({"error": "nickname required"}), 400
|
||||
player = get_or_create_player(nickname)
|
||||
return jsonify({
|
||||
"player": {"id": player.id, "nickname": player.nickname},
|
||||
"streaks": get_streaks(player.id),
|
||||
})
|
||||
|
||||
|
||||
@bp.route("/api/search")
|
||||
def api_search():
|
||||
q = (request.args.get("q") or "").strip().lower()
|
||||
if not q:
|
||||
return jsonify([])
|
||||
movies = Movie.query.filter(Movie.title.ilike(f"%{q}%")).limit(10).all()
|
||||
return jsonify([
|
||||
{"id": m.id, "title": m.title, "year": m.year}
|
||||
for m in movies
|
||||
])
|
||||
|
||||
|
||||
@bp.route("/api/start", methods=["POST"])
|
||||
def api_start():
|
||||
data = request.get_json(silent=True) or {}
|
||||
player_id = data.get("player_id")
|
||||
mode = data.get("mode", "daily")
|
||||
if not player_id:
|
||||
return jsonify({"error": "player_id required"}), 400
|
||||
if Player.query.get(player_id) is None:
|
||||
return jsonify({"error": "player not found"}), 404
|
||||
if mode not in ("daily", "unlimited"):
|
||||
return jsonify({"error": "invalid mode"}), 400
|
||||
|
||||
max_attempts = get_max_attempts()
|
||||
|
||||
if mode == "daily":
|
||||
game_date = _game_date()
|
||||
target = daily_target(game_date)
|
||||
# Bereits heute gespielt?
|
||||
existing = existing_daily_session(player_id, game_date)
|
||||
if existing and existing.status != "in_progress":
|
||||
return jsonify({
|
||||
"session_id": existing.id,
|
||||
"mode": mode,
|
||||
"max_attempts": max_attempts,
|
||||
"status": existing.status,
|
||||
"already_played": True,
|
||||
"target": _movie_public(target),
|
||||
"attempts_used": existing.attempts_used,
|
||||
})
|
||||
# In-progress Rueckgabe, sonst neue Runde
|
||||
if existing:
|
||||
return jsonify({
|
||||
"session_id": existing.id,
|
||||
"mode": mode,
|
||||
"max_attempts": max_attempts,
|
||||
"status": existing.status,
|
||||
"already_played": False,
|
||||
"attempts_used": existing.attempts_used,
|
||||
})
|
||||
session = Session(
|
||||
player_id=player_id, mode=mode, target_movie_id=target.id,
|
||||
status="in_progress", attempts_used=0, date=game_date,
|
||||
)
|
||||
db.session.add(session)
|
||||
db.session.commit()
|
||||
return jsonify({
|
||||
"session_id": session.id,
|
||||
"mode": mode,
|
||||
"max_attempts": max_attempts,
|
||||
"status": "in_progress",
|
||||
"already_played": False,
|
||||
"attempts_used": 0,
|
||||
})
|
||||
|
||||
# Unbegrenzt: neue Zufallsrunde
|
||||
target = random_target()
|
||||
if target is None:
|
||||
return jsonify({"error": "no movies"}), 500
|
||||
session = Session(
|
||||
player_id=player_id, mode="unlimited", target_movie_id=target.id,
|
||||
status="in_progress", attempts_used=0,
|
||||
)
|
||||
db.session.add(session)
|
||||
db.session.commit()
|
||||
return jsonify({
|
||||
"session_id": session.id,
|
||||
"mode": mode,
|
||||
"max_attempts": max_attempts,
|
||||
"status": "in_progress",
|
||||
"already_played": False,
|
||||
"attempts_used": 0,
|
||||
})
|
||||
|
||||
|
||||
@bp.route("/api/guess", methods=["POST"])
|
||||
def api_guess():
|
||||
data = request.get_json(silent=True) or {}
|
||||
session_id = data.get("session_id")
|
||||
movie_id = data.get("movie_id")
|
||||
if not session_id or not movie_id:
|
||||
return jsonify({"error": "session_id and movie_id required"}), 400
|
||||
|
||||
session = Session.query.get(session_id)
|
||||
if session is None:
|
||||
return jsonify({"error": "session not found"}), 404
|
||||
if session.status != "in_progress":
|
||||
return jsonify({"error": "session already finished", "status": session.status}), 409
|
||||
|
||||
guess = Movie.query.get(movie_id)
|
||||
if guess is None:
|
||||
return jsonify({"error": "movie not found"}), 404
|
||||
|
||||
target = Movie.query.get(session.target_movie_id)
|
||||
max_attempts = get_max_attempts()
|
||||
|
||||
compared = compare(guess, target)
|
||||
session.attempts_used += 1
|
||||
used = session.attempts_used
|
||||
finished = False
|
||||
status = "in_progress"
|
||||
won = False
|
||||
|
||||
if is_correct(compared):
|
||||
finish_session(session, True)
|
||||
finished = True
|
||||
won = True
|
||||
status = "won"
|
||||
elif used >= max_attempts:
|
||||
finish_session(session, False)
|
||||
finished = True
|
||||
won = False
|
||||
status = "lost"
|
||||
|
||||
response = {
|
||||
"session_id": session.id,
|
||||
"attempts_used": used,
|
||||
"max_attempts": max_attempts,
|
||||
"status": status,
|
||||
"finished": finished,
|
||||
"won": won,
|
||||
"row": compared,
|
||||
}
|
||||
if finished:
|
||||
response["target"] = _movie_public(target)
|
||||
response["streaks"] = get_streaks(session.player_id)
|
||||
return jsonify(response)
|
||||
|
||||
|
||||
@bp.route("/api/streaks", methods=["POST"])
|
||||
def api_streaks():
|
||||
data = request.get_json(silent=True) or {}
|
||||
player_id = data.get("player_id")
|
||||
if not player_id:
|
||||
return jsonify({"error": "player_id required"}), 400
|
||||
return jsonify(get_streaks(player_id) or {"error": "player not found"})
|
||||
|
||||
|
||||
@bp.route("/api/new-game", methods=["POST"])
|
||||
def api_new_game():
|
||||
"""Startet (fuer den Unbegrenzt-Modus) eine neue Zufallsrunde."""
|
||||
data = request.get_json(silent=True) or {}
|
||||
player_id = data.get("player_id")
|
||||
if not player_id or Player.query.get(player_id) is None:
|
||||
return jsonify({"error": "player not found"}), 404
|
||||
target = random_target()
|
||||
if target is None:
|
||||
return jsonify({"error": "no movies"}), 500
|
||||
session = Session(
|
||||
player_id=player_id, mode="unlimited", target_movie_id=target.id,
|
||||
status="in_progress", attempts_used=0,
|
||||
)
|
||||
db.session.add(session)
|
||||
db.session.commit()
|
||||
return jsonify({
|
||||
"session_id": session.id,
|
||||
"mode": "unlimited",
|
||||
"max_attempts": get_max_attempts(),
|
||||
"status": "in_progress",
|
||||
"attempts_used": 0,
|
||||
})
|
||||
|
||||
|
||||
def _movie_public(movie):
|
||||
return {
|
||||
"id": movie.id,
|
||||
"title": movie.title,
|
||||
"year": movie.year,
|
||||
"fsk": movie.fsk,
|
||||
"studio": movie.studio.name if movie.studio else None,
|
||||
"director": movie.director,
|
||||
"budget": movie.budget,
|
||||
"universe": movie.universe.name if movie.universe else None,
|
||||
"genres": [g.name for g in movie.genres],
|
||||
"platforms": [p.name for p in movie.platforms],
|
||||
}
|
||||
89
app/seed.py
Normal file
89
app/seed.py
Normal file
@@ -0,0 +1,89 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
from . import db
|
||||
from .models import Genre, Movie, Platform, Studio, Universe
|
||||
|
||||
# Feste Referenzlisten (die moeglichen Werte fuer Plattform/Genre/Studio/Universum)
|
||||
PLATFORMS = [
|
||||
"Netflix", "Disney+", "Prime Video", "Max", "Apple TV+", "Paramount+",
|
||||
"Blu-ray", "DVD", "4K UHD", "Cinema"
|
||||
]
|
||||
|
||||
GENRES = [
|
||||
"Action", "Abenteuer", "Animation", "Komödie", "Krimi", "Drama", "Fantasy",
|
||||
"Horror", "Mystery", "Romantik", "Sci-Fi", "Thriller", "Western", "Musical", "Krieg", "Biografie"
|
||||
]
|
||||
|
||||
STUDIOS = [
|
||||
"Warner Bros", "Disney", "Universal", "Paramount", "Sony Pictures",
|
||||
"20th Century", "MGM", "Lionsgate", "A24", "DreamWorks", "New Line",
|
||||
"Orion", "Neon", "CJ Entertainment", "Show East", "Dimension", "Netflix"
|
||||
]
|
||||
|
||||
UNIVERSES = [
|
||||
"MCU", "Star Wars", "DC", "Wizarding World", "Fast & Furious", "Herr der Ringe", "Spider-Man"
|
||||
]
|
||||
|
||||
|
||||
def _get_or_create(model, name):
|
||||
if not name:
|
||||
return None
|
||||
obj = model.query.filter_by(name=name).first()
|
||||
if obj is None:
|
||||
obj = model(name=name)
|
||||
db.session.add(obj)
|
||||
db.session.flush()
|
||||
return obj
|
||||
|
||||
|
||||
def seed_all():
|
||||
"""Idempotent: legt Referenzlisten und Filme an, ohne bestehende zu duplizieren."""
|
||||
# Referenzlisten
|
||||
for name in PLATFORMS:
|
||||
_get_or_create(Platform, name)
|
||||
for name in GENRES:
|
||||
_get_or_create(Genre, name)
|
||||
for name in STUDIOS:
|
||||
_get_or_create(Studio, name)
|
||||
for name in UNIVERSES:
|
||||
_get_or_create(Universe, name)
|
||||
db.session.commit()
|
||||
|
||||
# Filme aus JSON laden
|
||||
_seed_movies_from_json()
|
||||
|
||||
|
||||
def _seed_movies_from_json():
|
||||
data_path = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "data", "movies.json"
|
||||
)
|
||||
if not os.path.exists(data_path):
|
||||
return
|
||||
|
||||
with open(data_path, "r", encoding="utf-8") as f:
|
||||
movies = json.load(f)
|
||||
|
||||
# Nur hinzufuegen, wenn noch kein Film in der DB ist (idempotent ueber den ganzen Datensatz)
|
||||
if Movie.query.count() > 0:
|
||||
return
|
||||
|
||||
for entry in movies:
|
||||
movie = Movie(
|
||||
title=entry["title"],
|
||||
year=entry["year"],
|
||||
fsk=entry.get("fsk"),
|
||||
director=entry.get("director"),
|
||||
budget=entry.get("budget"),
|
||||
studio=_get_or_create(Studio, entry.get("studio")),
|
||||
universe=_get_or_create(Universe, entry.get("universe")),
|
||||
)
|
||||
db.session.add(movie)
|
||||
db.session.flush()
|
||||
|
||||
for gname in entry.get("genres", []):
|
||||
movie.genres.append(_get_or_create(Genre, gname))
|
||||
for pname in entry.get("platforms", []):
|
||||
movie.platforms.append(_get_or_create(Platform, pname))
|
||||
|
||||
db.session.commit()
|
||||
Reference in New Issue
Block a user