COSMO Project Map

by Pablo Costa — Cloud Engineer
Private Repository — Technical Reference Document

COSMO Workout Builder

Full-stack serverless fitness application — designed, architected, and built by Pablo Costa

Cloud Engineer and Senior IT Analyst with 10+ years of experience in cloud architecture, software development, and DevOps automation. COSMO is a production-grade SaaS fitness platform built entirely on AWS serverless — from Terraform infrastructure to React frontend — demonstrating end-to-end ownership of a complex, multi-service application.

The project features 27 Lambda functions, 21 DynamoDB tables, 80+ API routes, Stripe payment integration, real-time messaging, bilingual i18n, admin panel with exercise management, and automated media processing — all deployed via Terraform with zero manual AWS console operations.

AWS Solutions Architect Associate AWS Data Engineer Associate AWS AI Practitioner Azure Administrator AZ-104
27
Lambda Functions
21
DynamoDB Tables
80+
API Routes
43
React Pages
60+
Components
~80
Terraform Files
This repository is private. This interactive document serves as a comprehensive technical reference for recruiters and engineering teams to evaluate the project's architecture, scope, and engineering decisions without requiring source code access.

Project Dashboard

COSMO Workout Builder — full-stack serverless fitness app on AWS

27
Lambda Functions
21
DynamoDB Tables
80+
API Routes
43
React Pages
60+
Components

Tech Stack

Frontend
React 19 + Vite + TailwindCSS + React Router 7. i18n (en + pt-BR). Amplify hosting at cosmo.pablobhz.cloud
Backend
Python 3.11 Lambdas with shared layer (lambda_shared). API Gateway HTTP API at api.pablobhz.cloud
Database
DynamoDB (21 tables). Single-table design for messages. GSIs for queries. TTL for auto-cleanup.
Auth
AWS Cognito with Google OAuth. JWT authorizer on API Gateway. Admin group for panel access.
Payments
Stripe Checkout + Webhooks. Tiers: free (3 workouts), premium (unlimited), founder (lifetime).
IaC
Terraform modules: foundation, shared, data, auth, compute, frontend. ~80 .tf files.

Key URLs

ServiceURLPurpose
Frontendhttps://cosmo.pablobhz.cloudReact SPA (Amplify)
Public APIhttps://api.pablobhz.cloud/*Main API Gateway (up8szzocti)
Admin APIhttps://api.pablobhz.cloud/admin/*Admin API Gateway (rnxx0jo6fg)
CDNhttps://d15bqj3wrouqr4.cloudfront.netExercise images (CloudFront)
Authcosmo-auth.auth.us-east-1.amazoncognito.comCognito domain

System Architecture

How all pieces connect end-to-end

Browser (React SPA) │ ├─ cosmo.pablobhz.cloud (Amplify) │ └─ Vite build, React Router, i18n │ ├─ Auth: AWS Cognito │ ├─ Email + Google OAuth signup/login │ ├─ JWT token stored in localStorage │ └─ PostAuthentication trigger → post_auth Lambda │ └─ Logs lastLoginAt, checks Founder 50 slots │ ├─ API calls via apiClient.js │ └─ Authorization: Bearer {JWT} │ ├─ api.pablobhz.cloud (Custom Domain) │ │ │ ├─ Path prefix "" → Main API Gateway (up8szzocti) │ │ ├─ JWT Authorizer (Cognito) │ │ ├─ 80+ routes → 26 Lambda functions │ │ └─ Each Lambda → DynamoDB table(s) │ │ │ └─ Path prefix "admin" → Admin API Gateway (rnxx0jo6fg) │ ├─ Separate JWT Authorizer (admin client) │ └─ 12 routes → 5 Lambda functions │ ├─ S3 Buckets │ ├─ cosmo-exercise-assets → CloudFront CDN │ └─ cosmo-user-media → presigned URLs │ └─ Stripe ├─ Checkout Sessions (redirect flow) └─ Webhooks → POST /payments/webhook (signature verified)

Terraform Module Graph

terraform/main.tf │ ├─ module "foundation" → Route53 zone, tags ├─ module "shared" → Lambda layer (lambda_shared) ├─ module "data" → 21 DynamoDB tables ├─ module "auth" → Cognito + Admin API + 5 admin Lambdas ├─ module "compute" → Main API Gateway + 26 Lambdas + SSL ├─ module "frontend" → Amplify hosting │ ├─ aws_s3_bucket "exercise_assets" │ └─ S3 event trigger → gif_to_webp Lambda (auto GIF→WebP) ├─ aws_s3_bucket "user_media" └─ aws_cloudfront_distribution (CDN)

Lambda Handlers

26 compute + 5 auth + 1 asset = 32 total Lambda functions

