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
8 changes: 0 additions & 8 deletions backend/config/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,11 @@
Example: "v1" -> "v2" will change /api/v1/* to /api/v2/*
"""

# =============================================================================
# API VERSION CONFIGURATION
# =============================================================================

API_VERSION = "v1"

# =============================================================================
# DERIVED PREFIXES (auto-calculated from version)
# =============================================================================

# Current versioned API prefix: /api/v1
API_PREFIX = f"/api/{API_VERSION}"
Expand All @@ -22,9 +18,7 @@
# Routes here will be deprecated but still functional
LEGACY_API_PREFIX = "/api"

# =============================================================================
# DEPRECATION SETTINGS
# =============================================================================

# When True, legacy routes (/api/*) will include deprecation warning headers
LEGACY_DEPRECATION_ENABLED = True
Expand All @@ -33,9 +27,7 @@
DEPRECATION_HEADER = "X-API-Deprecated"
DEPRECATION_MESSAGE = f"This endpoint is deprecated. Please use {API_PREFIX} instead."

# =============================================================================
# HELPER FUNCTIONS
# =============================================================================

def get_versioned_prefix() -> str:
"""Get the current versioned API prefix."""
Expand Down
4 changes: 0 additions & 4 deletions backend/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@
All route modules import from here to avoid circular imports.
"""
from fastapi import HTTPException, Depends
from dotenv import load_dotenv

# Load env vars first
load_dotenv()

from services.indexer_optimized import OptimizedCodeIndexer
from services.repo_manager import RepositoryManager
Expand Down
12 changes: 7 additions & 5 deletions backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
CodeIntel Backend API
FastAPI backend for codebase intelligence
"""
# Load env vars once, before any service reads them
from dotenv import load_dotenv
load_dotenv()

from contextlib import asynccontextmanager
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
Expand Down Expand Up @@ -51,8 +55,7 @@ async def lifespan(app: FastAPI):
)


# ===== MIDDLEWARE =====

# MIDDLEWARE
class RequestSizeLimitMiddleware(BaseHTTPMiddleware):
"""Limit request body size to prevent abuse."""
MAX_REQUEST_SIZE = 10 * 1024 * 1024 # 10MB
Expand Down Expand Up @@ -81,7 +84,7 @@ async def dispatch(self, request: Request, call_next):
)


# ===== ROUTERS =====
# ROUTERS
# All API routes are prefixed with API_PREFIX (e.g., /api/v1)
# Route files define their sub-path (e.g., /auth, /repos)
# Final paths: /api/v1/auth, /api/v1/repos, etc.
Expand All @@ -104,8 +107,7 @@ async def dispatch(self, request: Request, call_next):
app.add_api_websocket_route(f"{API_PREFIX}/ws/repos/{{repo_id}}/indexing", websocket_repo_indexing)


# ===== ERROR HANDLERS =====

# ERROR HANDLERS
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
"""Handle validation errors with clear messages."""
Expand Down
10 changes: 0 additions & 10 deletions backend/middleware/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@ async def demo_search(auth: AuthContext = Depends(public_auth)):
from fastapi.security.http import HTTPAuthorizationCredentials


# ---------------------------------------------------------------------------
# Auth Context - unified return type for all auth methods
# ---------------------------------------------------------------------------

@dataclass
class AuthContext:
Expand All @@ -52,17 +50,13 @@ def identifier(self) -> str:
return self.user_id or self.api_key_name or "anonymous"


# ---------------------------------------------------------------------------
# Bearer token scheme (auto_error=False allows optional auth)
# ---------------------------------------------------------------------------

_bearer = HTTPBearer(auto_error=False)
_bearer_required = HTTPBearer(auto_error=True)


# ---------------------------------------------------------------------------
# Core validation functions
# ---------------------------------------------------------------------------

def _validate_jwt(token: str) -> Optional[AuthContext]:
"""Validate Supabase JWT token"""
Expand Down Expand Up @@ -143,9 +137,7 @@ def _authenticate(token: str) -> AuthContext:
)


# ---------------------------------------------------------------------------
# FastAPI Dependencies - use these in your routes
# ---------------------------------------------------------------------------

async def require_auth(
credentials: HTTPAuthorizationCredentials = Depends(_bearer_required)
Expand Down Expand Up @@ -177,9 +169,7 @@ async def public_auth(
return AuthContext(is_public=True)


# ---------------------------------------------------------------------------
# Legacy functions - kept for backwards compatibility
# ---------------------------------------------------------------------------

async def get_current_user(
credentials: HTTPAuthorizationCredentials = Depends(_bearer_required)
Expand Down
4 changes: 0 additions & 4 deletions backend/routes/playground.py
Original file line number Diff line number Diff line change
Expand Up @@ -785,9 +785,7 @@ async def validate_github_repo(request: ValidateRepoRequest, req: Request):
return result


# =============================================================================
# Anonymous Indexing Endpoint (#125)
# =============================================================================

@router.post("/index", status_code=202)
async def start_anonymous_indexing(
Expand Down Expand Up @@ -1030,9 +1028,7 @@ async def start_anonymous_indexing(
return response_data


# =============================================================================
# GET /playground/index/{job_id} - Check indexing job status (#126)
# =============================================================================

@router.get(
"/index/{job_id}",
Expand Down
3 changes: 0 additions & 3 deletions backend/services/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,9 @@
import hashlib
from typing import Optional, List, Dict
import os
from dotenv import load_dotenv

from services.observability import logger, metrics

load_dotenv()

# Configuration
REDIS_URL = os.getenv("REDIS_URL") # Railway/Cloud Redis URL
REDIS_HOST = os.getenv("REDIS_HOST", "localhost")
Expand Down
3 changes: 1 addition & 2 deletions backend/services/dependency_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,8 +399,7 @@ def _find_test_files(self, file_path: str, nodes: List[Dict]) -> List[str]:
return test_files


# ===== SUPABASE CACHING =====

# SUPABASE CACHING
def save_to_cache(self, repo_id: str, graph_data: Dict):
"""Save dependency graph to Supabase for caching"""
from services.supabase_service import get_supabase_service
Expand Down
3 changes: 0 additions & 3 deletions backend/services/indexer_optimized.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@

# Utils
import hashlib
from dotenv import load_dotenv
import time

# Search enhancement
Expand All @@ -39,8 +38,6 @@
# Observability
from services.observability import logger, trace_operation, track_time, capture_exception, add_breadcrumb, metrics

load_dotenv()

# Configuration
# Note: If using existing Pinecone index, match the dimension (1536 for small, 3072 for large)
EMBEDDING_MODEL = os.getenv("EMBEDDING_MODEL", "text-embedding-3-small")
Expand Down
8 changes: 0 additions & 8 deletions backend/services/observability.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ async def index_repo(repo_id: str):
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO" if IS_PRODUCTION else "DEBUG")


# =============================================================================
# STRUCTURED LOGGER
# =============================================================================

class StructuredLogger:
"""
Expand Down Expand Up @@ -112,9 +110,7 @@ def critical(self, message: str, **kwargs):
logger = StructuredLogger()


# =============================================================================
# SENTRY INTEGRATION HELPERS
# =============================================================================

def set_operation_context(operation: str, **kwargs):
"""
Expand Down Expand Up @@ -191,9 +187,7 @@ def capture_message(message: str, level: str = "info", **context):
pass


# =============================================================================
# PERFORMANCE TRACKING
# =============================================================================

@contextmanager
def track_time(operation: str, **tags):
Expand Down Expand Up @@ -307,9 +301,7 @@ def sync_wrapper(*args, **kwargs):
return decorator


# =============================================================================
# SIMPLE METRICS (in-memory counters)
# =============================================================================

class Metrics:
"""
Expand Down
14 changes: 0 additions & 14 deletions backend/services/playground_limiter.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@
from services.sentry import capture_exception


# =============================================================================
# DATA CLASSES
# =============================================================================

@dataclass
class PlaygroundLimitResult:
Expand Down Expand Up @@ -134,9 +132,7 @@ def _truncate_id(session_id: str) -> str:
return session_id


# =============================================================================
# MAIN CLASS
# =============================================================================

class PlaygroundLimiter:
"""
Expand All @@ -161,9 +157,7 @@ class PlaygroundLimiter:
has_repo = limiter.has_indexed_repo(session_token)
"""

# -------------------------------------------------------------------------
# Configuration
# -------------------------------------------------------------------------

# Rate limits
SESSION_LIMIT_PER_DAY = 50 # Per device (generous for conversion)
Expand Down Expand Up @@ -198,9 +192,7 @@ def __init__(self, redis_client=None):
"""
self.redis = redis_client

# -------------------------------------------------------------------------
# Session Data Methods (#127)
# -------------------------------------------------------------------------

def get_session_data(self, session_token: Optional[str]) -> SessionData:
"""
Expand Down Expand Up @@ -449,9 +441,7 @@ def create_session(self, session_token: str) -> bool:
capture_exception(e, operation="create_session")
return False

# -------------------------------------------------------------------------
# Rate Limiting Methods (existing, updated for hash storage)
# -------------------------------------------------------------------------

def check_limit(
self,
Expand Down Expand Up @@ -657,9 +647,7 @@ def _check_ip_limit(self, client_ip: str, record: bool) -> Tuple[bool, int]:
logger.error("IP limit check failed", error=str(e))
return True, self.IP_LIMIT_PER_DAY # Fail open

# -------------------------------------------------------------------------
# Helper Methods
# -------------------------------------------------------------------------

def _get_midnight_utc(self) -> datetime:
"""Get next midnight UTC for reset time."""
Expand Down Expand Up @@ -788,9 +776,7 @@ def get_usage_stats(self) -> dict:
return {"error": str(e), "redis_available": False}


# =============================================================================
# SINGLETON
# =============================================================================

_playground_limiter: Optional[PlaygroundLimiter] = None

Expand Down
10 changes: 4 additions & 6 deletions backend/services/sentry.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def init_sentry() -> bool:
sentry_dsn = os.getenv("SENTRY_DSN")

if not sentry_dsn:
print("ℹ️ Sentry DSN not configured - error tracking disabled")
print("[INFO] Sentry DSN not configured - error tracking disabled")
return False

try:
Expand Down Expand Up @@ -61,14 +61,14 @@ def init_sentry() -> bool:
include_local_variables=True,
)

print(f" Sentry initialized (environment: {environment})")
print(f"[OK] Sentry initialized (environment: {environment})")
return True

except ImportError:
print("⚠️ sentry-sdk not installed - error tracking disabled")
print("[WARN] sentry-sdk not installed - error tracking disabled")
return False
except Exception as e:
print(f"⚠️ Failed to initialize Sentry: {e}")
print(f"[WARN] Failed to initialize Sentry: {e}")
return False


Expand Down Expand Up @@ -97,9 +97,7 @@ def _filter_events(event, hint):
return event


# ============================================================================
# LEGACY FUNCTIONS - Use observability module for new code
# ============================================================================

def set_user_context(user_id: Optional[str] = None, email: Optional[str] = None):
"""
Expand Down
17 changes: 8 additions & 9 deletions backend/services/style_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Analyzes team coding patterns, naming conventions, and best practices
"""
from pathlib import Path
from typing import Dict, List, Set
from typing import Dict, List, Optional, Set
from collections import defaultdict, Counter
import re

Expand Down Expand Up @@ -137,7 +137,7 @@ def _check_type_hints(self, source_code: str, language: str) -> bool:
if language == 'python':
return '->' in source_code or ': ' in source_code
else:
return ': ' in source_code and 'interface' in source_code or 'type ' in source_code
return ': ' in source_code and ('interface' in source_code or 'type ' in source_code)

def analyze_repository_style(self, repo_path: str) -> Dict:
"""Analyze coding style patterns across repository"""
Expand Down Expand Up @@ -263,16 +263,15 @@ def analyze_repository_style(self, repo_path: str) -> Dict:
}


# ===== SUPABASE CACHING =====

def save_to_cache(self, repo_id: str, style_data: Dict):
# SUPABASE CACHING
def save_to_cache(self, repo_id: str, style_data: Dict) -> None:
"""Save style analysis to Supabase for caching"""
from services.supabase_service import get_supabase_service

db = get_supabase_service()

# Group by language
for language, data in style_data.get("languages", {}).items():
# analyze_repository_style returns "language_distribution" not "languages"
for language, data in style_data.get("language_distribution", {}).items():
analysis = {
"naming_convention": data.get("naming_conventions"),
"async_usage": data.get("async_usage"),
Expand All @@ -285,7 +284,7 @@ def save_to_cache(self, repo_id: str, style_data: Dict):

logger.debug("Cached code style analysis in Supabase", repo_id=repo_id)

def load_from_cache(self, repo_id: str) -> Dict:
def load_from_cache(self, repo_id: str) -> Optional[Dict]:
"""Load style analysis from Supabase cache"""
from services.supabase_service import get_supabase_service

Expand Down Expand Up @@ -319,7 +318,7 @@ def load_from_cache(self, repo_id: str) -> Dict:
logger.debug("Loaded cached code style from Supabase", repo_id=repo_id)

return {
"languages": languages,
"language_distribution": languages,
"top_imports": common_imports,
"patterns": patterns
}
Loading