A Rust application to showcase badge awards in the AT Protocol ecosystem.
1# CLAUDE.md 2 3This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 5## Development Commands 6 7- **Build**: `cargo build` 8- **Run application**: `cargo run --bin showcase` 9- **Run tests**: `cargo test` 10- **Run single test**: `cargo test <test_name>` 11- **Run all tests with databases**: `./scripts/test-all.sh` 12- **Run PostgreSQL tests**: `./scripts/test-postgres.sh` 13- **Run SQLite tests**: `./scripts/test-sqlite.sh` 14- **Check code**: `cargo check` 15- **Format code**: `cargo fmt` 16- **Lint code**: `cargo clippy` 17 18## Project Overview 19 20This is a Rust application named "showcase" using the 2024 edition. The project is a badge awards showcase application for the AT Protocol community that consumes Jetstream events, validates badge signatures, stores data in multiple database backends (SQLite/PostgreSQL), and provides a web interface to display badge awards. 21 22## Architecture 23 24- `src/bin/showcase.rs`: Main application binary entry point 25- `src/lib.rs`: Library crate exposing all modules with `#![warn(missing_docs)]` 26- `src/config.rs`: Configuration management via environment variables 27- `src/storage/`: Modular storage implementations with multiple backend support 28 - `src/storage/mod.rs`: Storage trait definitions and common types 29 - `src/storage/sqlite.rs`: SQLite database implementation 30 - `src/storage/postgres.rs`: PostgreSQL database implementation 31 - `src/storage/file_storage.rs`: File storage abstraction (local and S3) 32- `src/process.rs`: Badge processing logic, signature validation, and image handling 33- `src/consumer.rs`: Jetstream consumer for AT Protocol badge award events 34- `src/http.rs`: Axum-based HTTP server with API endpoints and template rendering 35- `src/errors.rs`: Comprehensive error handling using `thiserror` 36- `src/templates.rs`: Template engine integration 37 - With `reload` feature: Uses `AutoReloader` for live template reloading in development 38 - With `embed` feature: Embeds templates at compile time, takes `http_external` and `version` parameters for production 39- `build.rs`: Build script for template embedding when using `embed` feature 40- `Dockerfile`: Multi-stage Docker build configuration for production deployment 41- `templates/`: HTML templates (base.html, index.html, identity.html) 42- `static/`: Static assets (Pico CSS, images, badge storage) 43 44## Docker Deployment 45 46The `Dockerfile` provides a multi-stage build configuration for production deployment: 47 48### Build Stage 49- Uses `rust:1.87-slim` base image with build dependencies (`pkg-config`, `libssl-dev`) 50- Configurable build arguments: 51 - `FEATURES` (default: `embed,postgres,s3`): Cargo features to enable 52 - `TEMPLATES` (default: `./templates`): Source path for template files 53 - `STATIC` (default: `./static`): Source path for static assets 54- Builds with `--no-default-features` and only specified features for production optimization 55- Template embedding is handled at build time when `embed` feature is enabled 56 57### Runtime Stage 58- Uses minimal `gcr.io/distroless/cc-debian12` image for security and size optimization 59- Includes comprehensive OCI metadata labels for container identification 60- Exposes port 8080 with configurable environment variables: 61 - `HTTP_STATIC_PATH=/app/static`: Path to static assets 62 - `HTTP_PORT=8080`: HTTP server port 63- Production-ready configuration with embedded templates and minimal attack surface 64 65### Build Commands 66- **Build image**: `docker build -t showcase .` 67- **Run container**: `docker run -p 8080:8080 showcase` 68- **Custom features**: `docker build --build-arg FEATURES=embed,sqlite -t showcase .` 69 70## Database Schema 71 72### Tables 73 74#### `badges` 75Stores badge definition records from the AT Protocol. 76 77Key columns: 78- `aturi`: AT-URI of the badge definition 79- `cid`: Content identifier for the badge record 80- `name`: Human-readable badge name 81- `image`: Optional image reference (blob CID) 82- `record`: Full JSON record of the badge definition (added in migration 002) 83- `count`: Number of awards using this badge 84- Primary key: `(aturi, cid)` 85 86#### `awards` 87Stores individual badge awards to users. 88 89Key columns: 90- `aturi`: AT-URI of the award record (primary key) 91- `cid`: Content identifier for the award record 92- `did`: DID of the recipient 93- `badge`: AT-URI of the associated badge 94- `badge_cid`: Content identifier of the badge record 95- `badge_name`: Human-readable badge name 96- `validated_issuers`: JSON array of validated issuer DIDs 97- `record`: Full JSON record of the award 98 99#### `identities` 100Stores resolved DID documents and identity information. 101 102Key columns: 103- `did`: Decentralized identifier (primary key) 104- `handle`: AT Protocol handle 105- `record`: Full DID document JSON 106 107### Database Type Differences 108 109- **PostgreSQL**: Uses `JSONB` for JSON columns (better performance) and `TIMESTAMPTZ` for timestamps 110- **SQLite**: Uses `JSON` for JSON columns and `TIMESTAMP` for timestamps 111 112## Error Handling 113 114All error strings must use this format: 115 116 error-showcase-<domain>-<number> <message>: <details> 117 118Example errors: 119 120* error-showcase-resolve-1 Multiple DIDs resolved for method 121* error-showcase-plc-1 HTTP request failed: https://google.com/ Not Found 122* error-showcase-key-1 Error decoding key: invalid 123 124Errors are defined as enums in `src/errors.rs` using the `thiserror` library, organized by domain: 125- `ConfigError`: Configuration-related errors 126- `ConsumerError`: Jetstream consumer errors 127- `HttpError`: HTTP server errors 128- `ProcessError`: Badge processing errors 129- `StorageError`: Database and file storage errors 130 131Each error variant follows the naming pattern and includes structured error messages. 132 133## Dependencies and Features 134 135### Features 136- `default = ["reload", "sqlite", "postgres", "s3"]`: Development mode with all features enabled 137- `embed`: Production mode with embedded templates via `minijinja-embed` 138- `reload`: Development mode with live template reloading via `minijinja-autoreload` 139- `sqlite`: SQLite database backend support 140- `postgres`: PostgreSQL database backend support 141- `s3`: S3-compatible object storage support for file operations 142 143### Key Dependencies 144- **Web Framework**: `axum` 0.8 with `axum-template` for MiniJinja integration 145- **Database**: `sqlx` 0.8 with SQLite, PostgreSQL, JSON, and async support 146- **Template Engine**: `minijinja` 2.7 with conditional embed/reload features 147- **AT Protocol**: Released versions `atproto-client`, `atproto-identity`, `atproto-record`, `atproto-jetstream` 0.6.0 148- **Image Processing**: `image` 0.25 for badge image handling 149- **Object Storage**: `minio` 0.3 for S3-compatible storage operations 150- **Async Runtime**: `tokio` 1.41 with multi-threaded runtime and signal handling 151- **HTTP Client**: `reqwest` 0.12 with TLS and middleware support, `reqwest-chain` 1.0 for middleware chaining 152- **Cryptography**: `k256`, `p256`, `ecdsa`, `elliptic-curve` 0.13 for signature validation 153- **Serialization**: `serde`, `serde_json`, `serde_ipld_dagcbor` 154- **Utilities**: 155 - `async-trait` 0.1 for async trait implementations 156 - `base64` 0.22 for encoding/decoding 157 - `bytes` 1.10 for byte manipulation 158 - `duration-str` 0.11 for parsing duration strings 159 - `hickory-resolver` 0.25 for DNS resolution 160 - `lru` 0.12 for caching 161 - `multibase` 0.9 for multibase encoding 162 - `rand` 0.8 for random number generation 163 - `sha2` 0.10 for SHA-2 hashing 164 - `tower-http` 0.5 for static file serving 165 - `ulid` 1.2 for ULID generation 166 167## Configuration 168 169The application is configured via environment variables. Key configuration includes: 170 171### Environment Files 172- `.env.dev`: SQLite development configuration 173- `.env.test`: PostgreSQL test configuration 174 175### Required Variables 176- `EXTERNAL_BASE`: External hostname for the site 177- `BADGE_ISSUERS`: Semicolon-separated list of trusted badge issuer DIDs 178 179### Optional Variables (with defaults) 180- `HTTP_PORT` (8080): HTTP server port 181- `HTTP_STATIC_PATH`: Path to static assets directory 182- `HTTP_TEMPLATES_PATH`: Path to templates directory 183- `DATABASE_URL` (sqlite://showcase.db): Database connection string (SQLite or PostgreSQL) 184- `BADGE_IMAGE_STORAGE` (./badges): Badge image storage location 185 - Local filesystem: Path to directory (e.g., `./badges`) 186 - S3-compatible storage: `s3://[key]:[secret]@hostname/bucket[/optional_prefix]` 187- `PLC_HOSTNAME` (plc.directory): PLC server hostname 188- `HTTP_CLIENT_TIMEOUT` (10s): HTTP client timeout 189- `JETSTREAM_CURSOR_PATH`: Optional path for persisting Jetstream cursor state 190- `CERTIFICATE_BUNDLES`: Semicolon-separated list of certificate bundle paths 191- `USER_AGENT`: Custom user agent string for HTTP requests 192- `DNS_NAMESERVERS`: Semicolon-separated list of DNS nameservers 193- `RUST_LOG` (showcase=info,info): Logging configuration 194 195## Code Style 196 197### Error Handling 198- Use `ShowcaseError` enum from `src/errors.rs` for all application errors 199- Implement `From` traits for automatic error conversion 200- Always include detailed error messages following the established format 201- Prefer `thiserror` over `anyhow` for structured errors 202 203### Result Type 204 205The codebase defines a type alias in `src/lib.rs`: 206```rust 207pub type Result<T> = std::result::Result<T, ShowcaseError>; 208``` 209 210All functions should use this `showcase::Result<T>` type for consistency, where errors are one of the `ShowcaseError` variants defined in `src/errors.rs`. 211 212### Logging 213 214Use tracing for structured logging. 215 216All calls to `tracing::error`, `tracing::warn`, `tracing::info`, `tracing::debug`, and `tracing::trace` should be fully qualified. 217 218Do not use the `println!` macro in library code. 219 220Async calls should be instrumented using the `.instrument()` that references the `use tracing::Instrument;` trait. 221 222### Async Patterns 223- Use `tokio::spawn` for concurrent tasks with `TaskTracker` for graceful shutdown 224- Implement proper cancellation token handling for all background tasks 225- Use `Arc` for shared state across async boundaries 226 227### Storage Operations 228- Use the `Storage` trait for database operations to support multiple backends 229- Implement the `FileStorage` trait for file operations (local and S3) 230- Use SQLx with compile-time checked queries where possible 231- Implement proper transaction handling for multi-step operations 232- Follow the established migration pattern for schema changes 233 234### Web Framework 235- Use Axum extractors and response types consistently 236- Implement proper error handling with `IntoResponse` for `ShowcaseError` 237- Use `AppState` pattern for dependency injection 238 239## Testing 240 241The project uses standard Rust testing with `#[cfg(test)]` attributes. Tests should be placed inline with the code they test or in separate test modules within each source file. 242 243### Test Scripts 244- `scripts/test-all.sh`: Runs tests against both SQLite and PostgreSQL databases 245- `scripts/test-postgres.sh`: Runs tests specifically with PostgreSQL backend using Docker 246- `scripts/test-sqlite.sh`: Runs tests specifically with SQLite backend 247 248The PostgreSQL test script automatically starts a PostgreSQL container for testing purposes.