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>
103 lines
3.0 KiB
Python
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)
|