Skip to main content

Data Model

Every Mongoose model, its fields, and how they relate. See Architecture for how these are used across a request.

Relationships at a glance

User ──┬── owns ──> WorkoutPlan (createdBy is a username, not a ref)
├── has one ──> WorkoutProgress (unique per user — one in-progress workout at a time)
├── has many ──> WorkoutSession (completed workout history)
├── has many ──> ExerciseHistory (last known reps/weight/duration, bucketed by rep count)
├── has many ──> ExerciseLog (one row per completed set, for charting)
└── has many ──> Subscription ──> WorkoutPlan (at most one active at a time)

WorkoutPlan ── days[] ── exercises[] ──> Exercise (by ref, or grouped into a superset)

License — one singleton document for the whole instance (not per-user)

Ownership fields (createdBy, username) are plain strings (lowercase usernames), not Mongoose refs to User — the app doesn't enforce referential integrity at the database level for these; lookups join by username string.

Exercise (models/exercise.js)

The exercise library — built-in and user-submitted.

FieldTypeNotes
nameString, required, trimmed
descriptionString
primaryMuscles / secondaryMuscles[String]Values from the fixed MUSCLE_GROUPS list (config/constants.js)
equipment[String]Values from the fixed EQUIPMENT list
instructions[String]Ordered steps
typeenum reps | weight | duration, default repsWhat kind of value gets logged when a set is completed
imageStringMuscle-group diagram filename
exerciseImageStringExercise-specific photo filename
media[originalName]Uploaded photo/video attachments
isBuiltInBooleanSeeded exercises vs. user-submitted
createdByStringUsername

No explicit indexes beyond _id. Referenced by WorkoutPlan.days[].exercises[].exercise, ExerciseHistory.exercise, and ExerciseLog.exercise.

ExerciseHistory (models/exerciseHistory.js)

A "last known value" cache — one document per (username, exercise, reps) combination, so the same exercise at different rep counts (e.g. a 5-rep set vs. a 12-rep set) remembers its own last weight/duration independently.

FieldNotes
username, exercise (ref Exercise), repsCompound bucket key
lastReps, lastWeight, lastDurationPrefilled into the workout-execution UI
updatedAt

Unique compound index: { username: 1, exercise: 1, reps: 1 } — enforced at the database level, so there can never be two "last value" rows for the same person/exercise/rep-count bucket.

ExerciseLog (models/exerciseLog.js)

An append-only record of every completed set, used to build the trend charts on an exercise's detail page. Unlike ExerciseHistory (overwritten in place), this grows forever.

FieldNotes
username, exercise (ref Exercise), reps, weight, duration
sideenum left | right, when the exercise tracks sides independently
dateDefaults to now
sessionref WorkoutSession — which completed workout this set belongs to

Index: { username: 1, exercise: 1, date: 1 } (non-unique — supports the chronological chart query).

License (models/license.js)

A singleton document (_id: 'singleton') — one per instance, not per user. See Licensing System for the full lifecycle.

FieldNotes
instanceIdUUID, generated once on first access, identifies this install to the licensing service
keyThe license key, or an offline token (AWPOFFLINE.… prefix)
typeenum trial | lifetime | admin | null
statusenum active | expired | revoked | unknown | null
email, expiresAt
lastValidatedAt, lastValidationReachableBookkeeping for the revalidation scheduler — reachability failures don't change status

Subscription (models/subscription.js)

The join between a user and a plan they've subscribed to.

FieldNotes
username (lowercase), plan (ref WorkoutPlan, required)
lastCompletedDate the subscriber last finished this plan
isActiveWhether this is the user's current "active plan"

Indexes:

  • Unique { username: 1, plan: 1 } — can't subscribe to the same plan twice.
  • Unique partial index { username: 1, isActive: 1 } where isActive: true — enforces at most one active plan per user at the database level, not just in application code.

User (models/user.js)

FieldNotes
usernamerequired, unique, lowercase, trimmed
displayName
isAdminBoolean
passwordHashselect: false by default (must be explicitly requested); never set for LDAP users
mustChangePasswordDrives middleware/forcePasswordChange.js
lastLogin
themeColors{ light: {...}, dark: {...} }, each with nullable text, textSecondary, bg, accent, menuText, menuBg — null means "use the app default"

WorkoutPlan (models/workoutPlan.js)

The richest schema — a plan is nested days of nested exercises, with supersets as a further nested list inside an exercise slot.

supersetExerciseSchema (subdocument, no own _id): exercise (ref, required), reps, duration, notes, trackSides.

planExerciseSchema (subdocument, no own _id) — one slot in a day, either a regular exercise or a superset:

  • Regular-exercise fields: exercise (ref, required unless it's a superset), reps, duration, notes.
  • Shared fields: sets (default 3), restBetweenSets (default 60s), restAfterExercise (default 60s), order, trackSides.
  • Superset fields: isSuperset (Boolean), label, supersetExercises (array of supersetExerciseSchema).

daySchema (subdocument, no own _id): label (free text, not a calendar date), exercises (array of planExerciseSchema).

Top-level WorkoutPlan: name (required), description, difficulty (enum beginner/intermediate/advanced), category (enum strength/cardio/hiit/flexibility/sports-specific/hybrid), estimatedDuration, tags ([String]), days ([daySchema]), isPublic (default true), createdBy (String, required), subscriberCount (Number, default 0, denormalized count for the plan browser).

WorkoutProgress (models/workoutProgress.js)

An in-progress (paused or currently running) workout execution — distinct from a completed WorkoutSession.

FieldNotes
usernamerequired, unique — at most one in-progress workout per user, across every plan
plan (ref, required), planNameDenormalized name in case the plan is later renamed/deleted
dayIdx, dayLabelWhich day of the plan is being executed
exerciseStateMixed — mirrors the client-side execution state object verbatim (which sets are checked off, current exercise index, timers)
exIdx, elapsedSeconds, startedAt, lastSavedAt

Visible to admins under Admin → Sessions for clearing out entries stuck from a crashed tab.

WorkoutSession (models/workoutSession.js)

A completed workout, used for history, stats, and the profile activity heatmap.

FieldNotes
username (required)
plan (ref, optional)Optional because a CSV-imported session may not match an existing plan by name
planName (required)Denormalized so history survives the source plan being deleted
dayLabel, completedAt (default now), durationSeconds, exerciseCount

Index: { username: 1, completedAt: -1 } — supports the profile page's reverse-chronological history and heatmap queries.