forked from jamesp/sasa-membership
485 lines
18 KiB
Markdown
485 lines
18 KiB
Markdown
# Swansea Airport Stakeholders' Alliance Membership Management System
|
|
|
|
A membership management system for Swansea Airport Stakeholders' Alliance, built with FastAPI, React, MySQL-compatible storage, Square payments, SMTP2GO email services, and Docker Compose.
|
|
|
|
## Features
|
|
|
|
- **Authentication and accounts**: Registration, JSON/form login, JWT sessions, password reset, password change, and role-based access for members, admins, and super admins.
|
|
- **Member portal**: Dashboard with membership status, payment history, membership setup, account settings, profile editing, configurable profile questions, cookie notice, privacy policy, and terms of service pages.
|
|
- **Admin operations**: User listing/editing, admin-triggered member password reset emails, membership tier CRUD, manual payment recording, Square refunds, email template editing with escaped previews, SMTP2GO bounce management, profile-question management, and super-admin feature-flag reloads.
|
|
- **Membership tiers**: Configurable Personal, Aircraft Owners, Corporate, and custom tiers with annual fees, descriptions, active/inactive state, and benefits.
|
|
- **Memberships and payments**: Membership lifecycle tracking, Square card payments, cash/check/manual payments, dummy test payments, payment history, transaction IDs, refund state, and payment-to-membership linking.
|
|
- **Events and RSVPs**: Event CRUD, upcoming event listing, member RSVP updates, RSVP status tracking, attendance fields, and admin RSVP visibility.
|
|
- **Time handling**: Backend timestamps are stored and returned as UTC/Zulu, the frontend renders member-facing dates and times in Europe/London, and event entry is converted back to UTC before save.
|
|
- **ESP RFID**: Reader provisioning, heartbeat, time sync, tap capture, queued card writes, and admin review of readers/cards/attendance.
|
|
- **Volunteer and profile data**: Volunteer flag/level support, configurable member profile questions, conditional questions, admin-only answers, seeded aviation/volunteering questions, and data models for volunteer roles, assignments, schedules, and certificates.
|
|
- **Email system**: SMTP2GO-backed email sending, default database templates, editable templates, welcome/password-reset/test emails, bounce webhooks, bounce stats, cleanup, and manual deactivation.
|
|
- **Feature flags**: Backend feature-flag service with frontend context and admin status/reload controls.
|
|
- **Testing**: Fast frontend Vitest unit tests and backend pytest unit tests wired into `restart.sh`.
|
|
|
|
## Tech Stack
|
|
|
|
- **Backend**: FastAPI (Python 3.11)
|
|
- **Frontend**: React 18, TypeScript, Vite, Tailwind CSS
|
|
- **Database**: MySQL 8.0
|
|
- **Authentication**: JWT tokens with OAuth2
|
|
- **Containerization**: Docker & Docker Compose
|
|
- **ORM**: SQLAlchemy
|
|
- **Migrations**: Alembic
|
|
- **Payments**: Square Web Payments SDK and Square API
|
|
- **Email**: SMTP2GO
|
|
- **Tests**: Vitest and pytest
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
membership/
|
|
├── backend/
|
|
│ ├── alembic/ # Database migration scripts
|
|
│ │ ├── versions/ # Migration files
|
|
│ │ ├── env.py # Migration environment
|
|
│ │ └── script.py.mako # Migration template
|
|
│ ├── alembic.ini # Alembic configuration
|
|
│ ├── app/
|
|
│ │ ├── api/
|
|
│ │ │ ├── v1/
|
|
│ │ │ │ ├── auth.py # Authentication endpoints
|
|
│ │ │ │ ├── users.py # User management
|
|
│ │ │ │ ├── tiers.py # Membership tiers
|
|
│ │ │ │ ├── memberships.py # Membership management
|
|
│ │ │ │ ├── payments.py # Payment processing
|
|
│ │ │ │ ├── email.py # SMTP2GO email and bounces
|
|
│ │ │ │ ├── email_templates.py
|
|
│ │ │ │ ├── events.py # Events and RSVPs
|
|
│ │ │ │ ├── esp.py # ESP RFID provisioning, taps, attendance, write jobs
|
|
│ │ │ │ └── feature_flags.py
|
|
│ │ │ └── dependencies.py # Auth dependencies
|
|
│ │ ├── core/
|
|
│ │ │ ├── config.py # Configuration
|
|
│ │ │ ├── database.py # Database setup
|
|
│ │ │ ├── datetime.py # UTC helpers and Zulu serialization helpers
|
|
│ │ │ └── security.py # Security utilities
|
|
│ │ ├── models/
|
|
│ │ │ └── models.py # Database models
|
|
│ │ ├── schemas/
|
|
│ │ │ └── schemas.py # Pydantic schemas
|
|
│ │ └── main.py # Application entry point
|
|
│ ├── Dockerfile
|
|
│ └── requirements.txt
|
|
├── frontend/
|
|
│ ├── src/
|
|
│ │ ├── components/ # Dashboard, payment, admin, profile, ESP components
|
|
│ │ ├── contexts/ # Feature flag, toast, and confirm contexts
|
|
│ │ ├── pages/ # Login, register, dashboard, policy pages
|
|
│ │ ├── services/ # API clients
|
|
│ │ └── utils/ # Tested frontend logic and timezone helpers
|
|
├── docker-compose.yml
|
|
├── .env.example
|
|
└── README.md
|
|
```
|
|
|
|
## Getting Started
|
|
|
|
### Prerequisites
|
|
|
|
- Docker
|
|
- Docker Compose
|
|
|
|
### Installation
|
|
|
|
1. **Navigate to the project directory**
|
|
|
|
2. **The `.env` file is already configured** with default settings. You can customize:
|
|
- Square API credentials (for payment processing)
|
|
- SMTP2GO API key (for email notifications)
|
|
- Database password (if desired)
|
|
|
|
3. **Start the services**:
|
|
```bash
|
|
# Development mode (gateway + Vite hot reload)
|
|
docker compose up -d
|
|
|
|
# Production static frontend mode
|
|
docker compose --profile prod up -d frontend-prod backend
|
|
```
|
|
|
|
4. **Wait for services to be ready** (about 30 seconds for MySQL initialization):
|
|
```bash
|
|
docker compose logs -f
|
|
```
|
|
Press Ctrl+C when you see "Application startup complete"
|
|
|
|
5. **Access the application**:
|
|
- Frontend (HTTP): http://localhost:8050
|
|
- Frontend (HTTPS for Square): https://localhost:8443
|
|
- API: http://localhost:8050/api/v1
|
|
- API Documentation: http://localhost:8050/docs
|
|
- TLS certs are generated automatically by the gateway container on first start
|
|
|
|
## Restart and Test Gate
|
|
|
|
`restart.sh` rebuilds images with cache, runs the fast frontend and backend unit tests, then restarts the stack only if tests pass:
|
|
|
|
```bash
|
|
./restart.sh
|
|
```
|
|
|
|
The current fast test suite covers:
|
|
- frontend profile-question visibility and editability rules with Vitest
|
|
- backend profile-question option parsing, answer normalization/deserialization, select validation, and volunteer flag normalization with pytest
|
|
|
|
You can also run them individually:
|
|
|
|
```bash
|
|
docker compose run --rm frontend npm test
|
|
docker compose run --rm backend pytest -q
|
|
```
|
|
|
|
## Frontend Development vs Production
|
|
|
|
### Development Mode (Vite)
|
|
```bash
|
|
docker compose up -d
|
|
```
|
|
- Single entry point on port 8050
|
|
- Nginx gateway routes `/api/*` to backend and all other traffic to Vite
|
|
- Hot reloading and development features
|
|
- Access at: http://localhost:8050
|
|
|
|
### Production Mode (Nginx)
|
|
```bash
|
|
docker compose --profile prod up -d frontend-prod backend
|
|
```
|
|
- Frontend served by Nginx on port 8050
|
|
- Optimized static files, production-ready
|
|
- Access at: http://localhost:8050
|
|
|
|
### Configuration
|
|
|
|
Set your preferred defaults in `.env`:
|
|
```bash
|
|
# Deployment mode (for reference only)
|
|
MODE=dev
|
|
|
|
# Single host entrypoint port
|
|
APP_PORT=8050
|
|
|
|
# HTTPS entrypoint port for self-signed TLS in dev
|
|
APP_TLS_PORT=8443
|
|
DEV_CERT_CN=localhost
|
|
DEV_CERT_SANS=DNS:localhost,IP:127.0.0.1,IP:::1
|
|
|
|
# Frontend allowed hosts (comma-separated)
|
|
VITE_ALLOWED_HOSTS=sasaprod,localhost,members.sasalliance.org
|
|
```
|
|
|
|
If you access via a LAN hostname or IP, add it to `VITE_ALLOWED_HOSTS`.
|
|
If you change `APP_PORT`, use that port instead of `8050` in URLs.
|
|
If you change `APP_TLS_PORT`, use that port for HTTPS URLs.
|
|
If you change hostnames, update `DEV_CERT_CN` and `DEV_CERT_SANS` accordingly.
|
|
|
|
### Stopping Services
|
|
```bash
|
|
# Stop all services
|
|
docker compose down
|
|
|
|
# Stop production profile services
|
|
docker compose --profile prod down
|
|
```
|
|
|
|
## Default Credentials
|
|
|
|
**Admin Account**:
|
|
- Email: `admin@swanseaairport.org`
|
|
- Password: `admin123`
|
|
|
|
⚠️ **IMPORTANT**: Change the admin password immediately after first login!
|
|
|
|
## API Endpoints
|
|
|
|
### Authentication
|
|
- `POST /api/v1/auth/register` - Register new user
|
|
- `POST /api/v1/auth/login` - Login (OAuth2 form)
|
|
- `POST /api/v1/auth/login-json` - Login (JSON)
|
|
|
|
### Users
|
|
- `GET /api/v1/users/me` - Get current user profile
|
|
- `PUT /api/v1/users/me` - Update current user profile
|
|
- `GET /api/v1/users/` - List all users (admin)
|
|
- `GET /api/v1/users/{id}` - Get user by ID (admin)
|
|
- `DELETE /api/v1/users/{id}` - Delete user (admin)
|
|
|
|
### Membership Tiers
|
|
- `GET /api/v1/tiers/` - List all tiers
|
|
- `GET /api/v1/tiers/{id}` - Get tier by ID
|
|
- `POST /api/v1/tiers/` - Create tier (admin)
|
|
- `PUT /api/v1/tiers/{id}` - Update tier (admin)
|
|
- `DELETE /api/v1/tiers/{id}` - Delete tier (admin)
|
|
|
|
### Memberships
|
|
- `GET /api/v1/memberships/my-memberships` - Get current user's memberships
|
|
- `POST /api/v1/memberships/` - Create membership
|
|
- `GET /api/v1/memberships/{id}` - Get membership by ID
|
|
- `PUT /api/v1/memberships/{id}` - Update membership (admin)
|
|
- `GET /api/v1/memberships/` - List all memberships (admin)
|
|
- `DELETE /api/v1/memberships/{id}` - Delete membership (admin)
|
|
|
|
### Payments
|
|
- `GET /api/v1/payments/my-payments` - Get current user's payments
|
|
- `POST /api/v1/payments/` - Create payment
|
|
- `GET /api/v1/payments/{id}` - Get payment by ID
|
|
- `PUT /api/v1/payments/{id}` - Update payment (admin)
|
|
- `GET /api/v1/payments/` - List all payments (admin)
|
|
- `POST /api/v1/payments/manual-payment` - Record manual payment (admin)
|
|
- `GET /api/v1/payments/config/square` - Get frontend Square config
|
|
- `POST /api/v1/payments/square/process` - Process Square card payment
|
|
- `POST /api/v1/payments/square/refund` - Refund Square payment (admin)
|
|
|
|
### Profile Questions
|
|
- `GET /api/v1/users/me/profile-questions` - List active questions with current answers
|
|
- `PUT /api/v1/users/me/profile-answers` - Update editable answers
|
|
- `GET /api/v1/users/admin/profile-questions` - List all profile questions (admin)
|
|
- `POST /api/v1/users/admin/profile-questions` - Create profile question (admin)
|
|
- `PUT /api/v1/users/admin/profile-questions/{id}` - Update profile question (admin)
|
|
- `DELETE /api/v1/users/admin/profile-questions/{id}` - Deactivate profile question (admin)
|
|
- `GET /api/v1/users/admin/users/{id}/profile-answers` - View user answers (admin)
|
|
- `PUT /api/v1/users/admin/users/{id}/profile-answers` - Update user answers (admin)
|
|
|
|
### Events
|
|
- `GET /api/v1/events/` - List events
|
|
- `GET /api/v1/events/upcoming` - List upcoming events
|
|
- `POST /api/v1/events/` - Create event (admin)
|
|
- `PUT /api/v1/events/{id}` - Update event (admin)
|
|
- `DELETE /api/v1/events/{id}` - Delete event (admin)
|
|
- `GET /api/v1/events/{id}/rsvps` - List RSVPs (admin)
|
|
- `POST /api/v1/events/{id}/rsvp` - Create or update current user's RSVP
|
|
|
|
### Email and Feature Flags
|
|
- `POST /api/v1/email/test-email` - Send test email
|
|
- `POST /api/v1/email/test-welcome-email` - Send test welcome email
|
|
- `POST /api/v1/email/webhooks/smtp2go/bounce` - Receive SMTP2GO bounce webhook
|
|
- `GET /api/v1/email/bounces` - List bounces
|
|
- `GET /api/v1/email/bounces/stats` - Bounce statistics
|
|
- `GET /api/v1/email-templates/` - List templates
|
|
- `PUT /api/v1/email-templates/{template_key}` - Update template
|
|
- `GET /api/v1/feature-flags/flags` - List flags
|
|
- `POST /api/v1/feature-flags/flags/reload` - Reload flags (super admin)
|
|
|
|
### ESP RFID
|
|
- `POST /api/v1/esp/device/register` - Reader registration and one-time token issuance
|
|
- `GET /api/v1/esp/device/provisioning-status` - Poll reader provisioning
|
|
- `GET /api/v1/esp/device/time` - UTC clock sync for ESP firmware
|
|
- `POST /api/v1/esp/device/heartbeat` - Reader heartbeat with UTC server time
|
|
- `POST /api/v1/esp/device/taps` - RFID tap capture with UTC-normalized timestamps
|
|
- `GET /api/v1/esp/device/write-jobs/next` - Poll queued card write job
|
|
- `POST /api/v1/esp/device/write-jobs/{job_id}/complete` - Complete a queued write job
|
|
- `POST /api/v1/esp/device/dashboard-login` - Validate ESP-hosted dashboard login
|
|
- `GET /api/v1/esp/admin/readers` - Admin reader list
|
|
- `POST /api/v1/esp/admin/readers` - Admin reader create/provision fallback
|
|
- `PUT /api/v1/esp/admin/readers/{reader_id}` - Admin reader update / key rotation
|
|
- `POST /api/v1/esp/admin/readers/{reader_id}/approve` - Approve a reader
|
|
- `POST /api/v1/esp/admin/readers/{reader_id}/reject` - Reject a reader
|
|
- `DELETE /api/v1/esp/admin/readers/{reader_id}` - Delete a reader
|
|
- `GET /api/v1/esp/admin/cards` - Admin RFID card list
|
|
- `POST /api/v1/esp/admin/cards` - Create RFID card
|
|
- `PUT /api/v1/esp/admin/cards/{card_id}` - Update RFID card
|
|
- `GET /api/v1/esp/admin/write-jobs` - Admin queued write jobs
|
|
- `POST /api/v1/esp/admin/write-jobs` - Queue a write job
|
|
- `POST /api/v1/esp/admin/write-jobs/{job_id}/cancel` - Cancel a queued write job
|
|
- `GET /api/v1/esp/admin/taps` - Admin tap history
|
|
- `GET /api/v1/esp/admin/attendance` - Admin attendance sessions
|
|
- `POST /api/v1/esp/admin/attendance/close-stale` - Close stale attendance sessions
|
|
|
|
## Time Handling
|
|
|
|
- Database timestamps are stored as UTC and serialized as Zulu (`...Z`) in API responses.
|
|
- Frontend display uses Europe/London for member-facing dates and times.
|
|
- Event creation/editing converts London-local input back to UTC before sending it to the backend.
|
|
- ESP devices sync their clocks from `/api/v1/esp/device/time` and persist tap times as UTC.
|
|
|
|
## Docker Compose Commands
|
|
|
|
### Basic Operations
|
|
|
|
```bash
|
|
# Start all services
|
|
docker compose up -d
|
|
|
|
# View logs (all services)
|
|
docker compose logs -f
|
|
|
|
# View logs (specific service)
|
|
docker compose logs -f backend
|
|
docker compose logs -f gateway
|
|
|
|
# Stop services
|
|
docker compose down
|
|
|
|
# Stop and remove volumes (clean slate)
|
|
docker compose down -v
|
|
|
|
# Restart services
|
|
docker compose restart
|
|
|
|
# Rebuild after code changes
|
|
docker compose up -d --build
|
|
|
|
# Check service status
|
|
docker compose ps
|
|
```
|
|
|
|
### Database Operations
|
|
|
|
```bash
|
|
# Access MySQL CLI (using environment variables)
|
|
docker exec -it membership_mysql mysql -u "${DATABASE_USER}" -p"${DATABASE_PASSWORD}" "${DATABASE_NAME}"
|
|
|
|
# Create backup
|
|
docker exec membership_mysql mysqldump -u "${DATABASE_USER}" -p"${DATABASE_PASSWORD}" "${DATABASE_NAME}" > backup_$(date +%Y%m%d_%H%M%S).sql
|
|
|
|
# Restore database
|
|
docker exec -i membership_mysql mysql -u "${DATABASE_USER}" -p"${DATABASE_PASSWORD}" "${DATABASE_NAME}" < backup.sql
|
|
```
|
|
|
|
### Database Migrations
|
|
|
|
The application uses Alembic for database schema migrations. Migrations are automatically run when the backend container starts.
|
|
|
|
```bash
|
|
# Create a new migration (after making model changes)
|
|
sudo docker compose exec backend alembic revision --autogenerate -m "Description of changes"
|
|
|
|
# Apply migrations
|
|
sudo docker compose exec backend alembic upgrade head
|
|
|
|
# View migration status
|
|
sudo docker compose exec backend alembic current
|
|
|
|
# View migration history
|
|
sudo docker compose exec backend alembic history
|
|
```
|
|
|
|
**Note**: The `database/init.sql` file is deprecated. All schema changes should now be made through Alembic migrations.
|
|
|
|
## API Testing
|
|
|
|
You can use the interactive API documentation at http://localhost:8050/docs to test endpoints:
|
|
|
|
1. Register a new user
|
|
2. Login to get access token
|
|
3. Click "Authorize" button and enter: `Bearer <your_token>`
|
|
4. Test protected endpoints
|
|
|
|
Or use curl:
|
|
|
|
```bash
|
|
# Register
|
|
curl -X POST "http://localhost:8050/api/v1/auth/register" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"email": "user@example.com",
|
|
"password": "password123",
|
|
"first_name": "John",
|
|
"last_name": "Doe"
|
|
}'
|
|
|
|
# Login
|
|
curl -X POST "http://localhost:8050/api/v1/auth/login-json" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"email": "user@example.com",
|
|
"password": "password123"
|
|
}'
|
|
|
|
# Get profile (replace TOKEN with actual token)
|
|
curl -X GET "http://localhost:8050/api/v1/users/me" \
|
|
-H "Authorization: Bearer TOKEN"
|
|
```
|
|
|
|
## Default Membership Tiers
|
|
|
|
The system comes with three pre-configured tiers:
|
|
|
|
1. **Personal** - £5/year
|
|
- Access to member portal
|
|
- Meeting notifications
|
|
- Event participation
|
|
|
|
2. **Aircraft Owners** - £25/year
|
|
- All Personal benefits
|
|
- Priority event registration
|
|
- Aircraft owner resources
|
|
|
|
3. **Corporate** - £100/year
|
|
- All benefits
|
|
- Corporate recognition
|
|
- Promotional opportunities
|
|
- File access
|
|
|
|
## Security
|
|
|
|
- Passwords are hashed using bcrypt
|
|
- JWT tokens for authentication
|
|
- Role-based access control (Member, Admin, Super Admin)
|
|
- CORS protection
|
|
- Environment-based configuration
|
|
|
|
## Troubleshooting
|
|
|
|
### Services not starting
|
|
```bash
|
|
# Check status of all services
|
|
docker compose ps
|
|
|
|
# View all logs
|
|
docker compose logs
|
|
|
|
# View specific service logs
|
|
docker compose logs gateway
|
|
docker compose logs backend
|
|
|
|
# Restart all services
|
|
docker compose restart
|
|
|
|
# Full restart with rebuild
|
|
docker compose down
|
|
docker compose up -d --build
|
|
```
|
|
|
|
### Database connection issues
|
|
```bash
|
|
# Check backend connectivity and status
|
|
docker compose ps
|
|
|
|
# View backend logs for DB errors
|
|
docker compose logs backend
|
|
```
|
|
|
|
### Clean slate restart
|
|
```bash
|
|
# Stop everything and remove volumes
|
|
docker compose down -v
|
|
|
|
# Start fresh
|
|
docker compose up -d
|
|
|
|
# Wait for initialization
|
|
docker compose logs -f
|
|
```
|
|
|
|
## Remaining Roadmap
|
|
|
|
- [ ] Add member file upload/repository endpoints and UI
|
|
- [ ] Add richer volunteer role, assignment, schedule, and certificate screens on top of the existing models
|
|
- [ ] Implement automated renewal reminder batch jobs
|
|
- [ ] Add reporting and analytics
|
|
- [ ] Expand test coverage around authenticated API flows and payment/email service boundaries
|
|
|
|
## License
|
|
|
|
Copyright © 2024 Swansea Airport Stakeholders' Alliance
|
|
|
|
## Support
|
|
|
|
For issues or questions, please contact the development team.
|