diff --git a/backend/routers/features.py b/backend/routers/features.py index 2932bda..69766aa 100644 --- a/backend/routers/features.py +++ b/backend/routers/features.py @@ -2,11 +2,16 @@ Feature Management Endpoints for Mitai Jinkendo Admin-only CRUD for features registry. +User endpoint for feature usage overview (Phase 3). """ -from fastapi import APIRouter, HTTPException, Depends +from typing import Optional +from datetime import datetime + +from fastapi import APIRouter, HTTPException, Header, Depends from db import get_db, get_cursor, r2d from auth import require_admin, require_auth, check_feature_access +from routers.profiles import get_pid router = APIRouter(prefix="/api/features", tags=["features"]) @@ -136,3 +141,82 @@ def check_access(feature_id: str, session: dict = Depends(require_auth)): profile_id = session['profile_id'] result = check_feature_access(profile_id, feature_id) return result + + +@router.get("/usage") +def get_feature_usage(x_profile_id: Optional[str]=Header(default=None), session: dict=Depends(require_auth)): + """ + User: Get usage overview for all active features (Phase 3: Frontend Display). + + Returns list of all features with current usage, limits, and reset info. + Automatically includes new features from database - no code changes needed. + + Response: + [ + { + "feature_id": "weight_entries", + "name": "Gewichtseinträge", + "description": "Anzahl der Gewichtseinträge", + "category": "data", + "limit_type": "count", + "reset_period": "never", + "used": 5, + "limit": 10, + "remaining": 5, + "allowed": true, + "reset_at": null + }, + ... + ] + """ + pid = get_pid(x_profile_id) + + with get_db() as conn: + cur = get_cursor(conn) + + # Get all active features (dynamic - picks up new features automatically) + cur.execute(""" + SELECT id, name, description, category, limit_type, reset_period + FROM features + WHERE active = true + ORDER BY category, name + """) + features = [r2d(r) for r in cur.fetchall()] + + result = [] + for feature in features: + # Use existing check_feature_access to get usage and limits + # This respects user overrides, tier limits, and feature defaults + access = check_feature_access(pid, feature['id']) + + # Get reset date from user_feature_usage + cur.execute(""" + SELECT reset_at + FROM user_feature_usage + WHERE profile_id = %s AND feature_id = %s + """, (pid, feature['id'])) + usage_row = cur.fetchone() + + # Format reset_at as ISO string + reset_at = None + if usage_row and usage_row['reset_at']: + if isinstance(usage_row['reset_at'], datetime): + reset_at = usage_row['reset_at'].isoformat() + else: + reset_at = str(usage_row['reset_at']) + + result.append({ + 'feature_id': feature['id'], + 'name': feature['name'], + 'description': feature.get('description'), + 'category': feature.get('category'), + 'limit_type': feature['limit_type'], + 'reset_period': feature['reset_period'], + 'used': access['used'], + 'limit': access['limit'], + 'remaining': access['remaining'], + 'allowed': access['allowed'], + 'reset_at': reset_at + }) + + return result