feat: Add database migrations and auth system

- Add Alembic for database migrations
- Implement user authentication system
- Update frontend styles and components
- Add new test audio functionality
- Update stream management and UI
This commit is contained in:
oib
2025-07-02 09:37:03 +02:00
parent 39934115a1
commit 17616ac5b8
49 changed files with 5059 additions and 804 deletions

View File

@ -1,48 +1,116 @@
# list_streams.py — FastAPI route to list all public streams (users with stream.opus)
from fastapi import APIRouter
from fastapi import APIRouter, Request
from fastapi.responses import StreamingResponse, Response
from pathlib import Path
from fastapi.responses import StreamingResponse
import asyncio
router = APIRouter()
DATA_ROOT = Path("./data")
@router.get("/streams-sse")
def streams_sse():
return list_streams_sse()
async def streams_sse(request: Request):
print(f"[SSE] New connection from {request.client.host}")
print(f"[SSE] Request headers: {dict(request.headers)}")
# Add CORS headers for SSE
origin = request.headers.get('origin', '')
allowed_origins = ["https://dicta2stream.net", "http://localhost:8000", "http://127.0.0.1:8000"]
# Use the request origin if it's in the allowed list, otherwise use the first allowed origin
cors_origin = origin if origin in allowed_origins else allowed_origins[0]
headers = {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache, no-transform",
"Connection": "keep-alive",
"Access-Control-Allow-Origin": cors_origin,
"Access-Control-Allow-Credentials": "true",
"Access-Control-Expose-Headers": "Content-Type",
"X-Accel-Buffering": "no" # Disable buffering for nginx
}
# Handle preflight requests
if request.method == "OPTIONS":
print("[SSE] Handling OPTIONS preflight request")
headers.update({
"Access-Control-Allow-Methods": "GET, OPTIONS",
"Access-Control-Allow-Headers": request.headers.get("access-control-request-headers", "*"),
"Access-Control-Max-Age": "86400" # 24 hours
})
return Response(status_code=204, headers=headers)
print("[SSE] Starting SSE stream")
async def event_wrapper():
try:
async for event in list_streams_sse():
yield event
except Exception as e:
print(f"[SSE] Error in event generator: {str(e)}")
import traceback
traceback.print_exc()
yield f"data: {json.dumps({'error': True, 'message': str(e)})}\n\n"
return StreamingResponse(
event_wrapper(),
media_type="text/event-stream",
headers=headers
)
import json
import datetime
def list_streams_sse():
async def event_generator():
txt_path = Path("./public_streams.txt")
if not txt_path.exists():
print(f"[{datetime.datetime.now()}] [SSE] No public_streams.txt found")
yield f"data: {json.dumps({'end': True})}\n\n"
return
try:
with txt_path.open("r") as f:
for line in f:
line = line.strip()
if not line:
continue
try:
stream = json.loads(line)
print(f"[{datetime.datetime.now()}] [SSE] Yielding stream: {stream}")
yield f"data: {json.dumps(stream)}\n\n"
await asyncio.sleep(0) # Yield control to event loop
except Exception as e:
print(f"[{datetime.datetime.now()}] [SSE] JSON decode error: {e}")
continue # skip malformed lines
print(f"[{datetime.datetime.now()}] [SSE] Yielding end event")
yield f"data: {json.dumps({'end': True})}\n\n"
except Exception as e:
print(f"[{datetime.datetime.now()}] [SSE] Exception: {e}")
yield f"data: {json.dumps({'end': True, 'error': True})}\n\n"
return StreamingResponse(event_generator(), media_type="text/event-stream")
async def list_streams_sse():
print("[SSE] Starting stream generator")
txt_path = Path("./public_streams.txt")
if not txt_path.exists():
print(f"[SSE] No public_streams.txt found")
yield f"data: {json.dumps({'end': True})}\n\n"
return
try:
# Send initial ping
print("[SSE] Sending initial ping")
yield ":ping\n\n"
# Read and send the file contents
with txt_path.open("r") as f:
for line in f:
line = line.strip()
if not line:
continue
try:
# Parse the JSON to validate it
stream = json.loads(line)
print(f"[SSE] Sending stream data: {stream}")
# Send the data as an SSE event
event = f"data: {json.dumps(stream)}\n\n"
yield event
# Small delay to prevent overwhelming the client
await asyncio.sleep(0.1)
except json.JSONDecodeError as e:
print(f"[SSE] JSON decode error: {e} in line: {line}")
continue
except Exception as e:
print(f"[SSE] Error processing line: {e}")
continue
print("[SSE] Sending end event")
yield f"data: {json.dumps({'end': True})}\n\n"
except Exception as e:
print(f"[SSE] Error in stream generator: {str(e)}")
import traceback
traceback.print_exc()
yield f"data: {json.dumps({'error': True, 'message': str(e)})}\n\n"
finally:
print("[SSE] Stream generator finished")
def list_streams():
txt_path = Path("./public_streams.txt")