Skip to content

Commit 999e21c

Browse files
authored
Merge pull request #98 from DevanshuNEU/feat/issue-96-user-tier-system
feat: User tier & limits system (#96)
2 parents bf40c59 + 320b447 commit 999e21c

7 files changed

Lines changed: 1054 additions & 0 deletions

File tree

backend/dependencies.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from services.rate_limiter import RateLimiter, APIKeyManager
1818
from services.supabase_service import get_supabase_service
1919
from services.input_validator import InputValidator, CostController
20+
from services.user_limits import init_user_limits_service, get_user_limits_service
2021

2122
# Service instances (singleton pattern)
2223
indexer = OptimizedCodeIndexer()
@@ -31,6 +32,12 @@
3132
api_key_manager = APIKeyManager(get_supabase_service().client)
3233
cost_controller = CostController(get_supabase_service().client)
3334

35+
# User tier and limits management
36+
user_limits = init_user_limits_service(
37+
supabase_client=get_supabase_service().client,
38+
redis_client=cache.redis if cache.redis else None
39+
)
40+
3441

3542
def get_repo_or_404(repo_id: str, user_id: str) -> dict:
3643
"""

backend/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from routes.search import router as search_router
2626
from routes.analysis import router as analysis_router
2727
from routes.api_keys import router as api_keys_router
28+
from routes.users import router as users_router
2829

2930

3031
# Lifespan context manager for startup/shutdown
@@ -86,6 +87,7 @@ async def dispatch(self, request: Request, call_next):
8687
app.include_router(search_router, prefix=API_PREFIX)
8788
app.include_router(analysis_router, prefix=API_PREFIX)
8889
app.include_router(api_keys_router, prefix=API_PREFIX)
90+
app.include_router(users_router, prefix=API_PREFIX)
8991

9092
# WebSocket endpoint (versioned)
9193
app.add_api_websocket_route(f"{API_PREFIX}/ws/index/{{repo_id}}", websocket_index)

backend/routes/users.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
"""User routes - profile and usage information."""
2+
from fastapi import APIRouter, Depends
3+
4+
from dependencies import user_limits
5+
from middleware.auth import require_auth, AuthContext
6+
from services.observability import logger
7+
from services.user_limits import TIER_LIMITS, UserTier
8+
9+
router = APIRouter(prefix="/users", tags=["Users"])
10+
11+
12+
@router.get("/usage")
13+
def get_user_usage(auth: AuthContext = Depends(require_auth)):
14+
"""
15+
Get current user's usage and limits.
16+
17+
Returns:
18+
- tier: Current subscription tier
19+
- repositories: Current count and limit
20+
- limits: All tier limits
21+
- features: Available features for tier
22+
"""
23+
user_id = auth.user_id
24+
25+
if not user_id:
26+
logger.warning("Usage check without user_id", identifier=auth.identifier)
27+
# Return free tier defaults for API key users
28+
free_limits = TIER_LIMITS[UserTier.FREE]
29+
return {
30+
"tier": "free",
31+
"repositories": {
32+
"current": 0,
33+
"limit": free_limits.max_repos,
34+
"display": f"0/{free_limits.max_repos}"
35+
},
36+
"limits": {
37+
"max_files_per_repo": free_limits.max_files_per_repo,
38+
"max_functions_per_repo": free_limits.max_functions_per_repo,
39+
"playground_searches_per_day": free_limits.playground_searches_per_day,
40+
},
41+
"features": {
42+
"priority_indexing": free_limits.priority_indexing,
43+
"mcp_access": free_limits.mcp_access,
44+
}
45+
}
46+
47+
usage = user_limits.get_usage_summary(user_id)
48+
logger.info("Usage retrieved", user_id=user_id, tier=usage.get("tier"))
49+
50+
return usage
51+
52+
53+
@router.get("/limits/check-repo-add")
54+
def check_can_add_repo(auth: AuthContext = Depends(require_auth)):
55+
"""
56+
Check if user can add another repository.
57+
58+
Call this before showing "Add Repository" button to
59+
disable it if limit reached.
60+
"""
61+
user_id = auth.user_id
62+
63+
if not user_id:
64+
# API key users - return allowed with free tier info
65+
free_limits = TIER_LIMITS[UserTier.FREE]
66+
return {
67+
"allowed": True,
68+
"message": "OK",
69+
"tier": "free",
70+
"limit": free_limits.max_repos
71+
}
72+
73+
result = user_limits.check_repo_count(user_id)
74+
return result.to_dict()

0 commit comments

Comments
 (0)