Resource Reserver is a booking system that prevents double-booking of shared assets such as rooms, equipment, labs, or desks. It includes a web app for daily use, a CLI for automation, and a FastAPI backend for integrations.
This README is the single source of documentation for this repository. It is written for both non-technical and technical readers.
- Overview
- Features
- Glossary
- Quick Start (No Technical Knowledge Required)
- Quick Start (Developers)
- Architecture
- Screenshots
- Web App Guide
- Admin and Operations Guide
- CLI Guide
- API Guide
- Configuration
- Data Import and Export
- Operations and Monitoring
- Testing
- Troubleshooting
- License
Resource Reserver lets teams share resources without collisions. Users can check availability, reserve time, join waitlists when resources are busy, and subscribe to calendar feeds. Admins can manage access, approvals, quotas, and integrations.
- Resource catalog with tags, status, groups, and hierarchy.
- Availability rules: business hours, blackout dates, available slots, and next available.
- Reservations with conflict detection, recurring schedules, and approval workflow.
- Waitlist with offers and acceptance.
- Calendar integration (subscription URL, personal feed, per-reservation export).
- Notifications center, email reminders, and WebSocket live updates.
- Analytics dashboards and CSV exports.
- Audit logs with filters and export.
- Webhooks with HMAC signing, retries, and delivery history.
- Security: JWT auth, refresh tokens, MFA, password policy, RBAC roles, OAuth2 clients.
- Rate limiting, quotas, metrics, and health checks.
- PWA support (offline page and asset caching) and multi-language UI (en, es, fr).
- Resource: The item you can reserve (room, desk, equipment).
- Reservation: A confirmed time slot on a resource.
- Waitlist: A queue for a resource when your desired time is not available.
- Business hours: Allowed hours for reservations.
- Blackout date: A date when reservations are not allowed.
- Approval: A review step for resources that require approval before a reservation becomes active.
- Quota: Limits on API usage and rate limits per user or tier.
This option uses Docker to run everything.
-
Install Docker Desktop:
-
Get the project folder:
- If you already have the folder, skip this step.
- Option A (download): download the project ZIP, unzip it, and open the folder.
- Option B (git):
git clone <repo-url>thencd Resource-Reserver
-
Open a terminal and go to the project folder:
cd Resource-Reserver- Start the app:
docker compose up -d- Open the app:
- Web app: http://localhost:8081 (or custom
FRONTEND_PORTin.env) - API docs: http://localhost:8000/docs
- Create the first admin account:
- Visit http://localhost:8081/setup and follow the prompts.
- Add resources and make a reservation.
To stop everything:
docker compose downdocker compose up -d --buildDefault ports:
- Frontend: http://localhost:8081 (configurable via
FRONTEND_PORT) - Backend API: http://localhost:8000 (configurable via
BACKEND_PORT)
By default the backend uses SQLite. To use PostgreSQL instead:
- Start the postgres profile:
docker compose --profile postgres up -d --build- Set
DATABASE_URLfor the backend:
- In Docker:
DATABASE_URL=postgresql://postgres:postgres@postgres:5432/resource_reserver - On your host:
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/resource_reserver
docker compose --profile dev up --buildPorts in dev profile:
- Backend (hot reload): http://localhost:8001
- Frontend (hot reload): http://localhost:3001
Prerequisites:
- Python 3.11+
- Node 20+ and npm
Backend:
python -m venv venv
source venv/bin/activate
pip install -r apps/backend/requirements.txt
uvicorn --app-dir apps/backend app.main:app --reload --host 0.0.0.0 --port 8000Frontend:
cd apps/frontend
npm ci
printf "NEXT_PUBLIC_API_URL=http://localhost:8000\n" > .env.local
npm run devCLI:
pip install -e apps/backend
resource-reserver-cli auth register
resource-reserver-cli auth loginIf you use mise, these tasks are already defined:
mise run up
mise run dev
mise run test
mise run lint
mise run format
mise run build
mise run help./devThe script installs mise if needed, sets up dependencies, and starts dev servers.
flowchart LR
User[User Browser] --> Web[Next.js Web App]
CLI[CLI User] --> API[FastAPI API]
Web --> API
API --> DB[(Database)]
API --> Redis[(Redis Cache)]
API --> WS[WebSocket /ws]
API --> Email[SMTP Email]
API --> Hooks[Webhook Targets]
sequenceDiagram
participant User
participant UI
participant API
participant DB
participant Approver
participant WS
User->>UI: Request reservation
UI->>API: POST /api/v1/reservations
alt Resource requires approval
API->>DB: Create reservation (pending_approval)
API->>WS: approval_request
Approver->>API: POST /api/v1/approvals/{id}/respond
API->>DB: Update reservation status
API->>WS: reservation_approved or reservation_rejected
else Available
API->>DB: Create reservation (active)
API->>WS: reservation_created
end
opt Resource is busy
UI->>API: POST /api/v1/waitlist
API->>DB: Enqueue waitlist entry
API->>WS: waitlist_offer (when a slot opens)
end
flowchart TB
Compose[docker compose] --> Backend[backend container :8000]
Compose --> Frontend[frontend container :3000]
Compose --> Redis[redis container :6379]
Compose --> Postgres[postgres container :5432]
Backend --> Data[(volumes)]
Frontend --> Users[users]
erDiagram
USER ||--o{ RESERVATION : books
USER ||--o{ WAITLIST : joins
USER ||--o{ NOTIFICATION : receives
USER ||--o{ WEBHOOK : owns
RESOURCE_GROUP ||--o{ RESOURCE : contains
RESOURCE ||--o{ RESERVATION : has
RESOURCE ||--o{ WAITLIST : has
RESOURCE ||--o{ BUSINESS_HOURS : defines
RESOURCE ||--o{ BLACKOUT_DATE : has
RESERVATION ||--o| APPROVAL_REQUEST : may_require
| Overview | Authentication |
|---|---|
![]() |
![]() |
| Resources | Reservations |
|---|---|
![]() |
![]() |
| System |
|---|
![]() |
📸 All screenshots shown above are available in the
screenshotsdirectory.
- First run: visit
/setupto create the initial admin account. - Register and log in from
/loginafter setup is complete. - Password rules: at least 8 characters with uppercase, lowercase, number, and special character; cannot include the username.
- Account lockout: 5 failed login attempts triggers a 15 minute lockout.
- MFA: enable or disable multi-factor authentication from the account dialog.
- Preferences: configure email notifications and reminder timing.
- Language selector: English, Spanish, and French.
- Offline support: the app registers a service worker for offline page and static asset caching.
- Summary cards show total resources, availability, active and upcoming reservations.
- Open the system status dialog to check health and service metrics.
- Search, filter by status, and sort by name, status, or ID.
- Filter by tags (AND logic: shows resources with ALL selected tags).
- Add resources with name, description, tags, and availability status (admin only).
- Edit resource details including name, description, and tags (admin only).
- Upload resources from CSV (see Data Import and Export).
- Reserve a resource, view its schedule, or set it to maintenance.
- Manage business hours and blackout dates for each resource.
- Manage tags globally: rename or delete tags across all resources (admin only).
- Create one-time or recurring reservations.
- Reserve resources that are currently in use (for future time slots).
- View My Reservations and Upcoming reservations.
- Cancel your own reservations (admins can cancel any reservation).
- View reservation history.
- Export calendar events as .ics or subscribe via a personal feed URL.
- Join the waitlist from a resource when it is busy.
- Choose flexible time if you can accept nearby slots.
- Accept offers when a slot becomes available.
- View utilization, peak times, and popular resources.
- Export reservations and utilization reports as CSV.
- Create and manage webhook subscriptions.
- Test webhooks and view delivery history.
- Open the notification center and mark items as read.
Default roles are admin, user, and guest. Manage roles at /admin/roles.
Admin-only actions:
- Create, edit, and delete resources
- Upload resources via CSV
- Set resource availability (available/unavailable)
- Manage tags globally (rename, delete)
- Cancel any user's reservation
- Manage users and roles
- Configure business hours and blackout dates
All authenticated users can:
- View and search resources
- Create reservations on available resources
- Reserve resources that are currently in use (for future time slots)
- Cancel their own reservations
- Join waitlists
- Approvals: resources can require approval; manage settings via
/api/v1/approvals/resources/{resource_id}/settings. - Resource groups: build hierarchies and assign resources via
/api/v1/resource-groups. - Quotas and rate limits: view and manage via
/api/v1/quotas(admin endpoints require admin role). - Audit logs: view, filter, and export via
/api/v1/audit. - Setup reopen: set
SETUP_REOPEN_TOKENand call/setup/unlockwith headerX-Setup-Token.
Some admin operations are API-only and are not surfaced in the UI yet.
Install the CLI:
pip install -e apps/backendConfiguration:
- API base URL uses
API_URL(default: http://localhost:8000) - Tokens are stored in
~/.reservation-cli(override withCLI_CONFIG_DIR)
Common commands:
resource-reserver-cli commands
resource-reserver-cli auth register
resource-reserver-cli auth login
resource-reserver-cli auth logout
resource-reserver-cli resources list --details
resource-reserver-cli reservations create 12 "2025-01-10 09:00" "2025-01-10 10:00"
resource-reserver-cli waitlist join --resource 12 --start "2025-01-10 09:00" --end "2025-01-10 10:00"
resource-reserver-cli mfa setup
resource-reserver-cli mfa enable
resource-reserver-cli roles list
resource-reserver-cli oauth listBase URL: http://localhost:8000/api/v1
Authentication:
- Register:
POST /api/v1/register - Login (form data):
POST /api/v1/token - Refresh:
POST /api/v1/token/refresh - Logout:
POST /api/v1/logout - Current user:
GET /api/v1/users/me - Preferences:
PATCH /api/v1/users/me/preferences
Example login:
curl -X POST http://localhost:8000/api/v1/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=user1&password=password"Time formats:
- Use ISO-8601 with timezone (example:
2025-01-10T09:00:00Z).
WebSocket:
- Connect to
ws://localhost:8000/ws?token=<JWT>. - Event types:
resource_status_changed,reservation_created,reservation_cancelled,waitlist_offer,approval_request,reservation_approved,reservation_rejected.
Webhook events (examples):
reservation.created,reservation.cancelled,reservation.updated,reservation.expiredresource.created,resource.updated,resource.deleted,resource.unavailable,resource.availableuser.created,waitlist.offer,waitlist.accepted,waitlist.expired
Endpoint map (key groups and examples):
- System:
/health,/ready,/live,/metrics,/api/versions,/api/v1/metrics/summary - Resources:
/api/v1/resources,/api/v1/resources/search,/api/v1/resources/upload - Reservations:
/api/v1/reservations,/api/v1/reservations/recurring,/api/v1/reservations/my - Availability:
/api/v1/resources/{id}/business-hours,/api/v1/resources/{id}/available-slots,/api/v1/resources/{id}/blackout-dates - Waitlist:
/api/v1/waitlist,/api/v1/waitlist/{id},/api/v1/waitlist/{id}/accept - Calendar:
/api/v1/calendar/subscription-url,/api/v1/calendar/feed/{token}.ics,/api/v1/calendar/export/{id}.ics - Notifications:
/api/v1/notifications,/api/v1/notifications/{id}/read - Analytics:
/api/v1/analytics/dashboard,/api/v1/analytics/utilization,/api/v1/analytics/export/utilization.csv - Approvals:
/api/v1/approvals/pending,/api/v1/approvals/{id}/respond - Resource groups:
/api/v1/resource-groups,/api/v1/resource-groups/tree - Search:
/api/v1/search/resources,/api/v1/search/reservations,/api/v1/search/saved - Bulk ops:
/api/v1/bulk/reservations,/api/v1/bulk/reservations/import,/api/v1/bulk/reservations/export - Webhooks:
/api/v1/webhooks,/api/v1/webhooks/events,/api/v1/webhooks/{id}/deliveries - Audit:
/api/v1/audit/logs,/api/v1/audit/export/csv,/api/v1/audit/filters - Quotas:
/api/v1/quotas/config,/api/v1/quotas/my-usage,/api/v1/quotas/users
Full OpenAPI docs are available at:
Create .env in the repo root. Example:
DATABASE_URL=sqlite:///./data/db/resource_reserver_dev.db
SECRET_KEY=your-secret-key-change-in-production
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=30
REFRESH_TOKEN_EXPIRE_DAYS=7
API_URL=http://localhost:8000
DEFAULT_CSV_PATH=data/csv/resources.csv
DEBUG=false
ENVIRONMENT=development
RATE_LIMIT_ENABLED=true
RATE_LIMIT_ANONYMOUS=20/minute
RATE_LIMIT_AUTHENTICATED=100/minute
RATE_LIMIT_ADMIN=500/minute
RATE_LIMIT_AUTH=5/minute
RATE_LIMIT_HEAVY=10/minute
REDIS_URL=redis://localhost:6379/0
CACHE_ENABLED=true
CACHE_TTL_RESOURCES=30
CACHE_TTL_STATS=60
CACHE_TTL_USER_SESSION=300
EMAIL_ENABLED=false
SMTP_HOST=localhost
SMTP_PORT=587
SMTP_USER=
SMTP_PASSWORD=
SMTP_FROM=noreply@resource-reserver.local
SMTP_FROM_NAME=Resource Reserver
SMTP_TLS=true
SMTP_SSL=false
EMAIL_TEMPLATES_DIR=apps/backend/app/templates/email
SETUP_REOPEN_TOKEN=Notes:
DATABASE_URLdefaults to SQLite if unset; set it explicitly for consistency.- CORS origins are defined in
apps/backend/app/config.py(defaults allow localhost ports 3000, 8080, 8000). API_URLis used for calendar subscription URLs.
NEXT_PUBLIC_API_URL=http://localhost:8000Environment variables:
API_URLsets the CLI base URL.CLI_CONFIG_DIRsets the token/config directory (default:~/.reservation-cli).
- Upload resources from CSV in the Resources tab or via
POST /api/v1/resources/upload. - Sample CSV files are available in
data/csv/resources.csvanddata/csv/demo-resources.csv. - Bulk reservation import/export:
POST /api/v1/bulk/reservations/importGET /api/v1/bulk/reservations/export
- Analytics exports:
GET /api/v1/analytics/export/utilization.csvGET /api/v1/analytics/export/reservations.csv
- Audit exports:
GET /api/v1/audit/export/csvGET /api/v1/audit/export/json
- Health endpoints:
/health,/ready,/live. - Metrics:
/metrics(Prometheus format),/api/v1/metrics/summary(JSON). - Background tasks:
- Expired reservation cleanup runs continuously.
- Email reminders are sent every 15 minutes for upcoming reservations.
- Cache behavior:
- If Redis is unavailable, the app runs without cache and logs a warning.
- Rate limiting:
- Per-endpoint limits are configured via
RATE_LIMIT_*variables. - Tiered quotas are available in
/api/v1/quotas/config.
- Per-endpoint limits are configured via
Backend:
cd apps/backend
pytest tests/ -vFrontend:
cd apps/frontend
npm run lint
npm run test
npm run test:e2e- 401 Unauthorized: log in again or refresh the token.
- 403 Forbidden: you may not have the required role or permission.
- Setup already complete:
/setupredirects. UseSETUP_REOPEN_TOKENand/setup/unlockto reopen. - CORS errors: make sure the frontend points to the correct API URL and CORS allows your origin.
- 429 Too Many Requests: wait for the rate limit window to reset.
When upgrading to a new version, the database schema may have changed. If you see errors like no such column or resources/data disappearing after a rebuild, follow these steps:
Option 1: Run Alembic Migrations (Recommended)
# Inside the backend container
docker compose exec backend alembic upgrade heads
# If you see "Multiple head revisions" error:
docker compose exec backend alembic merge heads -m "merge branches"
docker compose exec backend alembic upgrade headOption 2: Manual Column Addition (Quick Fix)
If migrations fail due to schema drift, you can add missing columns directly:
docker compose exec backend python -c "
import sqlite3
conn = sqlite3.connect('/app/data/db/resource_reserver.db')
cursor = conn.cursor()
# Check current columns
cursor.execute('PRAGMA table_info(resources)')
columns = [col[1] for col in cursor.fetchall()]
print('Current columns:', columns)
# Add missing columns (example: description)
if 'description' not in columns:
cursor.execute('ALTER TABLE resources ADD COLUMN description TEXT')
conn.commit()
print('Added description column')
conn.close()
"Then restart the backend:
docker compose restart backendOption 3: Fresh Start (Development Only)
If you don't need existing data, remove the volume and start fresh:
docker compose down -v
docker compose up -dChecking Database Health
To verify your database has all required columns:
docker compose exec backend python -c "
import sqlite3
conn = sqlite3.connect('/app/data/db/resource_reserver.db')
cursor = conn.cursor()
cursor.execute('SELECT COUNT(*) FROM resources')
print(f'Resources: {cursor.fetchone()[0]}')
cursor.execute('PRAGMA table_info(resources)')
print('Columns:', [col[1] for col in cursor.fetchall()])
conn.close()
"MIT License. See LICENSE.




