from flask import Blueprint, jsonify, send_from_directory from server.database import Session from models.models import EventMedia import os from flask import Response, abort, session as flask_session # Blueprint for direct file downloads by media ID files_bp = Blueprint("files", __name__, url_prefix="/api/files") # Reuse the same media root convention as eventmedia.py BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) MEDIA_ROOT = os.path.join(BASE_DIR, "media") @files_bp.route("//", methods=["GET"]) def download_media_file(media_id: int, filename: str): """ Download the stored media file for a given EventMedia ID. URL format example: /api/files/26/LPUV4I_Folien_Nowitzki_Bewertungskriterien.pptx Behavior: - Looks up EventMedia by ID - Validates requested filename against stored metadata (best-effort) - Serves the file from server/media using the stored relative file_path """ session = Session() media = session.query(EventMedia).get(media_id) if not media: session.close() return jsonify({"error": "Not found"}), 404 # Prefer the stored relative file_path; fall back to the URL/filename rel_path = media.file_path or media.url # Basic filename consistency check to avoid leaking other files # Only enforce if media.url is present if media.url and os.path.basename(filename) != os.path.basename(media.url): session.close() return jsonify({ "error": "Filename mismatch", "expected": os.path.basename(media.url), "got": os.path.basename(filename), }), 400 abs_path = os.path.join(MEDIA_ROOT, rel_path) # Ensure file exists if not os.path.isfile(abs_path): session.close() return jsonify({"error": "File not found on server"}), 404 # Serve as attachment (download) directory = os.path.dirname(abs_path) served_name = os.path.basename(abs_path) session.close() return send_from_directory(directory, served_name, as_attachment=True) @files_bp.route("/converted/", methods=["GET"]) def download_converted(relpath: str): """Serve converted files (e.g., PDFs) relative to media/converted.""" abs_path = os.path.join(MEDIA_ROOT, relpath) if not abs_path.startswith(MEDIA_ROOT): return jsonify({"error": "Invalid path"}), 400 if not os.path.isfile(abs_path): return jsonify({"error": "File not found"}), 404 return send_from_directory(os.path.dirname(abs_path), os.path.basename(abs_path), as_attachment=True) @files_bp.route('/stream/') def stream_file(filename: str): """Stream a media file via nginx X-Accel-Redirect after basic auth checks. The nginx config must define an internal alias for /internal_media/ that points to the media folder (for example: /opt/infoscreen/server/media/). """ # Basic session-based auth: adapt to your project's auth logic if needed user_role = flask_session.get('role') if not user_role: return abort(403) # Normalize path to avoid directory traversal safe_path = os.path.normpath('/' + filename).lstrip('/') abs_path = os.path.join(MEDIA_ROOT, safe_path) if not os.path.isfile(abs_path): return abort(404) # Return X-Accel-Redirect header to let nginx serve the file efficiently internal_path = f'/internal_media/{safe_path}' resp = Response() resp.headers['X-Accel-Redirect'] = internal_path # Optional: set content-type if you want (nginx can detect it) return resp