Compute (26)
Auth (5)
Assets (1)
HandlerFunction NameRoutesTables UsedTimeout
unified_exercises_handler.pycosmo-exercisesGET/PUT/POST/DELETE /exercisescosmo-exercises30s
workouts_handler.pycosmo-workoutsGET/POST/DELETE /workoutscosmo-workouts, cosmo-users30s
sessions_handler.pycosmo-sessionsGET/POST/DELETE /sessionscosmo-workout-sessions, cosmo-users, cosmo-plateau-alerts30s
session_draft_handler.pycosmo-session-draftsGET/PUT /session-draftscosmo-session-drafts15s
measurements_handler.pycosmo-measurementsGET/POST/DELETE /measurementscosmo-measurements30s
profile_handler.pycosmo-profileGET/PUT /profilecosmo-users15s
calendar_handler.pycosmo-calendarGET/POST/DELETE /calendarcosmo-calendar30s
achievements_handler.pycosmo-achievementsGET/POST/PUT /achievementscosmo-achievements30s
community_workouts_handler.pycosmo-community-workoutsGET/POST/DELETE /community-workoutscosmo-community-workouts30s
community_exercises_handler.pycosmo-community-exercisesGET/POST/PUT/DELETE /community-exercisescosmo-community-exercises30s
messages_handler.pycosmo-messagesGET/POST /messages, PUT /read, GET /unread, GET/POST /board, GET/POST /board/accept, GET /users/searchcosmo-messages, cosmo-users15s
trainer_links_handler.pycosmo-trainer-linksGET/POST/PUT/DELETE /trainer-linkscosmo-trainer-links30s
plateau_alerts_handler.pycosmo-plateau-alertsGET/POST/PUT/DELETE /plateau-alertscosmo-plateau-alerts30s
feedback_handler.pycosmo-feedbackPOST /feedback, GET/PUT /admin/feedbackcosmo-feedback30s
user_layout_handler.pycosmo-user-layoutGET/PUT /user-layoutcosmo-user-layout15s
user_media_handler.pycosmo-user-mediaPOST /user-media/upload-url, DELETE /user-media/{type}cosmo-users + S330s
share_handler.pycosmo-sharePOST/GET /sharecosmo-workouts30s
payments_handler.pycosmo-paymentsPOST /payments/create-checkout, POST /webhook, POST /cancel, GET /statuscosmo-users, cosmo-subscriptions + Stripe15s
tier_handler.pycosmo-tierGET /tier, POST /tier/check-featurecosmo-users, cosmo-tier-config15s
analytics_handler.pycosmo-analyticsPOST /analytics/event, GET /analytics/summarycosmo-analytics-events, cosmo-users10s
app_config_handler.pycosmo-app-configGET/PUT /app-config, GET/PUT /app-config/i18n, GET /app-config/foundercosmo-app-config, cosmo-tier-config15s
gym_hub_handler.pycosmo-gym-hubGET /gyms, GET /gyms/{gymId}/memberscosmo-users30s
gym_reviews_handler.pycosmo-gym-reviewsGET/POST /gym-reviewscosmo-gym-reviews30s
guest_workouts_handler.pycosmo-guest-workoutsPOST/GET /guest-workoutscosmo-workouts-guest30s
pdf_export_handler.pycosmo-pdf-exportPOST /export-pdf— (reportlab layer)60s / 512MB
admin_trainer_handler.pycosmo-admin-trainerGET/PUT /admin/trainer-requestscosmo-users30s
HandlerFunction NameRoutes / TriggerTables Used
admin_users_handler.pycosmo-admin-usersGET /users, PUT /users/{id}/*cosmo-users + Cognito
admin_dashboard_handler.pycosmo-admin-analyticsGET /analyticscosmo-exercises + Cognito
admin_presigned_url_handler.pycosmo-admin-presigned-urlPOST /presigned-urlS3 (cosmo-exercise-assets)
api_docs_handler.pycosmo-api-docsGET /api-docsAPI Gateway (route discovery)
post_auth_handler.pycosmo-post-authCognito PostAuthentication triggercosmo-users, cosmo-tier-config

S3-triggered Lambda for media processing. Defined in root-level terraform/gif_to_webp.tf.

HandlerFunction NameTriggerTables UsedLayer
gif_to_webp_handler.pycosmo-gif-to-webpS3 PutObject on exercises/**/*.gifcosmo-exercisesPillow (PIL)

Flow: Admin uploads GIF → S3 event → Lambda downloads GIF → converts to WebP via Pillow → uploads .webp back to S3 → updates gifUrl in DynamoDB to CDN WebP URL.

Lambda Shared Layer

lambda_shared — eliminates ~800 lines of duplicated code across all handlers

