The smokesignal.events web application

CLAUDE.md#

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

Project Overview#

Smokesignal is a Rust-based event and RSVP management application built for the AT Protocol ecosystem. It provides decentralized identity management, OAuth authentication, event coordination, real-time event streaming, search capabilities, and email notifications. The application features RSVP acceptance workflows, private event content with conditional display, profile caching, and content storage with S3 support. It supports both standard AT Protocol OAuth (PDS) and AIP (AT Protocol Improvement Proposal) OAuth flows, with backend selection determined by runtime configuration.

Tech Stack#

  • Language: Rust (edition 2024, minimum version 1.90)
  • Web Framework: Axum with async/await
  • Database: PostgreSQL with SQLx migrations
  • Caching: Redis/Valkey for sessions, token management, and DID documents
  • Search: OpenSearch for full-text event search
  • Storage: Filesystem (default) or S3-compatible object storage
  • Templates: MiniJinja (server-side rendering with optional reloading, localized structure)
  • Frontend: HTMX + Alpine.js + Bulma CSS + FontAwesome
  • Frontend Build: Vite 6 + TypeScript 5.7 + Biome 2 + LightningCSS
  • Authentication: AT Protocol OAuth with JOSE/JWT (P-256 ECDSA)
  • Internationalization: Fluent for i18n support
  • Static Assets: Rust Embed for production builds
  • HTTP Client: Reqwest with middleware chain, retry logic, and compression
  • Cryptography: P-256 ECDSA with elliptic-curve and p256 crates
  • Task Management: Tokio with task tracking and graceful shutdown
  • DNS Resolution: Hickory resolver with DoH/DoT support
  • Real-time: TAP consumer for AT Protocol events with backfill support
  • Email: Lettre for SMTP-based email notifications
  • Image Processing: Image resizing and optimization for avatars and content
  • Rate Limiting: Redis-backed throttling for API endpoints
  • Content Processing: HTML sanitization, minification, and CSS inlining

Development Commands#

Building & Development#

# Development build with template reloading (default)
cargo build --bin smokesignal

# Production build with embedded templates
cargo build --bin smokesignal --no-default-features -F embed

# Type checking and linting
cargo check
cargo clippy

# Run tests
cargo test

Running Services#

# Start main application server (development mode)
cargo run --bin smokesignal

# Generate cryptographic keys
cargo run --bin crypto -- key

Database Operations#

# Run database migrations
sqlx migrate run

# Reset database with migrations
sqlx database reset

Frontend Development#

# Install dependencies (from src-js directory)
cd src-js && npm install

# Build frontend assets (outputs to static/js/bundle.js and static/css/bundle.css)
npm run build

# Watch mode for development (rebuilds on file changes)
npm run dev

# Format code
npm run format

# Lint code
npm run lint

# Type check
npm run typecheck

# Run all checks
npm run check

Architecture Overview#

Core Structure#

/src-js/              # Frontend source (TypeScript, CSS)
├── src/              # TypeScript source files
│   ├── main.ts       # Entry point
│   ├── types.ts      # Shared TypeScript types
│   ├── components/   # Reusable UI components
│   └── features/     # Feature-specific code (maps, events, lfg)
├── styles/           # CSS source files
│   ├── main.css      # CSS entry point
│   ├── base/         # Variables, utilities
│   ├── components/   # Component styles
│   └── features/     # Feature styles
├── package.json
├── vite.config.ts
├── tsconfig.json
└── biome.json

