Files
media_crawler/backend/app.py
renjue 82581d2949 Implement full media crawler workflow with Flask backend and Vue frontend.
Add TMDB search and media detail pages, HDHive resource ingestion flow, unified error handling, Docker single-container runtime, and project docs/config updates for local deployment.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 16:16:18 +08:00

103 lines
3.0 KiB
Python

from flask import Flask, jsonify, request
from flask_cors import CORS
from config import Config
from error_handling import AppServiceError, normalize_exception
from services.media_service import (
get_media_resources,
search_media_by_keyword,
validate_media_query,
validate_media_type,
)
from services.orchestrator import run_ingest_task
from storage import init_db, list_logs, list_tasks
def create_app():
def error_response(error, fallback_status=400):
normalized = normalize_exception(error)
status = normalized.status or fallback_status
return jsonify({"error": normalized.to_dict()}), status
app = Flask(__name__)
CORS(app)
init_db()
@app.get("/api/health")
def health():
return jsonify({"ok": True})
@app.post("/api/tasks")
def create_task():
data = request.get_json(silent=True) or {}
tmdb_id = str(data.get("tmdbId", "")).strip()
media_type = data.get("type", "movie")
keyword = str(data.get("keyword", "")).strip()
if not tmdb_id:
return error_response(
AppServiceError(
"tmdbId is required",
category="validation",
code="INVALID_INPUT",
status=400,
provider="api",
),
400,
)
if media_type not in ("movie", "tv"):
return error_response(
AppServiceError(
"type must be movie or tv",
category="validation",
code="INVALID_INPUT",
status=400,
provider="api",
),
400,
)
task = run_ingest_task(
{
"tmdbId": tmdb_id,
"type": media_type,
"keyword": keyword,
"slug": str(data.get("slug", "")).strip(),
}
)
return jsonify(task)
@app.get("/api/media/search")
def search_media_handler():
query = str(request.args.get("query", "")).strip()
media_type = str(request.args.get("type", "movie")).strip()
try:
validate_media_query(query, media_type)
return jsonify(search_media_by_keyword(query, media_type))
except Exception as error:
return error_response(error)
@app.get("/api/media/<media_type>/<tmdb_id>")
def media_detail_handler(media_type, tmdb_id):
try:
validate_media_type(media_type)
return jsonify(get_media_resources(media_type, str(tmdb_id)))
except Exception as error:
return error_response(error)
@app.get("/api/tasks")
def get_tasks():
return jsonify({"items": list_tasks()})
@app.get("/api/tasks/<task_id>/logs")
def get_task_logs(task_id):
return jsonify({"items": list_logs(task_id)})
return app
app = create_app()
if __name__ == "__main__":
app.run(host="0.0.0.0", port=Config.FLASK_RUN_PORT, debug=Config.FLASK_DEBUG)