response.py
CORS headers + HTTP response builders. Single source of truth for Access-Control-Allow-* headers.
CORS_HEADERS ok(body) err(status, msg) options_response()
auth.py
JWT extraction from API Gateway event. Supports both injected claims and manual Bearer token decode.
get_user_id(event) get_claims(event) is_admin(event) require_auth decorator
dynamo.py
DynamoDB table initialization from environment variables. UTC timestamp helper.
get_table(env_var, default) utc_now_iso()
serializers.py
Type conversion for DynamoDB: Decimal→int/float, float→Decimal, empty string→None. Body parser.
decimals_to_json(obj) to_dynamo_safe(obj) parse_body(event)

Import pattern (every handler)

from lambda_shared.response import CORS_HEADERS, ok, err, options_response
from lambda_shared.auth import get_user_id, get_claims
from lambda_shared.dynamo import get_table, utc_now_iso
from lambda_shared.serializers import parse_body, decimals_to_json

Exception: feedback_handler.py uses its own local CORS headers and helpers instead of the shared layer (identified in R23 CORS diagnosis, not yet migrated).

API Routes

All endpoints on both API Gateways

Public API (80+ routes)
Admin API (12 routes)
MethodRouteHandlerAuth
Exercises
GET/exercisesunified_exercises_handlerJWT
POST/exercisesunified_exercises_handlerJWT (admin)
PUT/exercisesunified_exercises_handlerJWT (admin)
DELETE/exercisesunified_exercises_handlerJWT (admin)
Workouts
GET/workoutsworkouts_handlerJWT
POST/workoutsworkouts_handlerJWT
DELETE/workoutsworkouts_handlerJWT
Sessions
GET/sessionssessions_handlerJWT
POST/sessionssessions_handlerJWT
DELETE/sessionssessions_handlerJWT
Profile
GET/profileprofile_handlerJWT
PUT/profileprofile_handlerJWT
Messages & DMs
GET/messagesmessages_handlerJWT
POST/messagesmessages_handlerJWT
PUT/messages/readmessages_handlerJWT
GET/messages/unreadmessages_handlerJWT
GET/messages/boardmessages_handlerJWT
POST/messages/boardmessages_handlerJWT
GET/messages/board/acceptmessages_handlerJWT
POST/messages/board/acceptmessages_handlerJWT
GET/messages/users/searchmessages_handlerJWT
Payments
POST/payments/create-checkoutpayments_handlerJWT
POST/payments/webhookpayments_handlerPublic (Stripe sig)
POST/payments/cancelpayments_handlerJWT
GET/payments/statuspayments_handlerJWT
Measurements
GET/measurementsmeasurements_handlerJWT
POST/measurementsmeasurements_handlerJWT
DELETE/measurementsmeasurements_handlerJWT
Calendar
GET/calendarcalendar_handlerJWT
POST/calendarcalendar_handlerJWT
DELETE/calendarcalendar_handlerJWT
Community
GET/community-workoutscommunity_workouts_handlerJWT
POST/community-workoutscommunity_workouts_handlerJWT
DELETE/community-workoutscommunity_workouts_handlerJWT
GET/community-exercisescommunity_exercises_handlerJWT
POST/community-exercisescommunity_exercises_handlerJWT
PUT/community-exercisescommunity_exercises_handlerJWT
DELETE/community-exercisescommunity_exercises_handlerJWT
Session Drafts
GET/session-draftssession_draft_handlerJWT
PUT/session-draftssession_draft_handlerJWT
Guest Workouts
POST/guest-workoutsguest_workouts_handlerPublic
GET/guest-workoutsguest_workouts_handlerPublic
Other
GET/PUT/user-layoutuser_layout_handlerJWT
GET/POST/PUT/DELETE/achievementsachievements_handlerJWT
GET/POST/PUT/DELETE/trainer-linkstrainer_links_handlerJWT
GET/POST/PUT/DELETE/plateau-alertsplateau_alerts_handlerJWT
GET/POST/shareshare_handlerJWT / Public
GET/tiertier_handlerJWT
POST/tier/check-featuretier_handlerJWT
GET/gymsgym_hub_handlerJWT
GET/POST/gym-reviewsgym_reviews_handlerJWT / Public
POST/feedbackfeedback_handlerPublic
POST/analytics/eventanalytics_handlerJWT
GET/PUT/app-configapp_config_handlerJWT
GET/app-config/founderapp_config_handlerJWT
POST/user-media/upload-urluser_media_handlerJWT
POST/export-pdfpdf_export_handlerJWT

