1. Updated UpdateStripeSettingsRequest - Added publishable_key field
2. Updated update_stripe_settings endpoint - Now validates and stores:
- stripe_publishable_key (not encrypted - it's public)
- stripe_secret_key (encrypted)
- stripe_webhook_secret (encrypted)
- Also validates that publishable and secret keys are from the same environment (both test or both live)
3. Added new public endpoint GET /api/config/stripe - Returns the publishable key to the frontend (no auth required since it's meant to be public)
4. Updated get_stripe_status endpoint - Now includes publishable_key_prefix and publishable_key_set in the response
- Added get_directory_config() and save_directory_config() helper functions
- Created 4 new endpoints:
- GET /api/directory/config - Public endpoint for frontend
- GET /api/admin/directory/config - Admin view with metadata
- PUT /api/admin/directory/config - Update configuration
- POST /api/admin/directory/config/reset - Reset to defaults
- Fixed a bug: Changed SystemSettings.key → SystemSettings.setting_key (correct column name)
- Added JSON serialization/deserialization for storing config in Text column
- Public theme config endpoint for frontend initialization (with 5-min cache)/- Admin CRUD for theme settings (get, update, reset)/- Logo and favicon upload/delete via Cloudflare R2 storage
**Problem:** Admin had users.create permission but couldn't use it due to workflow requiring superadmin-only /admin/roles endpoint.
**Solution:** Created scalable endpoint that adapts role selection to user's permission level.
**Changes:**
- NEW: GET /admin/roles/assignable endpoint with intelligent role filtering
- Superadmin: Returns all roles
- Admin: Returns admin, finance, non-elevated custom roles (excludes superadmin)
- Prevents privilege escalation via permission comparison
- UPDATED: InviteStaffDialog now uses /admin/roles/assignable
- Removed 403 fallback logic (no longer needed)
- Backend handles role filtering dynamically
- UPDATED: AdminStaff 'Invite Staff' button back to permission-based
- Changed from user.role === 'superadmin' to hasPermission('users.create')
- Both admin and superadmin can now invite staff with role restrictions
**Security:**
- ✅ Privilege escalation blocked (admin can't create superadmin)
- ✅ Custom roles filtered by permission comparison
- ✅ Multi-layer enforcement (frontend + backend)
**Files Modified:**
- backend/server.py (+94 lines)
- frontend/src/components/InviteStaffDialog.js (-14 lines)
- frontend/src/pages/admin/AdminStaff.js (1 line changed)
- RBAC_IMPLEMENTATION_FINAL.md (new documentation)
**Testing:**
- Superadmin can assign all roles including superadmin ✓
- Admin can assign admin and finance ✓
- Admin cannot see/assign superadmin ✓
- Custom role elevation detection working ✓
## New Endpoints
- **GET /admin/events/{event_id}**: Get single event details (admin)
- Allows viewing unpublished events
- Returns full event with RSVP count
## Enhanced Endpoints
- **PUT /admin/events/{event_id}/attendance**: Accept batch updates
- Add BatchAttendanceUpdate model for array of updates
- Support both single and bulk attendance marking
- Return count of updated records
- **GET /events**: Include user RSVP status in response
- Query current user's RSVP for each event
- Enable calendar color coding by status
- **GET /events/{event_id}**: Include user RSVP status
- Query current user's RSVP for event details
- Maintain consistency with list endpoint
## Bug Fixes
- **GET /members/event-activity**: Fix timezone comparison
- Add timezone-aware conversion for event.end_at
- Resolve "can't compare offset-naive and offset-aware" error
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>