Legacy App Analysis

Analysis of the existing Range Day app (FlutterFlow + Supabase). Source code is in tmp/range_day/ (not committed to the repo). This document is the reference for migration planning and feature parity decisions.

Architecture

  • Frontend: FlutterFlow (generates Dart/Flutter code)
  • Backend: Supabase (Postgres + Auth + Realtime + Storage)
  • Auth: Supabase Auth (email/password)
  • Push notifications: Firebase Cloud Messaging (FCM)
  • Analytics: Firebase Analytics + Mixpanel (user identification)
  • API: One Supabase Edge Function (search-users-by-name)
  • State management: FlutterSecureStorage for persisted state, in-memory for session state

No backend logic layer — the app talks directly to Supabase. All business logic is in Dart on the client.

Database Schema (27 tables)

Users & Auth

users

ColumnTypeNotes
idString (UUID v4)PK, from Supabase Auth
emailString
first_name, last_name, nameStringRedundant name fields
user_tagStringUnique display handle
photo_urlStringProfile photo
phone_numberString
is_adminbool
is_influencerboolContent creator flag
opt_in_emailsbool
social_notificationsbool
trex_notificationsbool
new_item_notificationsbool
sales_notificationsbool
fcm_tokenList<String>Push notification tokens
favorited_drillsList<int>FK to drills
favorited_assessmentsList<int>
favorited_qualsList<int>
favorited_coursesList<int>FK to courses
deleted_custom_drillsintCounter
dry_fire_drill_countintCounter
created_atDateTime

admin_users — Simple table with just id (String). Separate from users.is_admin.

Drills

Three separate drill tables exist in the legacy app. The new schema unifies these into a single Drill entity with type/visibility fields.

drills (T.REX-curated)

ColumnTypeNotes
idintPK (integer, not UUID)
drill_nameString
drill_descriptionString
alpha_value, charlie_value, delta_value, mike_valueintUSPSA target zone point values
no_shoot_valueintPenalty for hitting no-shoot target
drill_targetsList<String>Target identifiers
total_roundsintRound count
drill_stepsdynamic (JSON)Structured steps
possible_pointsintMax possible score
image_urlStringDrill diagram
stage_image_urlStringStage layout image
hits_needed_image_urlStringRequired hits diagram
video_url, video_url_youtubeStringInstructional video
categoryStringDrill category
loadoutdynamic (JSON)Required equipment
is_activeboolPublish flag
created_at, last_update_timeDateTime

custom_drills (user-created)

ColumnTypeNotes
idintPK
user_idStringFK to users
drill_name, drill_descriptionString
target_countint
drill_stepsList<dynamic>JSON
par_timedouble
alphaValue, charlieValue, deltaValue, missValue, noShootValueintScoring values
distance_metricString
stage_imageString
custom_stageList<dynamic>JSON — custom stage layout
is_drillboolDistinguishes drills from assessments
created_atDateTime

dry_fire_drills

ColumnTypeNotes
idintPK
drill_name, drill_descriptionString
recommended_par_timedouble
video_url, image_url, video_url_youtubeString
indexintDisplay ordering
created_at, last_update_timeDateTime

Courses

courses (from curated drills)

ColumnTypeNotes
idintPK
course_titleString
stage_countint
user_idStringFK to users (creator)
coursesList<dynamic>JSON — ordered drill references
drill_idsList<int>FK to drills
created_atDateTime

custom_courses (from custom drills)

ColumnTypeNotes
idintPK
course_titleString
stage_countint
user_idStringFK to users
coursesList<dynamic>JSON — ordered drill references
created_atDateTime

Completed Drills (Scores)

completed_drills

ColumnTypeNotes
idintPK
user_idStringFK to users
drill_nameStringEmbedded snapshot (same as our score portability)
drill_descriptionStringEmbedded snapshot
total_timedouble
hit_factordoublePoints / time
total_pointsint
notesStringUser notes
target_scoresList<dynamic>JSON — per-target breakdown
drill_stepsList<dynamic>JSON — step results
alphaValue, charlieValue, deltaValue, missValue, noShootValueintScoring params snapshot
distance_metricString
categoryString
is_custom_drillboolIndicates source table
is_community_drillbool
user_uploaded_photoStringUser media
user_uploaded_videoStringUser media
created_atDateTime

completed_multi_shooter_drills

ColumnTypeNotes
idintPK
user_idStringFK to users (organizer)
drill_name, drill_descriptionString
shooter_resultsList<dynamic>JSON — per-shooter scores
par_timedouble
shooter_countint
unique_idStringDedup key
is_custom_drillbool
alphaValue, charlieValue, deltaValue, missValue, noShootValueint
distance_metricString
created_atDateTime

completed_custom_courses

ColumnTypeNotes
idintPK
user_idStringFK to users
course_titleString
stage_count, shooter_countint
course_resultsList<dynamic>JSON — per-drill results
is_msdboolMulti-shooter flag
is_community_coursebool
created_atDateTime

