Developer Guide
Local development, testing, and adding features.
Guide for local development, testing, and extending AttendiBot.
Prerequisites
| Tool | Version | Purpose |
|---|---|---|
| Rust | stable | Backend bot + API |
| Node.js | 20+ | Web app |
| npm | latest | Web dependencies |
| Docker | latest | PostgreSQL (local) |
Local development setup
1. Start the backend
cd backend
cp .env.example .env
# Edit .env — set DISCORD_TOKEN and API_INTERNAL_SECRET
docker compose up db -d
cargo run -p attendibot-botPostgres runs on host port 5433 (avoids conflict with system Postgres).
Default DATABASE_URL: postgres://attendibot:attendibot@localhost:5433/attendibot
The bot starts the HTTP server on port 8080 and runs migrations automatically.
2. Start the web app
cd web
cp .env.example .env.local
# Set AUTH_SECRET, AUTH_DISCORD_ID, AUTH_DISCORD_SECRET,
# ATTENDIBOT_API_BASE_URL=http://localhost:8080,
# ATTENDIBOT_API_INTERNAL_SECRET (must match backend)
npm install
npm run dev3. Configure Discord
See Discord Setup. For instant slash command registration during development, set DISCORD_GUILD_ID in backend .env.
Backend development
Project structure
backend/crates/bot/src/
├── main.rs # Entry point, concurrent task spawning
├── config.rs # Environment variable parsing
├── commands/ # Slash commands (Poise)
├── voice/ # Voice tracking, fairness, reset
├── http/ # Axum router and handlers
├── services/ # Business logic layer
├── logs/ # Discord embed builders
└── events/ # Discord event handlersCode quality
cd backend
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-featuresIntegration tests require DATABASE_URL (CI provides Postgres automatically).
Adding a slash command
- Create a command function in
commands/using Poise:
#[poise::command(slash_command, check = "crate::commands::checks::guild_only")]
pub async fn my_command(ctx: Context<'_>) -> Result<(), Error> {
ctx.say("Hello!").await?;
Ok(())
}
pub fn my_command_wrapper() -> poise::Command<Data, Error> {
my_command()
}- Register in
commands/mod.rs:
pub fn all() -> Vec<Command<Data, Error>> {
vec![
// ...existing commands
my_module::my_command_wrapper(),
]
}-
For admin commands, use
check = "crate::commands::checks::admin_only"and add as an/adminsubcommand invoice_admin.rs. -
Restart the bot. With
DISCORD_GUILD_IDset, commands appear instantly.
Adding an API route
- Add handler in
http/handlers/guild.rs(or new handler module) - Register route in
http/router.rs - Add service function in
services/ - Add DAL function in
web/src/lib/attendibot-api.ts - Create dashboard page or extend existing panel
Database migrations
Create a new SQL file in backend/migrations/ with the next sequential number:
backend/migrations/007_my_feature.sqlMigrations run automatically on bot startup via sqlx::migrate!.
Web development
Project structure
web/src/
├── app/
│ ├── (marketing)/ # Public pages
│ └── dashboard/ # Admin dashboard
├── components/ # React components
├── lib/
│ ├── attendibot-api.ts # Rust API client (server-side)
│ ├── dal/ # Data access layer
│ └── source.ts # Fumadocs documentation source
└── auth.ts # Auth.js configurationCode quality
cd web
npm run build # Type check + production build
npm run lint # ESLint (if configured)Adding a dashboard page
- Create page in
app/dashboard/your-feature/page.tsx - Add sidebar link in
components/dashboard/sidebar.tsx - Add API client methods in
lib/attendibot-api.ts - Create client components in
components/dashboard/as needed
Dashboard pages use server components for data fetching and client components for interactivity.
CI pipeline
GitHub Actions at .github/workflows/ci.yml:
| Job | Steps |
|---|---|
| Backend | cargo fmt --check, clippy, test with Postgres service |
| Web | npm ci, npm run build |
| Docker | Build verification |
Run the same checks locally before pushing.
Environment variables reference
Backend
See backend/.env.example and Deployment.
Web
See web/.env.example and Deployment.