All admin routes require JWT from admin Cognito client. Accessed via api.pablobhz.cloud/admin/* (prefix stripped by domain mapping).

MethodRoute (as seen by Lambda)Handler
GET/usersadmin_users_handler
PUT/users/{id}/adminadmin_users_handler
PUT/users/{id}/confirmadmin_users_handler
PUT/users/{id}/disableadmin_users_handler
PUT/users/{id}/enableadmin_users_handler
PUT/users/{id}/tieradmin_users_handler
PUT/users/{id}/lifetimeadmin_users_handler
GET/analyticsadmin_dashboard_handler
POST/presigned-urladmin_presigned_url_handler
GET/api-docsapi_docs_handler
GET/feedbackfeedback_handler (R25)
PUT/feedbackfeedback_handler (R25)

DynamoDB Tables

21 tables, all PAY_PER_REQUEST billing

TableKey DesignGSIsTTLUsed By
cosmo-exercisespk/skby-muscle-groupexercises, admin
cosmo-workoutspk=USER#, sk=WORKOUT#|TRASH#|SHARE#workouts, share
cosmo-workout-sessionspk=USER#, sk=SESSION#ts#idby-workoutsessions
cosmo-usersuserIdGSI_GYMprofile, payments, tier, gym, messages
cosmo-measurementspk=USER#, sk=MEASUREMENT#date#idmeasurements
cosmo-calendarpk=USER#, sk=DATE#by-monthcalendar
cosmo-achievementspk=USER#, sk=BADGE#achievements
cosmo-community-workoutspk=COMMUNITY, sk=WORKOUT#by-author, by-categorycommunity_workouts
cosmo-community-exercisespk=AUTHOR#, sk=EXERCISE#by-musclecommunity_exercises
cosmo-messagespk=CONV#|USER#|BOARD#, sk=MSG#|CONV#ttl (90d board)messages
cosmo-trainer-linkspk=TRAINER#, sk=STUDENT#by-studenttrainer_links
cosmo-plateau-alertspk=USER#, sk=PLATEAU#plateau_alerts, sessions
cosmo-feedbackpk=FEEDBACK#, sk=METAby-date, by-contextfeedback
cosmo-user-layoutpk=userId, sk=LAYOUTuser_layout
cosmo-subscriptionspk=userId, sk=subscriptionIdpayments
cosmo-tier-configpk=CONFIG, sk=TIERStier, app_config, post_auth
cosmo-workouts-guestpk=GUEST#, sk=WORKOUT#24hguest_workouts
cosmo-gym-reviewspk=GYM#, sk=REVIEW#gym_reviews
cosmo-analytics-eventspk=USER#, sk=EVENT#ts#idby-event-namettlanalytics
cosmo-app-configpk=OPTIONS|I18N#, sk=typeapp_config
cosmo-session-drafts(per-user)session_draft

Pages & Router

43 pages across 4 route groups

App (17)
Public (8)
Auth (4)
Admin (12)

Wrapped in AppLayout (desktop sidebar + mobile bottom nav)

RouteComponentAPI Endpoints Used
/dashboardUserDashboardGET /sessions, /achievements, /plateau-alerts
/builderWorkoutBuilderGET /exercises, POST /workouts
/sessionWorkoutSessionPOST /sessions, PUT /session-drafts
/session-v2WorkoutSessionV2PagePOST /sessions (V2 session with stack/list modes, 3-state SetButton, inline editing)
/workoutsMyWorkoutsGET/DELETE /workouts
/profileProfilePageGET/PUT /profile, POST /user-media/upload-url
/calendarCalendarPageGET/POST/DELETE /calendar
/communityCommunityPageGET/POST /community-workouts, /community-exercises
/messagesMessagesPageGET/POST /messages, /board, /users/search
/achievementsAchievementsPageGET /achievements
/gymsGymsPageGET /gyms, /gym-reviews
/coachCoachDashboardGET/POST/DELETE /trainer-links
/settingsSettingsPageGET/PUT /user-layout, PUT /profile (workoutViewMode)
/upgradeUpgradePagePOST /payments/create-checkout, /cancel, GET /status
/statisticsStatisticsPageGET /sessions?stats=true
/onboardingOnboardingPagePUT /profile (initial setup)

Wrapped in PublicLayout (Navbar + Footer)

RouteComponent
/LandingPage
/contactContactPage
/aboutAboutPage
/faqFAQPage
/termsTermsPage
/terms-gateTermsGatePage
/shared/:shareIdSharedWorkout (public view)
/maintenanceMaintenancePage (env: VITE_MAINTENANCE_MODE, bypasses /admin routes)
RouteComponent
/loginLoginPage
/signupSignupPage
/forgot-passwordForgotPasswordPage
/auth/callbackAuthCallback (Google OAuth)

Wrapped in AdminLayout. Accessed via /admin/*

RouteComponentAdmin API Endpoint
/adminAdminDashboardGET /analytics
/admin/usersUserManagementGET /users, PUT /users/{id}/*
/admin/exercisesExerciseListGET /exercises (main API)
/admin/exercises/:idExerciseEditorPUT /exercises (main API)
/admin/dedupExerciseDedupGET /exercises
/admin/optionsAdminOptionsPageGET/PUT /app-config
/admin/translationsAdminTranslationsPageGET/PUT /app-config/i18n
/admin/feedbackFeedbackPanelGET/PUT /feedback
/admin/api-docsApiDocsGET /api-docs
/admin/loginAdminLogin

Components

60+ reusable components organized by domain

Layout (5)
AppLayout, DesktopSidebar, MobileBottomNav, MobileHeader, HamburgerMenu
Common (7)
Navbar, Footer, Modal, ConfirmModal, ErrorBoundary, TermsBanner, SignupPrompt
UI (7)
Button, Card, Badge, Avatar, SectionHeader, BottomSheet, UpgradePrompt, FounderBadge, FeedbackFAB
Workout (12)
WorkoutPanel, WorkoutItemForm, TemplatesTab, CalendarTab, FavoritesTab, HistoryTab, MeasurementsTab, PRsTab, StatisticsTab, PlaceholderTab, LogPastSessionModal, PlateauBanner
Workout Session V2 (7)
WorkoutSessionV2 (orchestrator), ActiveCard (active exercise w/ set table), SetButton (3-state: ready/active/rest), EditableCell (inline reps/kg editing), NextCard (next exercise preview), RemCard (remaining exercises compact), SessionSummary (post-workout stats)
Dashboard (7)
StatsOverview, WeeklyCalendar, MuscleChart, WorkoutCountCard, PlateauAlerts, RecentBadges, RecentSessions
Exercise (4)
ExerciseCard (with multi-image GifModal carousel), ExerciseList, ExerciseConfigPanel, EquipmentBadge
Share (2)
ShareModal, ShareTabButton
Gym (1)
GymReviewModal
Coach (2)
AssignWorkoutModal, StudentDetailModal

API Client Modules

24 frontend API modules — all import from apiClient.js

ModuleBackend LambdaDynamoDB Table
api/auth.jsCognito (Amplify SDK)
api/exercises.jsunified_exercises_handlercosmo-exercises
api/workouts.jsworkouts_handlercosmo-workouts
api/sessions.jssessions_handlercosmo-workout-sessions
api/profile.jsprofile_handlercosmo-users
api/measurements.jsmeasurements_handlercosmo-measurements
api/calendar.jscalendar_handlercosmo-calendar
api/achievements.jsachievements_handlercosmo-achievements
api/messages.jsmessages_handlercosmo-messages
api/communityWorkouts.jscommunity_workouts_handlercosmo-community-workouts
api/communityExercises.jscommunity_exercises_handlercosmo-community-exercises
api/trainerLinks.jstrainer_links_handlercosmo-trainer-links
api/share.jsshare_handlercosmo-workouts
api/tier.jstier_handlercosmo-users, cosmo-tier-config
api/feedback.jsfeedback_handlercosmo-feedback
api/gyms.jsgym_hub_handlercosmo-users
api/gymReviews.jsgym_reviews_handlercosmo-gym-reviews
api/userLayout.jsuser_layout_handlercosmo-user-layout
api/userMedia.jsuser_media_handlercosmo-users + S3
api/plateauAlerts.jsplateau_alerts_handlercosmo-plateau-alerts
api/pdfExport.jspdf_export_handler
api/analytics.jsanalytics_handlercosmo-analytics-events
api/adminApi.jsadmin_users, admin_analyticscosmo-users + Cognito
api/adminTrainer.jsadmin_trainer_handlercosmo-users
api/adminUpload.jsadmin_presigned_url_handlerS3 (presigned URL + direct upload)

Hooks & Utilities

Custom React hooks and helper modules

Hooks (7)

HookPurpose
useFormState.jsForm state management with dirty tracking
useMediaQuery.jsResponsive breakpoint detection
useUserStats.jsAggregated user statistics (sessions, PRs, streaks)
usePlusMenu.jsPlus button menu options (user-customizable)
useTierGate.jsFeature gating by subscription tier
useAppConfig.jsDynamic app config from API (equipment, categories, muscles, difficulty, hubs). Cached in sessionStorage with 5min TTL, falls back to appDefaults.js
useUnreadMessages.jsPolls GET /messages/unread every 30s for sidebar badge

Utilities (12)

FilePurpose
apiClient.jsCentralized HTTP client with typed errors (ApiError), auth headers, 401 handling
authUtils.jsgetCurrentUser, clearStaleTokens, profile extraction
profileCache.jslocalStorage cache for user profile (avoids redundant API calls)
storageHelpers.jsSafe localStorage get/set with JSON parse/stringify
formatters.jsDate, number, unit formatting (kg/lbs, duration, etc.)
workoutUtils.jsWorkout-specific helpers (volume calc, exercise grouping)
shareHelper.jsShare link generation and clipboard copy
errorHandler.jsConsistent error handling with toast notifications
confirmDialog.jsProgrammatic confirmation dialogs
loadingState.jsLoading/error/data state management
analytics.jsEvent tracking (POST /analytics/event)
handleSaveError.jsxError boundary for save operations

Terraform Modules

6 modules, ~80 .tf files

ModuleFilesPurposeKey Resources
foundation3Base infrastructureRoute53 zone, tags, naming
shared1 + python/Lambda shared layeraws_lambda_layer_version (lambda_shared)
data22DynamoDB tables21 aws_dynamodb_table resources + outputs
auth13Cognito + Admin APIUser pool, OAuth, admin API Gateway, 5 admin Lambdas
compute36Main API + LambdasAPI Gateway, 26 Lambdas, SSL cert, custom domain, IAM
frontend2Amplify hostingaws_amplify_app, domain association

Root-level Terraform (non-module)

FilePurposeKey Resources
gif_to_webp.tfGIF→WebP auto-conversionIAM role, Pillow Lambda layer, Lambda function, S3 event notification trigger
terraform_assets.tfS3 + CloudFront for exercisesaws_s3_bucket (exercise_assets, user_media), aws_cloudfront_distribution

Lambda Layers

LayerPurposeUsed By
lambda_sharedresponse, auth, dynamo, serializersAll compute + auth Lambdas
reportlabPDF generation librarypdf_export_handler only
stripeStripe Python SDKpayments_handler only
pillowPIL image processing (GIF→WebP)gif_to_webp_handler only

Auth & Admin API

Cognito setup and the admin API Gateway

Authentication Architecture Cognito User Pool (us-east-1_wlAozOjDW) ├─ Email provider (SES) ├─ Google OAuth (social login) ├─ Password policies ├─ MFA: optional │ ├─ App Client (main app) │ └─ Used by: Frontend React app │ └─ Audience for main API JWT authorizer │ ├─ Admin Client (admin panel) │ └─ Used by: Admin panel │ └─ Audience for admin API JWT authorizer │ ├─ Admin Group ("Admins") │ └─ Members get admin panel access │ └─ PostAuthentication Trigger └─ post_auth_handler.py ├─ Logs lastLoginAt to cosmo-users └─ Checks Founder 50 slots → grants founder status Domain Mapping (api.pablobhz.cloud) ├─ Key: "" (root) → Main API (up8szzocti) stage "dev" └─ Key: "admin" → Admin API (rnxx0jo6fg) stage "admin" └─ Important: /admin prefix is STRIPPED before reaching Lambda R18-001: Multi-Domain OAuth (amplifyConfig.js) ├─ getOAuthRedirects() — resolves redirect URLs at runtime │ ├─ cosmofit.com.br / www.cosmofit.com.br → https://cosmofit.com.br/auth/callback │ └─ cosmo.pablobhz.cloud / localhost → https://cosmo.pablobhz.cloud/auth/callback ├─ Single build serves both domains — no env-specific builds needed ├─ Uses Amplify v6 Auth.Cognito config format └─ Cognito OAuth domain: cosmo-auth.auth.us-east-1.amazoncognito.com

Hosting & CDN

Amplify, S3, CloudFront

Amplify (Frontend)
Auto-deploys from GitHub main branch. Builds with Vite, serves from frontend/dist. SPA catch-all routes to /index.html.
cosmo.pablobhz.cloud · cosmofit.com.br
S3: Exercise Assets
Exercise images and videos. Served via CloudFront CDN. Admin uploads via presigned URLs.
d15bqj3wrouqr4.cloudfront.net
S3: User Media
Profile photos. Direct presigned URL uploads from frontend. Referenced in cosmo-users table.
cosmo-user-media bucket

Flow: Authentication

End-to-end auth from login to API call

1. User logs in (email or Google) │ ├─ amplifyConfig.js → resolves OAuth redirects for current domain (R18-001) │ └─ cosmofit.com.br | cosmo.pablobhz.cloud → matching callback URL │ ├─ api/auth.js → AWS Cognito via Amplify SDK │ └─ Returns JWT (idToken) stored in localStorage │ ├─ Cognito PostAuthentication trigger fires │ └─ post_auth_handler.py │ ├─ SET lastLoginAt in cosmo-users │ └─ Check Founder 50 slots → atomically grant if available │ ├─ 2. App.jsx: getCurrentUser() │ ├─ Decode JWT → extract userId, email, groups │ ├─ GET /profile → user metadata (tier, role, displayName) │ └─ Cache in localStorage via profileCache.js │ ├─ 3. Every API call │ └─ apiClient.js adds Authorization: Bearer {token} │ ├─ getAuthToken() tries: │ │ 1. Amplify session (auto-refreshes) │ │ 2. Amplify localStorage idToken (with expiry check) │ │ 3. Manual userToken (backward compat) │ └─ On 401 → clearStaleTokens() → ApiError.NOT_AUTHENTICATED │ └─ 4. Lambda side └─ lambda_shared/auth.py: get_user_id(event) └─ Extracts userId from JWT claims in API Gateway context

Flow: Payments (Stripe)

Checkout, subscription management, cancellation

Checkout Flow │ ├─ User clicks "Upgrade" on /upgrade page │ └─ POST /payments/create-checkout { priceId } │ ├─ payments_handler.handle_create_checkout() │ ├─ Validate user exists and is not already premium │ ├─ Create Stripe Checkout Session │ │ ├─ success_url: frontend_url/upgrade?success=true │ │ └─ cancel_url: frontend_url/upgrade │ └─ Return { sessionUrl } → frontend redirects │ ├─ User completes payment on Stripe │ ├─ Stripe Webhook → POST /payments/webhook (public, signature-verified) │ └─ handle_checkout_completed() │ ├─ SET tier = "premium" in cosmo-users │ ├─ SET subscription_id, tier_since │ ├─ REMOVE cancel_at_period_end, cancel_requested_at │ └─ Record in cosmo-subscriptions Cancel Flow │ ├─ Premium user clicks "Cancel" on /upgrade │ └─ POST /payments/cancel │ ├─ handle_cancel_subscription() │ ├─ Validate: tier = "premium" (not "free", not "premium_lifetime") │ ├─ stripe.Subscription.modify(cancel_at_period_end=True) │ ├─ SET cancel_at_period_end, cancel_requested_at in cosmo-users │ └─ Return { cancelAt: period_end_date } │ └─ Later: Stripe webhook → customer.subscription.deleted └─ handle_subscription_deleted() └─ SET tier = "free", REMOVE subscription_id + cancel fields Tier System ├─ free: 3 workouts max ├─ premium: unlimited (monthly subscription) └─ founder: unlimited (lifetime, first 50 users)

Flow: Messages

DMs and Community Board

DM Flow │ ├─ User opens /messages → DM tab │ ├─ GET /messages → conversation list (polls 30s) │ └─ UserSearchInput: GET /messages/users/search?q=name (debounced 400ms) │ ├─ User selects conversation │ ├─ GET /messages?with={userId} → messages (polls 5s) │ ├─ PUT /messages/read → mark as read │ └─ POST /messages { to, text } → send DM │ └─ Privacy check: recipient.accept_messages !== false │ ├─ If false → 403 "does not accept messages" │ └─ If true/unset → allow (opt-out model) │ ├─ DynamoDB key design: │ ├─ Messages: pk=CONV#{sorted_a|b}, sk=MSG#{ts}#{uuid} │ └─ Index: pk=USER#{id}, sk=CONV#{otherId} → lastMsg, unreadCount Board Flow │ ├─ User opens /messages → Board tab │ ├─ GET /messages/board/accept → check disclaimer │ ├─ If not accepted → POST /messages/board/accept │ ├─ GET /messages/board → board messages (polls 5s) │ └─ POST /messages/board { text } → post (max 500 chars) │ └─ Board messages: pk=BOARD#general, sk=MSG#{ts}#{uuid} └─ TTL: auto-delete after 90 days

Flow: Workout Session V2

R12-001: Complete live workout experience with 3-state set tracking

Session V2 Flow │ ├─ User taps "Start Workout" on /workouts (MyWorkouts.jsx) │ └─ Navigate to /session-v2?workoutId=xxx │ ├─ WorkoutSessionV2Page.jsx — route wrapper │ ├─ Loads workout from API via getWorkoutAsync(workoutId) │ ├─ Loads profile for workoutViewMode preference (stack/list) │ └─ Renders WorkoutSessionV2 orchestrator │ ├─ WorkoutSessionV2.jsx — main orchestrator │ ├─ Manages exercise list, active index, completion state │ ├─ Stack Mode: ActiveCard (large) → NextCard (medium) → RemCards (compact) │ ├─ List Mode: All exercises vertical, completed=collapsed, active=expanded │ └─ Toggle ▣/☰ switches modes, saved to profile │ ├─ ActiveCard.jsx — the exercise being worked │ ├─ Set table: rows of SetButton + EditableCell (reps) + EditableCell (weight) │ ├─ + / − buttons to add/remove sets │ └─ Accumulates statsRef (duration, rest, skipped) │ ├─ SetButton.jsx — 3-state machine │ ├─ GREEN (ready): "INICIAR" — fields editable — tap → RED │ ├─ RED (active): ascending timer — fields LOCKED 🔒 — tap → YELLOW │ └─ YELLOW (rest): countdown timer — fields editable — auto→GREEN or tap to skip │ ├─ EditableCell.jsx — inline editing │ ├─ Idle: looks like text, +/− buttons visible │ ├─ Editing: input with purple border, +/− hidden │ └─ canEdit = (isActive && !fieldsLocked) || set.done │ ├─ On "Finish Workout": │ ├─ Collects all stats → builds payload │ └─ POST /sessions (existing sessions_handler.py) │ └─ Payload: workoutId, workoutName, startedAt, finishedAt, durationSeconds, exercises[{sets[]}] │ └─ SessionSummary.jsx — post-workout stats ├─ Total duration, volume per exercise (sets × reps × kg) ├─ Rest analysis (planned vs actual) ├─ Skipped/modified sets highlighted └─ Premium CTA at bottom

Flow: Media Pipeline

R19-003 & R19-010: Exercise image upload, auto-conversion, and gallery

Upload Flow (Admin) │ ├─ Admin opens ExerciseEditor → uploads GIF/JPG/PNG/WebP │ ├─ Primary image: single main image (gifUrl) │ └─ Gallery: up to 10 additional images (media.gallery[]) │ ├─ adminUpload.js orchestrates: │ ├─ POST /admin/presigned-url → gets S3 presigned PUT URL │ ├─ PUT file directly to S3 (XHR with progress tracking) │ └─ PUT /exercises → saves CDN URL to exercise record │ ├─ GIF Auto-Conversion (R19-003): │ ├─ S3 PutObject event on exercises/**/*.gif │ ├─ Triggers gif_to_webp_handler.py Lambda │ ├─ Pillow converts GIF → WebP (animated preserved) │ ├─ Uploads .webp to same S3 path │ └─ Updates DynamoDB gifUrl to CDN WebP URL │ └─ Gallery UI (R19-010): ├─ ExerciseEditor: thumbnail grid, hover to promote (★) or remove (✕) ├─ ExerciseCard: GifModal carousel with prev/next arrows + dot navigation └─ "Ver animação" button shows +N count when gallery exists

