""" Tier Limits Management Endpoints for Mitai Jinkendo Admin-only matrix editor for Tier x Feature limits. """ from fastapi import APIRouter, HTTPException, Depends from db import get_db, get_cursor, r2d from auth import require_admin router = APIRouter(prefix="/api/tier-limits", tags=["tier-limits"]) @router.get("") def get_tier_limits_matrix(session: dict = Depends(require_admin)): """ Admin: Get complete Tier x Feature matrix. Returns: { "tiers": [{id, name}, ...], "features": [{id, name, category}, ...], "limits": { "tier_id:feature_id": limit_value, ... } } """ with get_db() as conn: cur = get_cursor(conn) # Get all tiers (including inactive - admin needs to configure all) cur.execute("SELECT id, name, sort_order FROM tiers ORDER BY sort_order") tiers = [r2d(r) for r in cur.fetchall()] # Get all features cur.execute(""" SELECT id, name, category, limit_type, default_limit, reset_period FROM features WHERE active = true ORDER BY category, name """) features = [r2d(r) for r in cur.fetchall()] # Get all tier_limits cur.execute("SELECT tier_id, feature_id, limit_value FROM tier_limits") limits = {} for row in cur.fetchall(): key = f"{row['tier_id']}:{row['feature_id']}" limits[key] = row['limit_value'] return { "tiers": tiers, "features": features, "limits": limits } @router.put("") def update_tier_limit(data: dict, session: dict = Depends(require_admin)): """ Admin: Update single tier limit. Body: { "tier_id": "free", "feature_id": "weight_entries", "limit_value": 30 // NULL = unlimited, 0 = disabled } """ tier_id = data.get('tier_id') feature_id = data.get('feature_id') limit_value = data.get('limit_value') # Can be None (NULL) if not tier_id or not feature_id: raise HTTPException(400, "tier_id und feature_id fehlen") with get_db() as conn: cur = get_cursor(conn) # Upsert tier_limit cur.execute(""" INSERT INTO tier_limits (tier_id, feature_id, limit_value) VALUES (%s, %s, %s) ON CONFLICT (tier_id, feature_id) DO UPDATE SET limit_value = EXCLUDED.limit_value, updated = CURRENT_TIMESTAMP """, (tier_id, feature_id, limit_value)) conn.commit() return {"ok": True} @router.put("/batch") def update_tier_limits_batch(data: dict, session: dict = Depends(require_admin)): """ Admin: Batch update multiple tier limits. Body: { "updates": [ {"tier_id": "free", "feature_id": "weight_entries", "limit_value": 30}, {"tier_id": "free", "feature_id": "ai_calls", "limit_value": 0}, ... ] } """ updates = data.get('updates', []) if not updates: raise HTTPException(400, "updates array fehlt") with get_db() as conn: cur = get_cursor(conn) for update in updates: tier_id = update.get('tier_id') feature_id = update.get('feature_id') limit_value = update.get('limit_value') if not tier_id or not feature_id: continue # Skip invalid entries cur.execute(""" INSERT INTO tier_limits (tier_id, feature_id, limit_value) VALUES (%s, %s, %s) ON CONFLICT (tier_id, feature_id) DO UPDATE SET limit_value = EXCLUDED.limit_value, updated = CURRENT_TIMESTAMP """, (tier_id, feature_id, limit_value)) conn.commit() return {"ok": True, "updated": len(updates)} @router.delete("") def delete_tier_limit(tier_id: str, feature_id: str, session: dict = Depends(require_admin)): """ Admin: Delete tier limit (falls back to feature default). Query params: ?tier_id=...&feature_id=... """ if not tier_id or not feature_id: raise HTTPException(400, "tier_id und feature_id fehlen") with get_db() as conn: cur = get_cursor(conn) cur.execute(""" DELETE FROM tier_limits WHERE tier_id = %s AND feature_id = %s """, (tier_id, feature_id)) conn.commit() return {"ok": True}