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>
This commit is contained in:
102
backend/app.py
Normal file
102
backend/app.py
Normal file
@@ -0,0 +1,102 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user