Social & Friends

friendships

ColumnTypeNotes
idintPK
user_idStringFK to users
friend_idStringFK to users
statusint0=pending, 1=accepted
is_notification_openbool
created_atDateTime

friend_requests

ColumnTypeNotes
friendship_idintFK to friendships
friend_idStringFK to users (recipient)
requester_idStringFK to users (sender)
requester_tagStringDisplay name
requester_photoString
request_timeDateTime

my_friends (denormalized view of accepted friendships)

ColumnTypeNotes
idintPK
user_idStringFK to users
friend_idStringFK to users
user_tagString
friend_photoString
statusint
frienship_start_timeDateTimeNote: typo in column name

influencer_followers

ColumnTypeNotes
idintPK
influencer_idStringFK to users
follower_idStringFK to users
created_atDateTime

friends_leaderboard

ColumnTypeNotes
user_idStringFK to users
friend_idStringFK to users
friend_user_tagString
drill_nameString
hit_factordouble
completed_atDateTime
friend_photo_urlString

Challenges

challenges

ColumnTypeNotes
idintPK
created_byStringFK to users
drill_idintFK to drills
drill_nameString
expires_atDateTime
participant_countint
participantsList<String>FK to users
is_custom_drillbool
is_completedbool
created_atDateTime

user_challenges

ColumnTypeNotes
idintPK
challenge_idintFK to challenges
user_idStringFK to users
statusString’pending’, ‘accepted’, ‘completed’
accepted_at, completed_atDateTime
total_timedouble
total_pointsint
hit_factordouble
notesString
target_scores, drill_stepsList<dynamic>JSON
user_tagString
alphaValue, charlieValue, deltaValue, missValue, noShootValueint
user_uploaded_photo, user_uploaded_videoString
is_notification_openbool
created_atDateTime

Community Content

community_drills

ColumnTypeNotes
idintPK
added_byStringFK to users (who shared it)
drill_owned_byStringFK to users (original creator)
drill_owner_nameString
drill_idintFK to drills
course_idintFK to courses
qual_idint
itemNameString
is_favoritedbool
created_atDateTime

Achievements

achievements

ColumnTypeNotes
idintPK
title, descriptionString
categoryString
photo_urlStringBadge image

user_achievements

ColumnTypeNotes
idintPK
user_idStringFK to users
achievement_idintFK to achievements
created_atDateTime

Notifications & Messaging

notifications

ColumnTypeNotes
idintPK
sender_id, recipient_idStringFK to users
title, bodyString
initial_page_nameStringDeep link target
parameter_dataStringDeep link params
created_atDateTime

in_app_messages

ColumnTypeNotes
idintPK
user_idStringFK to users
title, bodyString
is_challengebool
challenge_idintFK to challenges
is_readbool
created_atDateTime

push_press (push notification content)

ColumnTypeNotes
idintPK
title, bodyStringContent
notification_title, notification_bodyStringPush display text
image_url, video_urlStringRich media
linkString
drill_idintFK to drills
dry_fire_drill_idintFK to dry_fire_drills
social, trex_messages, new_drills, salesboolCategory flags
created_atDateTime

read_push_press — tracks which users have read which push notifications.

Performance & Analytics

hit_factors

ColumnTypeNotes
idintPK
user_idStringFK to users
nameStringDrill name
timedouble
hit_factordoublePerformance metric
alpha, charlie, delta, miss, no_shootintShot breakdown
created_atDateTime

global_hitfactor_averages — aggregate view: drill_name, average_hit_factor, total_completions.

Analytics views (admin, read-only):

  • new_users_per_day / new_users_per_week
  • new_completed_drills_per_day / new_completed_drills_per_week
  • new_custom_drills_per_day / new_custom_drills_per_week

Features (40+ screens)

Drill Execution

  • Preconfigured drills — T.REX-curated drills with descriptions, videos, diagrams
  • Custom drills — user-created drills with custom scoring, targets, steps
  • Dry fire drills — timer-based practice drills, no live fire
  • Designated target drills — specific target engagement patterns (Bill Drill, etc.)
  • Multi-shooter drills — multiple shooters scored simultaneously on one drill

Courses

  • Custom courses — bundle multiple drills into a sequence
  • Multi-shooter courses — courses with multiple shooters
  • Course drill selection — pick which drills to include

Scoring

  • USPSA-style scoring — alpha (A-zone), charlie (C-zone), delta (D-zone), miss, no-shoot
  • Hit factor — total points divided by time (key performance metric)
  • Per-target breakdown — individual target scores within a drill
  • Step-based scoring — drills broken into timed steps
  • User media — photos and videos uploaded per completed drill

Past Results

  • Drill history — all completed drills with full score details
  • Multi-shooter history — per-shooter breakdowns
  • Course history — per-drill breakdowns within courses
  • Designated target results — specialized result view

Social

  • User search — find users by name (Supabase Edge Function)
  • Friend requests — send, accept, decline
  • Friends list — view accepted friends
  • Friends leaderboard — compare hit factors on drills among friends
  • Influencer following — follow content creators

