# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Development Commands - **Build**: `cargo build` - **Run application**: `cargo run --bin showcase` - **Run tests**: `cargo test` - **Run single test**: `cargo test ` - **Run all tests with databases**: `./scripts/test-all.sh` - **Run PostgreSQL tests**: `./scripts/test-postgres.sh` - **Run SQLite tests**: `./scripts/test-sqlite.sh` - **Check code**: `cargo check` - **Format code**: `cargo fmt` - **Lint code**: `cargo clippy` ## Project Overview This 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. ## Architecture - `src/bin/showcase.rs`: Main application binary entry point - `src/lib.rs`: Library crate exposing all modules with `#![warn(missing_docs)]` - `src/config.rs`: Configuration management via environment variables - `src/storage/`: Modular storage implementations with multiple backend support - `src/storage/mod.rs`: Storage trait definitions and common types - `src/storage/sqlite.rs`: SQLite database implementation - `src/storage/postgres.rs`: PostgreSQL database implementation - `src/storage/file_storage.rs`: File storage abstraction (local and S3) - `src/process.rs`: Badge processing logic, signature validation, and image handling - `src/consumer.rs`: Jetstream consumer for AT Protocol badge award events - `src/http.rs`: Axum-based HTTP server with API endpoints and template rendering - `src/errors.rs`: Comprehensive error handling using `thiserror` - `src/templates.rs`: Template engine integration - With `reload` feature: Uses `AutoReloader` for live template reloading in development - With `embed` feature: Embeds templates at compile time, takes `http_external` and `version` parameters for production - `build.rs`: Build script for template embedding when using `embed` feature - `Dockerfile`: Multi-stage Docker build configuration for production deployment - `templates/`: HTML templates (base.html, index.html, identity.html) - `static/`: Static assets (Pico CSS, images, badge storage) ## Docker Deployment The `Dockerfile` provides a multi-stage build configuration for production deployment: ### Build Stage - Uses `rust:1.87-slim` base image with build dependencies (`pkg-config`, `libssl-dev`) - Configurable build arguments: - `FEATURES` (default: `embed,postgres,s3`): Cargo features to enable - `TEMPLATES` (default: `./templates`): Source path for template files - `STATIC` (default: `./static`): Source path for static assets - Builds with `--no-default-features` and only specified features for production optimization - Template embedding is handled at build time when `embed` feature is enabled ### Runtime Stage - Uses minimal `gcr.io/distroless/cc-debian12` image for security and size optimization - Includes comprehensive OCI metadata labels for container identification - Exposes port 8080 with configurable environment variables: - `HTTP_STATIC_PATH=/app/static`: Path to static assets - `HTTP_PORT=8080`: HTTP server port - Production-ready configuration with embedded templates and minimal attack surface ### Build Commands - **Build image**: `docker build -t showcase .` - **Run container**: `docker run -p 8080:8080 showcase` - **Custom features**: `docker build --build-arg FEATURES=embed,sqlite -t showcase .` ## Database Schema ### Tables #### `badges` Stores badge definition records from the AT Protocol. Key columns: - `aturi`: AT-URI of the badge definition - `cid`: Content identifier for the badge record - `name`: Human-readable badge name - `image`: Optional image reference (blob CID) - `record`: Full JSON record of the badge definition (added in migration 002) - `count`: Number of awards using this badge - Primary key: `(aturi, cid)` #### `awards` Stores individual badge awards to users. Key columns: - `aturi`: AT-URI of the award record (primary key) - `cid`: Content identifier for the award record - `did`: DID of the recipient - `badge`: AT-URI of the associated badge - `badge_cid`: Content identifier of the badge record - `badge_name`: Human-readable badge name - `validated_issuers`: JSON array of validated issuer DIDs - `record`: Full JSON record of the award #### `identities` Stores resolved DID documents and identity information. Key columns: - `did`: Decentralized identifier (primary key) - `handle`: AT Protocol handle - `record`: Full DID document JSON ### Database Type Differences - **PostgreSQL**: Uses `JSONB` for JSON columns (better performance) and `TIMESTAMPTZ` for timestamps - **SQLite**: Uses `JSON` for JSON columns and `TIMESTAMP` for timestamps ## Error Handling All error strings must use this format: error-showcase-- :
Example errors: * error-showcase-resolve-1 Multiple DIDs resolved for method * error-showcase-plc-1 HTTP request failed: https://google.com/ Not Found * error-showcase-key-1 Error decoding key: invalid Errors are defined as enums in `src/errors.rs` using the `thiserror` library, organized by domain: - `ConfigError`: Configuration-related errors - `ConsumerError`: Jetstream consumer errors - `HttpError`: HTTP server errors - `ProcessError`: Badge processing errors - `StorageError`: Database and file storage errors Each error variant follows the naming pattern and includes structured error messages. ## Dependencies and Features ### Features - `default = ["reload", "sqlite", "postgres", "s3"]`: Development mode with all features enabled - `embed`: Production mode with embedded templates via `minijinja-embed` - `reload`: Development mode with live template reloading via `minijinja-autoreload` - `sqlite`: SQLite database backend support - `postgres`: PostgreSQL database backend support - `s3`: S3-compatible object storage support for file operations ### Key Dependencies - **Web Framework**: `axum` 0.8 with `axum-template` for MiniJinja integration - **Database**: `sqlx` 0.8 with SQLite, PostgreSQL, JSON, and async support - **Template Engine**: `minijinja` 2.7 with conditional embed/reload features - **AT Protocol**: Released versions `atproto-client`, `atproto-identity`, `atproto-record`, `atproto-jetstream` 0.6.0 - **Image Processing**: `image` 0.25 for badge image handling - **Object Storage**: `minio` 0.3 for S3-compatible storage operations - **Async Runtime**: `tokio` 1.41 with multi-threaded runtime and signal handling - **HTTP Client**: `reqwest` 0.12 with TLS and middleware support, `reqwest-chain` 1.0 for middleware chaining - **Cryptography**: `k256`, `p256`, `ecdsa`, `elliptic-curve` 0.13 for signature validation - **Serialization**: `serde`, `serde_json`, `serde_ipld_dagcbor` - **Utilities**: - `async-trait` 0.1 for async trait implementations - `base64` 0.22 for encoding/decoding - `bytes` 1.10 for byte manipulation - `duration-str` 0.11 for parsing duration strings - `hickory-resolver` 0.25 for DNS resolution - `lru` 0.12 for caching - `multibase` 0.9 for multibase encoding - `rand` 0.8 for random number generation - `sha2` 0.10 for SHA-2 hashing - `tower-http` 0.5 for static file serving - `ulid` 1.2 for ULID generation ## Configuration The application is configured via environment variables. Key configuration includes: ### Environment Files - `.env.dev`: SQLite development configuration - `.env.test`: PostgreSQL test configuration ### Required Variables - `EXTERNAL_BASE`: External hostname for the site - `BADGE_ISSUERS`: Semicolon-separated list of trusted badge issuer DIDs ### Optional Variables (with defaults) - `HTTP_PORT` (8080): HTTP server port - `HTTP_STATIC_PATH`: Path to static assets directory - `HTTP_TEMPLATES_PATH`: Path to templates directory - `DATABASE_URL` (sqlite://showcase.db): Database connection string (SQLite or PostgreSQL) - `BADGE_IMAGE_STORAGE` (./badges): Badge image storage location - Local filesystem: Path to directory (e.g., `./badges`) - S3-compatible storage: `s3://[key]:[secret]@hostname/bucket[/optional_prefix]` - `PLC_HOSTNAME` (plc.directory): PLC server hostname - `HTTP_CLIENT_TIMEOUT` (10s): HTTP client timeout - `JETSTREAM_CURSOR_PATH`: Optional path for persisting Jetstream cursor state - `CERTIFICATE_BUNDLES`: Semicolon-separated list of certificate bundle paths - `USER_AGENT`: Custom user agent string for HTTP requests - `DNS_NAMESERVERS`: Semicolon-separated list of DNS nameservers - `RUST_LOG` (showcase=info,info): Logging configuration ## Code Style ### Error Handling - Use `ShowcaseError` enum from `src/errors.rs` for all application errors - Implement `From` traits for automatic error conversion - Always include detailed error messages following the established format - Prefer `thiserror` over `anyhow` for structured errors ### Result Type The codebase defines a type alias in `src/lib.rs`: ```rust pub type Result = std::result::Result; ``` All functions should use this `showcase::Result` type for consistency, where errors are one of the `ShowcaseError` variants defined in `src/errors.rs`. ### Logging Use tracing for structured logging. All calls to `tracing::error`, `tracing::warn`, `tracing::info`, `tracing::debug`, and `tracing::trace` should be fully qualified. Do not use the `println!` macro in library code. Async calls should be instrumented using the `.instrument()` that references the `use tracing::Instrument;` trait. ### Async Patterns - Use `tokio::spawn` for concurrent tasks with `TaskTracker` for graceful shutdown - Implement proper cancellation token handling for all background tasks - Use `Arc` for shared state across async boundaries ### Storage Operations - Use the `Storage` trait for database operations to support multiple backends - Implement the `FileStorage` trait for file operations (local and S3) - Use SQLx with compile-time checked queries where possible - Implement proper transaction handling for multi-step operations - Follow the established migration pattern for schema changes ### Web Framework - Use Axum extractors and response types consistently - Implement proper error handling with `IntoResponse` for `ShowcaseError` - Use `AppState` pattern for dependency injection ## Testing The 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. ### Test Scripts - `scripts/test-all.sh`: Runs tests against both SQLite and PostgreSQL databases - `scripts/test-postgres.sh`: Runs tests specifically with PostgreSQL backend using Docker - `scripts/test-sqlite.sh`: Runs tests specifically with SQLite backend The PostgreSQL test script automatically starts a PostgreSQL container for testing purposes.