diff --git a/README.md b/README.md index c2709c5..215c808 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,143 @@ -# beatmatchr -# beatmatchr +# beatmatchr backend + +A lightweight FastAPI service that powers beatmatchr. This guide walks through +the steps required to run the backend locally. + +## Prerequisites + +- Python 3.10 or newer installed and available on your `PATH`. +- [`ffmpeg`](https://ffmpeg.org/) installed (macOS: `brew install ffmpeg`, + Ubuntu/Debian: `sudo apt install ffmpeg`). +- Docker and Docker Compose (v2) for running Postgres and Redis. + +## 1. Create and activate a virtual environment + +```bash +python3 -m venv .venv +source .venv/bin/activate # Windows: .venv\Scripts\activate +``` + +## 2. Install Python dependencies + +With the virtual environment active, install the requirements: + +```bash +pip install --upgrade pip +pip install -r requirements.txt +``` + +## 3. Start Postgres and Redis via Docker Compose + +A `docker-compose.yml` file is provided at the repository root. Launch the +services in the background: + +```bash +docker compose up -d +``` + +This spins up: + +- Postgres on port `5432` with database/user/password `beatmatchr`. +- Redis on port `6379` for task queue usage. + +If you need to tear everything down (including volumes), run: + +```bash +docker compose down -v +``` + +## 4. Configure environment variables + +Export environment variables that the backend expects. The defaults line up with +`docker-compose.yml`, so you can simply create a `.env` file (or export them in +your shell): + +```bash +export POSTGRES_USER=beatmatchr +export POSTGRES_PASSWORD=beatmatchr +export POSTGRES_DB=beatmatchr +export POSTGRES_HOST=localhost +export POSTGRES_PORT=5432 +export REDIS_HOST=localhost +export REDIS_PORT=6379 +export REDIS_DB=0 +export CELERY_BROKER_URL=redis://localhost:6379/0 +export CELERY_RESULT_BACKEND=redis://localhost:6379/0 +``` + +Feel free to adjust these if you change the Docker Compose configuration. + +## 5. Run database migrations / initialize tables + +If you have Alembic migrations configured, apply them with: + +```bash +alembic upgrade head +``` + +If migrations are not available yet, you can run your project-specific database +bootstrap script (for example `python -m backend.db init_db`) to create tables +manually. Ensure the `DATABASE_URL` environment variable is pointing at the +Postgres instance from the Docker Compose stack. + +## 6. Start the FastAPI application + +Launch the app with `uvicorn` (hot reload optional): + +```bash +uvicorn backend.main:app --reload +``` + +The API will be available at . + +## 7. Start the Celery/RQ worker + +For Celery, point to the Celery app defined in your project (replace the module +path if needed): + +```bash +celery -A backend.worker.celery_app worker --loglevel=info +``` + +If you are using RQ instead of Celery, start a worker referencing the same Redis +instance: + +```bash +rq worker beatmatchr --url redis://localhost:6379/0 +``` + +## 8. Example API usage + +Replace `PROJECT_ID` with the UUID returned from the *create project* call. + +### Create a project + +```bash +curl -X POST "http://localhost:8000/projects" \ + -H "Content-Type: application/json" \ + -d '{"name": "Demo Project", "description": "My first beatmatch"}' +``` + +### Upload audio to a project + +```bash +curl -X POST "http://localhost:8000/projects/PROJECT_ID/audio" \ + -F "file=@/path/to/local/file.wav" +``` + +### Add a source clip by URL + +```bash +curl -X POST "http://localhost:8000/projects/PROJECT_ID/sources" \ + -H "Content-Type: application/json" \ + -d '{"url": "https://example.com/audio.mp3", "start": 0, "end": 30}' +``` + +### Fetch lyrics for a track + +```bash +curl "http://localhost:8000/projects/PROJECT_ID/lyrics" +``` + +With the services running and environment variables configured, you should be +able to follow the steps above to interact with the beatmatchr backend locally. diff --git a/backend/config.py b/backend/config.py index b2773be..3f0d830 100644 --- a/backend/config.py +++ b/backend/config.py @@ -1,7 +1,64 @@ +"""Application configuration utilities.""" from __future__ import annotations import os from functools import lru_cache +from typing import Any, Dict + + +class Settings: + """Configuration values loaded from environment variables. + + The defaults are tuned for local development and match the docker-compose + configuration provided in this repository. Each value can be overridden by + setting the corresponding environment variable before starting the + application. + """ + + def __init__(self) -> None: + self.postgres_user: str = os.getenv("POSTGRES_USER", "beatmatchr") + self.postgres_password: str = os.getenv("POSTGRES_PASSWORD", "beatmatchr") + self.postgres_db: str = os.getenv("POSTGRES_DB", "beatmatchr") + self.postgres_host: str = os.getenv("POSTGRES_HOST", "localhost") + self.postgres_port: int = int(os.getenv("POSTGRES_PORT", "5432")) + + self.redis_host: str = os.getenv("REDIS_HOST", "localhost") + self.redis_port: int = int(os.getenv("REDIS_PORT", "6379")) + self.redis_db: int = int(os.getenv("REDIS_DB", "0")) + + self.database_url: str = os.getenv( + "DATABASE_URL", + ( + "postgresql+asyncpg://" + f"{self.postgres_user}:{self.postgres_password}" + f"@{self.postgres_host}:{self.postgres_port}/{self.postgres_db}" + ), + ) + + redis_base_url = f"redis://{self.redis_host}:{self.redis_port}/{self.redis_db}" + self.redis_url: str = os.getenv("REDIS_URL", redis_base_url) + self.celery_broker_url: str = os.getenv("CELERY_BROKER_URL", self.redis_url) + self.celery_result_backend: str = os.getenv( + "CELERY_RESULT_BACKEND", self.redis_url + ) + + def dict(self) -> Dict[str, Any]: + """Return the settings as a plain dictionary (useful for debugging).""" + + return { + "postgres_user": self.postgres_user, + "postgres_password": self.postgres_password, + "postgres_db": self.postgres_db, + "postgres_host": self.postgres_host, + "postgres_port": self.postgres_port, + "database_url": self.database_url, + "redis_host": self.redis_host, + "redis_port": self.redis_port, + "redis_db": self.redis_db, + "redis_url": self.redis_url, + "celery_broker_url": self.celery_broker_url, + "celery_result_backend": self.celery_result_backend, + } from pathlib import Path from typing import Optional @@ -47,6 +104,9 @@ class Settings(BaseSettings): @lru_cache() def get_settings() -> Settings: + """Return a cached instance of :class:`Settings`.""" + + return Settings() """Return cached application settings instance.""" settings = Settings() diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a86e4dd --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,36 @@ +version: "3.9" + +services: + postgres: + image: postgres:15 + restart: unless-stopped + environment: + POSTGRES_USER: beatmatchr + POSTGRES_PASSWORD: beatmatchr + POSTGRES_DB: beatmatchr + ports: + - "5432:5432" + volumes: + - postgres-data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U beatmatchr"] + interval: 10s + timeout: 5s + retries: 5 + + redis: + image: redis:7 + restart: unless-stopped + ports: + - "6379:6379" + volumes: + - redis-data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + +volumes: + postgres-data: + redis-data: diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f347096 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,12 @@ +fastapi>=0.110 +uvicorn[standard]>=0.24 +sqlalchemy>=2.0 +asyncpg>=0.29 +databases[postgresql]>=0.7 +alembic>=1.12 +aiofiles>=23.2 +pydantic>=2.0 +celery>=5.3 +redis>=5.0 +rq>=1.15 +python-dotenv>=1.0