|
21 | 21 | QueryRequest |
22 | 22 | ) |
23 | 23 | from logger import get_logger |
| 24 | +from rate_limiter import query_limiter, indexing_limiter, general_limiter |
24 | 25 |
|
25 | 26 | logger = get_logger(__name__) |
26 | 27 |
|
|
29 | 30 | # Controls how many characters of each snippet and total context we send to coding model |
30 | 31 | TOTAL_CONTEXT_LIMIT = 4000 |
31 | 32 |
|
| 33 | + |
| 34 | +def _get_client_ip(request: Request) -> str: |
| 35 | + """Get client IP address from request.""" |
| 36 | + forwarded = request.headers.get("X-Forwarded-For") |
| 37 | + if forwarded: |
| 38 | + return forwarded.split(",")[0].strip() |
| 39 | + return request.client.host if request.client else "unknown" |
| 40 | + |
32 | 41 | @asynccontextmanager |
33 | 42 | async def lifespan(app: FastAPI): |
34 | 43 | # Project registry is auto-initialized when needed via create_project |
@@ -113,8 +122,18 @@ def api_delete_project(project_id: str): |
113 | 122 |
|
114 | 123 |
|
115 | 124 | @app.post("/api/projects/index") |
116 | | -def api_index_project(request: IndexProjectRequest, background_tasks: BackgroundTasks): |
| 125 | +def api_index_project(http_request: Request, request: IndexProjectRequest, background_tasks: BackgroundTasks): |
117 | 126 | """Index/re-index a project in the background.""" |
| 127 | + # Rate limiting for indexing operations (more strict) |
| 128 | + client_ip = _get_client_ip(http_request) |
| 129 | + allowed, retry_after = indexing_limiter.is_allowed(client_ip) |
| 130 | + if not allowed: |
| 131 | + return JSONResponse( |
| 132 | + {"error": "Rate limit exceeded for indexing", "retry_after": retry_after}, |
| 133 | + status_code=429, |
| 134 | + headers={"Retry-After": str(retry_after)} |
| 135 | + ) |
| 136 | + |
118 | 137 | try: |
119 | 138 | project = get_project_by_id(request.project_id) |
120 | 139 | if not project: |
@@ -149,8 +168,18 @@ def index_callback(): |
149 | 168 |
|
150 | 169 |
|
151 | 170 | @app.post("/api/query") |
152 | | -def api_query(request: QueryRequest): |
| 171 | +def api_query(http_request: Request, request: QueryRequest): |
153 | 172 | """Query a project using semantic search (PyCharm-compatible).""" |
| 173 | + # Rate limiting |
| 174 | + client_ip = _get_client_ip(http_request) |
| 175 | + allowed, retry_after = query_limiter.is_allowed(client_ip) |
| 176 | + if not allowed: |
| 177 | + return JSONResponse( |
| 178 | + {"error": "Rate limit exceeded", "retry_after": retry_after}, |
| 179 | + status_code=429, |
| 180 | + headers={"Retry-After": str(retry_after)} |
| 181 | + ) |
| 182 | + |
154 | 183 | try: |
155 | 184 | project = get_project_by_id(request.project_id) |
156 | 185 | if not project: |
|
0 commit comments