Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -1,779 +1,779 @@
|
|
1 |
-
from flask import Flask,jsonify,request
|
2 |
-
from models import bcrypt, get_db_connection, add_quiz, add_question_from_master, add_ques_llm, create_quiz, create_quiz_master, recording_issue, record_feedback,create_quiz_by_id, add_question_to_db, add_theme_if_not_exists, profile_added_db, view_profile_db, view_quiz_score_db, get_recent_quizzes_db, fetch_quiz_for_theme_db, get_quiz_details_db
|
3 |
-
from config import Config
|
4 |
-
from signup import signup_route
|
5 |
-
from login import login_route
|
6 |
-
from submit_response import submit_quiz
|
7 |
-
import traceback
|
8 |
-
import threading
|
9 |
-
import urllib.parse
|
10 |
-
from generate_question import generate_ques
|
11 |
-
from generate_ques_random import generate_question_random
|
12 |
-
import logging
|
13 |
-
from flask_jwt_extended import JWTManager, jwt_required, get_jwt_identity
|
14 |
-
from beat_the_ai import beat_the_ai
|
15 |
-
from question_verify import verify_question
|
16 |
-
import re
|
17 |
-
import json
|
18 |
-
import mysql.connector
|
19 |
-
from contin_gen import continuous_generation
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
from leader_board import leaderboard_overall, leaderboard_daily,leaderboard_theme, leaderboard_weekly
|
24 |
-
|
25 |
-
logging.basicConfig(
|
26 |
-
filename='app.log',
|
27 |
-
level=logging.DEBUG,
|
28 |
-
format='%(asctime)s - %(levelname)s - %(message)s'
|
29 |
-
)
|
30 |
-
app = Flask(__name__)
|
31 |
-
app.config.from_object(Config)
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
@app.route('/')
|
36 |
-
def home():
|
37 |
-
return jsonify({"message": "Quiz App is Running", "status": "success"})
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
bcrypt.init_app(app)
|
42 |
-
|
43 |
-
jwt = JWTManager(app)
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
@app.route("/signup", methods = ['GET','POST'])
|
48 |
-
def signup():
|
49 |
-
logging.info("Signup route accessed")
|
50 |
-
return signup_route()
|
51 |
-
|
52 |
-
@app.route("/login",methods = ['GET','POST'])
|
53 |
-
def login():
|
54 |
-
logging.info("Login route accessed")
|
55 |
-
return login_route()
|
56 |
-
|
57 |
-
@app.route('/submit_quiz', methods=['POST'])
|
58 |
-
@jwt_required()
|
59 |
-
def submit_quiz_route():
|
60 |
-
logging.info("Submit quiz route accessed")
|
61 |
-
return submit_quiz()
|
62 |
-
|
63 |
-
|
64 |
-
@app.route('/leaderboard_overall', methods=['GET'])
|
65 |
-
@jwt_required()
|
66 |
-
def leaderboard_overall_route():
|
67 |
-
logging.info("Overall leaderboard route accessed")
|
68 |
-
return leaderboard_overall()
|
69 |
-
|
70 |
-
@app.route('/leaderboard_daily', methods=['GET'])
|
71 |
-
@jwt_required()
|
72 |
-
def leaderboard_daily_route():
|
73 |
-
logging.info('Daily leaderboard route accessed')
|
74 |
-
return leaderboard_daily()
|
75 |
-
|
76 |
-
@app.route('/leaderboard_weekly', methods=['GET'])
|
77 |
-
@jwt_required()
|
78 |
-
def leaderboard_weekly_route():
|
79 |
-
logging.info('Weekly leaderboard route accessed')
|
80 |
-
return leaderboard_weekly()
|
81 |
-
|
82 |
-
@app.route('/leaderboard_theme', methods=['GET'])
|
83 |
-
@jwt_required()
|
84 |
-
def leaderboard_theme_route():
|
85 |
-
logging.info('theme-wise leaderboard route accessed')
|
86 |
-
return leaderboard_theme()
|
87 |
-
|
88 |
-
@app.route('/add_question_master', methods=['POST'])
|
89 |
-
@jwt_required()
|
90 |
-
def add_question_master():
|
91 |
-
logging.info('add_question by master route accessed')
|
92 |
-
status = add_question_from_master()
|
93 |
-
if status == 'True':
|
94 |
-
return jsonify({"message": "Question added to question bank"}), 201
|
95 |
-
elif status == "Duplicate":
|
96 |
-
logging.info("Duplicate question detected for the user.")
|
97 |
-
return jsonify({"error": "Duplicate question detected."}), 400
|
98 |
-
else:
|
99 |
-
return jsonify({"error": "Failed to add question in the database."}), 500
|
100 |
-
|
101 |
-
|
102 |
-
@app.route('/add_question_llm', methods=['POST'])
|
103 |
-
@jwt_required()
|
104 |
-
def add_question_route():
|
105 |
-
logging.info('add question route accessed')
|
106 |
-
if request.method == 'POST':
|
107 |
-
logging.info("Received a POST request to add a question to the question bank.")
|
108 |
-
|
109 |
-
theme = request.form.get('theme')
|
110 |
-
theme = theme.lower()
|
111 |
-
|
112 |
-
|
113 |
-
add_theme_if_not_exists(theme)
|
114 |
-
|
115 |
-
if not theme:
|
116 |
-
return jsonify({"error": "Please provide the theme."}), 400
|
117 |
-
|
118 |
-
while True:
|
119 |
-
|
120 |
-
ques = generate_ques(theme)
|
121 |
-
logging.debug("Generated question from LLM: %s", ques)
|
122 |
-
|
123 |
-
|
124 |
-
question_match = re.search(r'Question:\s*(.*)', ques)
|
125 |
-
options_matches = re.findall(r'([A-D])\)\s*(.*)', ques)
|
126 |
-
correct_answer_match = re.search(r'Correct answer:\s*([A-D])', ques)
|
127 |
-
difficulty_match = re.search(r'Difficulty level:\s*(.*)', ques)
|
128 |
-
|
129 |
-
if question_match and options_matches and correct_answer_match:
|
130 |
-
question = question_match.group(1).strip()
|
131 |
-
options = [f"{opt[0]}) {opt[1].strip()}" for opt in options_matches]
|
132 |
-
correct_option = correct_answer_match.group(1).strip().upper()
|
133 |
-
difficulty = difficulty_match.group(1).strip()
|
134 |
-
|
135 |
-
logging.debug("Parsed question: %s", question)
|
136 |
-
logging.debug("Parsed options: %s", options)
|
137 |
-
logging.debug("Parsed correct option: %s", correct_option)
|
138 |
-
logging.debug("Parsed difficulty: %s", difficulty)
|
139 |
-
|
140 |
-
|
141 |
-
if correct_option not in ['A', 'B', 'C', 'D']:
|
142 |
-
return jsonify({"error": "The correct option is invalid."}), 400
|
143 |
-
|
144 |
-
|
145 |
-
status = add_ques_llm(theme, question, options, correct_option, difficulty)
|
146 |
-
|
147 |
-
if status == 'True':
|
148 |
-
return jsonify({"message": "Question added to question bank"})
|
149 |
-
elif status == "Duplicate":
|
150 |
-
logging.info("Duplicate question detected, generating a new question.")
|
151 |
-
continue
|
152 |
-
else:
|
153 |
-
return jsonify({"error": "Failed to add question in the database."}), 500
|
154 |
-
else:
|
155 |
-
return jsonify({"error": "Question format is incorrect or missing correct option."}), 400
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
@app.route('/create_quiz_master', methods=['POST'])
|
160 |
-
@jwt_required()
|
161 |
-
def create_quiz_master_route():
|
162 |
-
logging.info('create quiz master route accessed')
|
163 |
-
|
164 |
-
user_id_creator = get_jwt_identity()
|
165 |
-
if request.method == 'POST':
|
166 |
-
theme = request.form.get('theme')
|
167 |
-
theme = theme.lower()
|
168 |
-
add_theme_if_not_exists(theme)
|
169 |
-
num_questions = request.form.get('num_questions')
|
170 |
-
|
171 |
-
if not theme or not num_questions.isdigit() or int(num_questions) <= 0 or int(num_questions) >= 11:
|
172 |
-
return jsonify({"error": "Please provide a valid theme and a positive integer less than or equal to 10 for the number of questions."}), 400
|
173 |
-
return create_quiz_master(user_id_creator, theme, num_questions)
|
174 |
-
|
175 |
-
@app.route('/report', methods=['POST'])
|
176 |
-
@jwt_required()
|
177 |
-
def report_route():
|
178 |
-
logging.info('report route accessed')
|
179 |
-
user_id = get_jwt_identity()
|
180 |
-
|
181 |
-
if request.method == 'POST':
|
182 |
-
theme = request.form.get('theme')
|
183 |
-
theme = theme.lower()
|
184 |
-
ques_id= request.form.get('ques_id')
|
185 |
-
issue_description = request.form.get('issue_description')
|
186 |
-
|
187 |
-
if not ques_id or not issue_description or not theme:
|
188 |
-
return jsonify({"error":"Missing ques_id,issue_description or theme"}),400
|
189 |
-
|
190 |
-
return recording_issue(theme,ques_id,issue_description)
|
191 |
-
|
192 |
-
|
193 |
-
@app.route('/submit_feedback', methods=['POST'])
|
194 |
-
@jwt_required()
|
195 |
-
def submit_feedback():
|
196 |
-
user_id = get_jwt_identity()
|
197 |
-
rating= request.form.get('rating')
|
198 |
-
comments=request.form.get('comments')
|
199 |
-
if not user_id or not rating:
|
200 |
-
return jsonify({"error": "user_id and rating are required"}), 400
|
201 |
-
return record_feedback(user_id, rating, comments)
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
@app.route('/create_quiz_from_bank', methods=['POST'])
|
206 |
-
@jwt_required()
|
207 |
-
def create_quiz_id_route():
|
208 |
-
logging.info('create quiz route accessed')
|
209 |
-
|
210 |
-
|
211 |
-
if request.method == 'POST':
|
212 |
-
theme = request.form.get('theme')
|
213 |
-
theme = theme.lower()
|
214 |
-
|
215 |
-
num_questions = request.form.get('num_questions')
|
216 |
-
|
217 |
-
if not theme or not num_questions or not str(num_questions).isdigit() or int(num_questions) <= 0:
|
218 |
-
return jsonify({"error": "Please provide a valid theme and a positive integer for the number of questions."}), 400
|
219 |
-
return create_quiz_by_id(theme, num_questions)
|
220 |
-
|
221 |
-
|
222 |
-
@app.route('/generate_question_random_theme', methods=['POST'])
|
223 |
-
def generate_question_random_endpoint():
|
224 |
-
while True:
|
225 |
-
generated_content = generate_question_random().strip()
|
226 |
-
pattern = re.compile(r"""
|
227 |
-
Theme:\s*(?P<theme>.*?)\n
|
228 |
-
Question:\s*(?P<question>.*?)\n
|
229 |
-
A\)\s*(?P<option_a>.*?)\n
|
230 |
-
B\)\s*(?P<option_b>.*?)\n
|
231 |
-
C\)\s*(?P<option_c>.*?)\n
|
232 |
-
D\)\s*(?P<option_d>.*?)\n
|
233 |
-
Correct\s*answer:\s*(?P<correct_option>[A-D])\n
|
234 |
-
Difficulty\s*level:\s*(?P<difficulty>\w+)
|
235 |
-
""", re.VERBOSE | re.DOTALL)
|
236 |
-
|
237 |
-
match = pattern.search(generated_content)
|
238 |
-
if match:
|
239 |
-
theme = match.group("theme").strip()
|
240 |
-
theme = theme.lower()
|
241 |
-
|
242 |
-
add_theme_if_not_exists(theme)
|
243 |
-
question = match.group("question").strip()
|
244 |
-
# Combine question and options into one string.
|
245 |
-
question_options = (
|
246 |
-
f"{question}\n"
|
247 |
-
f"A) {match.group('option_a').strip()}\n"
|
248 |
-
f"B) {match.group('option_b').strip()}\n"
|
249 |
-
f"C) {match.group('option_c').strip()}\n"
|
250 |
-
f"D) {match.group('option_d').strip()}"
|
251 |
-
)
|
252 |
-
correct_option = match.group("correct_option").strip()
|
253 |
-
difficulty = match.group("difficulty").strip()
|
254 |
-
|
255 |
-
|
256 |
-
print("Storing data in database...")
|
257 |
-
print(f"Theme: {theme}")
|
258 |
-
print("Question and Options:")
|
259 |
-
print(question_options)
|
260 |
-
print(f"Correct Option: {correct_option}")
|
261 |
-
print(f"Difficulty: {difficulty}")
|
262 |
-
print("-" * 40)
|
263 |
-
db_response = add_question_to_db(theme, question_options, correct_option, difficulty)
|
264 |
-
|
265 |
-
if db_response == "Duplicate":
|
266 |
-
print("Duplicate detected! Regenerating...")
|
267 |
-
continue
|
268 |
-
elif db_response is None:
|
269 |
-
return jsonify({"error": "Database insertion failed"}), 500
|
270 |
-
else:
|
271 |
-
return jsonify({
|
272 |
-
"message": "Question added successfully!",
|
273 |
-
"theme": theme,
|
274 |
-
"question": question,
|
275 |
-
|
276 |
-
"difficulty": difficulty
|
277 |
-
})
|
278 |
-
|
279 |
-
else:
|
280 |
-
print("Parsing failed for the following content:")
|
281 |
-
print(generated_content)
|
282 |
-
|
283 |
-
@app.route('/add_theme', methods=['POST'])
|
284 |
-
@jwt_required()
|
285 |
-
def add_theme():
|
286 |
-
logging.info('Add theme route accessed')
|
287 |
-
try:
|
288 |
-
|
289 |
-
if request.method == 'POST':
|
290 |
-
theme = request.form.get('theme')
|
291 |
-
theme = theme.lower()
|
292 |
-
|
293 |
-
if not theme:
|
294 |
-
return jsonify({"error": "Theme is required"}), 400
|
295 |
-
|
296 |
-
theme_added = add_theme_if_not_exists(theme)
|
297 |
-
|
298 |
-
if theme_added:
|
299 |
-
return jsonify({"message": "Theme added successfully"}), 201
|
300 |
-
else:
|
301 |
-
return jsonify({"message": "Theme already exists"}), 200
|
302 |
-
|
303 |
-
except Exception as e:
|
304 |
-
return jsonify({"error": str(e)}), 500
|
305 |
-
|
306 |
-
@app.route('/beat_the_ai', methods=['POST'])
|
307 |
-
@jwt_required()
|
308 |
-
def beat_the_ai_endpoint():
|
309 |
-
score = request.form.get('score')
|
310 |
-
theme = request.form.get('theme')
|
311 |
-
theme = theme.lower()
|
312 |
-
|
313 |
-
if score > 100:
|
314 |
-
return jsonify({"message": "You Won!"}), 200
|
315 |
-
|
316 |
-
add_theme_if_not_exists(theme)
|
317 |
-
while True:
|
318 |
-
generated_content = beat_the_ai(theme, score)
|
319 |
-
pattern = re.compile(r"""
|
320 |
-
(?:Theme:\s*(?P<theme>.*?)\n)? # Theme (optional)
|
321 |
-
Question:\s*(?P<question>.*?)\n
|
322 |
-
A\)\s*(?P<option_a>.*?)\n
|
323 |
-
B\)\s*(?P<option_b>.*?)\n
|
324 |
-
C\)\s*(?P<option_c>.*?)\n
|
325 |
-
D\)\s*(?P<option_d>.*?)\n
|
326 |
-
Correct\s*answer:\s*(?P<correct_option>[A-D])\s*\n?
|
327 |
-
Difficulty\s*level:\s*(?P<difficulty>\w+)\s*$
|
328 |
-
""", re.VERBOSE | re.DOTALL)
|
329 |
-
|
330 |
-
match = pattern.search(generated_content)
|
331 |
-
if match:
|
332 |
-
theme = match.group("theme").strip()
|
333 |
-
theme = theme.lower()
|
334 |
-
question = match.group("question").strip()
|
335 |
-
# Combine question and options into one string.
|
336 |
-
question_options = (
|
337 |
-
f"{question}\n"
|
338 |
-
f"A) {match.group('option_a').strip()}\n"
|
339 |
-
f"B) {match.group('option_b').strip()}\n"
|
340 |
-
f"C) {match.group('option_c').strip()}\n"
|
341 |
-
f"D) {match.group('option_d').strip()}"
|
342 |
-
)
|
343 |
-
correct_option = match.group("correct_option").strip()
|
344 |
-
difficulty = match.group("difficulty").strip()
|
345 |
-
|
346 |
-
# Replace the following print statements with your actual database storage logic.
|
347 |
-
print("Storing data in database...")
|
348 |
-
print(f"Theme: {theme}")
|
349 |
-
print("Question and Options:")
|
350 |
-
print(question_options)
|
351 |
-
print(f"Correct Option: {correct_option}")
|
352 |
-
print(f"Difficulty: {difficulty}")
|
353 |
-
print("-" * 40)
|
354 |
-
db_response = add_question_to_db(theme, question_options, correct_option, difficulty)
|
355 |
-
|
356 |
-
if db_response == "Duplicate":
|
357 |
-
print("Duplicate detected! Regenerating...")
|
358 |
-
continue
|
359 |
-
elif db_response is None:
|
360 |
-
return jsonify({"error": "Database insertion failed"}), 500
|
361 |
-
else:
|
362 |
-
return jsonify({
|
363 |
-
"message": "Question added successfully!",
|
364 |
-
"theme": theme,
|
365 |
-
"question": question_options,
|
366 |
-
"Correct Option": correct_option,
|
367 |
-
"difficulty": difficulty
|
368 |
-
})
|
369 |
-
|
370 |
-
else:
|
371 |
-
print("Parsing failed for the following content:")
|
372 |
-
print(generated_content)
|
373 |
-
|
374 |
-
|
375 |
-
|
376 |
-
@app.route('/edit_profile', methods=['POST'])
|
377 |
-
@jwt_required()
|
378 |
-
def edit_profile_endpoint():
|
379 |
-
logging.info("Edit profile route accessed")
|
380 |
-
if request.method == 'POST':
|
381 |
-
first_name = request.form.get('first_name')
|
382 |
-
last_name = request.form.get('last_name')
|
383 |
-
organisation = request.form.get('organisation')
|
384 |
-
industry = request.form.get('industry')
|
385 |
-
bio = request.form.get('bio')
|
386 |
-
|
387 |
-
|
388 |
-
|
389 |
-
if not first_name or not last_name:
|
390 |
-
return jsonify({"error": "First name and last name is required"}), 400
|
391 |
-
response = profile_added_db(first_name, last_name, organisation, industry, bio)
|
392 |
-
|
393 |
-
return response
|
394 |
-
|
395 |
-
@app.route('/view_profile', methods=['GET'])
|
396 |
-
@jwt_required()
|
397 |
-
def view_profile_endpoint():
|
398 |
-
logging.info("View profile route accessed")
|
399 |
-
response = view_profile_db()
|
400 |
-
return response
|
401 |
-
|
402 |
-
@app.route('/view_quiz_score', methods=['POST'])
|
403 |
-
@jwt_required()
|
404 |
-
def view_quiz_score_endpoint():
|
405 |
-
logging.info("View quiz score route accessed")
|
406 |
-
|
407 |
-
user_id = get_jwt_identity()
|
408 |
-
quiz_id = request.form.get('quiz_id')
|
409 |
-
theme = request.form.get('theme')
|
410 |
-
theme = theme.lower()
|
411 |
-
|
412 |
-
if not user_id or not quiz_id or not theme:
|
413 |
-
return jsonify({"error": "user_id, quiz_id, and theme are required"}), 400
|
414 |
-
|
415 |
-
try:
|
416 |
-
connection = get_db_connection()
|
417 |
-
if not connection:
|
418 |
-
return jsonify({"error": "Database connection failed."}), 500
|
419 |
-
|
420 |
-
cursor = connection.cursor(dictionary=True)
|
421 |
-
|
422 |
-
|
423 |
-
cursor.execute("SELECT theme_quiz_table FROM themes WHERE theme = %s", (theme,))
|
424 |
-
theme_entry = cursor.fetchone()
|
425 |
-
|
426 |
-
if not theme_entry:
|
427 |
-
logging.warning(f"Theme '{theme}' does not exist in the themes table.")
|
428 |
-
return jsonify({"error": "Invalid theme or theme not supported."}), 404
|
429 |
-
|
430 |
-
theme_quiz_table = theme_entry['theme_quiz_table']
|
431 |
-
logging.info(f"Theme '{theme}' maps to table '{theme_quiz_table}'.")
|
432 |
-
|
433 |
-
response = view_quiz_score_db(user_id, theme_quiz_table, theme, quiz_id)
|
434 |
-
return response
|
435 |
-
|
436 |
-
except mysql.connector.Error as err:
|
437 |
-
logging.error("MySQL Error: %s", err)
|
438 |
-
return jsonify({"error": "Database operation failed."}), 500
|
439 |
-
|
440 |
-
except Exception as e:
|
441 |
-
logging.error("Unexpected error: %s", str(e))
|
442 |
-
return jsonify({"error": "An unexpected error occurred."}), 500
|
443 |
-
|
444 |
-
finally:
|
445 |
-
if 'cursor' in locals():
|
446 |
-
cursor.close()
|
447 |
-
if 'connection' in locals():
|
448 |
-
connection.close()
|
449 |
-
logging.info("Database connection closed.")
|
450 |
-
|
451 |
-
|
452 |
-
@app.route('/recent_quizzes', methods=['GET'])
|
453 |
-
@jwt_required()
|
454 |
-
def recent_quizzes_endpoint():
|
455 |
-
logging.info("Recent quizzes route accessed")
|
456 |
-
|
457 |
-
user_id = get_jwt_identity()
|
458 |
-
if not user_id:
|
459 |
-
return jsonify({"error": "User authentication required"}), 401
|
460 |
-
|
461 |
-
response = get_recent_quizzes_db(user_id)
|
462 |
-
|
463 |
-
if "error" in response:
|
464 |
-
return jsonify(response), 500
|
465 |
-
if "message" in response:
|
466 |
-
return jsonify(response), 404
|
467 |
-
|
468 |
-
return jsonify(response), 200
|
469 |
-
|
470 |
-
|
471 |
-
@app.route('/fetch_quiz_for_theme', methods=['GET'])
|
472 |
-
@jwt_required()
|
473 |
-
def fetch_quiz_endpoint():
|
474 |
-
logging.info("Fetch quiz route accessed")
|
475 |
-
|
476 |
-
user_id = get_jwt_identity()
|
477 |
-
if not user_id:
|
478 |
-
return jsonify({"error": "User authentication required"}), 401
|
479 |
-
|
480 |
-
theme = request.form.get('theme')
|
481 |
-
if not theme:
|
482 |
-
return jsonify({"error": "Theme is required"}), 400
|
483 |
-
|
484 |
-
result = fetch_quiz_for_theme_db(user_id, theme)
|
485 |
-
|
486 |
-
if "error" in result:
|
487 |
-
return jsonify(result), 500 if "Database" in result["error"] else 404
|
488 |
-
|
489 |
-
return jsonify(result), 200
|
490 |
-
|
491 |
-
@app.route('/get_all_themes', methods=['GET'])
|
492 |
-
@jwt_required()
|
493 |
-
def get_all_themes():
|
494 |
-
logging.info("Get all themes route accessed")
|
495 |
-
|
496 |
-
try:
|
497 |
-
connection = get_db_connection()
|
498 |
-
cursor = connection.cursor(dictionary=True)
|
499 |
-
|
500 |
-
cursor.execute("SELECT theme FROM themes")
|
501 |
-
themes = cursor.fetchall()
|
502 |
-
|
503 |
-
# Return only theme names in a list
|
504 |
-
theme_list = [row['theme'] for row in themes]
|
505 |
-
return jsonify({"themes": theme_list}), 200
|
506 |
-
|
507 |
-
except mysql.connector.Error as err:
|
508 |
-
logging.error("Database error while fetching themes: %s", err)
|
509 |
-
return jsonify({"error": "Database error occurred"}), 500
|
510 |
-
|
511 |
-
except Exception as e:
|
512 |
-
logging.error("Unexpected error while fetching themes: %s", str(e))
|
513 |
-
return jsonify({"error": "Unexpected error occurred"}), 500
|
514 |
-
|
515 |
-
finally:
|
516 |
-
if 'cursor' in locals():
|
517 |
-
cursor.close()
|
518 |
-
if 'connection' in locals():
|
519 |
-
connection.close()
|
520 |
-
logging.info("Database connection closed")
|
521 |
-
|
522 |
-
|
523 |
-
@app.route('/view_attempted_quiz', methods=['POST'])
|
524 |
-
@jwt_required()
|
525 |
-
def view_attempted_quiz_endpoint():
|
526 |
-
logging.info("View attempted quiz route accessed")
|
527 |
-
|
528 |
-
user_id = get_jwt_identity()
|
529 |
-
quiz_id = request.form.get('quiz_id')
|
530 |
-
theme = request.form.get('theme')
|
531 |
-
theme = theme.lower()
|
532 |
-
|
533 |
-
if not user_id or not quiz_id or not theme:
|
534 |
-
return jsonify({"error": "user_id, quiz_id, and theme are required"}), 400
|
535 |
-
|
536 |
-
try:
|
537 |
-
connection = get_db_connection()
|
538 |
-
if not connection:
|
539 |
-
return jsonify({"error": "Database connection failed."}), 500
|
540 |
-
|
541 |
-
cursor = connection.cursor(dictionary=True)
|
542 |
-
|
543 |
-
cursor.execute("SELECT theme_quiz_table, theme_bank FROM themes WHERE theme = %s", (theme,))
|
544 |
-
result = cursor.fetchone()
|
545 |
-
if not result:
|
546 |
-
return jsonify({"error": "Invalid theme."}), 404
|
547 |
-
|
548 |
-
theme_quiz_table = result['theme_quiz_table']
|
549 |
-
theme_bank = result['theme_bank']
|
550 |
-
|
551 |
-
cursor.execute(f"""
|
552 |
-
SELECT questions_by_llm, correct_options_llm
|
553 |
-
FROM {theme_quiz_table}
|
554 |
-
WHERE quiz_id = %s
|
555 |
-
""", (quiz_id,))
|
556 |
-
quiz_data = cursor.fetchone()
|
557 |
-
if not quiz_data:
|
558 |
-
return jsonify({"error": "Quiz not found."}), 404
|
559 |
-
|
560 |
-
question_ids = json.loads(quiz_data['questions_by_llm'])
|
561 |
-
correct_options = json.loads(quiz_data['correct_options_llm'])
|
562 |
-
|
563 |
-
cursor.execute("""
|
564 |
-
SELECT user_response, score, time_taken
|
565 |
-
FROM quiz_response
|
566 |
-
WHERE quiz_id = %s AND user_id_attempt = %s AND theme = %s
|
567 |
-
""", (quiz_id, user_id, theme))
|
568 |
-
response_data = cursor.fetchone()
|
569 |
-
if not response_data:
|
570 |
-
return jsonify({"error": "User has not attempted this quiz."}), 404
|
571 |
-
|
572 |
-
user_responses = json.loads(response_data['user_response'])
|
573 |
-
|
574 |
-
question_details = []
|
575 |
-
for ques_id in question_ids:
|
576 |
-
cursor.execute(f"""
|
577 |
-
SELECT ques_id, question_by_llm, correct_option_llm
|
578 |
-
FROM {theme_bank}
|
579 |
-
WHERE ques_id = %s
|
580 |
-
""", (ques_id,))
|
581 |
-
q = cursor.fetchone()
|
582 |
-
if not q:
|
583 |
-
continue
|
584 |
-
question_details.append({
|
585 |
-
"ques_id": q['ques_id'],
|
586 |
-
"question": q['question_by_llm'],
|
587 |
-
"correct_option": correct_options[i],
|
588 |
-
"user_response": user_responses[i]
|
589 |
-
})
|
590 |
-
|
591 |
-
return jsonify({
|
592 |
-
"quiz_id": quiz_id,
|
593 |
-
"theme": theme,
|
594 |
-
"score": response_data['score'],
|
595 |
-
"time_taken": response_data['time_taken'],
|
596 |
-
"questions": question_details
|
597 |
-
}), 200
|
598 |
-
|
599 |
-
except mysql.connector.Error as err:
|
600 |
-
logging.error("MySQL Error: %s", err)
|
601 |
-
return jsonify({"error": "Database operation failed."}), 500
|
602 |
-
|
603 |
-
except Exception as e:
|
604 |
-
logging.error("Unexpected error: %s", str(e))
|
605 |
-
return jsonify({"error": "An unexpected error occurred."}), 500
|
606 |
-
|
607 |
-
finally:
|
608 |
-
if 'cursor' in locals():
|
609 |
-
cursor.close()
|
610 |
-
if 'connection' in locals():
|
611 |
-
connection.close()
|
612 |
-
logging.info("Database connection closed.")
|
613 |
-
|
614 |
-
|
615 |
-
@app.route('/get_attempted_quizzes', methods=['POST'])
|
616 |
-
@jwt_required()
|
617 |
-
def get_attempted_quizzes():
|
618 |
-
user_id = get_jwt_identity()
|
619 |
-
theme = request.form.get('theme')
|
620 |
-
theme = theme.lower()
|
621 |
-
if not theme:
|
622 |
-
return jsonify({"error": "Theme is required"}), 400
|
623 |
-
|
624 |
-
try:
|
625 |
-
conn = get_db_connection()
|
626 |
-
cursor = conn.cursor()
|
627 |
-
cursor.execute("""
|
628 |
-
SELECT DISTINCT quiz_id
|
629 |
-
FROM quiz_response
|
630 |
-
WHERE user_id_attempt = %s AND theme = %s
|
631 |
-
""", (user_id, theme))
|
632 |
-
ids = [row[0] for row in cursor.fetchall()]
|
633 |
-
return jsonify({"theme": theme, "quiz_ids": ids}), 200
|
634 |
-
|
635 |
-
except mysql.connector.Error as err:
|
636 |
-
logging.error("DB error: %s", err)
|
637 |
-
return jsonify({"error": "Database error"}), 500
|
638 |
-
finally:
|
639 |
-
cursor.close()
|
640 |
-
conn.close()
|
641 |
-
|
642 |
-
|
643 |
-
@app.route('/user_theme_stats', methods=['POST'])
|
644 |
-
@jwt_required()
|
645 |
-
def user_theme_stats():
|
646 |
-
logging.info("User theme stats route accessed")
|
647 |
-
|
648 |
-
theme = request.form.get('theme')
|
649 |
-
if not theme:
|
650 |
-
return jsonify({"error": "Theme is required"}), 400
|
651 |
-
|
652 |
-
theme = theme.lower()
|
653 |
-
user_id = get_jwt_identity()
|
654 |
-
|
655 |
-
try:
|
656 |
-
connection = get_db_connection()
|
657 |
-
cursor = connection.cursor(dictionary=True)
|
658 |
-
|
659 |
-
cursor.execute("""
|
660 |
-
SELECT quiz_id, score, time_taken
|
661 |
-
FROM quiz_response
|
662 |
-
WHERE theme = %s AND user_id_attempt = %s
|
663 |
-
""", (theme, user_id))
|
664 |
-
responses = cursor.fetchall()
|
665 |
-
|
666 |
-
if not responses:
|
667 |
-
return jsonify({
|
668 |
-
"message": f"No stats found for user in theme '{theme}'",
|
669 |
-
"total_score": "0/0",
|
670 |
-
"accuracy": "0%",
|
671 |
-
"avg_time_seconds": 0
|
672 |
-
}), 200
|
673 |
-
|
674 |
-
quiz_ids = [str(r['quiz_id']) for r in responses]
|
675 |
-
quiz_ids_str = ','.join(quiz_ids)
|
676 |
-
theme_quiz_table = f"theme_{theme}"
|
677 |
-
|
678 |
-
cursor.execute(f"""
|
679 |
-
SELECT quiz_id, num_questions
|
680 |
-
FROM {theme_quiz_table}
|
681 |
-
WHERE quiz_id IN ({quiz_ids_str})
|
682 |
-
""")
|
683 |
-
quiz_data = cursor.fetchall()
|
684 |
-
quiz_question_map = {q['quiz_id']: q['num_questions'] for q in quiz_data}
|
685 |
-
|
686 |
-
total_score = 0
|
687 |
-
total_possible = 0
|
688 |
-
total_time = 0
|
689 |
-
valid_time_count = 0
|
690 |
-
|
691 |
-
for r in responses:
|
692 |
-
quiz_id = r['quiz_id']
|
693 |
-
score = r['score']
|
694 |
-
time_str = r['time_taken']
|
695 |
-
|
696 |
-
num_q = quiz_question_map.get(quiz_id)
|
697 |
-
if num_q:
|
698 |
-
total_score += score
|
699 |
-
total_possible += num_q
|
700 |
-
|
701 |
-
if time_str:
|
702 |
-
try:
|
703 |
-
if ':' in time_str:
|
704 |
-
parts = list(map(int, time_str.strip().split(':')))
|
705 |
-
if len(parts) == 3:
|
706 |
-
hrs, mins, secs = parts
|
707 |
-
seconds = hrs * 3600 + mins * 60 + secs
|
708 |
-
elif len(parts) == 2:
|
709 |
-
mins, secs = parts
|
710 |
-
seconds = mins * 60 + secs
|
711 |
-
else:
|
712 |
-
seconds = int(parts[0])
|
713 |
-
|
714 |
-
else:
|
715 |
-
seconds = int(float(time_str))
|
716 |
-
|
717 |
-
total_time += seconds
|
718 |
-
valid_time_count += 1
|
719 |
-
except Exception as e:
|
720 |
-
logging.warning(f"Failed to parse time: {time_str}, error: {e}")
|
721 |
-
|
722 |
-
avg_time_seconds = round(total_time / valid_time_count, 2) if valid_time_count else 0
|
723 |
-
accuracy = round((total_score / total_possible) * 100, 2) if total_possible else 0
|
724 |
-
|
725 |
-
return jsonify({
|
726 |
-
"total_score": f"{total_score}/{total_possible}",
|
727 |
-
"avg_time_seconds": avg_time_seconds,
|
728 |
-
"accuracy": f"{accuracy}%"
|
729 |
-
})
|
730 |
-
|
731 |
-
except mysql.connector.Error as e:
|
732 |
-
logging.error("MySQL error: %s", e)
|
733 |
-
return jsonify({"error": "Database error"}), 500
|
734 |
-
|
735 |
-
except Exception as e:
|
736 |
-
logging.error("Unexpected error: %s", e)
|
737 |
-
return jsonify({"error": "Unexpected error"}), 500
|
738 |
-
|
739 |
-
finally:
|
740 |
-
if 'cursor' in locals():
|
741 |
-
cursor.close()
|
742 |
-
if 'connection' in locals():
|
743 |
-
connection.close()
|
744 |
-
|
745 |
-
|
746 |
-
@app.route('/share_attempted_quiz', methods= ['GET'])
|
747 |
-
@jwt_required()
|
748 |
-
def share_quiz_endpoint():
|
749 |
-
user_id = get_jwt_identity()
|
750 |
-
if not user_id:
|
751 |
-
return jsonify({"error": "User authentication required"}), 401
|
752 |
-
|
753 |
-
quiz_id = request.form.get('quiz_id')
|
754 |
-
if not quiz_id:
|
755 |
-
return jsonify({"error": "Quiz ID is required"}), 400
|
756 |
-
|
757 |
-
|
758 |
-
quiz_data = get_quiz_details_db(user_id, quiz_id)
|
759 |
-
|
760 |
-
if "error" in quiz_data:
|
761 |
-
return jsonify(quiz_data), 500
|
762 |
-
if "message" in quiz_data:
|
763 |
-
return jsonify(quiz_data), 404
|
764 |
-
|
765 |
-
|
766 |
-
query_string = urllib.parse.urlencode(quiz_data)
|
767 |
-
|
768 |
-
|
769 |
-
shareable_link = f"http://example.com/shared_quiz?{query_string}"
|
770 |
-
|
771 |
-
return jsonify({"shareable_link": shareable_link}), 200
|
772 |
-
|
773 |
-
if __name__ == '__main__':
|
774 |
-
# logging.info("Starting the Flask application...")
|
775 |
-
# thread = threading.Thread(target=continuous_generation, daemon=True)
|
776 |
-
# thread.start()
|
777 |
-
logging.info("Starting the application...")
|
778 |
-
app.run(host='0.0.0.0',port=5000,debug=True)
|
779 |
-
|
|
|
1 |
+
from flask import Flask,jsonify,request
|
2 |
+
from models import bcrypt, get_db_connection, add_quiz, add_question_from_master, add_ques_llm, create_quiz, create_quiz_master, recording_issue, record_feedback,create_quiz_by_id, add_question_to_db, add_theme_if_not_exists, profile_added_db, view_profile_db, view_quiz_score_db, get_recent_quizzes_db, fetch_quiz_for_theme_db, get_quiz_details_db
|
3 |
+
from config import Config
|
4 |
+
from signup import signup_route
|
5 |
+
from login import login_route
|
6 |
+
from submit_response import submit_quiz
|
7 |
+
import traceback
|
8 |
+
import threading
|
9 |
+
import urllib.parse
|
10 |
+
from generate_question import generate_ques
|
11 |
+
from generate_ques_random import generate_question_random
|
12 |
+
import logging
|
13 |
+
from flask_jwt_extended import JWTManager, jwt_required, get_jwt_identity
|
14 |
+
from beat_the_ai import beat_the_ai
|
15 |
+
from question_verify import verify_question
|
16 |
+
import re
|
17 |
+
import json
|
18 |
+
import mysql.connector
|
19 |
+
from contin_gen import continuous_generation
|
20 |
+
|
21 |
+
|
22 |
+
|
23 |
+
from leader_board import leaderboard_overall, leaderboard_daily,leaderboard_theme, leaderboard_weekly
|
24 |
+
|
25 |
+
logging.basicConfig(
|
26 |
+
filename=os.path.join('/tmp', 'app.log'),
|
27 |
+
level=logging.DEBUG,
|
28 |
+
format='%(asctime)s - %(levelname)s - %(message)s'
|
29 |
+
)
|
30 |
+
app = Flask(__name__)
|
31 |
+
app.config.from_object(Config)
|
32 |
+
|
33 |
+
|
34 |
+
|
35 |
+
@app.route('/')
|
36 |
+
def home():
|
37 |
+
return jsonify({"message": "Quiz App is Running", "status": "success"})
|
38 |
+
|
39 |
+
|
40 |
+
|
41 |
+
bcrypt.init_app(app)
|
42 |
+
|
43 |
+
jwt = JWTManager(app)
|
44 |
+
|
45 |
+
|
46 |
+
|
47 |
+
@app.route("/signup", methods = ['GET','POST'])
|
48 |
+
def signup():
|
49 |
+
logging.info("Signup route accessed")
|
50 |
+
return signup_route()
|
51 |
+
|
52 |
+
@app.route("/login",methods = ['GET','POST'])
|
53 |
+
def login():
|
54 |
+
logging.info("Login route accessed")
|
55 |
+
return login_route()
|
56 |
+
|
57 |
+
@app.route('/submit_quiz', methods=['POST'])
|
58 |
+
@jwt_required()
|
59 |
+
def submit_quiz_route():
|
60 |
+
logging.info("Submit quiz route accessed")
|
61 |
+
return submit_quiz()
|
62 |
+
|
63 |
+
|
64 |
+
@app.route('/leaderboard_overall', methods=['GET'])
|
65 |
+
@jwt_required()
|
66 |
+
def leaderboard_overall_route():
|
67 |
+
logging.info("Overall leaderboard route accessed")
|
68 |
+
return leaderboard_overall()
|
69 |
+
|
70 |
+
@app.route('/leaderboard_daily', methods=['GET'])
|
71 |
+
@jwt_required()
|
72 |
+
def leaderboard_daily_route():
|
73 |
+
logging.info('Daily leaderboard route accessed')
|
74 |
+
return leaderboard_daily()
|
75 |
+
|
76 |
+
@app.route('/leaderboard_weekly', methods=['GET'])
|
77 |
+
@jwt_required()
|
78 |
+
def leaderboard_weekly_route():
|
79 |
+
logging.info('Weekly leaderboard route accessed')
|
80 |
+
return leaderboard_weekly()
|
81 |
+
|
82 |
+
@app.route('/leaderboard_theme', methods=['GET'])
|
83 |
+
@jwt_required()
|
84 |
+
def leaderboard_theme_route():
|
85 |
+
logging.info('theme-wise leaderboard route accessed')
|
86 |
+
return leaderboard_theme()
|
87 |
+
|
88 |
+
@app.route('/add_question_master', methods=['POST'])
|
89 |
+
@jwt_required()
|
90 |
+
def add_question_master():
|
91 |
+
logging.info('add_question by master route accessed')
|
92 |
+
status = add_question_from_master()
|
93 |
+
if status == 'True':
|
94 |
+
return jsonify({"message": "Question added to question bank"}), 201
|
95 |
+
elif status == "Duplicate":
|
96 |
+
logging.info("Duplicate question detected for the user.")
|
97 |
+
return jsonify({"error": "Duplicate question detected."}), 400
|
98 |
+
else:
|
99 |
+
return jsonify({"error": "Failed to add question in the database."}), 500
|
100 |
+
|
101 |
+
|
102 |
+
@app.route('/add_question_llm', methods=['POST'])
|
103 |
+
@jwt_required()
|
104 |
+
def add_question_route():
|
105 |
+
logging.info('add question route accessed')
|
106 |
+
if request.method == 'POST':
|
107 |
+
logging.info("Received a POST request to add a question to the question bank.")
|
108 |
+
|
109 |
+
theme = request.form.get('theme')
|
110 |
+
theme = theme.lower()
|
111 |
+
|
112 |
+
|
113 |
+
add_theme_if_not_exists(theme)
|
114 |
+
|
115 |
+
if not theme:
|
116 |
+
return jsonify({"error": "Please provide the theme."}), 400
|
117 |
+
|
118 |
+
while True:
|
119 |
+
|
120 |
+
ques = generate_ques(theme)
|
121 |
+
logging.debug("Generated question from LLM: %s", ques)
|
122 |
+
|
123 |
+
|
124 |
+
question_match = re.search(r'Question:\s*(.*)', ques)
|
125 |
+
options_matches = re.findall(r'([A-D])\)\s*(.*)', ques)
|
126 |
+
correct_answer_match = re.search(r'Correct answer:\s*([A-D])', ques)
|
127 |
+
difficulty_match = re.search(r'Difficulty level:\s*(.*)', ques)
|
128 |
+
|
129 |
+
if question_match and options_matches and correct_answer_match:
|
130 |
+
question = question_match.group(1).strip()
|
131 |
+
options = [f"{opt[0]}) {opt[1].strip()}" for opt in options_matches]
|
132 |
+
correct_option = correct_answer_match.group(1).strip().upper()
|
133 |
+
difficulty = difficulty_match.group(1).strip()
|
134 |
+
|
135 |
+
logging.debug("Parsed question: %s", question)
|
136 |
+
logging.debug("Parsed options: %s", options)
|
137 |
+
logging.debug("Parsed correct option: %s", correct_option)
|
138 |
+
logging.debug("Parsed difficulty: %s", difficulty)
|
139 |
+
|
140 |
+
|
141 |
+
if correct_option not in ['A', 'B', 'C', 'D']:
|
142 |
+
return jsonify({"error": "The correct option is invalid."}), 400
|
143 |
+
|
144 |
+
|
145 |
+
status = add_ques_llm(theme, question, options, correct_option, difficulty)
|
146 |
+
|
147 |
+
if status == 'True':
|
148 |
+
return jsonify({"message": "Question added to question bank"})
|
149 |
+
elif status == "Duplicate":
|
150 |
+
logging.info("Duplicate question detected, generating a new question.")
|
151 |
+
continue
|
152 |
+
else:
|
153 |
+
return jsonify({"error": "Failed to add question in the database."}), 500
|
154 |
+
else:
|
155 |
+
return jsonify({"error": "Question format is incorrect or missing correct option."}), 400
|
156 |
+
|
157 |
+
|
158 |
+
|
159 |
+
@app.route('/create_quiz_master', methods=['POST'])
|
160 |
+
@jwt_required()
|
161 |
+
def create_quiz_master_route():
|
162 |
+
logging.info('create quiz master route accessed')
|
163 |
+
|
164 |
+
user_id_creator = get_jwt_identity()
|
165 |
+
if request.method == 'POST':
|
166 |
+
theme = request.form.get('theme')
|
167 |
+
theme = theme.lower()
|
168 |
+
add_theme_if_not_exists(theme)
|
169 |
+
num_questions = request.form.get('num_questions')
|
170 |
+
|
171 |
+
if not theme or not num_questions.isdigit() or int(num_questions) <= 0 or int(num_questions) >= 11:
|
172 |
+
return jsonify({"error": "Please provide a valid theme and a positive integer less than or equal to 10 for the number of questions."}), 400
|
173 |
+
return create_quiz_master(user_id_creator, theme, num_questions)
|
174 |
+
|
175 |
+
@app.route('/report', methods=['POST'])
|
176 |
+
@jwt_required()
|
177 |
+
def report_route():
|
178 |
+
logging.info('report route accessed')
|
179 |
+
user_id = get_jwt_identity()
|
180 |
+
|
181 |
+
if request.method == 'POST':
|
182 |
+
theme = request.form.get('theme')
|
183 |
+
theme = theme.lower()
|
184 |
+
ques_id= request.form.get('ques_id')
|
185 |
+
issue_description = request.form.get('issue_description')
|
186 |
+
|
187 |
+
if not ques_id or not issue_description or not theme:
|
188 |
+
return jsonify({"error":"Missing ques_id,issue_description or theme"}),400
|
189 |
+
|
190 |
+
return recording_issue(theme,ques_id,issue_description)
|
191 |
+
|
192 |
+
|
193 |
+
@app.route('/submit_feedback', methods=['POST'])
|
194 |
+
@jwt_required()
|
195 |
+
def submit_feedback():
|
196 |
+
user_id = get_jwt_identity()
|
197 |
+
rating= request.form.get('rating')
|
198 |
+
comments=request.form.get('comments')
|
199 |
+
if not user_id or not rating:
|
200 |
+
return jsonify({"error": "user_id and rating are required"}), 400
|
201 |
+
return record_feedback(user_id, rating, comments)
|
202 |
+
|
203 |
+
|
204 |
+
|
205 |
+
@app.route('/create_quiz_from_bank', methods=['POST'])
|
206 |
+
@jwt_required()
|
207 |
+
def create_quiz_id_route():
|
208 |
+
logging.info('create quiz route accessed')
|
209 |
+
|
210 |
+
|
211 |
+
if request.method == 'POST':
|
212 |
+
theme = request.form.get('theme')
|
213 |
+
theme = theme.lower()
|
214 |
+
|
215 |
+
num_questions = request.form.get('num_questions')
|
216 |
+
|
217 |
+
if not theme or not num_questions or not str(num_questions).isdigit() or int(num_questions) <= 0:
|
218 |
+
return jsonify({"error": "Please provide a valid theme and a positive integer for the number of questions."}), 400
|
219 |
+
return create_quiz_by_id(theme, num_questions)
|
220 |
+
|
221 |
+
|
222 |
+
@app.route('/generate_question_random_theme', methods=['POST'])
|
223 |
+
def generate_question_random_endpoint():
|
224 |
+
while True:
|
225 |
+
generated_content = generate_question_random().strip()
|
226 |
+
pattern = re.compile(r"""
|
227 |
+
Theme:\s*(?P<theme>.*?)\n
|
228 |
+
Question:\s*(?P<question>.*?)\n
|
229 |
+
A\)\s*(?P<option_a>.*?)\n
|
230 |
+
B\)\s*(?P<option_b>.*?)\n
|
231 |
+
C\)\s*(?P<option_c>.*?)\n
|
232 |
+
D\)\s*(?P<option_d>.*?)\n
|
233 |
+
Correct\s*answer:\s*(?P<correct_option>[A-D])\n
|
234 |
+
Difficulty\s*level:\s*(?P<difficulty>\w+)
|
235 |
+
""", re.VERBOSE | re.DOTALL)
|
236 |
+
|
237 |
+
match = pattern.search(generated_content)
|
238 |
+
if match:
|
239 |
+
theme = match.group("theme").strip()
|
240 |
+
theme = theme.lower()
|
241 |
+
|
242 |
+
add_theme_if_not_exists(theme)
|
243 |
+
question = match.group("question").strip()
|
244 |
+
# Combine question and options into one string.
|
245 |
+
question_options = (
|
246 |
+
f"{question}\n"
|
247 |
+
f"A) {match.group('option_a').strip()}\n"
|
248 |
+
f"B) {match.group('option_b').strip()}\n"
|
249 |
+
f"C) {match.group('option_c').strip()}\n"
|
250 |
+
f"D) {match.group('option_d').strip()}"
|
251 |
+
)
|
252 |
+
correct_option = match.group("correct_option").strip()
|
253 |
+
difficulty = match.group("difficulty").strip()
|
254 |
+
|
255 |
+
|
256 |
+
print("Storing data in database...")
|
257 |
+
print(f"Theme: {theme}")
|
258 |
+
print("Question and Options:")
|
259 |
+
print(question_options)
|
260 |
+
print(f"Correct Option: {correct_option}")
|
261 |
+
print(f"Difficulty: {difficulty}")
|
262 |
+
print("-" * 40)
|
263 |
+
db_response = add_question_to_db(theme, question_options, correct_option, difficulty)
|
264 |
+
|
265 |
+
if db_response == "Duplicate":
|
266 |
+
print("Duplicate detected! Regenerating...")
|
267 |
+
continue
|
268 |
+
elif db_response is None:
|
269 |
+
return jsonify({"error": "Database insertion failed"}), 500
|
270 |
+
else:
|
271 |
+
return jsonify({
|
272 |
+
"message": "Question added successfully!",
|
273 |
+
"theme": theme,
|
274 |
+
"question": question,
|
275 |
+
|
276 |
+
"difficulty": difficulty
|
277 |
+
})
|
278 |
+
|
279 |
+
else:
|
280 |
+
print("Parsing failed for the following content:")
|
281 |
+
print(generated_content)
|
282 |
+
|
283 |
+
@app.route('/add_theme', methods=['POST'])
|
284 |
+
@jwt_required()
|
285 |
+
def add_theme():
|
286 |
+
logging.info('Add theme route accessed')
|
287 |
+
try:
|
288 |
+
|
289 |
+
if request.method == 'POST':
|
290 |
+
theme = request.form.get('theme')
|
291 |
+
theme = theme.lower()
|
292 |
+
|
293 |
+
if not theme:
|
294 |
+
return jsonify({"error": "Theme is required"}), 400
|
295 |
+
|
296 |
+
theme_added = add_theme_if_not_exists(theme)
|
297 |
+
|
298 |
+
if theme_added:
|
299 |
+
return jsonify({"message": "Theme added successfully"}), 201
|
300 |
+
else:
|
301 |
+
return jsonify({"message": "Theme already exists"}), 200
|
302 |
+
|
303 |
+
except Exception as e:
|
304 |
+
return jsonify({"error": str(e)}), 500
|
305 |
+
|
306 |
+
@app.route('/beat_the_ai', methods=['POST'])
|
307 |
+
@jwt_required()
|
308 |
+
def beat_the_ai_endpoint():
|
309 |
+
score = request.form.get('score')
|
310 |
+
theme = request.form.get('theme')
|
311 |
+
theme = theme.lower()
|
312 |
+
|
313 |
+
if score > 100:
|
314 |
+
return jsonify({"message": "You Won!"}), 200
|
315 |
+
|
316 |
+
add_theme_if_not_exists(theme)
|
317 |
+
while True:
|
318 |
+
generated_content = beat_the_ai(theme, score)
|
319 |
+
pattern = re.compile(r"""
|
320 |
+
(?:Theme:\s*(?P<theme>.*?)\n)? # Theme (optional)
|
321 |
+
Question:\s*(?P<question>.*?)\n
|
322 |
+
A\)\s*(?P<option_a>.*?)\n
|
323 |
+
B\)\s*(?P<option_b>.*?)\n
|
324 |
+
C\)\s*(?P<option_c>.*?)\n
|
325 |
+
D\)\s*(?P<option_d>.*?)\n
|
326 |
+
Correct\s*answer:\s*(?P<correct_option>[A-D])\s*\n?
|
327 |
+
Difficulty\s*level:\s*(?P<difficulty>\w+)\s*$
|
328 |
+
""", re.VERBOSE | re.DOTALL)
|
329 |
+
|
330 |
+
match = pattern.search(generated_content)
|
331 |
+
if match:
|
332 |
+
theme = match.group("theme").strip()
|
333 |
+
theme = theme.lower()
|
334 |
+
question = match.group("question").strip()
|
335 |
+
# Combine question and options into one string.
|
336 |
+
question_options = (
|
337 |
+
f"{question}\n"
|
338 |
+
f"A) {match.group('option_a').strip()}\n"
|
339 |
+
f"B) {match.group('option_b').strip()}\n"
|
340 |
+
f"C) {match.group('option_c').strip()}\n"
|
341 |
+
f"D) {match.group('option_d').strip()}"
|
342 |
+
)
|
343 |
+
correct_option = match.group("correct_option").strip()
|
344 |
+
difficulty = match.group("difficulty").strip()
|
345 |
+
|
346 |
+
# Replace the following print statements with your actual database storage logic.
|
347 |
+
print("Storing data in database...")
|
348 |
+
print(f"Theme: {theme}")
|
349 |
+
print("Question and Options:")
|
350 |
+
print(question_options)
|
351 |
+
print(f"Correct Option: {correct_option}")
|
352 |
+
print(f"Difficulty: {difficulty}")
|
353 |
+
print("-" * 40)
|
354 |
+
db_response = add_question_to_db(theme, question_options, correct_option, difficulty)
|
355 |
+
|
356 |
+
if db_response == "Duplicate":
|
357 |
+
print("Duplicate detected! Regenerating...")
|
358 |
+
continue
|
359 |
+
elif db_response is None:
|
360 |
+
return jsonify({"error": "Database insertion failed"}), 500
|
361 |
+
else:
|
362 |
+
return jsonify({
|
363 |
+
"message": "Question added successfully!",
|
364 |
+
"theme": theme,
|
365 |
+
"question": question_options,
|
366 |
+
"Correct Option": correct_option,
|
367 |
+
"difficulty": difficulty
|
368 |
+
})
|
369 |
+
|
370 |
+
else:
|
371 |
+
print("Parsing failed for the following content:")
|
372 |
+
print(generated_content)
|
373 |
+
|
374 |
+
|
375 |
+
|
376 |
+
@app.route('/edit_profile', methods=['POST'])
|
377 |
+
@jwt_required()
|
378 |
+
def edit_profile_endpoint():
|
379 |
+
logging.info("Edit profile route accessed")
|
380 |
+
if request.method == 'POST':
|
381 |
+
first_name = request.form.get('first_name')
|
382 |
+
last_name = request.form.get('last_name')
|
383 |
+
organisation = request.form.get('organisation')
|
384 |
+
industry = request.form.get('industry')
|
385 |
+
bio = request.form.get('bio')
|
386 |
+
|
387 |
+
|
388 |
+
|
389 |
+
if not first_name or not last_name:
|
390 |
+
return jsonify({"error": "First name and last name is required"}), 400
|
391 |
+
response = profile_added_db(first_name, last_name, organisation, industry, bio)
|
392 |
+
|
393 |
+
return response
|
394 |
+
|
395 |
+
@app.route('/view_profile', methods=['GET'])
|
396 |
+
@jwt_required()
|
397 |
+
def view_profile_endpoint():
|
398 |
+
logging.info("View profile route accessed")
|
399 |
+
response = view_profile_db()
|
400 |
+
return response
|
401 |
+
|
402 |
+
@app.route('/view_quiz_score', methods=['POST'])
|
403 |
+
@jwt_required()
|
404 |
+
def view_quiz_score_endpoint():
|
405 |
+
logging.info("View quiz score route accessed")
|
406 |
+
|
407 |
+
user_id = get_jwt_identity()
|
408 |
+
quiz_id = request.form.get('quiz_id')
|
409 |
+
theme = request.form.get('theme')
|
410 |
+
theme = theme.lower()
|
411 |
+
|
412 |
+
if not user_id or not quiz_id or not theme:
|
413 |
+
return jsonify({"error": "user_id, quiz_id, and theme are required"}), 400
|
414 |
+
|
415 |
+
try:
|
416 |
+
connection = get_db_connection()
|
417 |
+
if not connection:
|
418 |
+
return jsonify({"error": "Database connection failed."}), 500
|
419 |
+
|
420 |
+
cursor = connection.cursor(dictionary=True)
|
421 |
+
|
422 |
+
|
423 |
+
cursor.execute("SELECT theme_quiz_table FROM themes WHERE theme = %s", (theme,))
|
424 |
+
theme_entry = cursor.fetchone()
|
425 |
+
|
426 |
+
if not theme_entry:
|
427 |
+
logging.warning(f"Theme '{theme}' does not exist in the themes table.")
|
428 |
+
return jsonify({"error": "Invalid theme or theme not supported."}), 404
|
429 |
+
|
430 |
+
theme_quiz_table = theme_entry['theme_quiz_table']
|
431 |
+
logging.info(f"Theme '{theme}' maps to table '{theme_quiz_table}'.")
|
432 |
+
|
433 |
+
response = view_quiz_score_db(user_id, theme_quiz_table, theme, quiz_id)
|
434 |
+
return response
|
435 |
+
|
436 |
+
except mysql.connector.Error as err:
|
437 |
+
logging.error("MySQL Error: %s", err)
|
438 |
+
return jsonify({"error": "Database operation failed."}), 500
|
439 |
+
|
440 |
+
except Exception as e:
|
441 |
+
logging.error("Unexpected error: %s", str(e))
|
442 |
+
return jsonify({"error": "An unexpected error occurred."}), 500
|
443 |
+
|
444 |
+
finally:
|
445 |
+
if 'cursor' in locals():
|
446 |
+
cursor.close()
|
447 |
+
if 'connection' in locals():
|
448 |
+
connection.close()
|
449 |
+
logging.info("Database connection closed.")
|
450 |
+
|
451 |
+
|
452 |
+
@app.route('/recent_quizzes', methods=['GET'])
|
453 |
+
@jwt_required()
|
454 |
+
def recent_quizzes_endpoint():
|
455 |
+
logging.info("Recent quizzes route accessed")
|
456 |
+
|
457 |
+
user_id = get_jwt_identity()
|
458 |
+
if not user_id:
|
459 |
+
return jsonify({"error": "User authentication required"}), 401
|
460 |
+
|
461 |
+
response = get_recent_quizzes_db(user_id)
|
462 |
+
|
463 |
+
if "error" in response:
|
464 |
+
return jsonify(response), 500
|
465 |
+
if "message" in response:
|
466 |
+
return jsonify(response), 404
|
467 |
+
|
468 |
+
return jsonify(response), 200
|
469 |
+
|
470 |
+
|
471 |
+
@app.route('/fetch_quiz_for_theme', methods=['GET'])
|
472 |
+
@jwt_required()
|
473 |
+
def fetch_quiz_endpoint():
|
474 |
+
logging.info("Fetch quiz route accessed")
|
475 |
+
|
476 |
+
user_id = get_jwt_identity()
|
477 |
+
if not user_id:
|
478 |
+
return jsonify({"error": "User authentication required"}), 401
|
479 |
+
|
480 |
+
theme = request.form.get('theme')
|
481 |
+
if not theme:
|
482 |
+
return jsonify({"error": "Theme is required"}), 400
|
483 |
+
|
484 |
+
result = fetch_quiz_for_theme_db(user_id, theme)
|
485 |
+
|
486 |
+
if "error" in result:
|
487 |
+
return jsonify(result), 500 if "Database" in result["error"] else 404
|
488 |
+
|
489 |
+
return jsonify(result), 200
|
490 |
+
|
491 |
+
@app.route('/get_all_themes', methods=['GET'])
|
492 |
+
@jwt_required()
|
493 |
+
def get_all_themes():
|
494 |
+
logging.info("Get all themes route accessed")
|
495 |
+
|
496 |
+
try:
|
497 |
+
connection = get_db_connection()
|
498 |
+
cursor = connection.cursor(dictionary=True)
|
499 |
+
|
500 |
+
cursor.execute("SELECT theme FROM themes")
|
501 |
+
themes = cursor.fetchall()
|
502 |
+
|
503 |
+
# Return only theme names in a list
|
504 |
+
theme_list = [row['theme'] for row in themes]
|
505 |
+
return jsonify({"themes": theme_list}), 200
|
506 |
+
|
507 |
+
except mysql.connector.Error as err:
|
508 |
+
logging.error("Database error while fetching themes: %s", err)
|
509 |
+
return jsonify({"error": "Database error occurred"}), 500
|
510 |
+
|
511 |
+
except Exception as e:
|
512 |
+
logging.error("Unexpected error while fetching themes: %s", str(e))
|
513 |
+
return jsonify({"error": "Unexpected error occurred"}), 500
|
514 |
+
|
515 |
+
finally:
|
516 |
+
if 'cursor' in locals():
|
517 |
+
cursor.close()
|
518 |
+
if 'connection' in locals():
|
519 |
+
connection.close()
|
520 |
+
logging.info("Database connection closed")
|
521 |
+
|
522 |
+
|
523 |
+
@app.route('/view_attempted_quiz', methods=['POST'])
|
524 |
+
@jwt_required()
|
525 |
+
def view_attempted_quiz_endpoint():
|
526 |
+
logging.info("View attempted quiz route accessed")
|
527 |
+
|
528 |
+
user_id = get_jwt_identity()
|
529 |
+
quiz_id = request.form.get('quiz_id')
|
530 |
+
theme = request.form.get('theme')
|
531 |
+
theme = theme.lower()
|
532 |
+
|
533 |
+
if not user_id or not quiz_id or not theme:
|
534 |
+
return jsonify({"error": "user_id, quiz_id, and theme are required"}), 400
|
535 |
+
|
536 |
+
try:
|
537 |
+
connection = get_db_connection()
|
538 |
+
if not connection:
|
539 |
+
return jsonify({"error": "Database connection failed."}), 500
|
540 |
+
|
541 |
+
cursor = connection.cursor(dictionary=True)
|
542 |
+
|
543 |
+
cursor.execute("SELECT theme_quiz_table, theme_bank FROM themes WHERE theme = %s", (theme,))
|
544 |
+
result = cursor.fetchone()
|
545 |
+
if not result:
|
546 |
+
return jsonify({"error": "Invalid theme."}), 404
|
547 |
+
|
548 |
+
theme_quiz_table = result['theme_quiz_table']
|
549 |
+
theme_bank = result['theme_bank']
|
550 |
+
|
551 |
+
cursor.execute(f"""
|
552 |
+
SELECT questions_by_llm, correct_options_llm
|
553 |
+
FROM {theme_quiz_table}
|
554 |
+
WHERE quiz_id = %s
|
555 |
+
""", (quiz_id,))
|
556 |
+
quiz_data = cursor.fetchone()
|
557 |
+
if not quiz_data:
|
558 |
+
return jsonify({"error": "Quiz not found."}), 404
|
559 |
+
|
560 |
+
question_ids = json.loads(quiz_data['questions_by_llm'])
|
561 |
+
correct_options = json.loads(quiz_data['correct_options_llm'])
|
562 |
+
|
563 |
+
cursor.execute("""
|
564 |
+
SELECT user_response, score, time_taken
|
565 |
+
FROM quiz_response
|
566 |
+
WHERE quiz_id = %s AND user_id_attempt = %s AND theme = %s
|
567 |
+
""", (quiz_id, user_id, theme))
|
568 |
+
response_data = cursor.fetchone()
|
569 |
+
if not response_data:
|
570 |
+
return jsonify({"error": "User has not attempted this quiz."}), 404
|
571 |
+
|
572 |
+
user_responses = json.loads(response_data['user_response'])
|
573 |
+
|
574 |
+
question_details = []
|
575 |
+
for ques_id in question_ids:
|
576 |
+
cursor.execute(f"""
|
577 |
+
SELECT ques_id, question_by_llm, correct_option_llm
|
578 |
+
FROM {theme_bank}
|
579 |
+
WHERE ques_id = %s
|
580 |
+
""", (ques_id,))
|
581 |
+
q = cursor.fetchone()
|
582 |
+
if not q:
|
583 |
+
continue
|
584 |
+
question_details.append({
|
585 |
+
"ques_id": q['ques_id'],
|
586 |
+
"question": q['question_by_llm'],
|
587 |
+
"correct_option": correct_options[i],
|
588 |
+
"user_response": user_responses[i]
|
589 |
+
})
|
590 |
+
|
591 |
+
return jsonify({
|
592 |
+
"quiz_id": quiz_id,
|
593 |
+
"theme": theme,
|
594 |
+
"score": response_data['score'],
|
595 |
+
"time_taken": response_data['time_taken'],
|
596 |
+
"questions": question_details
|
597 |
+
}), 200
|
598 |
+
|
599 |
+
except mysql.connector.Error as err:
|
600 |
+
logging.error("MySQL Error: %s", err)
|
601 |
+
return jsonify({"error": "Database operation failed."}), 500
|
602 |
+
|
603 |
+
except Exception as e:
|
604 |
+
logging.error("Unexpected error: %s", str(e))
|
605 |
+
return jsonify({"error": "An unexpected error occurred."}), 500
|
606 |
+
|
607 |
+
finally:
|
608 |
+
if 'cursor' in locals():
|
609 |
+
cursor.close()
|
610 |
+
if 'connection' in locals():
|
611 |
+
connection.close()
|
612 |
+
logging.info("Database connection closed.")
|
613 |
+
|
614 |
+
|
615 |
+
@app.route('/get_attempted_quizzes', methods=['POST'])
|
616 |
+
@jwt_required()
|
617 |
+
def get_attempted_quizzes():
|
618 |
+
user_id = get_jwt_identity()
|
619 |
+
theme = request.form.get('theme')
|
620 |
+
theme = theme.lower()
|
621 |
+
if not theme:
|
622 |
+
return jsonify({"error": "Theme is required"}), 400
|
623 |
+
|
624 |
+
try:
|
625 |
+
conn = get_db_connection()
|
626 |
+
cursor = conn.cursor()
|
627 |
+
cursor.execute("""
|
628 |
+
SELECT DISTINCT quiz_id
|
629 |
+
FROM quiz_response
|
630 |
+
WHERE user_id_attempt = %s AND theme = %s
|
631 |
+
""", (user_id, theme))
|
632 |
+
ids = [row[0] for row in cursor.fetchall()]
|
633 |
+
return jsonify({"theme": theme, "quiz_ids": ids}), 200
|
634 |
+
|
635 |
+
except mysql.connector.Error as err:
|
636 |
+
logging.error("DB error: %s", err)
|
637 |
+
return jsonify({"error": "Database error"}), 500
|
638 |
+
finally:
|
639 |
+
cursor.close()
|
640 |
+
conn.close()
|
641 |
+
|
642 |
+
|
643 |
+
@app.route('/user_theme_stats', methods=['POST'])
|
644 |
+
@jwt_required()
|
645 |
+
def user_theme_stats():
|
646 |
+
logging.info("User theme stats route accessed")
|
647 |
+
|
648 |
+
theme = request.form.get('theme')
|
649 |
+
if not theme:
|
650 |
+
return jsonify({"error": "Theme is required"}), 400
|
651 |
+
|
652 |
+
theme = theme.lower()
|
653 |
+
user_id = get_jwt_identity()
|
654 |
+
|
655 |
+
try:
|
656 |
+
connection = get_db_connection()
|
657 |
+
cursor = connection.cursor(dictionary=True)
|
658 |
+
|
659 |
+
cursor.execute("""
|
660 |
+
SELECT quiz_id, score, time_taken
|
661 |
+
FROM quiz_response
|
662 |
+
WHERE theme = %s AND user_id_attempt = %s
|
663 |
+
""", (theme, user_id))
|
664 |
+
responses = cursor.fetchall()
|
665 |
+
|
666 |
+
if not responses:
|
667 |
+
return jsonify({
|
668 |
+
"message": f"No stats found for user in theme '{theme}'",
|
669 |
+
"total_score": "0/0",
|
670 |
+
"accuracy": "0%",
|
671 |
+
"avg_time_seconds": 0
|
672 |
+
}), 200
|
673 |
+
|
674 |
+
quiz_ids = [str(r['quiz_id']) for r in responses]
|
675 |
+
quiz_ids_str = ','.join(quiz_ids)
|
676 |
+
theme_quiz_table = f"theme_{theme}"
|
677 |
+
|
678 |
+
cursor.execute(f"""
|
679 |
+
SELECT quiz_id, num_questions
|
680 |
+
FROM {theme_quiz_table}
|
681 |
+
WHERE quiz_id IN ({quiz_ids_str})
|
682 |
+
""")
|
683 |
+
quiz_data = cursor.fetchall()
|
684 |
+
quiz_question_map = {q['quiz_id']: q['num_questions'] for q in quiz_data}
|
685 |
+
|
686 |
+
total_score = 0
|
687 |
+
total_possible = 0
|
688 |
+
total_time = 0
|
689 |
+
valid_time_count = 0
|
690 |
+
|
691 |
+
for r in responses:
|
692 |
+
quiz_id = r['quiz_id']
|
693 |
+
score = r['score']
|
694 |
+
time_str = r['time_taken']
|
695 |
+
|
696 |
+
num_q = quiz_question_map.get(quiz_id)
|
697 |
+
if num_q:
|
698 |
+
total_score += score
|
699 |
+
total_possible += num_q
|
700 |
+
|
701 |
+
if time_str:
|
702 |
+
try:
|
703 |
+
if ':' in time_str:
|
704 |
+
parts = list(map(int, time_str.strip().split(':')))
|
705 |
+
if len(parts) == 3:
|
706 |
+
hrs, mins, secs = parts
|
707 |
+
seconds = hrs * 3600 + mins * 60 + secs
|
708 |
+
elif len(parts) == 2:
|
709 |
+
mins, secs = parts
|
710 |
+
seconds = mins * 60 + secs
|
711 |
+
else:
|
712 |
+
seconds = int(parts[0])
|
713 |
+
|
714 |
+
else:
|
715 |
+
seconds = int(float(time_str))
|
716 |
+
|
717 |
+
total_time += seconds
|
718 |
+
valid_time_count += 1
|
719 |
+
except Exception as e:
|
720 |
+
logging.warning(f"Failed to parse time: {time_str}, error: {e}")
|
721 |
+
|
722 |
+
avg_time_seconds = round(total_time / valid_time_count, 2) if valid_time_count else 0
|
723 |
+
accuracy = round((total_score / total_possible) * 100, 2) if total_possible else 0
|
724 |
+
|
725 |
+
return jsonify({
|
726 |
+
"total_score": f"{total_score}/{total_possible}",
|
727 |
+
"avg_time_seconds": avg_time_seconds,
|
728 |
+
"accuracy": f"{accuracy}%"
|
729 |
+
})
|
730 |
+
|
731 |
+
except mysql.connector.Error as e:
|
732 |
+
logging.error("MySQL error: %s", e)
|
733 |
+
return jsonify({"error": "Database error"}), 500
|
734 |
+
|
735 |
+
except Exception as e:
|
736 |
+
logging.error("Unexpected error: %s", e)
|
737 |
+
return jsonify({"error": "Unexpected error"}), 500
|
738 |
+
|
739 |
+
finally:
|
740 |
+
if 'cursor' in locals():
|
741 |
+
cursor.close()
|
742 |
+
if 'connection' in locals():
|
743 |
+
connection.close()
|
744 |
+
|
745 |
+
|
746 |
+
@app.route('/share_attempted_quiz', methods= ['GET'])
|
747 |
+
@jwt_required()
|
748 |
+
def share_quiz_endpoint():
|
749 |
+
user_id = get_jwt_identity()
|
750 |
+
if not user_id:
|
751 |
+
return jsonify({"error": "User authentication required"}), 401
|
752 |
+
|
753 |
+
quiz_id = request.form.get('quiz_id')
|
754 |
+
if not quiz_id:
|
755 |
+
return jsonify({"error": "Quiz ID is required"}), 400
|
756 |
+
|
757 |
+
|
758 |
+
quiz_data = get_quiz_details_db(user_id, quiz_id)
|
759 |
+
|
760 |
+
if "error" in quiz_data:
|
761 |
+
return jsonify(quiz_data), 500
|
762 |
+
if "message" in quiz_data:
|
763 |
+
return jsonify(quiz_data), 404
|
764 |
+
|
765 |
+
|
766 |
+
query_string = urllib.parse.urlencode(quiz_data)
|
767 |
+
|
768 |
+
|
769 |
+
shareable_link = f"http://example.com/shared_quiz?{query_string}"
|
770 |
+
|
771 |
+
return jsonify({"shareable_link": shareable_link}), 200
|
772 |
+
|
773 |
+
if __name__ == '__main__':
|
774 |
+
# logging.info("Starting the Flask application...")
|
775 |
+
# thread = threading.Thread(target=continuous_generation, daemon=True)
|
776 |
+
# thread.start()
|
777 |
+
logging.info("Starting the application...")
|
778 |
+
app.run(host='0.0.0.0',port=5000,debug=True)
|
779 |
+
|