Expense claim system for 0x20 hackerspace. Members submit expenses via Mattermost, admins review and process payments.
- Mattermost Integration:
/expensescommand generates secure submission link (+ DM for mobile) - Expense Submission: Mobile-first form with camera capture, receipt upload (images/PDFs)
- Admin Dashboard: Review, approve/deny, track payments
- PDF Export: Generate reports with cover page, summaries, and embedded attachments
- Email Notifications: Notify admins on submission, notify members on status change
- Dark Theme: Modern, mobile-optimized interface
# 1. Setup
./setup-all.sh
# 2. Configure backend/.env (required vars)
ADMIN_PASSWORD=your-secure-password
SECRET_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))")
ACCESS_TOKEN_PUBLIC_KEY=<from-hsg-bot>
BOT_NOTIFY_URL=http://localhost:5000/notify
BOT_NOTIFY_SECRET=shared-secret
# 3. Run
./dev.sh- Frontend: http://localhost:5173
- Backend API: http://localhost:8000
- API Docs: http://localhost:8000/docs
┌─────────────┐ /expenses ┌─────────────┐
│ Mattermost │◄──────────────────►│ HSG Bot │
└─────────────┘ └──────┬──────┘
│ signed token
▼
┌─────────────┐ ┌─────────────┐
│ Frontend │◄──────────────────►│ Backend │
│ (React) │ REST API │ (FastAPI) │
└─────────────┘ └──────┬──────┘
│
▼
┌─────────────┐
│ SQLite │
└─────────────┘
| Variable | Description |
|---|---|
ADMIN_PASSWORD |
Password for admin login |
SECRET_KEY |
JWT signing key (use python3 -c "import secrets; print(secrets.token_hex(32))") |
ACCESS_TOKEN_PUBLIC_KEY |
Ed25519 public key for verifying Mattermost tokens |
BOT_NOTIFY_URL |
HSG bot endpoint for DM notifications |
BOT_NOTIFY_SECRET |
Shared secret with HSG bot |
| Variable | Default | Description |
|---|---|---|
SMTP_HOST |
- | SMTP server for email notifications |
SMTP_PORT |
587 | SMTP port |
SMTP_USER |
- | SMTP username |
SMTP_PASSWORD |
- | SMTP password |
SMTP_FROM_EMAIL |
- | Sender email address |
ADMIN_EMAIL |
- | Admin notification recipient |
FRONTEND_URL |
http://localhost:3000 | Frontend URL for CORS |
MAX_FILE_SIZE |
10485760 | Max upload size (10MB) |
ACCESS_TOKEN_REQUIRED |
true | Require signed token for submissions |
- Type
/expensesin Mattermost (link also sent as DM for mobile users) - Click the link to open the mobile-friendly form
- Choose payment method (bank transfer, cash, or bar tab)
- Add amount, description, and receipt photos (camera or upload)
- Submit — you'll get a DM when it's processed
- Go to
/adminand login withADMIN_PASSWORD - Review pending expenses
- Update status (pending → paid/denied)
- Fill payment details (pay date, paid from/to, responsible)
- Export PDF reports by date range
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/expenses/ |
Submit expense (requires valid token) |
| GET | /api/expenses/view/{token} |
View expense by view token |
| GET | /api/expenses/view/{token}/photo/{file} |
Get photo by view token |
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/admin/login |
Get JWT token |
| GET | /api/admin/expenses |
List expenses (filter: ?status=pending|paid|denied|deleted) |
| PATCH | /api/admin/expenses/{id} |
Update expense |
| DELETE | /api/admin/expenses/{id} |
Soft delete |
| POST | /api/admin/expenses/{id}/restore |
Restore deleted |
| POST | /api/admin/expenses/{id}/attachments |
Upload admin attachments |
| DELETE | /api/admin/expenses/{id}/photos/{file} |
Delete photo |
| GET | /api/admin/files/{type}/{file} |
Serve uploaded file |
docker compose up -d --buildExpects backend/.env and hsg-bot/.env to be configured. Uses Traefik for routing.
# Both services
./dev.sh
# Individual
./start-backend.sh # Backend on :8000
./start-frontend.sh # Frontend on :5173
# Database reset
cd backend
rm -rf data/expense_notes.db
python -c "from app.database import init_db; init_db()"- Frontend: React 18, Vite, pdf-lib, react-datepicker
- Backend: FastAPI, SQLAlchemy, python-jose
- Bot: FastAPI, Ed25519 signing
- Database: SQLite
expense-notes/
├── backend/
│ ├── app/
│ │ ├── main.py # FastAPI app
│ │ ├── config.py # Settings from env
│ │ ├── database.py # SQLAlchemy setup
│ │ ├── models.py # DB models
│ │ ├── schemas.py # Pydantic schemas
│ │ ├── crud.py # DB operations
│ │ ├── auth.py # JWT auth
│ │ ├── email_service.py # SMTP notifications
│ │ ├── bot_notification.py
│ │ └── routers/
│ │ ├── expenses.py # Public API
│ │ └── admin.py # Admin API
│ ├── uploads/ # File storage
│ └── data/ # SQLite DB
├── frontend/
│ └── src/
│ ├── components/
│ │ ├── ExpenseForm.jsx
│ │ ├── AdminDashboard.jsx # Includes PDF export
│ │ ├── ExpenseList.jsx
│ │ ├── ExpenseDetails.jsx
│ │ └── PhotoGallery.jsx
│ └── services/api.js
├── hsg-bot/
│ ├── main.py # FastAPI bot
│ ├── commands/ # Slash command handlers
│ └── services/ # Mattermost API, token signing
├── docker-compose.yml
└── CLAUDE.md # AI assistant context