Initial commit, prod ready

This commit is contained in:
2026-02-22 23:18:57 -05:00
commit 0d03be47a0
41 changed files with 5623 additions and 0 deletions

108
backend/library.py Normal file
View File

@@ -0,0 +1,108 @@
"""
Music library helpers — read folder tree, delete files/folders.
File serving is handled directly in main.py via FastAPI's FileResponse.
"""
import shutil
from pathlib import Path
from typing import Any
from config import MUSIC_DIR
def get_library() -> list[dict[str, Any]]:
"""
Walk the Music directory and return a nested structure:
[
{
"artist": "Queen",
"albums": [
{
"album": "A Night at the Opera",
"tracks": [
{ "filename": "01 - Bohemian Rhapsody.mp3", "path": "Queen/A Night at the Opera/01 - Bohemian Rhapsody.mp3" }
]
}
]
}
]
"""
artists: list[dict[str, Any]] = []
if not MUSIC_DIR.exists():
return artists
for artist_dir in sorted(MUSIC_DIR.iterdir()):
if not artist_dir.is_dir():
continue
albums: list[dict[str, Any]] = []
for album_dir in sorted(artist_dir.iterdir()):
if not album_dir.is_dir():
continue
tracks = []
for track_file in sorted(album_dir.iterdir()):
if track_file.suffix.lower() not in {".mp3", ".flac", ".m4a", ".ogg"}:
continue
rel = track_file.relative_to(MUSIC_DIR)
tracks.append({
"filename": track_file.name,
"path": rel.as_posix(),
"size": track_file.stat().st_size,
})
if tracks:
albums.append({"album": album_dir.name, "tracks": tracks})
if albums:
artists.append({"artist": artist_dir.name, "albums": albums})
return artists
def delete_path(rel_path: str) -> None:
"""
Delete a file or directory relative to MUSIC_DIR.
Raises ValueError if the resolved path escapes MUSIC_DIR (path traversal guard).
"""
target = (MUSIC_DIR / rel_path).resolve()
# Security: ensure target is inside MUSIC_DIR
if not str(target).startswith(str(MUSIC_DIR)):
raise ValueError("Path traversal detected.")
if not target.exists():
raise FileNotFoundError(f"Not found: {rel_path}")
if target.is_dir():
shutil.rmtree(target)
else:
target.unlink()
# Clean up empty parent directories (album → artist)
_remove_empty_parents(target.parent)
def _remove_empty_parents(directory: Path) -> None:
"""Walk up and remove directories that are now empty, stopping at MUSIC_DIR."""
current = directory
while current != MUSIC_DIR and current.is_dir():
if not any(current.iterdir()):
current.rmdir()
current = current.parent
else:
break
def resolve_track_path(rel_path: str) -> Path:
"""
Resolve a relative path (e.g. 'Artist/Album/track.mp3') to an absolute path
inside MUSIC_DIR. Raises ValueError on path traversal.
"""
target = (MUSIC_DIR / rel_path).resolve()
if not str(target).startswith(str(MUSIC_DIR)):
raise ValueError("Path traversal detected.")
if not target.exists():
raise FileNotFoundError(f"Not found: {rel_path}")
return target