Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions backend/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from services.rate_limiter import RateLimiter, APIKeyManager
from services.supabase_service import get_supabase_service
from services.input_validator import InputValidator, CostController
from services.user_limits import init_user_limits_service, get_user_limits_service

# Service instances (singleton pattern)
indexer = OptimizedCodeIndexer()
Expand All @@ -31,6 +32,12 @@
api_key_manager = APIKeyManager(get_supabase_service().client)
cost_controller = CostController(get_supabase_service().client)

# User tier and limits management
user_limits = init_user_limits_service(
supabase_client=get_supabase_service().client,
redis_client=cache.redis if cache.redis else None
)


def get_repo_or_404(repo_id: str, user_id: str) -> dict:
"""
Expand Down
2 changes: 2 additions & 0 deletions backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from routes.search import router as search_router
from routes.analysis import router as analysis_router
from routes.api_keys import router as api_keys_router
from routes.users import router as users_router


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

# WebSocket endpoint (versioned)
app.add_api_websocket_route(f"{API_PREFIX}/ws/index/{{repo_id}}", websocket_index)
Expand Down
74 changes: 74 additions & 0 deletions backend/routes/users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""User routes - profile and usage information."""
from fastapi import APIRouter, Depends

from dependencies import user_limits
from middleware.auth import require_auth, AuthContext
from services.observability import logger
from services.user_limits import TIER_LIMITS, UserTier

router = APIRouter(prefix="/users", tags=["Users"])


@router.get("/usage")
def get_user_usage(auth: AuthContext = Depends(require_auth)):
"""
Get current user's usage and limits.

Returns:
- tier: Current subscription tier
- repositories: Current count and limit
- limits: All tier limits
- features: Available features for tier
"""
user_id = auth.user_id

if not user_id:
logger.warning("Usage check without user_id", identifier=auth.identifier)
# Return free tier defaults for API key users
free_limits = TIER_LIMITS[UserTier.FREE]
return {
"tier": "free",
"repositories": {
"current": 0,
"limit": free_limits.max_repos,
"display": f"0/{free_limits.max_repos}"
},
"limits": {
"max_files_per_repo": free_limits.max_files_per_repo,
"max_functions_per_repo": free_limits.max_functions_per_repo,
"playground_searches_per_day": free_limits.playground_searches_per_day,
},
"features": {
"priority_indexing": free_limits.priority_indexing,
"mcp_access": free_limits.mcp_access,
}
}

usage = user_limits.get_usage_summary(user_id)
logger.info("Usage retrieved", user_id=user_id, tier=usage.get("tier"))

return usage


@router.get("/limits/check-repo-add")
def check_can_add_repo(auth: AuthContext = Depends(require_auth)):
"""
Check if user can add another repository.

Call this before showing "Add Repository" button to
disable it if limit reached.
"""
user_id = auth.user_id

if not user_id:
# API key users - return allowed with free tier info
free_limits = TIER_LIMITS[UserTier.FREE]
return {
"allowed": True,
"message": "OK",
"tier": "free",
"limit": free_limits.max_repos
}

result = user_limits.check_repo_count(user_id)
return result.to_dict()
Loading