Architecture
System design, data flow, and monorepo layout.
Technical overview of AttendiBot for developers and contributors.
System diagram
Monorepo layout
AttendiBot/
├── backend/ # Rust Discord bot + HTTP API
│ ├── crates/
│ │ ├── bot/ # Main binary (attendibot-bot)
│ │ └── shared/ # Shared types: signing, voice, subscription
│ └── migrations/ # SQL migrations (001–006)
├── web/ # Next.js marketing + dashboard
│ └── src/
│ ├── app/ # App Router pages
│ ├── components/ # UI components
│ └── lib/ # API client, auth, DAL
└── docs/ # DocumentationBackend process
Single Rust binary (attendibot-bot) runs concurrently:
| Task | Description |
|---|---|
| Discord client | Serenity + Poise — slash commands + GUILD_VOICE_STATES events |
| HTTP server | Axum on port 8080 — health, public verify, authenticated /api/v1 |
| Period reset scheduler | Archives signed period snapshots on schedule |
| Log destination scheduler | Posts embeds to Discord text channels |
Entry point: backend/crates/bot/src/main.rs
Crate responsibilities
attendibot-bot
- Discord event handlers and slash commands (
commands/) - Voice tracking logic (
voice/tracker.rs,voice/fairness.rs) - HTTP API handlers (
http/handlers/,http/router.rs) - Service layer (
services/) - Database repositories (
voice/repository.rs)
attendibot-shared
- Ed25519 signing (
signing.rs) - Voice types and payloads (
voice.rs) - Subscription tiers (
subscription.rs) - Log types (
logs.rs)
Voice tracking pipeline
- Discord sends
voice_state_updateevent voice/tracker.rsprocesses join / leave / move- Fairness rules evaluated (
voice/fairness.rs) - On leave: build session payload → sign with Ed25519 → persist to
voice_sessions - Update active session and period leaderboard counters
If signing is locked, completed sessions are dropped.
BFF authentication pattern
The web app acts as a Backend-for-Frontend (BFF):
- Admin signs in via Auth.js (Discord OAuth,
identify guildsscope) - Server-side DAL mints a short-lived HS256 JWT (60s TTL) with
user_idandguild_id - JWT sent to Rust API as
Authorization: Bearer <token> - Backend validates JWT and verifies Manage Server via Discord REST API
Implementation:
- Web:
web/src/lib/attendibot-api.ts - Backend auth:
backend/crates/bot/src/http/auth.rs
Public routes (/health, /verify/*) require no authentication.
Database schema
PostgreSQL via SQLx migrations. Key tables:
| Table | Purpose |
|---|---|
guild_signing_keys | Encrypted Ed25519 private keys + public keys |
guild_voice_settings | Tracking mode, reset schedule, fairness, subscription tier |
tracked_channels | Allowlist/denylist channel IDs |
leaderboard_periods | Period metadata and signed snapshots |
voice_sessions | Completed signed voice sessions |
active_voice_sessions | In-progress sessions |
log_destinations | Discord text channel log configs |
verified_role_rules | Auto-role attendance thresholds (Pro) |
guild_webhooks | Outbound webhook settings (Pro) |
Subscription enforcement
Tier limits checked in service layer (shared/subscription.rs):
- Free: 5 archived periods, no export/webhooks/verified roles
- Pro/Enterprise: unlimited history + premium features
HTTP API layers
| Layer | Routes | Auth |
|---|---|---|
| Public | /health, /verify/session/{id}, /verify/period/{guild_id}/{index} | None |
| Authenticated | /api/v1/guilds/{guild_id}/* | BFF JWT + Manage Server |
| Webhook | /webhooks/attendance | x-webhook-secret header (501 not implemented) |
See API Reference for full endpoint list.
Web app structure
| Area | Path | Purpose |
|---|---|---|
| Marketing | app/(marketing)/ | Landing, docs, FAQ, pricing |
| Dashboard | app/dashboard/ | Admin UI (server components + client panels) |
| Auth | auth.ts | Auth.js Discord provider |
| API client | lib/attendibot-api.ts | Server-side Rust API calls |
Dashboard pages fetch data server-side via the DAL, which mints internal JWTs per request.
CI pipeline
GitHub Actions (.github/workflows/ci.yml):
- Backend:
cargo fmt,clippy,test(with Postgres service) - Web:
npm ci,npm run build - Docker: build verification