A decentralized event management and credentialing system built on atproto.

CLAUDE.md#

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview#

Acudo is a Rust project that integrates with the AT Protocol (Bluesky's decentralized social networking protocol). The project uses the AT Protocol SDK crates for client operations, identity management, real-time event streaming via Jetstream, and OAuth authentication with Axum web framework integration.

Development Commands#

Building#

cargo build           # Build in debug mode
cargo build --release # Build in release mode

Running#

cargo run             # Run in debug mode
cargo run --release   # Run in release mode

Testing#

cargo test            # Run all tests
cargo test <test_name> # Run a specific test
cargo test -- --nocapture # Run tests with stdout output

Code Quality#

cargo fmt             # Format code according to Rust style guidelines
cargo fmt -- --check  # Check formatting without modifying files
cargo clippy          # Run the Rust linter
cargo clippy -- -D warnings # Treat warnings as errors

Dependency Management#

cargo update          # Update dependencies to latest compatible versions
cargo tree            # Display dependency tree

Architecture Notes#

This project is built around the AT Protocol ecosystem with the following key dependencies:

  • atproto-client: Core client functionality for AT Protocol operations
  • atproto-identity: Identity resolution and management
  • atproto-jetstream: Real-time event streaming from AT Protocol networks
  • atproto-oauth: OAuth authentication implementation
  • atproto-oauth-aip: AT Protocol Improvement Proposal OAuth implementation
  • atproto-oauth-axum: OAuth integration for the Axum web framework

The project uses Rust edition 2024, indicating use of the latest language features.

Project Structure#

The project follows a modular structure with clear separation of concerns:

Storage Layer (src/storage/)#

  • identity.rs - PostgreSQL implementation of DidDocumentStorage trait for persistent DID document storage with pagination support
  • oauth.rs - PostgreSQL implementation of OAuthRequestStorage trait for OAuth request management
  • rsvp.rs - PostgreSQL implementation for RSVP record storage with optimized queries and pagination
  • rsvp_extras.rs - Storage for additional RSVP metadata on a per-identity basis (deprecated - no longer in use)
  • ticket.rs - PostgreSQL implementation for ticket record storage with pagination support
  • badge.rs - PostgreSQL implementations for badge definitions, awards, and award rules
  • acudo_rule.rs - Storage for badge award rules with JSON-based rule evaluation
  • atpoauth.rs - OAuth session storage implementation
  • event.rs - Event record storage with CRUD operations and JSON field search capabilities

HTTP Layer (src/http/)#

  • server.rs - Axum router configuration with CORS, tracing, and timeout middleware
  • context.rs - Web application context and dependency injection using Axum's FromRef
  • middleware_auth.rs - Authentication middleware for extracting user identity from OAuth sessions
  • templates.rs - Template engine configuration for Minijinja
  • errors.rs - HTTP error handling and conversion utilities
  • Utilities:
    • utility_pagination.rs - Pagination utilities for admin pages with URL building and view helpers
    • utility_auth.rs - Authentication helper functions
    • utility_common.rs - Common HTTP utilities (datetime formatting, admin access checks)
    • utility_rule_evaluation.rs - Badge rule evaluation for tickets
  • View Models:
    • records_view.rs - Data structures for record creation views
    • event_view.rs - Event display view models
  • handle_*.rs - Individual HTTP handlers for different endpoints

Admin Interface (src/http/handle_admin_*.rs)#

  • handle_admin.rs - Main admin dashboard with authentication and authorization
  • handle_admin_acudo_rules.rs - CRUD operations for badge award rules management
  • handle_admin_did_documents.rs - Read-only views for DID document records with pagination
  • handle_admin_rsvps.rs - Read-only views for RSVP records with pagination
  • handle_admin_tickets.rs - Read-only views for ticket records with pagination
  • handle_admin_events.rs - Event management with import capabilities and JSON field search

Badge System (src/badges/)#

  • service.rs - Badge award service with rule evaluation engine
  • rules.rs - Rule definitions and evaluation logic using datalogic-rs

Core Components#

  • config.rs - Configuration management with environment variable loading
  • consumer.rs - Jetstream consumer for real-time AT Protocol event processing
  • errors.rs - Centralized error handling with standardized error formats
  • event.rs - Event management and metadata handling
  • process.rs - Event processing pipeline for RSVP records
  • identity_cache.rs - Caching layer for identity resolution with configurable TTL and size limits
  • tito.rs - Tito ticketing platform integration for webhook processing
  • AT Protocol Integration (src/atproto/)
    • auth.rs - Authentication utilities for PDS and AIP OAuth flows
    • mod.rs - Module exports

Templates (templates/)#

  • admin_base.html - Base template for admin pages with Bulma CSS and pagination macros
  • admin_*.html - Individual admin page templates with responsive design
  • base.html - Base template for public pages
  • Public page templates for home, tickets, authentication flows

Database (migrations/)#

  • Complete database schema with migrations for all storage types
  • Optimized indexes for efficient querying and JSON operations
  • Automatic timestamp management with triggers

HTTP Features#

The application provides comprehensive HTTP functionality across public and admin interfaces:

Public Interface#

  1. Home Page (GET /) - Displays event information, recent activity, and call to action to get tickets (or view tickets if logged in)
  2. Tickets Page (GET /tickets) - Where users can purchase or view their tickets (requires authentication)
    • Evaluates ticket purchases using badge rule engine to determine RSVP eligibility
    • Shows pending records (RSVPs and badges) that can be created
    • Provides unified record creation interface with signature verification
  3. Record Creation (POST /records/create) - Unified endpoint for creating multiple AT Protocol records
    • Cryptographically verifies all records before creation
    • Supports batch creation of RSVPs and badge awards
    • Requires valid issuer signatures on all records
  4. OAuth Authentication Flow:
    • GET /auth/login - Initiates login flow with PDS or AIP backend selection
    • POST /auth/login - Handles login form submission
    • GET /auth/callback - OAuth callback handler for AT Protocol OAuth or AIP OAuth backend
    • GET /logout - Logout endpoint that clears session
  5. Tito Integration (POST /api/webhooks/tito) - Webhook endpoint for Tito event processing
  6. Well-Known Endpoints:
    • GET /.well-known/did.json - did:web resolution endpoint
    • GET /.well-known/atproto-did - AT Protocol DID resolution
  7. OAuth Metadata (when OAuth server enabled):
    • GET /oauth/client-metadata.json - OAuth client metadata
    • GET /.well-known/jwks.json - JSON Web Key Set for OAuth

Admin Interface (Authentication Required)#

  1. Admin Dashboard (GET /admin) - Main admin panel with navigation to management and viewing tools
  2. Badge Rule Management (Full CRUD):
    • GET /admin/acudo-rules - List all badge award rules with filtering
    • GET /admin/acudo-rules/new - Create new badge award rule form
    • POST /admin/acudo-rules - Create new badge award rule
    • GET /admin/acudo-rules/{id}/edit - Edit existing rule form
    • POST /admin/acudo-rules/{id} - Update existing rule
    • POST /admin/acudo-rules/{id}/delete - Delete rule
  3. Event Management:
    • GET /admin/events - List events or view specific event details
    • GET /admin/events/import - Event import form
    • POST /admin/events/import - Import events from external sources
  4. Data Viewing (Read-only with Pagination):
    • GET /admin/did-documents - List DID documents with pagination
    • GET /admin/did-documents/{did} - View specific DID document details
    • GET /admin/rsvps - List RSVP records with pagination
    • GET /admin/rsvps/{aturi} - View specific RSVP record details
    • GET /admin/tickets - List ticket records with pagination
    • GET /admin/tickets/{ticket_id} - View specific ticket record details

Storage Implementation#

The project implements a comprehensive storage layer using PostgreSQL with support for AT Protocol storage traits and custom business logic:

Identity Storage (src/storage/identity.rs)#

  • PostgresDidDocumentStorage: Thread-safe storage using PostgreSQL connection pool
  • Database Schema: DID documents stored as JSONB with GIN indexing for efficient JSON queries
  • Implementation: atproto_identity::storage::DidDocumentStorage trait
  • Pagination: list_documents_paginated(page, page_size) for admin interface
  • Admin Features: list_all_documents() for comprehensive document access

OAuth Storage (src/storage/oauth.rs)#

  • PostgresOAuthRequestStorage: Thread-safe storage for OAuth authorization requests
  • Database Schema: OAuth requests with automatic expiration handling and efficient indexing
  • Implementation: atproto_oauth::storage::OAuthRequestStorage trait
  • Security Features: Automatic filtering of expired requests, secure storage of cryptographic keys

RSVP Storage (src/storage/rsvp.rs)#

  • PostgresRsvpStorage: Thread-safe storage for RSVP records with optimized queries
  • Database Schema: RSVP records stored with JSONB for flexible structure and efficient JSON queries
  • Optimized Queries: list_rsvps_by_identity_and_event() provides efficient filtering at the database level
  • PostgreSQL Features: Uses PostgreSQL JSON operators (-> and ->>) for fast subject URI filtering
  • Pagination: list_rsvps_paginated(page, page_size) for admin interface

Event Storage (src/storage/event.rs)#

  • PostgresEventStorage: Storage for event records with AT Protocol integration
  • Database Schema: Events stored with AT-URI, CID, and JSONB record data
  • Query Methods: search_events_by_json_field() for JSON field searches
  • Pagination: list_events_paginated(page, page_size) for admin interface
  • CRUD Operations: Full lifecycle management of event records

Ticket Storage (src/storage/ticket.rs)#

  • PostgresTicketStorage: Storage for ticket purchases and registrations
  • Database Schema: Ticket records with optional DID linking and Tito integration
  • Query Methods: list_tickets_by_identity(), list_tickets_by_email()
  • Pagination: list_tickets_paginated(page, page_size) for admin interface

Badge System Storage (src/storage/badge.rs)#

  • PostgresBadgeDefinitionStorage: Badge definition records
  • PostgresBadgeAwardStorage: Badge award records with recipient tracking
  • PostgresBadgeAwardRuleStorage: Legacy badge rule storage (deprecated)

AcudoRule Storage (src/storage/acudo_rule.rs)#

  • PostgresAcudoRuleStorage: Modern badge award rules with JSON-based evaluation
  • Database Schema: Rules with ULID IDs, event AT-URIs, and JSON rule bodies
  • CRUD Operations: Full lifecycle management of badge award rules
  • Rule Engine: Integration with datalogic-rs for flexible rule evaluation

OAuth Session Storage (src/storage/atpoauth.rs)#

  • PostgresOAuthSessionStorage: Session management for authenticated users
  • Security: Secure session token storage and validation

Migrations: Located in migrations/ directory, run with sqlx migrate run Testing: All storage implementations include comprehensive test suites using #[sqlx::test] macro

Database Setup#

The storage implementation requires a PostgreSQL database. For testing:

# Set DATABASE_URL environment variable
export DATABASE_URL="postgres://user:password@localhost/database_name"

# Or for tests, it defaults to:
# postgres://postgres:postgres@localhost/acudo_test

Testing#

All storage implementations use the #[sqlx::test] macro for automatic database isolation:

cargo test storage::identity     # Run identity storage tests  
cargo test storage::oauth        # Run OAuth storage tests
cargo test storage::rsvp         # Run RSVP storage tests
cargo test storage::event        # Run event storage tests
cargo test storage::ticket       # Run ticket storage tests
cargo test storage::badge        # Run badge storage tests
cargo test storage::acudo_rule   # Run AcudoRule storage tests
cargo test storage               # Run all storage tests
cargo test badges                # Run badge system tests
cargo test http                  # Run HTTP handler tests
cargo test identity_cache        # Run identity cache tests

The test suite includes:

  • Storage Tests: Database operations with automatic rollback per test
  • Rule Engine Tests: Badge award rule evaluation scenarios
  • HTTP Handler Tests: Request/response validation
  • Integration Tests: End-to-end functionality validation

AT Protocol Development Considerations#

When developing with AT Protocol:

  • OAuth flows typically require environment configuration for client credentials
  • Jetstream connections require websocket support for real-time events
  • Identity operations may need DID (Decentralized Identifier) resolution
  • Client operations interact with PDS (Personal Data Server) instances
  • DID document storage requires PostgreSQL database setup and migrations
  • OAuth request storage handles sensitive cryptographic material (private keys)
  • Implement periodic cleanup of expired OAuth requests for security and performance

Identity Caching#

The application includes a caching layer for identity resolution to improve performance:

  • Configurable TTL: Set cache time-to-live with IDENTITY_CACHE_TTL_MINUTES (default: 10 minutes)
  • Size Limits: Configure maximum cache size with IDENTITY_CACHE_SIZE (default: 500 entries)
  • LRU Eviction: Least recently used entries are evicted when cache is full
  • Thread-Safe: Uses Arc for concurrent access
  • Transparent: Works seamlessly with existing IdentityResolver implementations

Record Creation and Signatures#

The application uses a unified record creation system with cryptographic verification:

  • Batch Creation: Users can create multiple AT Protocol records (RSVPs, badges) in a single transaction
  • Signature Verification: All records are cryptographically verified using atproto_record::signature::verify
  • Issuer Validation: Only records signed by the configured issuer DID are accepted
  • Pre-computed Records: Records are generated server-side with proper signatures
  • User Selection: Users can choose which records to create via checkboxes
  • Security First: Invalid signatures result in immediate rejection before any AT Protocol operations

Error Handling#

All error strings must use this format:

error-acudo-<domain>-<number> <message>: <details>

Example errors:

  • error-acudo-resolve-1 Multiple DIDs resolved for method
  • error-acudo-plc-1 HTTP request failed: https://google.com/ Not Found
  • error-acudo-key-1 Error decoding key: invalid

Errors should be represented as enums using the thiserror library when possible using src/errors.rs as a reference and example.

Avoid creating new errors with the anyhow!(...) macro.

Badge Award Configuration#

The application supports a comprehensive badge award system that can be configured through JSON files or environment variables. This system allows you to define badges and rules that determine when they should be awarded to users based on their ticket purchases.

Configuration Structure#

The badge configuration consists of three main parts:

  1. Badge Definitions - Define the badges themselves (name, description, image, etc.)
  2. Award Rules - Define when badges should be awarded based on conditions
  3. System Settings - Global configuration for the badge system

Configuration Loading#

Badge configuration is now managed through the database using the admin interface. The legacy file-based configuration has been replaced with a comprehensive admin panel that provides:

  1. Database Storage: All badge rules are stored in the acudo_rules table
  2. Admin Interface: Full CRUD operations through the web admin panel at /admin/acudo-rules
  3. Dynamic Loading: Rules are loaded from the database when the application starts
  4. Real-time Management: Changes can be made through the admin interface without application restart

Rule Engine Configuration#

The rule engine now uses datalogic-rs for JSON-based rule evaluation. Rules are stored in the database using the acudo_rules table and loaded automatically when the application starts. Each rule has the following structure:

  • id: A globally unique ID (ULID) for the rule
  • aturis: A list of AT-URIs (events and/or badges) that will be created if the rule evaluates to true
  • create: The rule logic that determines if the records should be created (evaluated using JSON logic)
  • signature_extra: Optional additional fields to include in the record signatures

Example rule structure:

{
  "id": "01BX5ZZKZK6K000000000000",
  "aturis": [
    "at://did:plc:cbkjy5n7bk3ax2wplmtjofq2/community.lexicon.calendar.event/3luzkrwivzm2a",
    "at://did:web:oauth-masterclass.atproto.camp/community.lexicon.calendar.event/3lwtxdryiw22b"
  ],
  "create": {
    "and": [
      {
        "==": [
          "Oct 4 Individual Seat (Early Bird)",
          {
            "val": "ticket_type"
          }
        ]
      },
      {
        "<": [
          {
            "val": "purchase_at"
          },
          "2025-08-25T00:00:00Z"
        ]
      }
    ]
  },
  "signature_extra": {}
}

The rule engine evaluates tickets with the following data structure:

  • ticket_type: The type of ticket purchased (e.g., "Oct 4 Individual Seat (Early Bird)")
  • ticket_paid: The amount paid for the ticket (in cents)
  • purchase_at: The ISO 8601 timestamp of when the ticket was purchased
  • email: The ticket purchaser's email address
  • reference: The ticket reference number
  • Additional fields from the ticket's raw data

When a rule's create condition evaluates to true, the system will prepare AT Protocol records for each URI in the aturis array. These records are then signed by the issuer and presented to the user for selective creation.

Common Rule Patterns#

The JSON Logic engine supports various comparison and logical operators:

Ticket Type Matching#

{"==": ["VIP Pass", {"val": "ticket_type"}]}

Date Comparisons#

{"<": [{"val": "purchase_at"}, "2025-08-01T00:00:00Z"]}  // Before date
{">": [{"val": "purchase_at"}, "2025-08-15T00:00:00Z"]}  // After date

Complex Conditions#

{
  "and": [
    {"==": ["early-bird", {"val": "ticket_type"}]},
    {"<": [{"val": "purchase_at"}, "2025-08-01T00:00:00Z"]}
  ]
}

Price-based Rules#

{">": [{"val": "ticket_paid"}, 10000]}  // More than $100 (in cents)

Rule Logic#

Rules support complex logic through condition grouping:

  • all_of - All conditions must be satisfied (AND logic)
  • any_of - At least one condition must be satisfied (OR logic) [Not yet implemented]
  • none_of - None of the conditions must be satisfied (NOT logic) [Not yet implemented]

Example Configuration#

Here's a complete example of an AcudoRule that creates both an RSVP and a badge award for early bird ticket purchases:

{
  "id": "01J8XYZ123ABC",
  "aturis": [
    "at://did:plc:event123/community.lexicon.calendar.event/main-event",
    "at://did:plc:issuer123/community.lexicon.badge.award/early-bird"
  ],
  "create": {
    "and": [
      {"==": ["General Admission (Early Bird)", {"val": "ticket_type"}]},
      {"<": [{"val": "purchase_at"}, "2025-08-01T00:00:00Z"]}
    ]
  },
  "signature_extra": {
    "special_status": "early_supporter"
  }
}

This rule will create an RSVP and award an early bird badge when someone purchases a "General Admission (Early Bird)" ticket before August 1st, 2025.

Configuration Validation#

The system automatically validates configurations for:

  • All rules reference valid badge IDs
  • No duplicate badge IDs
  • No duplicate rule names
  • Rule conditions are properly formatted

Integration with Existing Systems#

The badge configuration integrates seamlessly with the existing badge storage and rule engine:

  • Badge definitions are converted to AT Protocol records
  • Rules are converted to the existing BadgeRule and BadgeRuleSet types
  • The rule engine evaluates configured rules during ticket processing
  • Awards are stored in the database and can be displayed to users

Tito Integration#

The application integrates with Tito for ticket management through webhooks:

  • Webhook Endpoint: POST /api/webhooks/tito receives ticket purchase events
  • Ticket Processing: Automatically creates ticket records in the database
  • DID Linking: Associates tickets with user DIDs when available
  • Event Mapping: Maps Tito events to AT Protocol event records
  • Badge Evaluation: Triggers badge rule evaluation on new ticket purchases

Admin Interface#

The application provides a comprehensive admin interface for managing system data and configuration:

Authentication and Authorization#

  • Admin DIDs: Configure admin access using the ADMIN_DIDS environment variable (semicolon-separated list)
  • Secure Access: All admin pages require AT Protocol authentication and admin DID verification
  • Hidden Access: Returns 404 for unauthorized users to hide admin functionality existence

Admin Dashboard (/admin)#

  • Management Tools: CRUD operations for AcudoRules and RsvpExtras
  • Data Viewing: Read-only access to DID documents, RSVP records, and ticket records
  • Professional UI: Bulma CSS framework with responsive design
  • Navigation: Clear separation between management and viewing tools

Pagination Support#

All admin list pages support pagination for efficient handling of large datasets:

  • Default Page Size: 20 records per page (configurable 20-100)
  • URL Parameters: ?page=N&page_size=M for custom pagination
  • Navigation: Previous/Next buttons with proper disabled states
  • Info Display: Shows current page, page size, and total record counts

Management Features#

  • AcudoRule Management: Create, edit, and delete badge award rules with JSON validation
  • Event Management: Import and manage events with JSON field search capabilities
  • Data Viewing: Comprehensive read-only access to all system records
  • Search and Filter: Efficient database-level filtering and sorting

Environment Variables#

  • ADMIN_DIDS - Semicolon-separated list of DID strings for admin access (e.g., "did:plc:abc123;did:plc:def456")
  • DATABASE_URL - PostgreSQL connection string for all storage operations
  • IDENTITY_CACHE_SIZE - Maximum number of cached identity documents (default: 500)
  • IDENTITY_CACHE_TTL_MINUTES - Cache time-to-live in minutes (default: 10)
  • OAUTH_BACKEND - OAuth backend selection: "pds" or "aip" (AT Protocol Improvement Proposal)
  • ISSUER_DID - DID of the event issuer for record signing
  • ISSUER_PRIVATE_KEY - Private key for signing AT Protocol records