AttendiBotAttendiBot

Developer Guide

Local development, testing, and adding features.

Guide for local development, testing, and extending AttendiBot.

Prerequisites

ToolVersionPurpose
RuststableBackend bot + API
Node.js20+Web app
npmlatestWeb dependencies
DockerlatestPostgreSQL (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-bot

Postgres 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 dev

Open http://localhost:3000

3. 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 handlers

Code quality

cd backend
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-features

Integration tests require DATABASE_URL (CI provides Postgres automatically).

Adding a slash command

  1. 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()
}
  1. Register in commands/mod.rs:
pub fn all() -> Vec<Command<Data, Error>> {
    vec![
        // ...existing commands
        my_module::my_command_wrapper(),
    ]
}
  1. For admin commands, use check = "crate::commands::checks::admin_only" and add as an /admin subcommand in voice_admin.rs.

  2. Restart the bot. With DISCORD_GUILD_ID set, commands appear instantly.

Adding an API route

  1. Add handler in http/handlers/guild.rs (or new handler module)
  2. Register route in http/router.rs
  3. Add service function in services/
  4. Add DAL function in web/src/lib/attendibot-api.ts
  5. 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.sql

Migrations 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 configuration

Code quality

cd web
npm run build    # Type check + production build
npm run lint     # ESLint (if configured)

Adding a dashboard page

  1. Create page in app/dashboard/your-feature/page.tsx
  2. Add sidebar link in components/dashboard/sidebar.tsx
  3. Add API client methods in lib/attendibot-api.ts
  4. 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:

JobSteps
Backendcargo fmt --check, clippy, test with Postgres service
Webnpm ci, npm run build
DockerBuild 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.


Edit on GitHub

On this page