Audiovault Developer Skill
🎯 Purpose
This skill transforms you into an expert Audiovault developer with deep knowledge of the project's architecture, features, and conventions.
📚 Project Knowledge
What is Audiovault?
Audiovault is a self-hosted music library manager that imports playlists from 7+ streaming platforms (Spotify, YouTube, Deezer, SoundCloud, Apple Music, Tidal, Amazon Music) and downloads tracks locally with intelligent fallback mechanisms.
Key Features:
- Multi-platform playlist import
- Robust download fallback system (cross-platform search, proxy support)
- Personal streaming server (Subsonic API)
- Watchlist with auto-sync (60-minute intervals)
- Beautiful glassmorphism UI with audio visualizer
- Last.fm integration (scrobbling, recommendations)
Architecture Overview
Frontend (React + TypeScript)
↓
REST API + WebSocket
↓
Backend (FastAPI + Python)
├─> SQLite/PostgreSQL (Data)
├─> Redis (Cache)
├─> yt-dlp (Downloads)
├─> APScheduler (Background jobs)
└─> External APIs (Spotify, YouTube, etc.)
Directory Structure (Critical Paths)
Audiovault/
├── backend/
│ ├── app/
│ │ ├── api/routes/ # API endpoints
│ │ ├── services/ # Business logic
│ │ ├── models/ # Database models
│ │ ├── schemas/ # Pydantic validation
│ │ ├── core/ # Config, security
│ │ └── main.py # App entry point
│ └── tests/
├── frontend/
│ ├── src/
│ │ ├── components/ # React components
│ │ ├── pages/ # Page components
│ │ ├── services/api/ # API clients
│ │ └── hooks/ # Custom hooks
│ └── package.json
├── .agent/ # AI agent config
└── docker-compose.yml # Deployment
🔑 Key Concepts
1. Download Fallback Chain
When a track download fails, Audiovault tries multiple strategies:
- Primary Source: Original URL from platform
- Alternative Queries: "Official Audio", "Lyrics Video"
- Cross-Platform: Try SoundCloud if YouTube fails
- Proxy: Use Invidious for geo-restricted content
Implementation: backend/app/services/download_service.py
2. Service Layer Pattern
Business logic lives in services, NOT in API routes:
# CORRECT
@router.post("/playlists/import")
async def import_playlist(
url: str,
db: AsyncSession = Depends(get_db)
):
service = PlaylistService(db)
return await service.import_playlist(url)
# WRONG (logic in route)
@router.post("/playlists/import")
async def import_playlist(url: str, db: AsyncSession = Depends(get_db)):
# ... 50 lines of business logic ...
3. Async Everything
Backend uses async SQLAlchemy and async FastAPI:
# CORRECT
async def get_playlist(db: AsyncSession, playlist_id: int):
result = await db.execute(select(Playlist).where(Playlist.id == playlist_id))
return result.scalar_one_or_none()
# WRONG (blocking)
def get_playlist(db: Session, playlist_id: int):
return db.query(Playlist).filter(Playlist.id == playlist_id).first()
4. React Query for State
Frontend uses React Query for server state:
// CORRECT
const { data: playlists, isLoading } = useQuery({
queryKey: ['playlists'],
queryFn: playlistApi.getAll
});
// WRONG (manual state management for server data)
const [playlists, setPlaylists] = useState([]);
useEffect(() => {
fetch('/api/playlists').then(res => res.json()).then(setPlaylists);
}, []);
5. WebSocket for Real-Time Updates
Download progress uses WebSocket notifications:
# Backend
await websocket_manager.broadcast({
"type": "download_progress",
"playlist_id": 123,
"progress": 45
})
// Frontend
const ws = new WebSocket('ws://localhost:8000/ws');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'download_progress') {
updateProgress(data.playlist_id, data.progress);
}
};
🛠️ Development Workflows
Adding a New Streaming Platform
- Create service in
backend/app/services/<platform>_service.py - Implement:
parse_url(url: str) -> ParsedURLfetch_playlist(url: str) -> PlaylistDatasearch_track(query: str) -> TrackURL
- Register in
backend/app/services/platform_registry.py - Add UI components in
frontend/src/components/platforms/ - Add platform logo and styling
- Update documentation
Adding a New API Endpoint
- Define Pydantic schema in
backend/app/schemas/ - Implement service method in
backend/app/services/ - Create route in
backend/app/api/routes/ - Add frontend API client in
frontend/src/services/api/ - Write tests in
backend/tests/andfrontend/src/__tests__/
Database Migration
- Modify model in
backend/app/models/ - Generate migration:
cd backend alembic revision --autogenerate -m "description" - Review generated migration in
backend/alembic/versions/ - Test migration:
alembic upgrade head # Apply alembic downgrade -1 # Rollback - Commit migration file
🧑💻 Code Conventions
Backend (Python)
- Formatting: Black (line length 100)
- Linting: Ruff
- Type Hints: Required for all function signatures
- Docstrings: Google style for public APIs
- Imports: Absolute imports, sorted
Frontend (TypeScript)
- Formatting: Prettier (2-space indentation)
- Linting: ESLint with TypeScript rules
- Components: Functional components with TypeScript
- Props: Interface definitions
- Exports: Named exports preferred
Commit Messages
feat(playlist): add Bandcamp integration
fix(download): handle geo-restricted content
docs(readme): update installation instructions
refactor(backend): improve service layer structure
test(frontend): add PlaylistCard component tests
🧛🔬 Testing Strategy
Backend Tests
- Location:
backend/tests/ - Framework: pytest with pytest-asyncio
- Coverage: Aim for 80%+
- Fixtures: Reusable in
conftest.py
Frontend Tests
- Location:
frontend/src/__tests__/orComponent.test.tsx - Framework: Vitest + React Testing Library
- Focus: User interactions, not implementation
🔗 Integration Points
Streaming Platform APIs
- Spotify: OAuth 2.0, requires client ID/secret
- YouTube: yt-dlp handles extraction
- Deezer: Public API, no authentication needed
- SoundCloud: Client ID required
- Apple Music: MusicKit JS (browser-based)
Subsonic API
- Version: v1.16.1
- Authentication: Legacy (plaintext password) or token-based
- Endpoints:
/rest/ping,/rest/getPlaylists,/rest/stream - Clients: Verified with Sonixd, Amperfy
⚡ Performance Tips
- Database Queries: Always use
selectinload()for relationships to avoid N+1 - React Rendering: Use
React.memo()for expensive components - API Calls: Batch requests where possible (e.g., bulk track imports)
- Downloads: Rate limit to avoid IP bans (configurable per platform)
- WebSocket: Throttle progress updates (max 10/second per playlist)
🐛 Common Pitfalls
❌ Forgetting to await async functions → Returns coroutine object, not result ❌ Not handling download failures → Use try/except with fallback chain ❌ Modifying database models without migration → Database out of sync ❌ Storing secrets in code → Use .env file ❌ Not validating user input → Use Pydantic schemas ❌ Mixing sync and async code → Backend must be fully async
📌 Quick Commands
# Start development environment
docker compose up -d --build
# Backend logs
docker compose logs -f backend
# Frontend logs
docker compose logs -f frontend
# Run backend tests
docker compose exec backend pytest
# Run frontend tests
docker compose exec frontend npm test
# Database shell
docker compose exec backend alembic current
# Format code
cd backend && black . && ruff check --fix .
cd frontend && npm run format && npm run lint --fix
Remember: Always read .agent/memory-bank/ files first to understand current project state before making changes.
Converted and distributed by TomeVault — claim your Tome and manage your conversions.