Your music, beautifully tracked. All yours. (coming soon) teal.fm
teal-fm atproto
1# Teal Development Guidelines 2 3## Build Commands 4- Dev server: `turbo dev --filter=@teal/aqua` 5- Build all: `pnpm build` 6- Build Rust: `pnpm build:rust` 7- Test: `pnpm test` 8- DB migrate: `pnpm db:migrate` 9- DB reset: `pnpm db:reset` 10- Lexicon gen: `pnpm lex:gen` 11- Setup: `./scripts/setup-sqlx.sh` 12 13## Project Structure 14``` 15teal/ 16├── apps/aqua/ # Main Rust/Axum web app 17├── services/ 18│ ├── cadet/ # AT Protocol jetstream consumer 19│ ├── rocketman/ # Jetstream library 20│ ├── satellite/ # Processing service 21│ ├── types/ # Shared types 22│ └── migrations/ # SQLx migrations 23├── lexicons/fm.teal.alpha/ # AT Protocol schemas 24│ ├── feed/ # Music play types 25│ ├── actor/ # Profile types 26│ └── stats/ # Analytics 27├── tools/lexicon-cli/ # Code generation 28└── compose.yaml # Docker setup 29``` 30 31## Tech Stack 32- **Backend**: Rust (Axum, SQLx, Tokio, Serde, Tracing) 33- **Database**: PostgreSQL + Garnet/Redis 34- **Frontend**: TypeScript, Turbo, pnpm, Biome 35- **Protocol**: AT Protocol, IPLD/CAR, WebSocket streams 36- **Deploy**: Docker Compose, Traefik 37 38## Services 39- **Aqua**: HTTP API, OAuth, play submission, profiles, CAR import 40- **Cadet**: Real-time jetstream consumer, background jobs, metrics 41- **Rocketman**: Reusable WebSocket consumer framework 42- **Satellite**: Analytics, aggregation, external APIs 43 44## Database Schema 45```sql 46artists (mbid UUID, name TEXT, play_count INTEGER) 47releases (mbid UUID, name TEXT, play_count INTEGER) 48recordings (mbid UUID, name TEXT, play_count INTEGER) 49plays (uri TEXT, did TEXT, rkey TEXT, cid TEXT, track_name TEXT, 50 played_time TIMESTAMPTZ, recording_mbid UUID, release_mbid UUID, 51 submission_client_agent TEXT, music_service_base_domain TEXT) 52profiles (did TEXT, display_name TEXT, description TEXT, avatar BLOB, banner BLOB) 53``` 54 55## AT Protocol Integration 56### Music Play Schema (`fm.teal.alpha.feed.play`) 57```json 58{ 59 "trackName": "string (required)", 60 "trackMbId": "string", "recordingMbId": "string", 61 "duration": "integer", "artists": [{"name": "string", "mbid": "string"}], 62 "releaseName": "string", "releaseMbId": "string", 63 "isrc": "string", "originUrl": "string", 64 "musicServiceBaseDomain": "string", "submissionClientAgent": "string", 65 "playedTime": "datetime" 66} 67``` 68 69### Profile Schema (`fm.teal.alpha.actor.profile`) 70```json 71{ 72 "displayName": "string", "description": "string", 73 "featuredItem": {"mbid": "string", "type": "album|track|artist"}, 74 "avatar": "blob", "banner": "blob", "createdAt": "datetime" 75} 76``` 77 78## Core Features 79- **Music Tracking**: primarily MusicBrainz metadata, multi-platform support, scrobbling logic 80- **Social**: Federated profiles, activity feeds, featured items, rich text 81- **Real-time**: WebSocket streams, CAR processing, background jobs 82- **Analytics**: Play counts, charts, materialized views 83 84## Development Setup 85```bash 86# Install & setup 87pnpm install 88cp apps/aqua/.env.example apps/aqua/.env 89./scripts/setup-sqlx.sh 90 91# Start dependencies 92docker compose -f compose.dev.yml up -d garnet postgres 93 94# Run dev server 95turbo dev --filter=@teal/aqua 96# Access: http://localhost:3000 97``` 98 99## Database Operations 100```bash 101pnpm db:create # Create DB 102pnpm db:migrate # Run migrations 103pnpm db:migrate:revert # Revert migration 104pnpm db:reset # Reset DB 105pnpm db:prepare # Prepare queries 106``` 107 108## Lexicon Management 109```bash 110pnpm lex:gen # Generate types 111pnpm lex:watch # Watch & regenerate 112pnpm lex:validate # Validate schemas 113pnpm lex:diff # Show changes 114``` 115 116## Rust Development Guidelines 117- **Error Handling**: Always use `anyhow::Result<T>` for fallible functions, never `Result<T, String>` 118- **Async Patterns**: Use `async/await` throughout, avoid `tokio::spawn` unless necessary for parallelism 119- **Database**: SQLx queries MUST be compile-time verified with `cargo sqlx prepare` 120- **Types**: Use workspace types from `types` crate, avoid duplicating structs across services 121- **Imports**: Group std → external → workspace → local, use `use` not `extern crate` 122- **Lifetimes**: Avoid explicit lifetimes unless required, let compiler infer 123- **Memory**: Use `Arc<T>` for shared ownership, `Rc<T>` only in single-threaded contexts 124- **Serialization**: Use `#[derive(Serialize, Deserialize)]` with serde, not manual impls 125- **Logging**: Use `tracing::info!`, `tracing::error!` etc, not `println!` or `log` crate 126- **Testing**: Place unit tests in `#[cfg(test)]` modules, integration tests in `tests/` directory 127 128## Git Workflow Guidelines 129- **CLAUDE: You MUST work in a branch, you can NOT push to main and thus you need to make a PR per-feature** 130- **Branch naming**: `feature/short-description`, `fix/issue-description`, `refactor/component-name` 131- **Commit format**: `type(scope): description` - e.g. `feat(cadet): add jetstream reconnection`, `fix(aqua): handle empty play submissions` 132- **Commit types**: `feat`, `fix`, `refactor`, `test`, `docs`, `chore`, `perf`, `style` 133- **Branch lifecycle**: Create from `main`, keep updated with `git rebase main`, squash before merge 134- **PR requirements**: All tests pass, no clippy warnings, SQLx queries prepared, lexicons validated 135- **Commit size**: Small, focused commits - one logical change per commit 136- **Message body**: Include WHY for non-obvious changes, reference issues with `#123` 137- **Breaking changes**: Mark with `BREAKING:` in commit body, update migration docs 138 139## Code Standards 140- **TypeScript**: Biome format (`pnpm fix`), explicit types 141- **DB**: Snake_case, migrations, indexes, constraints 142- **AT Protocol**: URI format, lexicon validation, federation-ready 143 144## Testing 145```bash 146pnpm test # All tests 147pnpm test:rust # Rust only 148cargo test # Service tests 149cargo tarpaulin # Coverage 150``` 151 152## Deployment 153```bash 154# Environment vars: DATABASE_URL, REDIS_URL, AT_PROTOCOL_JWT_SECRET 155docker compose build 156docker compose up -d 157docker compose exec aqua-api pnpm db:migrate 158``` 159 160## Architecture 161- **Microservices**: Rust services with shared types 162- **Federation**: AT Protocol for decentralized social music 163- **Scrobble When**: <2min = full play, 2min = half duration (max 4min) 164- **Data Flow**: WebSocket Cadet PostgreSQL Aqua API 165- **Caching**: Redis for sessions, jobs, API responses 166- **Monitoring**: Prometheus metrics, structured logging 167 168## Key Patterns 169- **Lexicon-first**: Schema definitions drive code generation 170- **Event-driven**: Real-time processing via jetstream 171- **Content-addressable**: CAR files for federated data 172- **Type-safe**: SQLx compile-time query verification 173- **Async-first**: Tokio runtime throughout 174 175## Rust Anti-Patterns to Avoid 176- **DON'T**: Use `unwrap()` or `expect()` in production code - use proper error handling 177- **DON'T**: Clone unnecessarily - prefer borrowing with `&` or using `Arc<T>` 178- **DON'T**: Use `String` when `&str` suffices - avoid unnecessary allocations 179- **DON'T**: Mix blocking and async code - use `tokio::task::spawn_blocking` for CPU work 180- **DON'T**: Ignore compiler warnings - *fix all clippy lints before committing* 181- **DON'T**: Write raw SQL strings - use SQLx query macros or builder patterns 182- **DON'T**: Use global state - pass dependencies through function parameters or structs 183- **DON'T**: Implement `Debug` manually - use `#[derive(Debug)]` unless custom formatting needed