Architecture
Project structure
calrs/
├── Cargo.toml Package manifest
├── Dockerfile Multi-stage Docker build
├── calrs.service systemd unit file
├── migrations/ SQLite schema (incremental)
│ ├── 001_initial.sql Core tables
│ ├── 002_auth.sql Users, sessions, auth config
│ ├── 003_username.sql Username support
│ ├── 004_oidc.sql OIDC columns
│ ├── 005_requires_confirmation.sql
│ ├── 006_group_event_types.sql
│ ├── 007_caldav_write.sql
│ ├── 008_recurrence_id.sql
│ ├── 009_uid_recurrence_unique.sql
│ └── 010_confirm_token.sql
├── templates/ Minijinja HTML templates
│ ├── base.html Base layout + CSS (light/dark mode)
│ ├── auth/ Login, registration
│ ├── dashboard.html User dashboard
│ ├── admin.html Admin panel
│ ├── source_form.html Add CalDAV source
│ ├── event_type_form.html Create/edit event types
│ ├── troubleshoot.html Availability troubleshoot timeline
│ ├── slots.html Slot picker (timezone-aware)
│ ├── book.html Booking form
│ ├── confirmed.html Confirmation / pending page
│ ├── booking_approved.html Token-based approve success
│ ├── booking_decline_form.html Token-based decline form
│ ├── booking_declined.html Token-based decline success
│ └── booking_action_error.html Invalid/expired token error
├── docs/ mdBook documentation
└── src/
├── main.rs CLI entry point (clap)
├── db.rs SQLite connection + migrations
├── models.rs Domain types
├── auth.rs Authentication (local + OIDC)
├── email.rs SMTP email with .ics invites + HTML templates
├── rrule.rs RRULE expansion (DAILY/WEEKLY/MONTHLY)
├── utils.rs Shared utilities (iCal splitting/parsing)
├── caldav/mod.rs CalDAV client (RFC 4791) + write-back
├── web/mod.rs Axum web server + handlers
└── commands/ CLI subcommands
├── source.rs
├── sync.rs
├── calendar.rs
├── event_type.rs
├── booking.rs
├── config.rs
└── user.rs
Database
SQLite in WAL mode. Single file, zero ops. Foreign keys with ON DELETE CASCADE.
Core tables
| Table | Purpose |
|---|---|
accounts | User profiles (name, email, timezone) |
users | Authentication (password hash, role, username) |
sessions | Server-side sessions |
caldav_sources | CalDAV server connections |
calendars | Discovered calendars |
events | Synced calendar events (unique on uid + recurrence_id) |
event_types | Bookable meeting templates |
availability_rules | Per-event-type availability (day + time range) |
bookings | Guest bookings |
smtp_config | SMTP settings |
auth_config | Registration, OIDC settings |
groups | OIDC groups |
user_groups | Group membership |
Web server
Axum 0.8 with Arc<AppState> shared state containing the SqlitePool and minijinja::Environment.
Route structure
| Route | Handler |
|---|---|
/auth/login, /auth/register | Authentication (redirects to dashboard if already logged in) |
/auth/oidc/login, /auth/oidc/callback | OIDC flow |
/dashboard | User dashboard |
/dashboard/admin | Admin panel + impersonation |
/dashboard/event-types/* | Event type CRUD |
/dashboard/sources/* | CalDAV source management |
/dashboard/bookings/* | Booking actions (confirm, cancel) |
/dashboard/troubleshoot/{id} | Availability troubleshoot timeline |
/booking/approve/{token} | Token-based booking approval (from email) |
/booking/decline/{token} | Token-based booking decline (from email) |
/u/{username} | Public user profile |
/u/{username}/{slug} | Public slot picker |
/u/{username}/{slug}/book | Booking form + submit |
/g/{group_slug}/{slug} | Group booking pages |
CalDAV client
Minimal RFC 4791 implementation:
- PROPFIND — principal discovery, calendar-home-set, calendar listing
- REPORT — event fetch (calendar-query)
- PUT — write events to calendar
- DELETE — remove events from calendar
- OPTIONS — connection test
Handles absolute and relative hrefs, BlueMind/Apple namespace prefixes, tags with attributes.
Templates
Minijinja 2 with file-based loader. Templates extend base.html which provides:
- CSS custom properties for theming
- Dark mode via
prefers-color-scheme - Responsive layout
- No JavaScript framework — vanilla JS only where needed (timezone detection, provider presets)
Lettre for SMTP with STARTTLS. All emails are HTML with plain text fallback (multipart/alternative). ICS generation is hand-crafted (no icalendar crate dependency for generation):
METHOD:REQUESTfor confirmationsMETHOD:PUBLISHfor guest confirmations (avoids mail server re-invites)METHOD:CANCELfor cancellations- Events include
ORGANIZER,ATTENDEE,LOCATION,STATUS
The approval request email includes Approve and Decline action buttons (table-based layout for email client compatibility). These link to token-based public endpoints that don’t require authentication.
Authentication flow
Local
- Registration/login form → POST with email + password
- Password verified with Argon2
- Session created in SQLite → session ID in HttpOnly cookie
- Extractors (
AuthUser,AdminUser) validate session on each request
OIDC
- User clicks “Sign in with SSO”
- Redirect to OIDC provider with PKCE challenge
- Provider redirects back with authorization code
- calrs exchanges code for tokens
- Extracts email, name, groups from ID token
- Links to existing user by email or creates new user
- Session created as with local auth
Dependencies
Key crates:
| Crate | Purpose |
|---|---|
clap | CLI argument parsing |
axum | Web framework |
sqlx | Async SQLite |
reqwest | HTTP client (CalDAV) |
minijinja | HTML templating |
lettre | SMTP email |
chrono + chrono-tz | Time and timezone handling |
argon2 | Password hashing |
openidconnect | OIDC client |
icalendar | ICS parsing |