feat: add GET /api/features/usage endpoint (Phase 3)
- Add user-facing usage overview endpoint - Returns all features with usage, limits, reset info - Fully dynamic - automatically includes new features - Phase 3: Frontend Display preparation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4e846605e9
commit
d10f605d66
|
|
@ -2,11 +2,16 @@
|
||||||
Feature Management Endpoints for Mitai Jinkendo
|
Feature Management Endpoints for Mitai Jinkendo
|
||||||
|
|
||||||
Admin-only CRUD for features registry.
|
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 db import get_db, get_cursor, r2d
|
||||||
from auth import require_admin, require_auth, check_feature_access
|
from auth import require_admin, require_auth, check_feature_access
|
||||||
|
from routers.profiles import get_pid
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/features", tags=["features"])
|
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']
|
profile_id = session['profile_id']
|
||||||
result = check_feature_access(profile_id, feature_id)
|
result = check_feature_access(profile_id, feature_id)
|
||||||
return result
|
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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user