Skip to content

Attend

CI

A web application for onboarding under-18 participants for in-person Hack Club events. Attend collects all information needed for safeguarding, travel, accommodation, and operational workflows—with a focus on security and data protection.

Features

  • Multi-event support — Manage multiple events with separate configurations
  • Participant onboarding — Multi-step wizard for profile, travel, accommodation, health, and guardian info
  • Guardian portal — Secure magic-link access for parents/guardians to complete consent forms
  • Role-based access control — Global Admin, Event Admin, Ops Staff, Safeguarding Lead roles
  • Sensitive data encryption — Medical and safeguarding information encrypted at rest
  • Mobile wallet passes — Apple Wallet and Google Wallet support for event check-in
  • Audit logging — Full trail of admin actions with PaperTrail and console1984
  • QR code check-in — Scan participants at event venues

Tech Stack

CategoryTechnology
FrameworkRuby 3.4 / Rails 8.1
DatabasePostgreSQL with UUID primary keys
FrontendHotwire (Turbo + Stimulus), Tailwind CSS
AuthenticationDevise + OmniAuth (Hack Club Auth)
AuthorizationPundit
Background JobsSolid Queue + Mission Control
CachingSolid Cache
Real-timeSolid Cable (Action Cable)
Asset PipelinePropshaft + Importmap
DeploymentDocker
StorageActive Storage with S3 (Cloudflare R2)

Getting Started

Prerequisites

  • Ruby 3.4+
  • PostgreSQL 15+
  • Node.js 18+ (for Tailwind CSS watcher)

Installation

# Clone the repository
git clone https://github.com/hackclub/attend.git
cd attend
# Install Ruby dependencies
bundle install
# Setup database
rails db:create db:migrate db:seed
# Start development server (Rails + Tailwind watcher)
bin/dev

The app will be available at https://localhost:3000.

Test Users (Development)

After running seeds, these accounts are available:

EmailPasswordRole
admin@hackclub.compassword123Global Admin
eventadmin@hackclub.compassword123Event Admin
ops@hackclub.compassword123Ops Staff
safeguarding@hackclub.compassword123Safeguarding Lead
participant@example.compassword123Participant

Configuration

Environment Variables

Copy .env.example to .env for development:

cp .env.example .env

Key variables:

VariableDescription
DATABASE_URLPostgreSQL connection string
HACK_CLUB_CLIENT_ID / HACK_CLUB_CLIENT_SECRETOAuth credentials
DOCUSEAL_API_KEY / DOCUSEAL_WEBHOOK_SECRETE-signature integration
SENTRY_DSNError monitoring

Rails Credentials

For production secrets, use Rails encrypted credentials:

EDITOR="code --wait" rails credentials:edit

Required credentials structure:

app_host: attend.hackclub.comdatabase_url: postgres://user:pass@host:5432/dbnamehack_club:
client_id: your_client_idclient_secret: your_client_secretdocuseal:
api_key: your_api_keywebhook_secret: your_secretsentry:
dsn: https://your-sentry-dsn# Encryption keys (generate with: bin/rails db:encryption:init)active_record_encryption:
primary_key: generated_keydeterministic_key: generated_keykey_derivation_salt: generated_salt

Optional: Wallet Passes

For Apple Wallet passes, add PassKit configuration:

passkit:
web_service_host: https://attend.hackclub.comapple_team_identifier: YOUR_TEAM_IDpass_type_identifier: pass.com.hackclub.attendcertificate_key: your_p12_passwordprivate_p12_certificate: /path/to/certificate.p12apple_intermediate_certificate: /path/to/WWDR.cer

For Google Wallet, see Google Wallet Prerequisites.


Architecture

User Roles

RoleAccess
ParticipantComplete onboarding for events, view their status
GuardianComplete consent forms via magic link
Global AdminFull access to all events and data
Event AdminFull access for assigned events
Ops StaffView/edit operational data (travel, accommodation)
Safeguarding LeadAccess to medical and safeguarding information

Data Model

Event
├── ParticipantEvent (join table with status)
│ ├── Participant
│ ├── GuardianParticipantEvent
│ │ ├── Guardian
│ │ └── EmergencyContact
│ ├── Travel (inbound/outbound)
│ │ └── TravelLeg
│ ├── Accommodation
│ ├── Medical (encrypted)
│ ├── Dietary
│ ├── Accessibility (encrypted)
│ ├── SafeguardingInfo (encrypted)
│ └── Consent (Docuseal tracking)
├── Incident (encrypted)
├── Note (encrypted)
├── Scan (check-in records)
└── EventRoleAssignment (staff roles)

