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.