Architecture¶
Interactive Diagram¶
Open Interactive Architecture Diagram →
Click any system in the diagram to view details, commands, and configuration.
Overview¶
CCBenefits is a full-stack application with a FastAPI backend, React web frontend, and React Native mobile app, deployed on Oracle Cloud with Docker Compose and Caddy for HTTPS.
backend/
ccbenefits/
main.py # FastAPI app, lifespan, static file serving
config.py # Environment config (secrets, CORS, token expiry, Grafana)
auth.py # Password hashing, JWT tokens, shared token resolution
dependencies.py # get_current_user FastAPI dependency
email.py # Email sender (Console / Resend)
observability.py # OpenTelemetry setup for Grafana Cloud
metrics.py # Business metric counters
middleware.py # Request logging with PII masking
oauth.py # Google/Apple ID token verification
oauth_helpers.py # Shared OAuth account resolution logic
models.py # SQLAlchemy ORM models (8 models, incl. UserOAuthAccount)
schemas.py # Pydantic request/response schemas
database.py # Engine, session, base (Postgres/SQLite)
utils.py # Period calculation, helpers
seed.py # Card template seed data
routers/
auth.py # Register, login, verify, refresh, password reset, OAuth
users.py # User profile CRUD
feedback.py # Feedback submit + admin list
card_templates.py # GET card templates
user_cards.py # CRUD user cards, usage logging, summaries
usage.py # PUT/DELETE individual usage records
frontend/
src/
contexts/ # AuthContext (auth state, login/logout)
hooks/ # useAuth hook
styles/ # Shared form styles
pages/ # Dashboard, CardDetail, AllCredits, AddCard, Login, Register,
# Profile, VerifyEmail, VerifyPending, AdminFeedback
components/ # BenefitRow, UsageModal, FeedbackModal, ProtectedRoute, etc.
services/api.ts # Axios API client with token interceptor
mobile/ # React Native (Expo) Android app
src/
services/api.ts # API client (async SecureStore + memory cache, auto local backend in dev)
contexts/ # AuthContext (token hydration, auth state)
navigation/ # Auth stack + App stack
screens/ # 10+ screens matching web functionality
components/ # UsageModal, YearPicker, PastYearBanner, ScreenWrapper, etc.
theme.ts # Dark theme (colors, spacing, radius)
scripts/
adb-screenshot-test.sh # Automated 8-screen smoke test with content assertions
CardTemplate — credit card definition (name, issuer, annual fee)
BenefitTemplate — a benefit belonging to a card (name, max value, period type, redemption type)
UserCard — a user’s instance of a card template (linked to User via
user_idFK,closed_datefor multi-year tracking)BenefitUsage — usage record per benefit per period
UserBenefitSetting — user’s perceived value override per benefit
Authentication¶
JWT-based authentication with email/password and OAuth (Google, Apple coming soon).
Access tokens (30 min) are sent as Authorization: Bearer headers. Refresh tokens
(7 days) are used to obtain new access tokens without re-login. JWTs include user
email for logging.
Email/password: Registration with bcrypt password hashing, email verification required before access. Password reset via opaque tokens (SHA-256 hashed in DB).
OAuth: Google Sign-In (web popup + mobile via expo-auth-session) and Apple Sign-In
(web redirect + mobile native, currently disabled pending Apple Developer account).
OAuth token verification happens server-side: Google via google-auth library,
Apple via JWKS public key verification with PyJWT.
Account linking: If an OAuth sign-in email matches an existing verified user,
the account is auto-linked. Users can have multiple sign-in methods (email/password +
Google + Apple). OAuth-only users have nullable hashed_password — they can add
a password later via the forgot-password flow.
Profile management: Users can link/unlink OAuth providers on the profile page. Unlinking the last sign-in method is blocked (must set a password or keep another provider).
All /api/user-cards/, /api/usage/, and /api/feedback/ endpoints require
authentication and filter data by the authenticated user. Card templates remain public.
Data Model¶
User — registered user (email, optional password hash, profile settings, verification status)
UserOAuthAccount — OAuth provider link (provider, provider_user_id, provider_email, FK to User)
Feedback — user feedback (category, message, timestamp)
Notifications¶
Users receive notifications about expiring credits, new periods, unused recaps, upcoming card renewals, and weekly utilization summaries.
Email: via Resend API. Transactional emails from
noreply@, notifications fromnotifications@Push: via Expo Push API → Firebase FCM → Android devices
Scheduling: APScheduler (BackgroundScheduler) runs hourly, timezone-aware user matching
Dedup:
NotificationLogtable with channel-based dedup (email and push independent)Unsubscribe: One-click opaque token endpoint (CAN-SPAM compliant, 60-day expiry)
Preferences: Per-user toggles for each notification type × channel, stored as JSON
Observability¶
Metrics and structured logs are exported to Grafana Cloud via OpenTelemetry OTLP:
Auto-instrumented: HTTP request duration/count (FastAPI), DB connections (SQLAlchemy)
Business counters: auth events, verifications, cards added, feedback, emails, notifications, scheduler jobs
Structured logging: JSON to stdout + OTel bridge to Grafana Loki
Request middleware: logs method, path, status, duration, action name, user context (masked)
PII masking: emails and sensitive fields masked before cloud export
Disabled when GRAFANA_OTLP_ENDPOINT is not set (development mode).
Dashboards (managed as code via grafanactl):
Service Health: request rate, latency, error rate, endpoints, status codes, downstream deps, logs
Business Metrics: registrations, logins, auth failures, cards, feedback, notifications
Dashboard YAML files live in grafana/dashboards/ and are synced to Grafana Cloud
on push to master via the grafana-sync.yml workflow.
Deployment¶
Production runs on Oracle Cloud Always Free tier (E2.1.Micro VM, 1 OCPU, 1GB RAM):
Cloudflare (DNS + SSL) → FastAPI app (1 worker) → PostgreSQL
GitHub Actions CI/CD:
Push to master (or manual trigger) → integration tests (Docker stack + API smoke + Playwright E2E)
→ manual approval via GitHub Environments → push to GHCR → SSH deploy to VM
Grafana dashboards:
Push to master (grafana/**) → grafanactl push → Grafana Cloud
Mobile App¶
React Native (Expo SDK 55) Android app with full feature parity to the web frontend.
10+ screens: Login, Register, Verify Pending, Dashboard, Card Detail, Add Card, All Credits (3 tabs), Notifications, Feedback, Profile
Multi-year tracking: Year picker on Dashboard, All Credits, and Card Detail with past-year amber warning banner
Close/reopen cards: Close with date picker (cross-platform: Alert.prompt on iOS, inline TextInput on Android), reopen with confirmation dialog
Google OAuth: Sign in with Google on login and register screens
Push notifications: expo-notifications + Firebase FCM, token registration on startup
Notification preferences: per-type email/push toggles with timezone selector
Shared API client: adapted from the web frontend’s
api.tswith asyncexpo-secure-storefor token persistence and in-memory cache for fast access. Auto-detects dev mode (__DEV__) and uses local backend (10.0.2.2:8000).Usage logging: tap any benefit to log/update/delete with period selector, binary toggle, and full/partial color coding (gold = partial, green = fully used)
Auth flow: conditional root navigator renders Auth stack, Verify Pending gate, or App stack based on user state and verification status
TanStack Query: same server-state management as web, with React Native-specific
netinfo(online/offline) andAppState(focus/refetch) listenersADB screenshot tests: 8-screen automated smoke tests with content assertions, auto-seeded test data, and baseline comparison (see
mobile/TESTING.md)
Key Concepts¶
Period Types: monthly, quarterly, semiannual, annual. Each benefit resets on its period boundary.
Redemption Types: binary (used or not) and continuous (partial amounts).
Perceived Value: Users can override the face value of benefits with their personal valuation, which flows into ROI calculations.