feat: serve melody .bsm binaries over plain HTTP instead of Firebase Storage
ESP32 devices can't spare the 40KB+ RAM a TLS client needs, so Firebase
Storage's HTTPS-only download URLs were blocking melody downloads. Binaries
are now written to local disk (./data/melody_binaries) and served through a
new unauthenticated /api/melodies/download/{pid} route, exposed publicly on
a separate melodies.bellsystems.net vhost (plain HTTP, no TLS) so the main
console domain can stay HTTPS-only with no exceptions. Preview audio still
uses Firebase Storage since it's only ever fetched by the HTTPS admin UI.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
from fastapi import APIRouter, Depends, UploadFile, File, Query, HTTPException, Response
|
||||
from fastapi.responses import FileResponse
|
||||
from typing import Optional
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from auth.models import TokenPayload
|
||||
@@ -13,6 +14,22 @@ from shared.audit import log_action
|
||||
router = APIRouter(prefix="/api/melodies", tags=["melodies"])
|
||||
|
||||
|
||||
@router.get("/download/{pid}")
|
||||
async def download_binary_by_pid(pid: str):
|
||||
"""Download a melody's .bsm binary by PID over plain HTTP.
|
||||
|
||||
No auth — ESP32 devices call this directly and can't afford a TLS client's
|
||||
memory footprint, so this is served from a plain-HTTP-only host
|
||||
(melodies.bellsystems.net) rather than the HTTPS console domain.
|
||||
"""
|
||||
path = await service.get_binary_path_by_pid(pid)
|
||||
return FileResponse(
|
||||
path=str(path),
|
||||
media_type="application/octet-stream",
|
||||
filename=f"{pid}.bsm",
|
||||
)
|
||||
|
||||
|
||||
@router.get("", response_model=MelodyListResponse)
|
||||
async def list_melodies(
|
||||
search: Optional[str] = Query(None),
|
||||
@@ -134,15 +151,20 @@ async def upload_file(
|
||||
|
||||
if file_type == "binary":
|
||||
content_type = "application/octet-stream"
|
||||
|
||||
url = service.upload_file_for_melody(
|
||||
melody_id=melody_id,
|
||||
melody_uid=melody.uid,
|
||||
melody_pid=melody.pid,
|
||||
file_bytes=contents,
|
||||
filename=file.filename,
|
||||
content_type=content_type,
|
||||
)
|
||||
url = service.save_binary_for_melody(
|
||||
melody_uid=melody.uid,
|
||||
pid=melody.pid,
|
||||
file_bytes=contents,
|
||||
)
|
||||
else:
|
||||
url = service.upload_file_for_melody(
|
||||
melody_id=melody_id,
|
||||
melody_uid=melody.uid,
|
||||
melody_pid=melody.pid,
|
||||
file_bytes=contents,
|
||||
filename=file.filename,
|
||||
content_type=content_type,
|
||||
)
|
||||
|
||||
# Update the melody document with the file URL
|
||||
if file_type == "preview":
|
||||
@@ -179,7 +201,7 @@ async def get_files(
|
||||
):
|
||||
"""Get storage file URLs for a melody."""
|
||||
melody = await service.get_melody(melody_id)
|
||||
return service.get_storage_files(melody_id, melody.uid)
|
||||
return service.get_storage_files(melody_id, melody.uid, melody.pid)
|
||||
|
||||
|
||||
@router.patch("/{melody_id}/set-outdated", response_model=MelodyInDB)
|
||||
|
||||
Reference in New Issue
Block a user