mirror of
https://github.com/wahyd4/you-music.git
synced 2026-08-08 20:59:47 +10:00
177 lines
6.2 KiB
Python
177 lines
6.2 KiB
Python
import os
|
|
from contextlib import asynccontextmanager
|
|
from pathlib import Path
|
|
from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.staticfiles import StaticFiles
|
|
from fastapi.responses import FileResponse
|
|
from app.api import music, playlist, download, search, stream, artist, settings as settings_api
|
|
from app.core.config import settings
|
|
from app.db.session import init_db
|
|
from app.services.scheduler import start_scheduler, stop_scheduler
|
|
import subprocess
|
|
import sys
|
|
|
|
|
|
async def run_migrations():
|
|
"""Run database migrations on startup"""
|
|
try:
|
|
print("🔄 Running database migrations...")
|
|
# Get the backend directory
|
|
backend_dir = Path(__file__).parent
|
|
|
|
# Run alembic upgrade head
|
|
result = subprocess.run(
|
|
[sys.executable, "-m", "alembic", "upgrade", "head"],
|
|
cwd=backend_dir,
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
|
|
if result.returncode == 0:
|
|
print("✅ Database migrations completed successfully")
|
|
if result.stdout:
|
|
for line in result.stdout.strip().split('\n'):
|
|
if line.strip():
|
|
print(f" {line}")
|
|
else:
|
|
print("⚠️ Migration warnings:")
|
|
if result.stderr:
|
|
for line in result.stderr.strip().split('\n'):
|
|
if line.strip():
|
|
print(f" {line}")
|
|
except Exception as e:
|
|
print(f"⚠️ Could not run migrations: {e}")
|
|
print(" You may need to run migrations manually: ./migrate.sh upgrade")
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
# Startup
|
|
print("=" * 80)
|
|
print("🎵 YouMusic Starting Up")
|
|
print("=" * 80)
|
|
print(f"📂 BASE_DIR: {settings.BASE_DIR}")
|
|
print(f"📂 MUSIC_DIR: {settings.MUSIC_DIR}")
|
|
print(f"📂 LOCAL_MUSIC_DIR: {settings.LOCAL_MUSIC_DIR or 'Not configured'}")
|
|
print(f"📂 UPLOAD_DIR: {settings.UPLOAD_DIR}")
|
|
print(f"📂 TEMP_DIR: {settings.TEMP_DIR}")
|
|
print(f"📂 DATABASE: {settings.DATABASE_URL}")
|
|
|
|
cache_dir = os.path.join(settings.BASE_DIR, "data", "cache")
|
|
artist_cache_dir = os.path.join(cache_dir, "artists")
|
|
artist_images_dir = os.path.join(cache_dir, "artist_images")
|
|
|
|
print(f"📂 CACHE_DIR: {cache_dir}")
|
|
print(f"📂 ARTIST_CACHE: {artist_cache_dir}")
|
|
print(f"📂 ARTIST_IMAGES: {artist_images_dir}")
|
|
print(f"🎬 FFMPEG: {settings.FFMPEG_LOCATION}")
|
|
print(f"⏰ AUTO_SCAN: {settings.AUTO_SCAN_ENABLED}")
|
|
print(f"⏱️ SCAN_INTERVAL: {settings.SCAN_INTERVAL}s ({settings.SCAN_INTERVAL // 3600}h)")
|
|
print("=" * 80)
|
|
|
|
os.makedirs(settings.MUSIC_DIR, exist_ok=True)
|
|
os.makedirs(settings.UPLOAD_DIR, exist_ok=True)
|
|
os.makedirs(cache_dir, exist_ok=True)
|
|
os.makedirs(artist_cache_dir, exist_ok=True)
|
|
os.makedirs(artist_images_dir, exist_ok=True)
|
|
|
|
if settings.LOCAL_MUSIC_DIR:
|
|
os.makedirs(settings.LOCAL_MUSIC_DIR, exist_ok=True)
|
|
print(f"✅ LOCAL_MUSIC_DIR created: {settings.LOCAL_MUSIC_DIR}")
|
|
|
|
# Run database migrations
|
|
await run_migrations()
|
|
|
|
await init_db()
|
|
|
|
# Start scheduler
|
|
start_scheduler()
|
|
|
|
print("✅ All directories created and database initialized")
|
|
print("=" * 80)
|
|
yield
|
|
# Shutdown
|
|
stop_scheduler()
|
|
print("🛑 Scheduler stopped")
|
|
|
|
|
|
app = FastAPI(
|
|
title="YouMusic API",
|
|
description="A modern web music player with download capabilities",
|
|
version="1.0.0",
|
|
lifespan=lifespan,
|
|
)
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
# Mount static files
|
|
os.makedirs(settings.MUSIC_DIR, exist_ok=True)
|
|
app.mount("/music", StaticFiles(directory=settings.MUSIC_DIR), name="music")
|
|
|
|
# Mount cache directory for artist images
|
|
cache_dir = os.path.join(settings.BASE_DIR, "data", "cache")
|
|
os.makedirs(cache_dir, exist_ok=True)
|
|
app.mount("/cache", StaticFiles(directory=cache_dir), name="cache")
|
|
|
|
# Include routers
|
|
app.include_router(music.router, prefix="/api/music", tags=["music"])
|
|
app.include_router(playlist.router, prefix="/api/playlists", tags=["playlists"])
|
|
app.include_router(download.router, prefix="/api/download", tags=["download"])
|
|
app.include_router(search.router, prefix="/api/search", tags=["search"])
|
|
app.include_router(stream.router, prefix="/api", tags=["stream"])
|
|
app.include_router(artist.router, prefix="/api/artists", tags=["artists"])
|
|
app.include_router(settings_api.router, prefix="/api/settings", tags=["settings"])
|
|
|
|
|
|
# Health check endpoint (must be before catch-all route)
|
|
@app.get("/health")
|
|
async def health():
|
|
return {"status": "healthy"}
|
|
|
|
|
|
# Mount frontend static files (built React app)
|
|
static_dir = Path(__file__).parent / "static"
|
|
if static_dir.exists():
|
|
app.mount("/assets", StaticFiles(directory=str(static_dir / "assets")), name="assets")
|
|
|
|
@app.get("/")
|
|
async def serve_frontend():
|
|
"""Serve the React frontend"""
|
|
index_file = static_dir / "index.html"
|
|
if index_file.exists():
|
|
return FileResponse(index_file)
|
|
return {"message": "YouMusic API is running (frontend not built)"}
|
|
|
|
@app.get("/{full_path:path}")
|
|
async def serve_spa(full_path: str):
|
|
"""Serve React app for all non-API routes (SPA routing)"""
|
|
# Skip API routes, health check, and static assets
|
|
if full_path.startswith(("api/", "music/", "cache/", "assets/", "health", "docs", "redoc", "openapi.json")):
|
|
return {"error": "Not found"}
|
|
|
|
# For all other routes, serve index.html (React Router handles client-side routing)
|
|
index_file = static_dir / "index.html"
|
|
if index_file.exists():
|
|
return FileResponse(index_file)
|
|
return {"error": "Frontend not found"}
|
|
else:
|
|
@app.get("/")
|
|
async def root():
|
|
return {"message": "YouMusic API is running (frontend not built)"}
|
|
|
|
@app.get("/health")
|
|
async def health_no_frontend():
|
|
return {"status": "healthy"}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
|