/src/
├── atproto/          # AT Protocol integration
│   ├── auth.rs       # Authentication utilities
│   ├── utils.rs      # AT Protocol utilities
│   └── lexicon/      # AT Protocol lexicon definitions
│       ├── profile.rs     # Profile lexicon
│       └── acceptance.rs  # Acceptance lexicon
├── bin/              # Executables
│   ├── smokesignal.rs # Main application server
│   └── crypto.rs     # Cryptographic key utilities
├── http/             # Web layer
│   ├── errors/       # HTTP error handling
│   ├── handle_*.rs   # Route handlers (OAuth, events, RSVPs, admin, XRPC)
│   ├── middleware_*.rs # Request middleware (auth, i18n)
│   ├── auth_utils.rs # Authentication utilities
│   ├── acceptance_utils.rs # RSVP acceptance utilities
│   ├── import_utils.rs # Event/RSVP import utilities
│   ├── event_form.rs # Event form handling
│   ├── event_view.rs # Event view helpers
│   ├── rsvp_form.rs  # RSVP form handling
│   ├── templates.rs  # Template rendering
│   ├── timezones.rs  # Timezone utilities
│   ├── pagination.rs # Pagination helpers
│   └── server.rs     # Server configuration
├── storage/          # Data access layer
│   ├── cache.rs      # Redis caching
│   ├── identity_profile.rs # User identity management
│   ├── profile.rs    # AT Protocol profile storage
│   ├── event.rs      # Event data operations
│   ├── acceptance.rs # RSVP acceptance storage
│   ├── oauth.rs      # OAuth session management
│   ├── denylist.rs   # Handle blocking
│   ├── content.rs    # Content storage abstraction (filesystem/S3)
│   ├── private_event_content.rs # Private event content
│   ├── notification.rs # Email notification preferences
│   ├── distributed_lock.rs # Redis-based distributed locking
│   └── atproto.rs    # AT Protocol specific storage
├── config.rs         # Environment configuration
├── key_provider.rs   # JWT key management
├── i18n.rs          # Internationalization
├── tap_processor.rs  # TAP event consumer and processor
├── processor.rs      # Content fetcher for events
├── service.rs        # Service document and DID management
├── emailer.rs        # Email sending functionality
├── email_templates.rs # Email template rendering
├── email_confirmation.rs # Email confirmation tokens
├── unsubscribe_token.rs # Email unsubscribe tokens
├── identity_cache.rs # Identity/profile caching
├── throttle.rs       # Rate limiting interface
├── throttle_redis.rs # Redis-based rate limiting
├── image.rs          # Image processing utilities
├── facets.rs         # AT Protocol facets parsing
├── task_identity_refresh.rs # Identity refresh task
├── task_oauth_requests_cleanup.rs # OAuth cleanup task
└── task_search_indexer.rs # OpenSearch indexing

Key Patterns#

  • Layered Architecture: HTTP → Business Logic → Data Access
  • Module-based Organization: Each handler in separate module with error types
  • Dependency Injection: Services passed through application context
  • Template-driven UI: Server-side rendering with optional reloading
  • Background Tasks: Multiple async workers for maintenance and processing
  • Event-driven: TAP consumer for real-time AT Protocol events with backfill support

Database Schema#

  • identity_profiles - User identity and preferences (including discovery settings)
  • identity_notifications - Email notification preferences and confirmation
  • oauth_requests - OAuth flow state with PKCE and DPoP support
  • oauth_sessions - Active sessions with token management
  • atproto_oauth_requests - AT Protocol OAuth request storage
  • events - Calendar events with location and timezone support
  • rsvps - Event responses and attendance tracking (with email sharing and validation)
  • acceptance_tickets - RSVP acceptance tickets for validation
  • acceptance_records - RSVP acceptance records
  • profiles - Cached AT Protocol profiles
  • private_event_content - Conditional event content based on display criteria
  • denylist - Handle blocking for moderation
  • did_documents - Cached DID documents for performance

Configuration Requirements#

