A Rust application to showcase badge awards in the AT Protocol ecosystem.

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 <test_name>
  • 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-<domain>-<number> <message>: <details>

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:

pub type Result<T> = std::result::Result<T, ShowcaseError>;

All functions should use this showcase::Result<T> 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.