amauricunha commited on
Commit
0516fcd
·
verified ·
1 Parent(s): 74229f5

Delete database.py

Browse files
Files changed (1) hide show
  1. database.py +0 -744
database.py DELETED
@@ -1,744 +0,0 @@
1
- # database.py
2
- import sqlite3
3
- import os
4
- import hashlib
5
- import secrets
6
- import smtplib
7
- from email.mime.text import MIMEText
8
- from email.mime.multipart import MIMEMultipart
9
- from datetime import datetime, timedelta
10
- from functools import wraps
11
- from flask import g, session, jsonify, request
12
- import logging
13
-
14
- # Configure logging
15
- logging.basicConfig(level=logging.INFO)
16
- logger = logging.getLogger(__name__)
17
-
18
- # Database configuration
19
- DATABASE_PATH = 'users.db'
20
-
21
- def get_db():
22
- """Get database connection"""
23
- if 'db' not in g:
24
- g.db = sqlite3.connect(DATABASE_PATH)
25
- g.db.row_factory = sqlite3.Row
26
- return g.db
27
-
28
- def close_db(e=None):
29
- """Close database connection"""
30
- db = g.pop('db', None)
31
- if db is not None:
32
- db.close()
33
-
34
- def init_db():
35
- """Initialize database with required tables"""
36
- try:
37
- db = sqlite3.connect(DATABASE_PATH)
38
- db.executescript('''
39
- -- Users table
40
- CREATE TABLE IF NOT EXISTS users (
41
- id INTEGER PRIMARY KEY AUTOINCREMENT,
42
- email TEXT UNIQUE NOT NULL,
43
- password_hash TEXT NOT NULL,
44
- salt TEXT NOT NULL,
45
- is_confirmed BOOLEAN DEFAULT FALSE,
46
- confirmation_token TEXT,
47
- reset_token TEXT,
48
- reset_token_expires TIMESTAMP,
49
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
50
- last_login TIMESTAMP,
51
- is_active BOOLEAN DEFAULT TRUE
52
- );
53
-
54
- -- User flashcards table
55
- CREATE TABLE IF NOT EXISTS user_flashcards (
56
- id INTEGER PRIMARY KEY AUTOINCREMENT,
57
- user_id INTEGER NOT NULL,
58
- term TEXT NOT NULL,
59
- translation TEXT,
60
- context_sentence TEXT,
61
- gapped_sentence TEXT,
62
- definition TEXT,
63
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
64
- study_count INTEGER DEFAULT 0,
65
- last_studied TIMESTAMP,
66
- difficulty_level INTEGER DEFAULT 1,
67
- FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
68
- );
69
-
70
- -- User study sessions table
71
- CREATE TABLE IF NOT EXISTS study_sessions (
72
- id INTEGER PRIMARY KEY AUTOINCREMENT,
73
- user_id INTEGER NOT NULL,
74
- session_type TEXT NOT NULL, -- 'flashcard', 'conversation', 'activity'
75
- duration_minutes INTEGER,
76
- cards_studied INTEGER DEFAULT 0,
77
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
78
- FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
79
- );
80
-
81
- -- User settings table
82
- CREATE TABLE IF NOT EXISTS user_settings (
83
- id INTEGER PRIMARY KEY AUTOINCREMENT,
84
- user_id INTEGER UNIQUE NOT NULL,
85
- preferred_model TEXT DEFAULT 'gemini:gemini-2.5-flash-latest',
86
- context_focus TEXT DEFAULT 'General/Social',
87
- voice_accent TEXT DEFAULT 'co.uk',
88
- daily_goal INTEGER DEFAULT 10,
89
- notification_enabled BOOLEAN DEFAULT TRUE,
90
- -- New settings for advanced features
91
- english_level TEXT DEFAULT 'B1', -- A1, A2, B1, B2, C1, C2
92
- study_goals TEXT, -- JSON: objectives like "business english", "technical vocabulary"
93
- preferred_content_types TEXT DEFAULT 'articles,videos', -- comma separated
94
- content_difficulty TEXT DEFAULT 'adaptive', -- 'easy', 'medium', 'hard', 'adaptive'
95
- study_schedule TEXT, -- JSON: preferred days/times
96
- auto_recommendations BOOLEAN DEFAULT TRUE,
97
- content_sources TEXT DEFAULT 'news,tech,business', -- preferred content sources
98
- FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
99
- );
100
-
101
- -- User articles/content table
102
- CREATE TABLE IF NOT EXISTS user_articles (
103
- id INTEGER PRIMARY KEY AUTOINCREMENT,
104
- user_id INTEGER NOT NULL,
105
- title TEXT NOT NULL,
106
- content TEXT NOT NULL,
107
- source_url TEXT,
108
- source_type TEXT DEFAULT 'manual', -- 'manual', 'web_search', 'recommended'
109
- category TEXT, -- user interest category
110
- difficulty_level TEXT, -- estimated difficulty
111
- word_count INTEGER,
112
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
113
- last_accessed TIMESTAMP,
114
- is_favorite BOOLEAN DEFAULT FALSE,
115
- study_progress REAL DEFAULT 0.0, -- 0.0 to 1.0
116
- FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
117
- );
118
-
119
- -- User interests/preferences table
120
- CREATE TABLE IF NOT EXISTS user_interests (
121
- id INTEGER PRIMARY KEY AUTOINCREMENT,
122
- user_id INTEGER NOT NULL,
123
- interest_category TEXT NOT NULL,
124
- weight REAL DEFAULT 1.0, -- importance weight
125
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
126
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
127
- FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
128
- UNIQUE(user_id, interest_category)
129
- );
130
-
131
- -- Content recommendations table
132
- CREATE TABLE IF NOT EXISTS content_recommendations (
133
- id INTEGER PRIMARY KEY AUTOINCREMENT,
134
- user_id INTEGER NOT NULL,
135
- article_id INTEGER,
136
- recommendation_reason TEXT,
137
- relevance_score REAL,
138
- recommended_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
139
- viewed BOOLEAN DEFAULT FALSE,
140
- accepted BOOLEAN DEFAULT FALSE,
141
- FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
142
- FOREIGN KEY (article_id) REFERENCES user_articles (id) ON DELETE CASCADE
143
- );
144
-
145
- -- Study plans table
146
- CREATE TABLE IF NOT EXISTS study_plans (
147
- id INTEGER PRIMARY KEY AUTOINCREMENT,
148
- user_id INTEGER NOT NULL,
149
- plan_name TEXT NOT NULL,
150
- target_level TEXT, -- A1, A2, B1, B2, C1, C2
151
- current_level TEXT,
152
- objectives TEXT, -- JSON string with objectives
153
- weekly_hours INTEGER DEFAULT 5,
154
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
155
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
156
- is_active BOOLEAN DEFAULT TRUE,
157
- completion_percentage REAL DEFAULT 0.0,
158
- FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
159
- );
160
-
161
- -- Study plan activities table
162
- CREATE TABLE IF NOT EXISTS study_plan_activities (
163
- id INTEGER PRIMARY KEY AUTOINCREMENT,
164
- plan_id INTEGER NOT NULL,
165
- activity_type TEXT NOT NULL, -- 'reading', 'flashcards', 'conversation', 'writing'
166
- content_reference TEXT, -- reference to article, flashcard set, etc.
167
- scheduled_date DATE,
168
- estimated_duration INTEGER, -- minutes
169
- actual_duration INTEGER,
170
- completed BOOLEAN DEFAULT FALSE,
171
- completed_at TIMESTAMP,
172
- difficulty_rating INTEGER, -- 1-5 user rating
173
- notes TEXT,
174
- FOREIGN KEY (plan_id) REFERENCES study_plans (id) ON DELETE CASCADE
175
- );
176
-
177
- -- User analytics table
178
- CREATE TABLE IF NOT EXISTS user_analytics (
179
- id INTEGER PRIMARY KEY AUTOINCREMENT,
180
- user_id INTEGER NOT NULL,
181
- metric_name TEXT NOT NULL,
182
- metric_value REAL NOT NULL,
183
- metric_date DATE NOT NULL,
184
- context_data TEXT, -- JSON with additional context
185
- FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
186
- );
187
-
188
- -- Token usage tracking table for admin
189
- CREATE TABLE IF NOT EXISTS token_usage (
190
- id INTEGER PRIMARY KEY AUTOINCREMENT,
191
- user_id INTEGER,
192
- api_provider TEXT NOT NULL, -- 'groq', 'gemini', etc.
193
- input_tokens INTEGER DEFAULT 0,
194
- output_tokens INTEGER DEFAULT 0,
195
- operation_type TEXT, -- 'conversation', 'content_analysis', 'recommendation', etc.
196
- tokens_used INTEGER GENERATED ALWAYS AS (input_tokens + output_tokens) STORED,
197
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
198
- FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
199
- );
200
-
201
- -- Create indexes for better performance
202
- CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
203
- CREATE INDEX IF NOT EXISTS idx_users_confirmation_token ON users(confirmation_token);
204
- CREATE INDEX IF NOT EXISTS idx_users_reset_token ON users(reset_token);
205
- CREATE INDEX IF NOT EXISTS idx_flashcards_user_id ON user_flashcards(user_id);
206
- CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON study_sessions(user_id);
207
- CREATE INDEX IF NOT EXISTS idx_settings_user_id ON user_settings(user_id);
208
- CREATE INDEX IF NOT EXISTS idx_articles_user_id ON user_articles(user_id);
209
- CREATE INDEX IF NOT EXISTS idx_articles_category ON user_articles(category);
210
- CREATE INDEX IF NOT EXISTS idx_interests_user_id ON user_interests(user_id);
211
- CREATE INDEX IF NOT EXISTS idx_recommendations_user_id ON content_recommendations(user_id);
212
- CREATE INDEX IF NOT EXISTS idx_study_plans_user_id ON study_plans(user_id);
213
- CREATE INDEX IF NOT EXISTS idx_plan_activities_plan_id ON study_plan_activities(plan_id);
214
- CREATE INDEX IF NOT EXISTS idx_analytics_user_date ON user_analytics(user_id, metric_date);
215
- CREATE INDEX IF NOT EXISTS idx_token_usage_user_id ON token_usage(user_id);
216
- CREATE INDEX IF NOT EXISTS idx_token_usage_provider ON token_usage(api_provider);
217
- CREATE INDEX IF NOT EXISTS idx_token_usage_date ON token_usage(created_at);
218
- ''')
219
- db.commit()
220
- db.close()
221
- logger.info("Database initialized successfully")
222
- return True
223
- except Exception as e:
224
- logger.error(f"Error initializing database: {e}")
225
- return False
226
-
227
- def hash_password(password, salt=None):
228
- """Hash password with salt"""
229
- if salt is None:
230
- salt = secrets.token_hex(32)
231
-
232
- password_hash = hashlib.pbkdf2_hmac(
233
- 'sha256',
234
- password.encode('utf-8'),
235
- salt.encode('utf-8'),
236
- 100000 # iterations
237
- )
238
- return password_hash.hex(), salt
239
-
240
- def verify_password(password, password_hash, salt):
241
- """Verify password against hash"""
242
- new_hash, _ = hash_password(password, salt)
243
- return new_hash == password_hash
244
-
245
- def generate_token():
246
- """Generate secure random token"""
247
- return secrets.token_urlsafe(32)
248
-
249
- def create_user(email, password):
250
- """Create new user account"""
251
- try:
252
- db = get_db()
253
-
254
- # Check if user already exists
255
- existing_user = db.execute(
256
- 'SELECT id FROM users WHERE email = ?', (email,)
257
- ).fetchone()
258
-
259
- if existing_user:
260
- return {'success': False, 'message': 'Email already registered'}
261
-
262
- # Hash password
263
- password_hash, salt = hash_password(password)
264
- confirmation_token = generate_token()
265
-
266
- # Insert user
267
- cursor = db.execute(
268
- '''INSERT INTO users (email, password_hash, salt, confirmation_token)
269
- VALUES (?, ?, ?, ?)''',
270
- (email, password_hash, salt, confirmation_token)
271
- )
272
- user_id = cursor.lastrowid
273
-
274
- # Create default user settings
275
- db.execute(
276
- '''INSERT INTO user_settings (user_id) VALUES (?)''',
277
- (user_id,)
278
- )
279
-
280
- db.commit()
281
-
282
- logger.info(f"User created: {email}")
283
- return {
284
- 'success': True,
285
- 'user_id': user_id,
286
- 'confirmation_token': confirmation_token,
287
- 'message': 'User created successfully'
288
- }
289
-
290
- except Exception as e:
291
- logger.error(f"Error creating user: {e}")
292
- return {'success': False, 'message': 'Internal server error'}
293
-
294
- def authenticate_user(email, password):
295
- """Authenticate user login"""
296
- try:
297
- db = get_db()
298
- user = db.execute(
299
- '''SELECT id, email, password_hash, salt, is_confirmed, is_active
300
- FROM users WHERE email = ?''', (email,)
301
- ).fetchone()
302
-
303
- if not user:
304
- return {'success': False, 'message': 'Invalid email or password'}
305
-
306
- if not user['is_active']:
307
- return {'success': False, 'message': 'Account is deactivated'}
308
-
309
- if not verify_password(password, user['password_hash'], user['salt']):
310
- return {'success': False, 'message': 'Invalid email or password'}
311
-
312
- if not user['is_confirmed']:
313
- return {'success': False, 'message': 'Please confirm your email before logging in'}
314
-
315
- # Update last login
316
- db.execute(
317
- 'UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = ?',
318
- (user['id'],)
319
- )
320
- db.commit()
321
-
322
- return {
323
- 'success': True,
324
- 'user_id': user['id'],
325
- 'email': user['email'],
326
- 'message': 'Login successful'
327
- }
328
-
329
- except Exception as e:
330
- logger.error(f"Error authenticating user: {e}")
331
- return {'success': False, 'message': 'Internal server error'}
332
-
333
- def confirm_email(token):
334
- """Confirm user email with token"""
335
- try:
336
- db = get_db()
337
- user = db.execute(
338
- 'SELECT id, email FROM users WHERE confirmation_token = ? AND is_confirmed = FALSE',
339
- (token,)
340
- ).fetchone()
341
-
342
- if not user:
343
- return {'success': False, 'message': 'Invalid or expired confirmation token'}
344
-
345
- db.execute(
346
- '''UPDATE users SET is_confirmed = TRUE, confirmation_token = NULL
347
- WHERE id = ?''',
348
- (user['id'],)
349
- )
350
- db.commit()
351
-
352
- logger.info(f"Email confirmed for user: {user['email']}")
353
- return {'success': True, 'message': 'Email confirmed successfully'}
354
-
355
- except Exception as e:
356
- logger.error(f"Error confirming email: {e}")
357
- return {'success': False, 'message': 'Internal server error'}
358
-
359
- def get_user_settings(user_id):
360
- """Get user settings"""
361
- try:
362
- db = get_db()
363
- settings = db.execute(
364
- '''SELECT preferred_model, context_focus, voice_accent, daily_goal, notification_enabled
365
- FROM user_settings WHERE user_id = ?''',
366
- (user_id,)
367
- ).fetchone()
368
-
369
- if settings:
370
- return dict(settings)
371
- return None
372
-
373
- except Exception as e:
374
- logger.error(f"Error getting user settings: {e}")
375
- return None
376
-
377
- def update_user_settings(user_id, settings):
378
- """Update user settings"""
379
- try:
380
- db = get_db()
381
- db.execute(
382
- '''UPDATE user_settings
383
- SET preferred_model = ?, context_focus = ?, voice_accent = ?,
384
- daily_goal = ?, notification_enabled = ?
385
- WHERE user_id = ?''',
386
- (settings.get('preferred_model'), settings.get('context_focus'),
387
- settings.get('voice_accent'), settings.get('daily_goal'),
388
- settings.get('notification_enabled'), user_id)
389
- )
390
- db.commit()
391
- return True
392
-
393
- except Exception as e:
394
- logger.error(f"Error updating user settings: {e}")
395
- return False
396
-
397
- def save_user_flashcard(user_id, flashcard_data):
398
- """Save flashcard to user's collection"""
399
- try:
400
- db = get_db()
401
- db.execute(
402
- '''INSERT INTO user_flashcards
403
- (user_id, term, translation, context_sentence, gapped_sentence, definition)
404
- VALUES (?, ?, ?, ?, ?, ?)''',
405
- (user_id, flashcard_data.get('term'), flashcard_data.get('translation'),
406
- flashcard_data.get('context_sentence'), flashcard_data.get('gapped_sentence'),
407
- flashcard_data.get('definition'))
408
- )
409
- db.commit()
410
- return True
411
-
412
- except Exception as e:
413
- logger.error(f"Error saving flashcard: {e}")
414
- return False
415
-
416
- def get_user_flashcards(user_id, limit=50):
417
- """Get user's flashcards"""
418
- try:
419
- db = get_db()
420
- flashcards = db.execute(
421
- '''SELECT * FROM user_flashcards
422
- WHERE user_id = ?
423
- ORDER BY created_at DESC
424
- LIMIT ?''',
425
- (user_id, limit)
426
- ).fetchall()
427
-
428
- return [dict(card) for card in flashcards]
429
-
430
- except Exception as e:
431
- logger.error(f"Error getting user flashcards: {e}")
432
- return []
433
-
434
- def record_study_session(user_id, session_type, duration_minutes=None, cards_studied=0):
435
- """Record a study session"""
436
- try:
437
- db = get_db()
438
- db.execute(
439
- '''INSERT INTO study_sessions (user_id, session_type, duration_minutes, cards_studied)
440
- VALUES (?, ?, ?, ?)''',
441
- (user_id, session_type, duration_minutes, cards_studied)
442
- )
443
- db.commit()
444
- return True
445
-
446
- except Exception as e:
447
- logger.error(f"Error recording study session: {e}")
448
- return False
449
-
450
- # Authentication decorators
451
- def login_required(f):
452
- """Decorator to require login"""
453
- @wraps(f)
454
- def decorated_function(*args, **kwargs):
455
- if 'user_id' not in session:
456
- return jsonify({'error': 'Authentication required'}), 401
457
- return f(*args, **kwargs)
458
- return decorated_function
459
-
460
- def get_current_user():
461
- """Get current logged in user"""
462
- if 'user_id' in session:
463
- try:
464
- db = get_db()
465
- user = db.execute(
466
- 'SELECT id, email, is_confirmed FROM users WHERE id = ? AND is_active = TRUE',
467
- (session['user_id'],)
468
- ).fetchone()
469
- return dict(user) if user else None
470
- except Exception as e:
471
- logger.error(f"Error getting current user: {e}")
472
- return None
473
- return None
474
-
475
- # Email functionality (for Hugging Face Spaces)
476
- def send_confirmation_email(email, token):
477
- """Send confirmation email (simplified for HF Spaces)"""
478
- try:
479
- # For Hugging Face Spaces, we'll use environment variables for SMTP
480
- smtp_server = os.environ.get('SMTP_SERVER', 'smtp.gmail.com')
481
- smtp_port = int(os.environ.get('SMTP_PORT', '587'))
482
- smtp_username = os.environ.get('SMTP_USERNAME')
483
- smtp_password = os.environ.get('SMTP_PASSWORD')
484
-
485
- if not all([smtp_username, smtp_password]):
486
- logger.warning("SMTP credentials not configured")
487
- return False
488
-
489
- # Create confirmation URL (will be updated with actual domain)
490
- base_url = os.environ.get('BASE_URL', 'http://localhost:7860')
491
- confirm_url = f"{base_url}/confirm-email?token={token}"
492
-
493
- # Create email
494
- msg = MIMEMultipart()
495
- msg['From'] = smtp_username
496
- msg['To'] = email
497
- msg['Subject'] = "Confirm your English Helper account"
498
-
499
- body = f"""
500
- <html>
501
- <body>
502
- <h2>Welcome to Dynamic English Study Studio!</h2>
503
- <p>Thank you for creating an account. Please click the link below to confirm your email address:</p>
504
- <p><a href="{confirm_url}" style="background-color: #4f46e5; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px;">Confirm Email</a></p>
505
- <p>If the button doesn't work, copy and paste this link into your browser:</p>
506
- <p>{confirm_url}</p>
507
- <p>This link will expire in 24 hours.</p>
508
- <p>If you didn't create this account, please ignore this email.</p>
509
- </body>
510
- </html>
511
- """
512
-
513
- msg.attach(MIMEText(body, 'html'))
514
-
515
- # Send email
516
- server = smtplib.SMTP(smtp_server, smtp_port)
517
- server.starttls()
518
- server.login(smtp_username, smtp_password)
519
- text = msg.as_string()
520
- server.sendmail(smtp_username, email, text)
521
- server.quit()
522
-
523
- logger.info(f"Confirmation email sent to {email}")
524
- return True
525
-
526
- except Exception as e:
527
- logger.error(f"Error sending confirmation email: {e}")
528
- return False
529
-
530
- # --- CONTENT CURATION FUNCTIONS ---
531
-
532
- def save_user_article(user_id, title, content, source_url=None, source_type='manual', category=None):
533
- """Save article/content for user"""
534
- try:
535
- db = get_db()
536
- word_count = len(content.split()) if content else 0
537
-
538
- cursor = db.execute(
539
- '''INSERT INTO user_articles
540
- (user_id, title, content, source_url, source_type, category, word_count)
541
- VALUES (?, ?, ?, ?, ?, ?, ?)''',
542
- (user_id, title, content, source_url, source_type, category, word_count)
543
- )
544
- article_id = cursor.lastrowid
545
- db.commit()
546
-
547
- logger.info(f"Article saved for user {user_id}: {title}")
548
- return {'success': True, 'article_id': article_id}
549
-
550
- except Exception as e:
551
- logger.error(f"Error saving article: {e}")
552
- return {'success': False, 'message': 'Failed to save article'}
553
-
554
- def get_user_articles(user_id, category=None, limit=50):
555
- """Get user's saved articles"""
556
- try:
557
- db = get_db()
558
-
559
- if category:
560
- articles = db.execute(
561
- '''SELECT * FROM user_articles
562
- WHERE user_id = ? AND category = ?
563
- ORDER BY created_at DESC LIMIT ?''',
564
- (user_id, category, limit)
565
- ).fetchall()
566
- else:
567
- articles = db.execute(
568
- '''SELECT * FROM user_articles
569
- WHERE user_id = ?
570
- ORDER BY created_at DESC LIMIT ?''',
571
- (user_id, limit)
572
- ).fetchall()
573
-
574
- return [dict(article) for article in articles]
575
-
576
- except Exception as e:
577
- logger.error(f"Error getting user articles: {e}")
578
- return []
579
-
580
- def update_user_interests(user_id, interests):
581
- """Update user's interests/categories"""
582
- try:
583
- db = get_db()
584
-
585
- # Clear existing interests
586
- db.execute('DELETE FROM user_interests WHERE user_id = ?', (user_id,))
587
-
588
- # Add new interests
589
- for interest, weight in interests.items():
590
- db.execute(
591
- '''INSERT INTO user_interests (user_id, interest_category, weight)
592
- VALUES (?, ?, ?)''',
593
- (user_id, interest, weight)
594
- )
595
-
596
- db.commit()
597
- return True
598
-
599
- except Exception as e:
600
- logger.error(f"Error updating user interests: {e}")
601
- return False
602
-
603
- def get_user_interests(user_id):
604
- """Get user's interests"""
605
- try:
606
- db = get_db()
607
- interests = db.execute(
608
- 'SELECT interest_category, weight FROM user_interests WHERE user_id = ?',
609
- (user_id,)
610
- ).fetchall()
611
-
612
- return {interest['interest_category']: interest['weight'] for interest in interests}
613
-
614
- except Exception as e:
615
- logger.error(f"Error getting user interests: {e}")
616
- return {}
617
-
618
- def create_study_plan(user_id, plan_name, target_level, current_level, objectives, weekly_hours=5):
619
- """Create new study plan"""
620
- try:
621
- db = get_db()
622
-
623
- cursor = db.execute(
624
- '''INSERT INTO study_plans
625
- (user_id, plan_name, target_level, current_level, objectives, weekly_hours)
626
- VALUES (?, ?, ?, ?, ?, ?)''',
627
- (user_id, plan_name, target_level, current_level, objectives, weekly_hours)
628
- )
629
- plan_id = cursor.lastrowid
630
- db.commit()
631
-
632
- logger.info(f"Study plan created for user {user_id}: {plan_name}")
633
- return {'success': True, 'plan_id': plan_id}
634
-
635
- except Exception as e:
636
- logger.error(f"Error creating study plan: {e}")
637
- return {'success': False, 'message': 'Failed to create study plan'}
638
-
639
- def get_user_study_plans(user_id):
640
- """Get user's study plans"""
641
- try:
642
- db = get_db()
643
- plans = db.execute(
644
- '''SELECT * FROM study_plans
645
- WHERE user_id = ?
646
- ORDER BY created_at DESC''',
647
- (user_id,)
648
- ).fetchall()
649
-
650
- return [dict(plan) for plan in plans]
651
-
652
- except Exception as e:
653
- logger.error(f"Error getting study plans: {e}")
654
- return []
655
-
656
- def add_study_activity(plan_id, activity_type, content_reference, scheduled_date, estimated_duration):
657
- """Add activity to study plan"""
658
- try:
659
- db = get_db()
660
-
661
- db.execute(
662
- '''INSERT INTO study_plan_activities
663
- (plan_id, activity_type, content_reference, scheduled_date, estimated_duration)
664
- VALUES (?, ?, ?, ?, ?)''',
665
- (plan_id, activity_type, content_reference, scheduled_date, estimated_duration)
666
- )
667
- db.commit()
668
- return True
669
-
670
- except Exception as e:
671
- logger.error(f"Error adding study activity: {e}")
672
- return False
673
-
674
- def get_study_activities(plan_id, date_range=None):
675
- """Get activities for study plan"""
676
- try:
677
- db = get_db()
678
-
679
- if date_range:
680
- start_date, end_date = date_range
681
- activities = db.execute(
682
- '''SELECT * FROM study_plan_activities
683
- WHERE plan_id = ? AND scheduled_date BETWEEN ? AND ?
684
- ORDER BY scheduled_date''',
685
- (plan_id, start_date, end_date)
686
- ).fetchall()
687
- else:
688
- activities = db.execute(
689
- '''SELECT * FROM study_plan_activities
690
- WHERE plan_id = ?
691
- ORDER BY scheduled_date''',
692
- (plan_id,)
693
- ).fetchall()
694
-
695
- return [dict(activity) for activity in activities]
696
-
697
- except Exception as e:
698
- logger.error(f"Error getting study activities: {e}")
699
- return []
700
-
701
- def record_analytics_metric(user_id, metric_name, metric_value, context_data=None):
702
- """Record analytics metric"""
703
- try:
704
- db = get_db()
705
-
706
- db.execute(
707
- '''INSERT INTO user_analytics (user_id, metric_name, metric_value, metric_date, context_data)
708
- VALUES (?, ?, ?, DATE('now'), ?)''',
709
- (user_id, metric_name, metric_value, context_data)
710
- )
711
- db.commit()
712
- return True
713
-
714
- except Exception as e:
715
- logger.error(f"Error recording analytics: {e}")
716
- return False
717
-
718
- def get_user_analytics(user_id, metric_name=None, days=30):
719
- """Get user analytics data"""
720
- try:
721
- db = get_db()
722
-
723
- if metric_name:
724
- analytics = db.execute(
725
- '''SELECT * FROM user_analytics
726
- WHERE user_id = ? AND metric_name = ?
727
- AND metric_date >= DATE('now', '-{} days')
728
- ORDER BY metric_date DESC'''.format(days),
729
- (user_id, metric_name)
730
- ).fetchall()
731
- else:
732
- analytics = db.execute(
733
- '''SELECT * FROM user_analytics
734
- WHERE user_id = ?
735
- AND metric_date >= DATE('now', '-{} days')
736
- ORDER BY metric_date DESC'''.format(days),
737
- (user_id,)
738
- ).fetchall()
739
-
740
- return [dict(metric) for metric in analytics]
741
-
742
- except Exception as e:
743
- logger.error(f"Error getting analytics: {e}")
744
- return []