Required Environment Variables#

  • HTTP_COOKIE_KEY - 64-character hex key for session encryption
  • DATABASE_URL - PostgreSQL connection string
  • REDIS_URL - Redis/Valkey connection string
  • EXTERNAL_BASE - Public base URL (e.g., https://smokesignal.events)
  • PLC_HOSTNAME - AT Protocol PLC server hostname
  • ADMIN_DIDS - Comma-separated admin user DIDs
  • SERVICE_KEY - Service identity key for did:web support
  • CONTENT_STORAGE - Storage backend configuration (filesystem path or S3 URL)

OAuth Backend Configuration#

  • OAUTH_BACKEND - OAuth backend to use: "pds" (default) or "aip"

When OAUTH_BACKEND=pds, the following variable is required:

  • SIGNING_KEYS - Path to JWK key set file for OAuth signing

When OAUTH_BACKEND=aip, the following variables are required:

  • AIP_HOSTNAME - AIP OAuth server hostname
  • AIP_CLIENT_ID - AIP OAuth client ID
  • AIP_CLIENT_SECRET - AIP OAuth client secret

Optional Environment Variables#

  • RUST_LOG - Logging configuration (default: info)
  • PORT - Server port (default: 3000)
  • BIND_ADDR - Bind address (default: 0.0.0.0)
  • TAP_HOSTNAME - TAP service hostname (default: localhost:2480)
  • TAP_PASSWORD - TAP admin password for authentication (optional)
  • ENABLE_TAP - Enable TAP consumer (default: true)
  • ENABLE_OPENSEARCH - Enable OpenSearch integration
  • ENABLE_TASK_OPENSEARCH - Enable search indexing task
  • OPENSEARCH_ENDPOINT - OpenSearch server endpoint
  • SMTP_HOST - SMTP server hostname for email notifications
  • SMTP_PORT - SMTP server port
  • SMTP_USERNAME - SMTP authentication username
  • SMTP_PASSWORD - SMTP authentication password
  • SMTP_FROM - Email sender address

Development Setup#

The project includes a complete DevContainer setup with PostgreSQL, Redis, and all Rust dependencies. Use the provided .devcontainer/ configuration for consistent development environment.

Key Development Features#

  • Template reloading in development mode (default reload feature)
  • Embedded templates for production builds (embed feature)
  • Localized template structure with language-specific directories (e.g., templates/en-us/)
  • SQLx compile-time query checking with migrations
  • Cryptographic key generation and management utilities
  • Comprehensive error handling with user-friendly messages
  • Internationalization support with Fluent
  • LRU caching for OAuth requests and DID documents
  • Graceful shutdown with task tracking and cancellation tokens
  • Real-time event ingestion via TAP with backfill support
  • Full-text search with OpenSearch
  • Email notifications with HTML templates and confirmation workflow
  • RSVP acceptance/validation workflow with tickets and records
  • Private event content with conditional display criteria
  • Service identity with did:web support
  • Content storage abstraction supporting filesystem and S3
  • Image processing for avatars and uploaded content
  • Redis-based rate limiting and distributed locking
  • Profile caching for AT Protocol profiles

Build Features#

  • default = ["reload", "s3"] - Development mode with S3 support
  • embed - Production with embedded templates
  • reload - Development with template reloading
  • s3 - S3-compatible storage support

Testing#

  • Unit tests embedded in source files using #[cfg(test)]
  • Integration tests for database operations and HTTP handlers
  • Run with cargo test
  • Database tests require running PostgreSQL instance with test database
  • OAuth flow tests use fixture data for AT Protocol interactions
  • Template rendering tests ensure UI consistency

Security Notes#

  • Uses JOSE/JWT with P-256 ECDSA keys for OAuth signing
  • PKCE OAuth flow with state parameter for CSRF protection
  • DPoP (Demonstration of Proof-of-Possession) support for token binding
  • Encrypted session cookies with secure flags
  • Forbids unsafe Rust code (#[forbid(unsafe_code)])
  • Input validation and HTML sanitization with Ammonia
  • Content Security Policy headers
  • Rate limiting and request timeouts

Visibility#

Types and methods should have the lowest visibility necessary, defaulting to private. If public visibility is necessary, attempt to make it public to the crate only. Using completely public visibility should be a last resort.

AT Protocol Integration#

  • Full OAuth 2.0 implementation with AT Protocol services using external atproto-* crates
  • Support for both standard AT Protocol (PDS) and AIP OAuth flows
  • DID-based identity management with PDS discovery
  • Integration with PLC (Public Ledger of Credentials) for DID resolution
  • Handle resolution and verification with LRU caching
  • Decentralized identity workflows with fallback mechanisms
  • AT Protocol lexicon support:
    • Events and RSVPs (community.lexicon.calendar.*)
    • Profiles (events.smokesignal.profile)
    • Acceptance records (events.smokesignal.calendar.acceptance)
    • Location data (community.lexicon.location)
  • TAP consumer for real-time event ingestion from AT Protocol with backfill
  • XRPC server endpoints for search and service operations
  • Service identity with did:web support for federated interactions
  • AT Protocol attestation support for verified claims
  • Uses external AT Protocol libraries (from tangled.org):
    • atproto-identity - Identity resolution and verification with LRU caching
    • atproto-oauth - OAuth flow implementation with LRU and zeroize
    • atproto-oauth-axum - Axum integration for OAuth
    • atproto-oauth-aip - AIP OAuth implementation
    • atproto-client - AT Protocol client functionality
    • atproto-record - Record management
    • atproto-tap - TAP event streaming with backfill support
    • atproto-xrpcs - XRPC server implementation
    • atproto-attestation - Attestation verification

Error Handling#

All error strings must use this format:

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

Example errors:

  • error-smokesignal-resolve-1 Multiple DIDs resolved for method
  • error-smokesignal-plc-1 HTTP request failed: https://google.com/ Not Found
  • error-smokesignal-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.