πŸ—ΊοΈ J4H Project Explainer

The complete tech stack β€” how all the pieces fit together

J4H (Journaling for Health) is a full-stack web application with a Python/Flask backend, a vanilla JavaScript frontend, a PostgreSQL database, AI-generated summaries, FHIR health record integration, end-to-end encryption, and a native Android app. Here is how all the pieces connect.
SYSTEM ARCHITECTURE
πŸ“± Android App ──▢ ☁️ Cloudflare ──▢ ☁️ Heroku ──▢ βš—οΈ Flask
🌐 Browser ──▢
Flask talks to:
πŸ—„οΈ PostgreSQL β€” stores encrypted diary entries
πŸ€– Claude API β€” generates AI summaries
πŸ₯ GTRI FHIR β€” reads patient health records
πŸ“§ Gmail SMTP β€” sends summaries and exports
πŸ”„
Request Flow
What happens when you open a diary entry
β–Ό
  1. You open j4h.org in your browser or Android app
  2. Cloudflare receives the request over HTTPS, proxies it to Heroku
  3. Gunicorn (running on Heroku) receives it and passes it to Flask
  4. Flask matches the URL to a route (e.g. /entries) and returns the HTML page
  5. The browser loads the page, then JavaScript calls fetch('/api/entries')
  6. Flask queries PostgreSQL and returns the entries as JSON β€” still encrypted as ENC:iv:ciphertext
  7. JavaScript calls decryptEntries() using the key stored in sessionStorage
  8. The decrypted entries are rendered in the browser β€” the server never saw the plaintext
πŸ”’
Security Model
How diary data is kept private end-to-end
β–Ό
  • HTTPS everywhere β€” Cloudflare enforces HTTPS and uses Full (Strict) SSL to Heroku
  • Passcode gate β€” the app requires a 4-digit class number before any page loads
  • Key derivation β€” the passcode is run through PBKDF2 (100,000 iterations) to produce a 256-bit AES key
  • Client-side encryption β€” entries are encrypted with AES-GCM before being sent to the server
  • Server stores ciphertext only β€” the database never contains plaintext diary entries
  • Session key β€” the derived key lives in sessionStorage and is wiped when the tab closes
  • Portable encryption β€” any device with the correct passcode can decrypt the data

What the server sees

ENC:abc123:xyz789...
Ciphertext only. Useless without the key.

What the browser sees

"Left knee pain after morning walk, level 4."
Plaintext after decryption.

πŸ—„οΈ
Data Layer
What gets stored and where
β–Ό
  • Diary entries β€” stored in PostgreSQL. Content and location are AES-GCM ciphertext. Pain level is plaintext integer.
  • AI summaries β€” stored in PostgreSQL after generation. Contain the Claude-generated text and date range.
  • Settings β€” key/value table storing selected patient ID and other preferences.
  • FHIR data β€” not stored locally. Fetched live from the GTRI FHIR server on every page load.
  • Encryption key β€” never stored on the server. Lives in browser sessionStorage only.
  • Exported history β€” the .j4h file is the full entry set re-encrypted as a single blob.
Database schema (simplified)
entries id, content(encrypted), pain_level, location(encrypted), created_at summaries id, summary, start_date, end_date, entry_count, created_at settings key, value
πŸ—οΈ
Infrastructure
Cloudflare, Heroku, and the domain
β–Ό
  • Domain β€” j4h.org registered and managed through Cloudflare
  • Cloudflare proxy β€” handles HTTPS, DDoS protection, and CDN caching for static assets
  • SSL mode β€” Full (Strict): encrypted browser β†’ Cloudflare β†’ Heroku with verified certificates
  • Heroku β€” Basic dyno ($7/month) + PostgreSQL Essential-0 ($5/month) = $12/month total
  • Heroku ACM β€” auto-provisioned Let's Encrypt SSL certificates for j4h.org and www.j4h.org
  • Deploy β€” git push heroku main triggers a build and zero-downtime deploy
The full request path
User β†’ j4h.org (HTTPS) β†’ Cloudflare (proxy, Full Strict SSL) β†’ Heroku (j4h-be058543801b.herokuapp.com) β†’ Gunicorn β†’ Flask β†’ PostgreSQL
πŸ“±
Mobile App
The Android app β€” same codebase, native shell
β–Ό
  • Built with Capacitor β€” wraps the web app in an Android WebView
  • The Android app loads https://j4h.org β€” it is the same app as the website
  • No separate mobile codebase β€” 100% code reuse
  • Built and signed in Android Studio from the android/ folder
  • App icons were generated programmatically with generate_icons_simple.py
  • App ID: com.j4h.healthdiary
Why not a dedicated mobile framework? React Native or Flutter would require rewriting the entire frontend. With Capacitor, the web app becomes the mobile app for free β€” including all encryption, charts, and FHIR features.
πŸ› οΈ
Development Workflow
How features are built, tested, and deployed
β–Ό
  1. Create a feature branch off main (e.g. git checkout -b vitals)
  2. Develop locally with python app.py β€” Flask runs at localhost:5000
  3. Run unit tests with pytest test_app.py -v (25 tests against a temp SQLite DB)
  4. Run smoke tests with pytest test_smoke.py -v (15 tests against the live j4h.org URLs)
  5. Deploy to Heroku: git push heroku branch:main
  6. Verify on the live site, then merge the branch into main
  7. Push main to GitHub: git push origin main
Key git branches used in this project
main β€” stable production code capacitor β€” Android app setup vitals β€” FHIR vitals chart persist β€” client-side encryption sendhistory β€” export / import history

Want to go deeper?

These explainers cover each layer in detail: