Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

TablePurpose
accountsUser profiles (name, email, timezone)
usersAuthentication (password hash, role, username)
sessionsServer-side sessions
caldav_sourcesCalDAV server connections
calendarsDiscovered calendars
eventsSynced calendar events (unique on uid + recurrence_id)
event_typesBookable meeting templates
availability_rulesPer-event-type availability (day + time range)
bookingsGuest bookings
smtp_configSMTP settings
auth_configRegistration, OIDC settings
groupsOIDC groups
user_groupsGroup membership

Web server

Axum 0.8 with Arc<AppState> shared state containing the SqlitePool and minijinja::Environment.

Route structure

RouteHandler
/auth/login, /auth/registerAuthentication (redirects to dashboard if already logged in)
/auth/oidc/login, /auth/oidc/callbackOIDC flow
/dashboardUser dashboard
/dashboard/adminAdmin 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}/bookBooking 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)

Email

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:REQUEST for confirmations
  • METHOD:PUBLISH for guest confirmations (avoids mail server re-invites)
  • METHOD:CANCEL for 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

  1. Registration/login form → POST with email + password
  2. Password verified with Argon2
  3. Session created in SQLite → session ID in HttpOnly cookie
  4. Extractors (AuthUser, AdminUser) validate session on each request

OIDC

  1. User clicks “Sign in with SSO”
  2. Redirect to OIDC provider with PKCE challenge
  3. Provider redirects back with authorization code
  4. calrs exchanges code for tokens
  5. Extracts email, name, groups from ID token
  6. Links to existing user by email or creates new user
  7. Session created as with local auth

Dependencies

Key crates:

CratePurpose
clapCLI argument parsing
axumWeb framework
sqlxAsync SQLite
reqwestHTTP client (CalDAV)
minijinjaHTML templating
lettreSMTP email
chrono + chrono-tzTime and timezone handling
argon2Password hashing
openidconnectOIDC client
icalendarICS parsing