Skip to content

Commit 8f27ec1

Browse files
fix(gamification): plan-feature gating used wrong plan slugs (#285) (#304)
get_gamification_features() gated leaderboard / achievements / store off the slugs 'professional' and 'basic', but the canonical platform_plans slugs are free / starter / pro / business / enterprise. Every tenant on pro, starter, or business fell through to the ELSE branch and had those features reported as locked — so students at paying schools saw "Upgrade to Professional" on features they already owned (e.g. the Point Store on a Pro school). It also hardcoded the feature tiers instead of reading platform_plans.features, the source get_plan_features() already uses. Rewrote it to resolve the plan the same way and read leaderboard / achievements / store straight from platform_plans.features, so the two functions can no longer drift. Verified on Code Academy Pro (alice@student.com): store now renders all 6 items, balance + per-item pricing correct, insufficient-coins state correct, and a successful redeem deducts coins and writes a gamification_redemptions row. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 4813dd7 commit 8f27ec1

2 files changed

Lines changed: 49 additions & 0 deletions

File tree

269 KB
Loading
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
-- Fix get_gamification_features: it gated gamification features off the wrong plan slugs.
2+
--
3+
-- The old CASE matched 'professional' / 'basic', but the canonical platform_plans
4+
-- slugs are free / starter / pro / business / enterprise. Every tenant on pro,
5+
-- starter, or business therefore fell through to the ELSE branch and had
6+
-- leaderboard / achievements / store wrongly reported as locked — so paying
7+
-- schools' students saw "Upgrade to Professional" on features they already own.
8+
--
9+
-- It also hardcoded the feature tiers instead of reading platform_plans.features,
10+
-- the authoritative source used by get_plan_features(). Fix: resolve the plan the
11+
-- same way get_plan_features() does and read store / leaderboard / achievements
12+
-- straight from platform_plans.features so the two functions can never drift.
13+
14+
CREATE OR REPLACE FUNCTION public.get_gamification_features(_tenant_id uuid)
15+
RETURNS jsonb
16+
LANGUAGE plpgsql
17+
STABLE SECURITY DEFINER
18+
AS $function$
19+
DECLARE
20+
_plan_slug VARCHAR;
21+
_features JSONB;
22+
BEGIN
23+
-- Resolve the tenant's plan slug (same path as get_plan_features)
24+
SELECT plan INTO _plan_slug FROM tenants WHERE id = _tenant_id;
25+
_plan_slug := COALESCE(_plan_slug, 'free');
26+
27+
-- Pull the authoritative feature flags for that plan
28+
SELECT pp.features INTO _features
29+
FROM platform_plans pp
30+
WHERE pp.slug = _plan_slug AND pp.is_active = true;
31+
32+
IF _features IS NULL THEN
33+
SELECT pp.features INTO _features FROM platform_plans pp WHERE pp.slug = 'free';
34+
END IF;
35+
36+
RETURN jsonb_build_object(
37+
-- xp / levels / streaks ship on every plan, including free
38+
'xp', true,
39+
'levels', true,
40+
'streaks', true,
41+
'leaderboard', COALESCE((_features ->> 'leaderboard')::boolean, false),
42+
'achievements', COALESCE((_features ->> 'achievements')::boolean, false),
43+
'store', COALESCE((_features ->> 'store')::boolean, false),
44+
-- custom gamification content unlocks at pro and above
45+
'custom_achievements', _plan_slug IN ('pro', 'business', 'enterprise'),
46+
'custom_store', _plan_slug IN ('pro', 'business', 'enterprise')
47+
);
48+
END;
49+
$function$;

0 commit comments

Comments
 (0)