Skip to content

Commit 8651f0d

Browse files
committed
fix: Add rate limiting to AI endpoints
1 parent 42c1038 commit 8651f0d

1 file changed

Lines changed: 44 additions & 0 deletions

File tree

‎app.py‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from flask import Flask, request, jsonify
55
from flask_cors import CORS
66
from ai_service import generate_book_note, get_ai_recommendations, get_book_mood_tags_safe
7+
import time
8+
from collections import defaultdict, deque
79

810
# Try to import enhanced mood analysis
911
try:
@@ -16,13 +18,43 @@
1618
app = Flask(__name__)
1719
CORS(app)
1820

21+
22+
RATE_LIMIT_WINDOW_SECONDS = 60
23+
RATE_LIMIT_REQUESTS = 30
24+
_request_log = defaultdict(deque)
25+
26+
27+
def _check_rate_limit():
28+
"""Return a 429 response if the client exceeded the rate limit."""
29+
client_id = request.remote_addr or "unknown"
30+
now = time.time()
31+
entries = _request_log[client_id]
32+
33+
while entries and now - entries[0] > RATE_LIMIT_WINDOW_SECONDS:
34+
entries.popleft()
35+
36+
if len(entries) >= RATE_LIMIT_REQUESTS:
37+
retry_after = int(RATE_LIMIT_WINDOW_SECONDS - (now - entries[0])) + 1
38+
response = jsonify({
39+
"success": False,
40+
"error": "Rate limit exceeded. Please try again soon."
41+
})
42+
return response, 429, {"Retry-After": str(retry_after)}
43+
44+
entries.append(now)
45+
return None
46+
1947
# Initialize AI service if available
2048
if MOOD_ANALYSIS_AVAILABLE:
2149
ai_service = AIBookService()
2250

2351
@app.route('/api/v1/analyze-mood', methods=['POST'])
2452
def handle_analyze_mood():
2553
"""Analyze book mood using GoodReads reviews."""
54+
limited = _check_rate_limit()
55+
if limited:
56+
return limited
57+
2658
if not MOOD_ANALYSIS_AVAILABLE:
2759
return jsonify({
2860
"success": False,
@@ -62,6 +94,10 @@ def handle_analyze_mood():
6294
@app.route('/api/v1/mood-tags', methods=['POST'])
6395
def handle_mood_tags():
6496
"""Get mood tags for a book."""
97+
limited = _check_rate_limit()
98+
if limited:
99+
return limited
100+
65101
try:
66102
data = request.get_json()
67103
if not data:
@@ -88,6 +124,10 @@ def handle_mood_tags():
88124
@app.route('/api/v1/mood-search', methods=['POST'])
89125
def handle_mood_search():
90126
"""Search for books based on mood/vibe."""
127+
limited = _check_rate_limit()
128+
if limited:
129+
return limited
130+
91131
try:
92132
data = request.get_json()
93133
if not data:
@@ -114,6 +154,10 @@ def handle_mood_search():
114154
@app.route('/api/v1/generate-note', methods=['POST'])
115155
def handle_generate_note():
116156
"""Generate AI-powered book note with optional mood analysis."""
157+
limited = _check_rate_limit()
158+
if limited:
159+
return limited
160+
117161
try:
118162
data = request.get_json()
119163
if not data:

0 commit comments

Comments
 (0)