Stay consistent with your workouts and compete with your friends for the best Gym Bro A Novessa product.
Status: working MVP. Live demo coming soon.
Log your workouts, set your own gym schedule, and see how you stack up against friends on consistency and progress, not just raw weight lifted.
- Workout logging: exercises, sets, reps, and weight, with your last session shown as a hint
- Custom schedule: choose your gym days; your streak only breaks if you miss a scheduled day
- Friends: send and accept requests; only accepted friends see your stats
- Crew leaderboard: weekly training volume, streak, and adherence % side by side, plus a friends' activity feed
- PRs and progress: automatic personal-record detection and a strength chart per lift
- Project structure and repository
- Database models
- Register and login
- Workout logging
- Schedule and streak logic
- Friends (request and accept)
- Leaderboard
- Tests
- Edit a logged workout
- Deployment
- Backend: Python, FastAPI
- Database: SQLite via the standard-library
sqlite3module (development) - Frontend: Jinja templates, HTML, CSS, a little JavaScript (mobile-first)
- Testing: pytest
git clone https://github.com/Vivan-coder/Novessa-GymBro.git
cd Novessa-GymBro
python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt
uvicorn app.main:app --reload
Then open http://127.0.0.1:8000. The SQLite database is created in data/ on first start (set GYMBRO_DATA to use another folder). Run the tests with python -m pytest.
Modelled on what Strong, Hevy and Strava do well. Each feature targets one reason people quit gym apps.
| Feature | Why |
|---|---|
| Streak hero + milestone bar (3, 7, 14, 30... days) | A visible next goal beats a bare number |
| Week strip (done / missed / rest) | See the week in one glance |
| "Last time" hint + "Use these sets" when logging | Progressive overload needs knowing what you did last |
| PR detection (Epley estimated 1RM) | Celebrates real progress: weight × (1 + reps/30) compares 5×100 with 3×105 fairly |
| Progress charts per lift (server-built SVG, no JS library) | Shows the strength curve going up |
| Rest timer | Replaces a second app on your phone |
| Crew: leaderboard (streak or volume) + activity feed | Social accountability, the main retention driver |
Removed as useless: the email field (nothing used it), separate Friends and Leaderboard pages (same data twice, now one Crew tab), and Schedule as a nav tab (set once, now a link from Home). The old URLs redirect.
Schema migration: adding PR tracking meant a new workouts.pr column. CREATE TABLE IF NOT EXISTS never alters an existing table, so db.init() checks PRAGMA table_info and runs ALTER TABLE for old databases. Existing data is kept (there is a test for this).
- Streaks follow your schedule. Rest days don't break a streak, and schedule changes only affect future days, so history is never rewritten.
- Streaks are calculated, not stored. Computing them from workout dates keeps them accurate if data changes.
- Consent for sharing. Friendships need an accepted request before any stats are visible.
- Consistency over raw lifts. Adherence % makes comparisons fair across experience levels.
Browser ──HTTP──> app/main.py (routes) ──> app/db.py (SQL + rules) ──> data/gymbro.db
│
└──> app/templates/*.html (Jinja2) ──> HTML back to the browser
| File | Job | Why it's separate |
|---|---|---|
app/main.py |
URL routes, validation, sessions | Thin web layer |
app/db.py |
Schema, password hashing, streak/PR/chart maths | Testable without HTTP |
app/templates/ |
HTML pages, all extending base.html |
One layout, no duplication |
app/static/style.css |
Styling | Cached by the browser |
tests/test_app.py |
End-to-end and unit tests | Safety net for changes |
users: username (unique, case-insensitive), password hash. (The legacyemailcolumn is kept but no longer collected.)workouts: one row per exercise per day, with aprflag;sets: reps and kg, many per workout.schedule: which weekdays (0 = Mon) a user plans to train.friendships:requester → addresseewithstatuspending/accepted.
Volume, streak and adherence are computed from workouts on demand, not stored. Stored counters can drift out of sync with the data. Computed values are always correct. The trade-off is query cost, which is trivial at this scale.
- Sessions. After login the server puts
uidin a signed cookie (StarletteSessionMiddleware). The cookie is tamper-proof, not encrypted, so never put secrets in it. The signing key is generated once and stored indata/secret.key. - Password storage. Passwords are hashed with a random salt and PBKDF2 (
db.hash_pw), compared in constant time (hmac.compare_digest). - Post/Redirect/Get. Every form POST ends in a 303 redirect, so pressing refresh doesn't resubmit.
- Security basics. Parameterised SQL (
?) blocks SQL injection. Jinja2 auto-escaping blocks XSS. All routes except/loginand/registerrequire a session (current_user). AnOrigincheck plusSameSite=Laxcookies protect against CSRF. - Streak rules are documented in
db.statsand pinned by a unit test with a fixed date. Takingtodayas a parameter makes time-dependent code testable. - Progressive enhancement. Forms work without JavaScript. The scripts on the log page only add the "Add set" button, the custom-exercise box, "last time" and the rest timer.
- CSRF is handled by an
Originheader check in middleware plusSameSite=Laxcookies. Per-form tokens are the more traditional approach, so try adding them as an exercise. - Login rate limit (5 failures per 5 minutes per IP and username) lives in memory, so it resets on restart and isn't shared across processes. Use Redis if you scale out.
- Set
https_only=Trueon the session middleware when deployed behind HTTPS. - You can delete a logged workout (own workouts only) but not edit one yet.
- The crew board ranks by streak, then adherence, then volume (or by volume with
?by=volume). Change the sort keys increw()if you prefer another rule. requirements.txtstill listssqlmodel/SQLAlchemyfrom the original plan; the app currently uses plainsqlite3. Adopt SQLModel or drop them.- For several users or machines, move from SQLite to PostgreSQL and add migrations (Alembic).
Built by Vivan, Computer Science student.