Changelog (R12 — R19)

Recent feature additions and improvements

CommitFeatureFiles ChangedDetails
R12-001Workout Session V27 new components + 1 new page + profile_handler + SettingsPage + App.jsxComplete live workout experience: 3-state SetButton (ready/active/rest), stack & list modes, inline-editable cells, session summary. Route: /session-v2. Profile field: workoutViewMode
R19-001Dynamic Hub ManagementappDefaults.js, ExerciseEditor.jsxAdded HUB_OPTIONS constant (11 curated exercise hubs). ExerciseEditor gets hub badge UI with inline hub creation
R19-002Maintenance ModeMaintenancePage.jsx, App.jsx, .env, .env.productionEnv var VITE_MAINTENANCE_MODE gates all non-admin routes. Full-page maintenance screen with bilingual text
R19-003GIF→WebP Auto-Conversiongif_to_webp.tf, gif_to_webp_handler.py, rezip_lambdas.shS3-triggered Lambda converts uploaded GIFs to WebP via Pillow. Pillow Lambda layer. Updates exercise gifUrl in DynamoDB
R19-004/005Admin Filter Persistence + Dismiss DuplicatesExerciseList.jsx, ExerciseEditor.jsxExerciseList filters/sort persisted to localStorage. ExerciseEditor duplicate detection now dismissable with sorted key pairs
R19-006/007Fix Hub “undefined” BugExerciseEditor.jsxRemoved all hub.emoji references causing “undefined” display after HUB_OPTIONS schema change
R19-008/009Hub CRUD + Dynamic OptionsAdminOptionsPage.jsx, useAppConfig.js, ExerciseEditor.jsxAdminOptionsPage gets HubSection (full CRUD: add/rename/remove hubs). useAppConfig now fetches HUBS from API. ExerciseEditor switched from static imports to useAppConfig() for dynamic equipment/categories/muscles/hubs
R19-010Multi-Image GalleryExerciseEditor.jsx, ExerciseCard.jsxGallery upload section in ExerciseEditor (max 10 images, promote-to-main, remove). GifModal in ExerciseCard replaced with carousel (arrows, dots, counter). +N gallery hint on “Ver animação” button

Environment Variables Introduced

VariableCommitPurpose
VITE_MAINTENANCE_MODER19-002Boolean flag to show maintenance page for all non-admin routes

Schema Changes

LocationCommitChange
cosmo-users (profile)R12-001Added workoutViewMode: "stack" | "list" (default: stack)
cosmo-app-configR19-008Added HUBS: { items: [{ key, label }] } option type
cosmo-exercises (media)R19-010Added media.gallery: string[] (up to 10 CDN URLs)