docs: Improve CLAUDE.md with project documentation
Add comprehensive guidance for Claude Code working in this repository: - Alembic config location clarification - Docker Compose scope (database services only) - Frontend serving explanation (FastAPI root route) - Tailwind config location (in HTML script tag) - Pytest async configuration details Generated with [Claude Code](https://claude.com/claude-code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
@@ -0,0 +1,384 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
**AudiOhm** is a music streaming application (Spotify alternative) with YouTube audio streaming. The architecture consists of:
|
||||||
|
|
||||||
|
- **Backend**: FastAPI (Python 3.13+) with async SQLAlchemy, PostgreSQL, and Redis
|
||||||
|
- **Frontend**: Pure HTML/JavaScript with Tailwind CSS (no framework - served directly by FastAPI)
|
||||||
|
- **Streaming**: YouTube audio via yt-dlp
|
||||||
|
- **Authentication**: JWT-based auth
|
||||||
|
|
||||||
|
The project uses a service-oriented architecture with clear separation between API routes, business logic (services), data models, and schemas.
|
||||||
|
|
||||||
|
### File Locations
|
||||||
|
|
||||||
|
- **Backend code**: `/opt/audiOhm/backend/app/`
|
||||||
|
- **Frontend code**: `/opt/audiOhm/backend/app/static/` and `/opt/audiOhm/backend/app/templates/`
|
||||||
|
- **Tests**: `/opt/audiOhm/backend/tests/`
|
||||||
|
- **Migrations**: `/opt/audiOhm/backend/alembic/` (config at `/opt/audiOhm/backend/alembic.ini`)
|
||||||
|
- **Working directory for all commands**: `/opt/audiOhm/backend/`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Development Commands
|
||||||
|
|
||||||
|
### Backend Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start backend with hot-reload (from /opt/audiOhm/backend)
|
||||||
|
uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
|
||||||
|
|
||||||
|
# Or using the module directly
|
||||||
|
python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Operations
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From /opt/audiOhm/backend
|
||||||
|
|
||||||
|
# Run migrations
|
||||||
|
alembic upgrade head
|
||||||
|
|
||||||
|
# Create a new migration
|
||||||
|
alembic revision --autogenerate -m "description"
|
||||||
|
|
||||||
|
# Rollback one migration
|
||||||
|
alembic downgrade -1
|
||||||
|
|
||||||
|
# View migration history
|
||||||
|
alembic history
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From /opt/audiOhm/backend
|
||||||
|
|
||||||
|
# Run all tests
|
||||||
|
pytest
|
||||||
|
|
||||||
|
# Run specific test file
|
||||||
|
pytest tests/api/test_auth.py
|
||||||
|
|
||||||
|
# Run with coverage
|
||||||
|
pytest --cov=app tests/
|
||||||
|
|
||||||
|
# Run specific test function
|
||||||
|
pytest tests/api/test_library.py::test_get_liked_tracks -v
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Services (PostgreSQL + Redis)
|
||||||
|
|
||||||
|
**Note**: Docker Compose is only for database services (PostgreSQL + Redis), not the FastAPI backend.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From /opt/audiOhm (not /opt/audiOhm/backend)
|
||||||
|
docker-compose -f docker/docker-compose.yml up -d
|
||||||
|
|
||||||
|
# Stop services
|
||||||
|
docker-compose -f docker/docker-compose.yml down
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
docker-compose -f docker/docker-compose.yml logs -f postgres
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code Quality
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From /opt/audiOhm/backend
|
||||||
|
|
||||||
|
# Format code
|
||||||
|
black app/ tests/
|
||||||
|
|
||||||
|
# Lint code
|
||||||
|
ruff check app/ tests/
|
||||||
|
|
||||||
|
# Type checking
|
||||||
|
mypy app/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend Development
|
||||||
|
|
||||||
|
The frontend is a single-page application (SPA) with no build process:
|
||||||
|
|
||||||
|
- **HTML**: `app/templates/index.html` - All views in one file, served directly by FastAPI at root route `/`
|
||||||
|
- **JavaScript**: `app/static/js/app.js` - All frontend logic
|
||||||
|
- **CSS**: `app/static/css/style.css` - Minimal custom CSS (Tailwind is primary)
|
||||||
|
- **Tailwind Config**: Defined in `<script>` tag within `index.html` (no separate config file)
|
||||||
|
- **No build step**: Edit files directly and refresh browser
|
||||||
|
|
||||||
|
After editing frontend files:
|
||||||
|
- Hard refresh browser: `Ctrl+Shift+R` (Linux/Windows) or `Cmd+Shift+R` (Mac)
|
||||||
|
- Clear browser cache if needed
|
||||||
|
- Check browser console for errors
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
### Backend Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
backend/app/
|
||||||
|
├── api/
|
||||||
|
│ ├── dependencies.py # FastAPI dependencies (DB session, auth)
|
||||||
|
│ └── v1/ # API route handlers (auth, music, playlists, library)
|
||||||
|
├── core/
|
||||||
|
│ ├── config.py # Pydantic Settings with environment variables
|
||||||
|
│ ├── database.py # SQLAlchemy async engine and session management
|
||||||
|
│ ├── security.py # JWT, password hashing
|
||||||
|
│ └── rate_limiter.py # Rate limiting with slowapi
|
||||||
|
├── models/ # SQLAlchemy ORM models (user, track, artist, album, playlist, etc.)
|
||||||
|
├── schemas/ # Pydantic schemas for request/response validation
|
||||||
|
├── services/ # Business logic layer
|
||||||
|
│ ├── auth_service.py
|
||||||
|
│ ├── music_service.py
|
||||||
|
│ ├── youtube_service.py
|
||||||
|
│ ├── playlist_service.py
|
||||||
|
│ └── library_service.py
|
||||||
|
├── static/
|
||||||
|
│ ├── css/style.css # Minimal custom CSS (Tailwind is primary)
|
||||||
|
│ └── js/app.js # All frontend JavaScript (SPA functionality)
|
||||||
|
└── templates/
|
||||||
|
└── index.html # Single-page app HTML with Tailwind classes
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Architecture Patterns
|
||||||
|
|
||||||
|
1. **Dependency Injection**: FastAPI's `Depends()` for database sessions (`DBSession`) and authentication (`CurrentUser`)
|
||||||
|
2. **Service Layer**: All business logic in `/services` - API routes are thin, delegating to services
|
||||||
|
3. **Repository Pattern**: Services handle database operations using async SQLAlchemy
|
||||||
|
4. **Async/Await**: All database operations and external API calls are async
|
||||||
|
5. **JWT Authentication**: Token-based auth with `get_current_user` dependency
|
||||||
|
|
||||||
|
### Type Aliases (from `app/api/dependencies.py`)
|
||||||
|
|
||||||
|
Important type aliases used throughout the codebase:
|
||||||
|
- `DBSession` = `Annotated[AsyncSession, Depends(get_db)]` - Inject database session
|
||||||
|
- `CurrentUser` = `Annotated[User, Depends(get_current_user)]` - Inject authenticated user
|
||||||
|
- `CurrentUserOptional` = `Annotated[Optional[User], Depends(get_current_user_optional)]` - Inject user if authenticated
|
||||||
|
- `AuthServiceDep` = `Annotated[AuthService, Depends(get_auth_service)]` - Inject auth service
|
||||||
|
|
||||||
|
Use these in route signatures for clean dependency injection:
|
||||||
|
```python
|
||||||
|
async def my_endpoint(
|
||||||
|
db: DBSession,
|
||||||
|
current_user: CurrentUser,
|
||||||
|
) -> dict:
|
||||||
|
# db and current_user are automatically injected
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Models
|
||||||
|
|
||||||
|
Key relationships:
|
||||||
|
- `User` → `Playlist` (one-to-many)
|
||||||
|
- `User` → `LikedTrack` (one-to-many)
|
||||||
|
- `User` → `ListeningHistory` (one-to-many)
|
||||||
|
- `Playlist` → `PlaylistTrack` → `Track` (many-to-many through join table)
|
||||||
|
- `Track` → `Artist` (many-to-one)
|
||||||
|
- `Track` → `Album` (many-to-one)
|
||||||
|
|
||||||
|
### Frontend Architecture
|
||||||
|
|
||||||
|
- **Single Page Application**: All views are in `index.html`, shown/hidden via JavaScript
|
||||||
|
- **State Management**: `AppState` object in `app.js` holds current state (track, queue, user, etc.)
|
||||||
|
- **DOM Elements**: `DOM` object caches all DOM references for performance
|
||||||
|
- **API Communication**: `fetch()` calls to `/api/v1/*` endpoints
|
||||||
|
- **Styling**: Tailwind CSS utility classes with custom theme extension (defined in `<script>` in `index.html`)
|
||||||
|
- **No Framework**: Pure vanilla JavaScript with DOM manipulation
|
||||||
|
- **Window Assignment**: All functions called from HTML `onclick` handlers must be assigned to `window` object
|
||||||
|
|
||||||
|
Key frontend functions:
|
||||||
|
- `showPage(pageId)` - Switch between views (home, search, library, etc.)
|
||||||
|
- `updatePlayerUI()` - Update player display
|
||||||
|
- `showToast(message, type)` - Show notification to user
|
||||||
|
- All functions that interact with `AppState`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Important Implementation Details
|
||||||
|
|
||||||
|
### Database Session Management
|
||||||
|
|
||||||
|
All database sessions are async and automatically committed/rolled back:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# In API routes
|
||||||
|
async def my_endpoint(db: DBSession):
|
||||||
|
result = await db.execute(select(User))
|
||||||
|
return result.scalars().all()
|
||||||
|
# Session auto-commits after successful handler
|
||||||
|
```
|
||||||
|
|
||||||
|
### Authentication Flow
|
||||||
|
|
||||||
|
1. User receives JWT token on login (`/api/v1/auth/login`)
|
||||||
|
2. Token stored in `localStorage` (`authToken`)
|
||||||
|
3. All API requests include `Authorization: Bearer <token>` header
|
||||||
|
4. `get_current_user` dependency validates token and injects user
|
||||||
|
|
||||||
|
### YouTube Integration
|
||||||
|
|
||||||
|
- `YouTubeService` handles YouTube search and stream URL extraction
|
||||||
|
- Audio streamed via `/api/v1/music/youtube/{video_id}/stream` endpoint
|
||||||
|
- Uses yt-dlp for URL extraction (not downloading)
|
||||||
|
|
||||||
|
### Frontend Routing
|
||||||
|
|
||||||
|
Client-side routing in `app.js`:
|
||||||
|
- `showPage(pageId)` function switches between views (home, search, library, etc.)
|
||||||
|
- No server-side routing for pages (only API endpoints)
|
||||||
|
- State persists across navigation
|
||||||
|
|
||||||
|
**How the SPA is served**: FastAPI's root route `/` (in `app/main.py`) serves `index.html`, which contains the entire single-page application. All other routes are API endpoints under `/api/v1/`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Environment Variables (.env in backend/)
|
||||||
|
|
||||||
|
Key variables (see `app/core/config.py` for full list):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Database
|
||||||
|
POSTGRES_HOST=localhost
|
||||||
|
POSTGRES_PORT=5432
|
||||||
|
POSTGRES_USER=spotify
|
||||||
|
POSTGRES_PASSWORD=spotify_password
|
||||||
|
POSTGRES_DB=spotify_le_2
|
||||||
|
|
||||||
|
# Redis
|
||||||
|
REDIS_HOST=localhost
|
||||||
|
REDIS_PORT=6379
|
||||||
|
|
||||||
|
# JWT
|
||||||
|
SECRET_KEY=change-this-in-production
|
||||||
|
ACCESS_TOKEN_EXPIRE_MINUTES=15
|
||||||
|
|
||||||
|
# Application
|
||||||
|
DEBUG=true
|
||||||
|
LOG_LEVEL=INFO
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tailwind CSS Configuration
|
||||||
|
|
||||||
|
Defined in `<script>` tag in `index.html`:
|
||||||
|
- Primary colors: Cyan/blue (#0ea5e9)
|
||||||
|
- Accent colors: Pink/magenta (#ec4899)
|
||||||
|
- Dark mode enabled by default (`class="dark"` on `<html>`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### Creating a New API Endpoint
|
||||||
|
|
||||||
|
1. Add Pydantic schemas in `app/schemas/`
|
||||||
|
2. Add business logic in `app/services/`
|
||||||
|
3. Add route handler in `app/api/v1/`
|
||||||
|
4. Register router in `app/main.py`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```python
|
||||||
|
# app/api/v1/my_feature.py
|
||||||
|
from fastapi import APIRouter, Depends
|
||||||
|
from app.api.dependencies import DBSession, CurrentUser
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
@router.get("/my-endpoint")
|
||||||
|
async def get_my_data(
|
||||||
|
db: DBSession,
|
||||||
|
current_user: CurrentUser,
|
||||||
|
) -> dict:
|
||||||
|
# Your logic here - typically delegate to a service
|
||||||
|
return {"data": "response"}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding Database Fields
|
||||||
|
|
||||||
|
1. Update model in `app/models/`
|
||||||
|
2. Create migration: `alembic revision --autogenerate -m "description"`
|
||||||
|
3. Review generated migration in `alembic/versions/`
|
||||||
|
4. Apply migration: `alembic upgrade head`
|
||||||
|
5. Update Pydantic schemas
|
||||||
|
|
||||||
|
**Important**: Always review auto-generated migrations before applying!
|
||||||
|
|
||||||
|
### Frontend State Updates
|
||||||
|
|
||||||
|
When modifying frontend behavior:
|
||||||
|
1. Modify `AppState` object for state changes
|
||||||
|
2. Update DOM elements cached in `DOM` object
|
||||||
|
3. Call `updatePlayerUI()` or `renderTracks()` as needed
|
||||||
|
4. Use `showToast(message, type)` for user feedback
|
||||||
|
5. **CRITICAL**: Assign new functions to `window` if called from HTML `onclick`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```javascript
|
||||||
|
// Define function
|
||||||
|
function myNewFunction() {
|
||||||
|
// Your logic
|
||||||
|
}
|
||||||
|
|
||||||
|
// MUST assign to window if called from HTML
|
||||||
|
window.myNewFunction = myNewFunction;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
- **Unit tests**: Test services and business logic
|
||||||
|
- **API tests**: Test endpoints with test database
|
||||||
|
- **Fixtures**: `conftest.py` provides test DB and auth setup
|
||||||
|
- Test files in `backend/tests/api/` mirror API structure
|
||||||
|
|
||||||
|
**Pytest Configuration** (`pytest.ini`):
|
||||||
|
- `asyncio_mode = auto` - Required for async test functions
|
||||||
|
- `asyncio_default_fixture_loop_scope = function` - Isolated event loop per test
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Issues
|
||||||
|
|
||||||
|
### Database Connection Issues
|
||||||
|
- Ensure Docker services are running: `docker-compose -f docker/docker-compose.yml ps`
|
||||||
|
- Check connection string in `.env` matches Docker config
|
||||||
|
- Run migrations: `alembic upgrade head`
|
||||||
|
|
||||||
|
### Authentication Errors (401)
|
||||||
|
- Check `SECRET_KEY` is set in `.env`
|
||||||
|
- Verify token format: `Authorization: Bearer <token>`
|
||||||
|
- Frontend: Check `localStorage.getItem('authToken')`
|
||||||
|
|
||||||
|
### YouTube Streaming Issues
|
||||||
|
- yt-dlp may need updates: `pip install --upgrade yt-dlp`
|
||||||
|
- Some videos may be region-restricted or unavailable
|
||||||
|
|
||||||
|
### Frontend Not Updating
|
||||||
|
- Hard refresh browser: Ctrl+Shift+R
|
||||||
|
- Check browser console for JavaScript errors
|
||||||
|
- Verify `app.js` functions are assigned to `window` object (for HTML onclick handlers)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Default Credentials
|
||||||
|
|
||||||
|
Admin user (created on first run):
|
||||||
|
- Email: `admin@example.com`
|
||||||
|
- Password: `admin123`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Documentation References
|
||||||
|
|
||||||
|
- **Tailwind refactor info**: `/opt/audiOhm/TAILWIND_REFACTOR.md`
|
||||||
|
- **Quick styling reference**: `/opt/audiOhm/QUICK_REFERENCE.md`
|
||||||
|
- **Installation guide**: `/opt/audiOhm/INSTALLATION.md`
|
||||||
|
- **API docs**: Available at `http://localhost:8000/api/docs` (Swagger UI) when backend is running
|
||||||
Reference in New Issue
Block a user