Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Lokhy87/gymApp/llms.txt

Use this file to discover all available pages before exploring further.

GymFlow is built as a classic three-tier web application. The Angular single-page application runs in the browser and communicates exclusively through a Symfony REST API. The API validates JWT tokens on every protected request, applies business logic, and persists data in a MariaDB database. All three tiers are packaged as Docker containers and wired together by Docker Compose.

Infrastructure services

The docker-compose.yml at the repository root defines two services that run on a shared gymflow_network bridge network.
ServiceContainerImage / BuildHost portContainer port
PHP/Symfony APIgymflow_php_serverBuilt from Dockerfile (PHP 8.3 + Apache)80508000
MariaDB databasegymflow_db_servermariadb:10.1133503306
The database volume (gymflow_db_mysql) is a named Docker volume so data persists across container restarts. Both containers are configured with restart: unless-stopped. The Dockerfile extends php:8.3-apache and adds Composer, the Symfony CLI, the pdo_mysql extension, and Xdebug (port 9003, trigger mode) for local development.

JWT authentication flow

GymFlow uses LexikJWT to issue and validate tokens. The sequence below shows the complete flow from login to an authenticated API call.
  1. Register — the client calls POST /api/register with email, username, and password. The ApiController hashes the password with Symfony’s UserPasswordHasherInterface and persists the new User entity.
  2. Login — the client calls POST /api/login_check with username (email) and password. LexikJWT validates the credentials via UserProvider::loadUserByIdentifier, which looks up the user by email. On success, LexikJWT returns a signed JWT.
  3. Authenticated requests — the client includes the token in every subsequent request as Authorization: Bearer <token>. Symfony’s security layer decodes and validates the token before the controller action runs.
  4. Frontend interception — the Angular authInterceptor reads the token from localStorage and clones each outgoing HttpRequest to add the Authorization header automatically. No individual service needs to set headers manually.
Browser                 Angular SPA              Symfony API              MariaDB
  │                        │                         │                       │
  │── POST /api/register ──▶                         │                       │
  │                        │── POST /api/register ──▶│                       │
  │                        │                         │── INSERT User ───────▶│
  │                        │◀── 201 { user: email } ─│                       │
  │                        │                         │                       │
  │── POST /api/login_check ▶                        │                       │
  │                        │── POST /api/login_check ▶                       │
  │                        │                         │── SELECT User ───────▶│
  │                        │◀── 200 { token: "..." } ─│                      │
  │                        │  (stored in localStorage)                       │
  │                        │                         │                       │
  │── GET /api/workouts ───▶                         │                       │
  │                        │── GET /api/workouts ────▶│                      │
  │                        │  Authorization: Bearer …│                       │
  │                        │                         │── SELECT Workout ────▶│
  │                        │◀── 200 [ ... ] ─────────│                       │
The POST /api/login_check route body uses the key username for the email value. This is a LexikJWT convention — the UserProvider resolves it against the email column in the database.

Backend architecture

The Symfony backend follows a standard layered structure under backend/src/.

Controllers

A single ApiController handles all public and authenticated REST endpoints under the /api prefix. Routes are defined with PHP 8 attributes (#[Route]). The controller delegates persistence to Doctrine via the EntityManagerInterface and uses injected repositories for reads.

Entities

Doctrine ORM entities map directly to database tables. The core entities are User, Workout, Exercises, MuscleGroups, Muscles, ExercisesMuscles, ExercisesVariants, TrainingGoal, TrainingLevel, TrainingMethod, WorkPlan, and WorkSplit.

Repositories

Each entity has a dedicated repository extending Doctrine’s ServiceEntityRepository. The WorkoutRepository is used directly in the controller for history and progress queries, including a custom QueryBuilder query that filters by user, exercise name, and date range.

Security

App\Security\UserProvider implements UserProviderInterface and loads users by email identifier. It also implements PasswordUpgraderInterface to automatically rehash passwords when Symfony detects a weaker algorithm. The stateless JWT firewall means refreshUser is rarely called in practice.

Key API endpoints

MethodPathAuth requiredDescription
GET/apiNoHealth check — returns project name, version, and status
POST/api/registerNoCreate a new user account
POST/api/login_checkNoExchange credentials for a JWT
GET/api/meYesReturn the authenticated user’s profile
PUT/api/meYesUpdate the authenticated user’s profile
GET/api/exercisesYesList all exercises, optionally filtered by muscle_group_id
GET/api/muscle_groupsYesList all muscle groups
GET/api/exercises_musclesYesList exercise–muscle associations with role data
POST/api/workoutsYesLog a new workout entry
PUT/api/workouts/{id}YesUpdate an existing workout
DELETE/api/workouts/{id}YesDelete a workout (owner-only enforced)
GET/api/historyYesReturn all workouts for the authenticated user
GET/api/progressYesReturn workout data for a named exercise over N months
DELETE /api/workouts/{id} and PUT /api/workouts/{id} perform an ownership check: if the workout’s user_id does not match the authenticated user’s ID, the API returns HTTP 403. Never skip this check in custom integrations.

Frontend architecture

The Angular application lives in frontend/src/app/ and is structured into four top-level directories.

Views

Page-level components grouped by feature: initial-page, login, register (public layout) and home, exercises, history, progress, profile (main layout behind auth). Routing is defined in app.routes.ts using two nested layout components — PublicLayout and MainLayout — that share a common shell.

Services

Injectable services wrap every API resource: AuthService (register, login, profile), WorkoutService (create workout), ExercisesService, MuscleGroupsService, HistoryService, and ProgressService. Each service reads the API base URL from environment.apiUrl (http://localhost:8050/api in development, /api in production).

Interceptors

The authInterceptor functional interceptor reads the JWT from localStorage under the key token, trims whitespace, and clones every outgoing request to add the Authorization: Bearer <token> header. Requests without a stored token are forwarded unchanged.

Shared

Reusable building blocks used across views. The interfaces/ directory defines TypeScript interfaces — Workout, CreateWorkoutRequest, CreateWorkoutResponse, UserProfile, and exercise/muscle-group shapes. The components/ directory contains shared UI components: card, modal, and sidebar.

Frontend dependencies

Key packages from package.json that shape the application:
PackageVersionPurpose
@angular/core^21.0.0Core Angular framework
@fullcalendar/angular^6.1.20Calendar view for workout history
chart.js^4.5.1Progress charts for strength over time
bootstrap^5.3.8UI layout and component styling
jwt-decode^4.0.0Decode JWT claims client-side
rxjs~7.8.0Reactive streams for HTTP and state

Environment configuration

The Angular build replaces src/environments/environment.ts with environment.prod.ts at build time.
// src/environments/environment.ts (development)
export const environment = {
  production: false,
  apiUrl: 'http://localhost:8050/api'
};
// src/environments/environment.prod.ts (production build)
export const environment = {
  production: true,
  apiUrl: '/api'
};
In production the frontend is served from the same origin as the API, so apiUrl is a relative path. In development the frontend (ng serve, port 4200) calls the Dockerised API directly on port 8050.

Docker deployment

Configure and run GymFlow in production using Docker Compose.

Environment variables

All configurable environment variables for the backend and database.

API authentication

Register, log in, and manage tokens with the authentication endpoints.

Quickstart

Get GymFlow running locally and log your first workout in minutes.

Build docs developers (and LLMs) love