External Integrations

ServicePurpose
DocusealEmbedded consent forms with webhook status updates

Development

Quick Commands

# Start development server with Tailwind watcher
bin/dev
# Rails server only
rails server
# Rails console
rails console
# Database operations
rails db:migrate # Run pending migrations
rails db:seed # Seed development data
rails db:reset # Drop, create, migrate, seed# Show all routes
rails routes
# Generate encryption keys
bin/rails db:encryption:init

Testing

# Run all tests
bundle exec rspec
# Run specific test files
bundle exec rspec spec/models/
bundle exec rspec spec/features/
# Run a single test file
bundle exec rspec spec/models/participant_spec.rb

Linting

# Run RuboCop
bin/rubocop
# Auto-fix safe violations
bin/rubocop -a
# Security audit
bundle exec brakeman
bundle exec bundler-audit check

Background Jobs

Jobs are processed by Solid Queue. In development, they run inline. View the job dashboard at /admin/jobs (requires Global Admin).


Deployment

Docker

Build and run with Docker:

docker build -t attend .
docker run -d -p 80:80 \
-e RAILS_MASTER_KEY=<value from config/master.key> \
--name attend attend

Set RAILS_MASTER_KEY as an environment variable on your production server. Hack Club's production instance builds this Dockerfile and deploys it on every merge to main.

Staging

RAILS_ENV=staging loads config/environments/production.rb verbatim and then overrides only the host, the Active Storage bucket, and the outbound-email guard, so staging exercises the same code paths as production.

What keeps staging from touching production's data:

  • Credentials. Staging reads config/credentials/staging.yml.enc, unlocked by its own RAILS_MASTER_KEY. Those credentials must name staging Postmark/Twilio/Airtable/Slack accounts. The app refuses to boot if that file is missing, because Rails would otherwise silently fall back to production's config/credentials.yml.enc. Open it with bin/rails credentials:edit --environment staging — it ships as an annotated template with every integration the app reads, so filling in the REPLACE_MEs is the whole job. secret_key_base and the active_record_encryption keys are already generated and deliberately differ from production's, so a staging session cookie is not valid on attend.hackclub.com.
  • Email. Every outbound message is rewritten to STAGING_MAIL_RECIPIENT, or dropped if that variable is unset. See app/mailers/staging_mail_interceptor.rb.
  • SMS and voice. Off until someone flips the twilio_enabled setting, which defaults to false on a fresh database.
  • Airtable.config/recurring.yml deliberately omits airtable_sync in staging — it writes back to the base it reads, so a second box running it every five minutes would fight production over the same records. Run it by hand when you need it.
  • Uploads. A separate hackclub-attend-staging R2 bucket (config/storage.yml).

Required environment variables on the staging deployment:

VariableValue
RAILS_ENVstaging
RAILS_MASTER_KEYcontents of config/credentials/staging.key
DATABASE_URLthe staging Postgres URL (no credentials fallback — a wrong value fails to boot)
APP_HOSTthe staging hostname, e.g. staging.attend.hackclub.com
STAGING_MAIL_RECIPIENTwhere redirected email should go; unset means email is dropped
SOLID_QUEUE_IN_PUMAtrue

Also needs doing outside the app: a Hack Club Auth redirect URI for the staging host, a Turnstile widget scoped to it, and the hackclub-attend-staging R2 bucket. Webhooks (Postmark, HelpScout, DocuSeal, Twilio) stay pointed at production, so staging never receives them.


Contributing

Getting Started

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/your-feature
  3. Make your changes
  4. Run tests: bundle exec rspec
  5. Run linter: bin/rubocop
  6. Commit with clear messages
  7. Push and open a Pull Request

Code Style

  • Follow the Rails Omakase style guide
  • Use UUID primary keys for all models
  • Encrypt sensitive data with Active Record Encryption
  • Add Pundit policies for new resources
  • Write specs for new features

Commit Messages

Use clear, descriptive commit messages:

Add flight tracking integration with FlightAware
- Implement FlightTrackingService with provider fallback
- Add TravelLeg model for multi-leg journeys
- Schedule background job for status updates

Security

  • Never commit secrets or credentials
  • Use Rails credentials for sensitive configuration
  • Report security issues via security.hackclub.com

License

GNU General Public License v3.0


Support

For questions or issues, open a GitHub issue or contact the Events team through the 'chat feature' on Attend, or through #attend

About

Onboarding, travel, and safeguarding for under-18 participants at in-person Hack Club events

Resources

Code of conduct

Contributing

Security policy

Stars

17 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages