A multi-space event calendar application with recurring events, iCal feeds, and OIDC authentication.
- Multi-space calendars with per-space event types
- Recurring events (RRULE / RFC 5545) with per-occurrence overrides
- Event statuses: confirmed, tentative, pending (draft), cancelled
- iCal feed endpoints for calendar subscriptions
- Widget API for embedding upcoming events (JSON or HTML)
- Claim-based permissions via OIDC provider
- (Internationalization (EN/DE) - there is currently no language picker but the capability is already implemented)
Next.js 15, React 19, TypeScript, tRPC 11, PostgreSQL, Drizzle ORM, BetterAuth (OIDC), shadcn/ui, FullCalendar, next-intl
# Start PostgreSQL and mock-oauth2-server
docker compose up -d
# Install dependencies
pnpm install
# Copy and adjust environment variables
cp .env.example .env
# Push database schema
pnpm db:push
# Start dev server
pnpm devThe app runs at http://localhost:3000.
The mock-oauth2-server (http://localhost:8080) provides test users via a login dropdown:
| User | Claims | Access |
|---|---|---|
| Admin | c4:admin |
Full access |
| Space Manager | c4:space:* |
All spaces |
| Event Editor | c4:event-type:* |
All event types |
| Viewer | (none) | Read-only |
| Command | Description |
|---|---|
pnpm dev |
Start dev server (Turbopack) |
pnpm build |
Production build |
pnpm start |
Start production server |
pnpm typecheck |
Run TypeScript type checking |
pnpm check |
Run Biome linter/formatter |
pnpm check:write |
Auto-fix lint/format issues |
pnpm db:push |
Push schema changes to database |
pnpm db:generate |
Generate a migration |
pnpm db:migrate |
Run migrations |
pnpm db:studio |
Open Drizzle Studio |
A Docker image is built and pushed to ghcr.io on every push to main via GitHub Actions. The image runs database migrations on startup, then starts the Next.js server.
docker pull ghcr.io/cccc/c4-event-scheduler:latestThe container requires a PostgreSQL database and an OIDC provider. Configure via environment variables (see .env.example):
docker run -p 3000:3000 \
-e DATABASE_URL="postgresql://user:pass@db:5432/c4_events" \
-e BETTER_AUTH_URL="https://events.example.com" \
-e BETTER_AUTH_SECRET="<random-secret>" \
-e BETTER_AUTH_OIDC_CLIENT_ID="c4-events-app" \
-e BETTER_AUTH_OIDC_CLIENT_SECRET="<client-secret>" \
-e BETTER_AUTH_OIDC_ISSUER="https://auth.example.com/realms/main" \
-e NEXT_PUBLIC_APP_URL="https://events.example.com" \
-e NEXT_PUBLIC_AUTH_SSO_ENABLED=true \
ghcr.io/cccc/c4-event-scheduler:latestGET /api/cal/all.ics— All public eventsGET /api/cal/{space}.ics— Events for a specific spaceGET /api/cal/{space}/{event-type}.ics— Filtered by event type
GET /api/widget/upcoming — Upcoming events as JSON or HTML.
| Parameter | Default | Description |
|---|---|---|
space |
all | Filter by space slug |
limit |
10 | Max events (1-50) |
months |
6 | Future range (1-24) |
format |
json | json or html |
locale |
de-DE | Date formatting locale |
See .env.example for the full list. Key variables:
| Variable | Description |
|---|---|
DATABASE_URL |
PostgreSQL connection string |
BETTER_AUTH_URL |
Public URL of the app |
BETTER_AUTH_SECRET |
Auth session secret |
BETTER_AUTH_OIDC_* |
OIDC provider configuration |
OIDC_CLAIM_PREFIX |
Prefix for permission claims (default: c4) |
OIDC_ROLES_CLAIM |
Dot-notation path to roles in OIDC token |
NEXT_PUBLIC_APP_URL |
Public app URL for feeds and callbacks |