raj-tomar001 commited on
Commit
2a70dbb
·
verified ·
1 Parent(s): ed363f8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +779 -779
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
+