Case studyدراسة حالة
BarC.
Calisthenics scoring and leaderboard app for iOS.تطبيق iOS لتقييم تمارين الكاليسثنكس ولوائح الصدارة.
Athletes log reps, weighted sets, or static holds and get a server-graded score from 0 to 1000, a medal from Iron to Diamond, and a place on friend, city, and country leaderboards.يسجّل الرياضيون التكرارات أو المجموعات بالأوزان أو تمارين الثبات، فيحصلون على نتيجة من 0 إلى 1000 يحتسبها الخادم، وميدالية من الحديد إلى الألماس، ومكان في لوائح الأصدقاء والمدينة والدولة.
- Roleالدور
- Sole engineerالمهندس الوحيد
- Platformالمنصة
- iOS
- Yearالسنة
- 2026
- Statusالحالة
- Live on the App Storeمتاح على App Store
- Stackالتقنيات
- React Native, Expo, TypeScript, Supabase, PostgreSQL
The productالمنتج
Screenshot pendingلقطة الشاشة قيد الإضافةscreen-1.webp
Screenshot pendingلقطة الشاشة قيد الإضافةscreen-2.webp
Screenshot pendingلقطة الشاشة قيد الإضافةscreen-3.webp
Ironحديد1–199
Bronzeبرونز200–499
Silverفضة500–799
Goldذهب800–949
Platinumبلاتين950–989
Diamondألماس990–1000
Problem and motivationالمشكلة والدافع
Calisthenics results are hard to compare. Reps, added load, hold time, progression and bodyweight all differ between athletes, so a leaderboard needs one score that the client cannot tamper with.Calisthenics results are hard to compare. Reps, added load, hold time, progression and bodyweight all differ between athletes, so a leaderboard needs one score that the client cannot tamper with.
My role and what I builtدوري وما بنيته
I built BarC on my own: the product, the Expo app, the Supabase backend and its Edge Functions, the scoring engine, and the App Store release.بنيت BarC وحدي: المنتج، وتطبيق Expo، وخادم Supabase ودواله الطرفية (Edge Functions)، ومحرّك التقييم، والإطلاق على App Store.
Shipped a React Native iOS app where athletes submit video-verified strength attempts and earn medal rankings.Shipped a React Native iOS app where athletes submit video-verified strength attempts and earn medal rankings.
Designed a server-authoritative scoring engine with versioned, immutable snapshots for stable leaderboard history.Designed a server-authoritative scoring engine with versioned, immutable snapshots for stable leaderboard history.
Secured PostgreSQL with row-level security, signed uploads, quotas, rate limiting, and a moderation pipeline.Secured PostgreSQL with row-level security, signed uploads, quotas, rate limiting, and a moderation pipeline.
Authored a transactional, self-verifying SQL migration to recalibrate scoring thresholds with automatic rollback.Authored a transactional, self-verifying SQL migration to recalibrate scoring thresholds with automatic rollback.
Technical architectureالبنية التقنية
Score pathمسار النتيجة
- Expo / React NativeiOS clientتطبيق iOSCollects raw input: reps, load, hold time, progression.Collects raw input: reps, load, hold time, progression.
- Edge Function · submit-performanceScoring engineمحرّك التقييمLoads profile, exercise and thresholds, then computes score and medal.Loads profile, exercise and thresholds, then computes score and medal.
- insert_performance_atomicPostgresPostgresAtomic insert with RLS and quotas. Stores the score and an immutable scoring snapshot.Atomic insert with RLS and quotas. Stores the score and an immutable scoring snapshot.
- leaderboard_rowsLeaderboardلائحة الصدارةRolls best scores into category and overall standings.Rolls best scores into category and overall standings.
Proof pathمسار الإثبات
- signed URLSigned uploadرفع موقَّع
- Supabase StorageProof videoفيديو الإثبات
- verification_statusModeration reviewمراجعة الإشراف
Key engineering decisionsقرارات هندسية أساسية
Scoring lives on the server onlyالتقييم على الخادم فقط
The app submits a raw result. The submit-performance Edge Function grades it and inserts the row with the service role. The performances table has no INSERT policy for any client role, so even a stolen token cannot write a score or a medal.يرسل التطبيق النتيجة الخام، فتقيّمها دالة submit-performance الطرفية وتُدرج الصف بصلاحية الخدمة. لا يملك جدول performances أي سياسة INSERT لأي دور من أدوار العميل، لذا لا يستطيع حتى رمز وصول مسروق كتابة نتيجة أو ميدالية.
Every result is an immutable snapshotكل نتيجة لقطة غير قابلة للتعديل
Each attempt permanently stores bodyweight, server-derived age, division, load, progression, raw index P, date, and the algorithm and config versions that graded it. Triggers block deletes and any update outside verification status and moderation note, for every role. Editing a profile only affects future attempts.تحفظ كل محاولة بشكل دائم وزن الجسم، والعمر المحسوب على الخادم، والفئة، والحمل، والتدرّج، والمؤشر الخام P، والتاريخ، وإصدارَي الخوارزمية والإعدادات اللذين قيّماها. تمنع المشغّلات (triggers) الحذف وأي تعديل خارج حالة التحقق وملاحظة الإشراف، لكل الأدوار. تعديل الملف الشخصي يؤثر في المحاولات اللاحقة فقط.
Privacy is enforced in the databaseالخصوصية تُفرض في قاعدة البيانات
Base tables are readable only by their owner. Other athletes are read through public views that null hidden fields while scores and tiers stay ranked. Date of birth is never exposed, private accounts are visible only to accepted friends, and minors default to private.لا يقرأ الجداول الأساسية إلا مالكها. تُقرأ بيانات الرياضيين الآخرين عبر واجهات عرض عامة تُخفي الحقول المحجوبة مع بقاء النتائج والفئات مرتّبة. لا يُكشف تاريخ الميلاد أبدًا، ولا تظهر الحسابات الخاصة إلا للأصدقاء المقبولين، وحسابات القاصرين خاصة افتراضيًا.
Proof takes a separate pathللإثبات مسار منفصل
Proof video goes up through a signed upload into Supabase Storage and lands in a moderation review that sets the result’s verification status, separate from the score path.يُرفع فيديو الإثبات عبر رابط رفع موقَّع إلى Supabase Storage، ثم يدخل مراجعة إشراف تحدد حالة التحقق للنتيجة، بمعزل عن مسار النتيجة.
How a score is computedكيف تُحتسب النتيجة
Body size is adjusted in exactly one place, the raw index P. P maps to a 0 to 1000 score through five percentile anchors per exercise and division, and medals are fixed bands over that score.يُعدَّل حجم الجسم في موضع واحد فقط هو المؤشر الخام P. ثم يتحول P إلى نتيجة من 0 إلى 1000 عبر خمس نقاط مرجعية مئينية لكل تمرين وفئة، والميداليات نطاقات ثابتة على هذه النتيجة.
- Weightedبالأوزان
- P = (BW + AW) / BW^0.667
- Repsالتكرارات
- P = R × Fsize
- Staticsالثبات
- P = D × (0.20 + 0.80 · L(T, Tcap)) × Fsize
Fsize = clamp((BW / 70)^α, 0.85, 1.20)
L(T, Tcap) = min(ln(1 + T) / ln(1 + Tcap), 1) // P = 0 when T < Tmin
BW bodyweight (kg) · AW added weight · R reps · α per-exercise exponent · D progression difficulty · T hold time · Tcap saturation hold · Fsize bodyweight size factorBW وزن الجسم (كغ) · AW الوزن الإضافي · R التكرارات · α أُسّ خاص بكل تمرين · D صعوبة التدرّج · T مدة الثبات · Tcap مدة التشبّع · Fsize معامل حجم الجسم
As implemented in algorithm v0.3.0. Anchors map P to 200, 500, 800, 950 and 990, piecewise linear, capped at 1000.كما هو مطبَّق في الخوارزمية v0.3.0. تحوّل النقاط المرجعية P إلى 200 و500 و800 و950 و990، خطيًّا على أجزاء، بحد أقصى 1000.
Scoring-system evolutionتطوّر نظام التقييم
Scoring changes ship as versioned configs. Every performance row stores the versions that graded it, so a later change never silently rescores an older result.تُطلَق تغييرات التقييم كإعدادات ذات إصدارات. يحفظ كل صف أداء الإصدارات التي قيّمته، فلا يعيد أي تغيير لاحق تقييم نتيجة قديمة بصمت.
- v0.4algorithm v0.2
Catalog updateتحديث الكتالوج
Trimmed the progression ladders on five statics exercises. Nordic curls out; Wall sit and L-sit on bar in. Core formulas unchanged.Trimmed the progression ladders on five statics exercises. Nordic curls out; Wall sit and L-sit on bar in. Core formulas unchanged.
- v0.5algorithm v0.2 → v0.2.1
Correctionتصحيح
v0.4 left the L-sit with no step above difficulty 0.75, which capped the whole exercise: in testing, a full L-sit held for 1,000,000 s still scored silver. Difficulty was regraded against the hardest step that exists. A same-day follow-up (algorithm v0.2.1) removed a second cap that kept statics Diamond out of reach for lighter athletes, even at the 70 kg reference bodyweight.v0.4 left the L-sit with no step above difficulty 0.75, which capped the whole exercise: in testing, a full L-sit held for 1,000,000 s still scored silver. Difficulty was regraded against the hardest step that exists. A same-day follow-up (algorithm v0.2.1) removed a second cap that kept statics Diamond out of reach for lighter athletes, even at the 70 kg reference bodyweight.
- v0.6algorithm v0.3.0
Recalibration shippedإطلاق إعادة المعايرةCurrentالحالي
Algorithm v0.3.0 replaced both v0.5 patches with a full recalibration: difficulty is absolute again, the hold-ceiling exception is gone, and every active exercise has new anchors. One transactional migration rewrote 220 threshold rows and 21 progression rows, backed up both tables first, and aborts on any failed check.Algorithm v0.3.0 replaced both v0.5 patches with a full recalibration: difficulty is absolute again, the hold-ceiling exception is gone, and every active exercise has new anchors. One transactional migration rewrote 220 threshold rows and 21 progression rows, backed up both tables first, and aborts on any failed check.
Challenges and lessonsالتحديات والدروس
Test the extremesاختبر الحالات القصوى
The v0.4 catalog looked fine on typical inputs. An absurd 1,000,000 second L-sit exposed a hidden cap at silver and led directly to the v0.5 correction.بدا كتالوج v0.4 سليمًا مع المدخلات المعتادة، لكن ثبات L-sit لمدة مليون ثانية كشف سقفًا خفيًا عند الفضة، وقاد مباشرة إلى تصحيح v0.5.
Stop stacking patchesتوقّف عن تكديس الترقيعات
v0.5 needed two same-day fixes. v0.6 replaced both with one recalibration, absolute difficulty and new anchors for every active exercise, instead of adding a third.احتاج v0.5 إلى إصلاحين في اليوم نفسه، فاستبدلهما v0.6 بإعادة معايرة واحدة، بصعوبة مطلقة ونقاط مرجعية جديدة لكل تمرين نشط، بدل إضافة إصلاح ثالث.
Make migrations prove themselvesاجعل الترحيلات تُثبت صحتها بنفسها
The v0.6 migration backed up both tables, rewrote 220 threshold rows and 21 progression rows in a single transaction, and aborts on any failed check, so it cannot land half-applied.أخذ ترحيل v0.6 نسخة احتياطية من الجدولين، وأعاد كتابة 220 صفًّا من العتبات و21 صفًّا من التدرّجات في معاملة واحدة، ويُلغى عند فشل أي فحص، فلا يمكن أن يُطبَّق جزئيًا.
Cap on purposeسقف مقصود
In v0.6 an exercise whose hardest progression sits below a medal anchor caps there by design: extra hold time never substitutes for skill. The app flags the hardest step so it never tells an athlete to move to a progression that does not exist.في v0.6، التمرين الذي يقع أصعب تدرّج فيه تحت النقطة المرجعية لميدالية ما يتوقف عندها عن قصد: فالثبات لمدة أطول لا يعوّض المهارة. ويميّز التطبيق أصعب خطوة حتى لا يطلب من الرياضي الانتقال إلى تدرّج غير موجود.
Linksروابط
- App StoreApp Storeapps.apple.com/us/app/barc/id6800418903
- trainbarc.com
- Private repo, walkthrough available on request.مستودع خاص، ويمكن تقديم عرض توضيحي عند الطلب.