Challenges

  • Create challenge — pick a drill, invite friends, set expiration
  • Accept/decline — respond to incoming challenges
  • Complete challenge — run the drill, submit score
  • Challenge results — compare scores across participants

Community

  • Community drills — browse drills shared by other users
  • Favorites — bookmark drills, assessments, quals, courses

Other

  • Range bag — track equipment/gear, suggested items per drill
  • Achievements — earn badges for milestones
  • Push notifications — categorized (social, T.REX, new drills, sales)
  • Notification preferences — per-category opt-in/out

Scoring Model: USPSA-Style

The legacy app uses USPSA (United States Practical Shooting Association) scoring terminology throughout. This is important for the migration and for the new scoring_params field:

ZoneMeaningTypical Points
Alpha (A)Center of target5 points
Charlie (C)Mid ring3 points
Delta (D)Outer ring1 point
Miss (M)Missed target entirely0 points + penalty
No-Shoot (NS)Hit a non-targetPenalty (configurable)

Hit Factor = Total Points / Total Time. This is the primary performance metric tracked across the app — used in leaderboards, personal history, and global averages.

Each drill defines its own point values per zone (alpha_value, charlie_value, etc.), allowing non-standard scoring for custom drills.

In the new system, USPSA scoring maps to a built-in ScoringProfile with calculation method hit_factor. The per-drill point value overrides in the legacy app map to the zone_overrides field on the new Drill entity. See ScoringProfile.


Data Model Observations

Patterns Worth Noting

  1. Score portability already existscompleted_drills embeds drill name, description, and scoring params. The legacy app independently arrived at the same design we documented in Score Portability.
  2. JSON columns everywhere — drill_steps, target_scores, course_results, shooter_results are all unstructured JSON. Works but prevents database-level queries and validation.
  3. Denormalized tablesmy_friends duplicates friendships, friends_leaderboard duplicates completed_drills + friendships. Performance optimization for Supabase’s direct-query model.
  4. Test tablesdrills_test_table and dry_fire_drills_test_table are copies of production tables used for testing. Not migrated.
  5. No events concept — the legacy app has no formal “event” entity. Multi-shooter drills are ad-hoc, not organized under an event. This is the core capability Range Day Pro adds.

Migration Complexity by Table

ComplexityTables
Straightforwardusers, drills, custom_drills, dry_fire_drills, courses, custom_courses
Moderate (JSON normalization)completed_drills, completed_multi_shooter_drills, completed_custom_courses
Decision neededchallenges, user_challenges, friendships, friend_requests, achievements, user_achievements, community_drills
Skiptest tables, analytics views (regenerated from new data), denormalized views (my_friends, friends_leaderboard)

Features Requiring Decision

These features exist in the legacy app but are not currently in the Range Day Pro roadmap. Each needs a keep/defer/drop decision before migration planning is complete:

FeatureLegacy ImplementationRecommendationNotes
ChallengesFull system — create, invite, accept, complete, resultsDiscussPopular feature (10+ screens). Could map to lightweight events.
Friends / social graphFriend requests, friends list, leaderboardDiscussOrg membership may replace this. Or friends could exist alongside orgs.
LeaderboardsPer-drill hit factor rankings among friendsDiscussCould be per-org or per-event in the new model.
Dry fire drillsSeparate table and categoryFold into Drill typeNo structural reason for a separate table. Add a type enum value.
Range bagEquipment tracking, suggested items per drillDeferNice-to-have, not core to MVP.
AchievementsBadges earned for milestonesDeferGamification, not core to MVP.
FavoritesBookmarked drills, coursesKeep (MVP)Simple feature, high usability value.
User mediaPhotos/videos per completed drillDiscussRequires file storage (S3/DO Spaces). Changes data model and infra.
Push notificationsFCM, categorizedKeep (MVP)Needed for event reminders, score updates. Missing from current docs.
Hit factorUSPSA performance metricKeep (MVP)Core scoring concept. Fold into scoring_params.
Influencer systemFollow content creatorsDefer/DropNot relevant for MVP customer segment (church security teams).
Community drill sharingBrowse/copy shared drillsKeepAlready covered by drill visibility (public/published) in the new model.
Admin analytics viewsUser/drill growth dashboardsDeferObservability stack covers operational metrics. Admin reporting is post-MVP.

External Services

ServiceUsageMigration Impact
Supabase AuthEmail/password authenticationUsers migrated with null password_hash. See Auth Architecture.
Supabase PostgresAll application dataETL migration to new Postgres.
Supabase StorageUser-uploaded photos/videosIf user media is kept, files must be migrated to new storage (DO Spaces or similar).
Firebase Cloud MessagingPush notificationsReplace with new push notification system if push is kept for MVP.
Firebase AnalyticsEvent trackingReplace with observability stack. See Observability.
MixpanelUser identificationDrop — not needed with new observability.