From c5e3004f0261fdb60dfda71dd337b0d898bd5fbe Mon Sep 17 00:00:00 2001 From: Gaumit Kauts <123269559+Gaumit-Kauts@users.noreply.github.com> Date: Sun, 15 Feb 2026 00:01:05 -0700 Subject: [PATCH] updated api_routed and db_queries for audio files --- .../__pycache__/api_routes.cpython-311.pyc | Bin 30212 -> 31972 bytes .../__pycache__/db_queries.cpython-311.pyc | Bin 20652 -> 23262 bytes backend/api_routes.py | 29 +++++++++ backend/db_queries.py | 57 +++++++++++++++++- 4 files changed, 85 insertions(+), 1 deletion(-) diff --git a/backend/__pycache__/api_routes.cpython-311.pyc b/backend/__pycache__/api_routes.cpython-311.pyc index 62518569a7a202961e4af67b850365603ab3a650..c53c11b0a217ef5671936550c04424f8ba81de1a 100644 GIT binary patch delta 6750 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 delta 5592 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 sXm4_f!m)xgMdL+dMQ`~_#v>`o6cu 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 YedK!ZXG46t6dV<2e{tZqCKDR)zrPO_vH$=8 delta 4495 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/audio-url") +def api_post_audio_url(post_id: int): + """ + Get signed URL for original audio/video so users can play it. + Private posts require owner user_id in query params. + """ + row = get_audio_post_by_id(post_id) + if not row: + return _error("Post not found.", 404) + + visibility = row.get("visibility") + owner_id = row.get("user_id") + requester_id = request.args.get("user_id", type=int) + expires_in = request.args.get("expires_in", default=3600, type=int) + expires_in = min(max(60, expires_in), 86400) + + if visibility == "private" and requester_id != owner_id: + return _error("Not authorized to access this private audio.", 403) + + try: + result = get_original_audio_url(post_id=post_id, expires_in=expires_in) + return jsonify(result) + except ValueError as e: + return _error(str(e), 404) + except Exception as e: + return _error(str(e), 500) + + @api.post("/posts//files") def api_add_file(post_id: int): payload = request.get_json(force=True, silent=False) or {} diff --git a/backend/db_queries.py b/backend/db_queries.py index 0aee46a..a9e3255 100644 --- a/backend/db_queries.py +++ b/backend/db_queries.py @@ -3,7 +3,7 @@ Supabase data layer aligned with TitanForge/schema.sql. """ import os -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, Optional, Tuple from dotenv import load_dotenv from supabase import Client, create_client @@ -36,6 +36,17 @@ def _paginate(page: int, limit: int) -> tuple[int, int]: return start, end +def _parse_bucket_path(stored_path: str) -> Tuple[str, str]: + """ + Convert stored path like 'archives/user/uuid/original/file.mp4' + into ('archives', 'user/uuid/original/file.mp4'). + """ + parts = (stored_path or "").split("/", 1) + if len(parts) != 2 or not parts[0] or not parts[1]: + raise ValueError(f"Invalid storage path format: {stored_path}") + return parts[0], parts[1] + + def upload_storage_object( bucket: str, object_path: str, @@ -56,6 +67,50 @@ def upload_storage_object( ) +def get_original_audio_url(post_id: int, expires_in: int = 3600) -> Dict[str, Any]: + """ + Return a signed URL for the original audio/video archive file. + """ + response = ( + supabase.table("archive_files") + .select("path, content_type") + .eq("post_id", post_id) + .eq("role", "original_audio") + .limit(1) + .execute() + ) + row = _first(response) + if not row: + raise ValueError("Original audio file not found for this post.") + + bucket, object_path = _parse_bucket_path(row["path"]) + + signed = supabase.storage.from_(bucket).create_signed_url(object_path, expires_in) + + # Supabase python client can return dict or object with .get depending on version. + if isinstance(signed, dict): + signed_url = ( + signed.get("signedURL") + or signed.get("signedUrl") + or signed.get("data", {}).get("signedUrl") + or signed.get("data", {}).get("signedURL") + ) + else: + signed_url = None + + if not signed_url: + raise RuntimeError("Failed to create signed URL for original audio.") + + return { + "post_id": post_id, + "bucket": bucket, + "object_path": object_path, + "content_type": row.get("content_type"), + "signed_url": signed_url, + "expires_in": expires_in, + } + + # ==================== Users ==================== def create_user(payload: Dict[str, Any]) -> Dict[str, Any]: