44from flask import Flask , request , jsonify
55from flask_cors import CORS
66from 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
911try :
1618app = Flask (__name__ )
1719CORS (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
2048if MOOD_ANALYSIS_AVAILABLE :
2149 ai_service = AIBookService ()
2250
2351@app .route ('/api/v1/analyze-mood' , methods = ['POST' ])
2452def 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' ])
6395def 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' ])
89125def 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' ])
115155def 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