EnglishStudyHelper / admin_module.py
amauricunha's picture
Upload 6 files
625c7c9 verified
Raw
History Blame Contribute Delete
21.6 kB
# admin_module.py - Administrative interface for English Helper
import os
import json
import hashlib
from datetime import datetime, timedelta
from functools import wraps
from flask import session, request, jsonify, redirect, url_for
import sqlite3
from database import get_db_connection
import logging
logger = logging.getLogger(__name__)
class AdminManager:
def __init__(self):
self.admin_credentials = self._load_admin_credentials()
self.token_costs = {
'groq': {'input': 0.00000059, 'output': 0.00000079}, # per token
'gemini': {'input': 0.00000125, 'output': 0.00000375} # per token
}
def _load_admin_credentials(self):
"""Load admin credentials from environment variables (Hugging Face secrets)"""
try:
# Try to load from Hugging Face secrets format
admin_user = os.environ.get('ADMIN_USERNAME', 'admin')
admin_pass = os.environ.get('ADMIN_PASSWORD', 'admin123')
# For security, hash the password
admin_pass_hash = hashlib.sha256(admin_pass.encode()).hexdigest()
return {
'username': admin_user,
'password_hash': admin_pass_hash,
'original_password': admin_pass # Store for initial comparison
}
except Exception as e:
logger.error(f"Error loading admin credentials: {e}")
# Fallback credentials
return {
'username': 'admin',
'password_hash': hashlib.sha256('admin123'.encode()).hexdigest(),
'original_password': 'admin123'
}
def authenticate_admin(self, username, password):
"""Authenticate admin user"""
try:
if username != self.admin_credentials['username']:
return False
# Check password hash
password_hash = hashlib.sha256(password.encode()).hexdigest()
return password_hash == self.admin_credentials['password_hash']
except Exception as e:
logger.error(f"Admin authentication error: {e}")
return False
def is_admin_logged_in(self):
"""Check if admin is logged in"""
authenticated = session.get('admin_authenticated', False)
username = session.get('admin_username')
login_time = session.get('admin_login_time')
# Debug logging
logger.info(f"Admin auth check: authenticated={authenticated}, username={username}, login_time={login_time}")
# Check if session has expired (24 hours)
if authenticated and login_time:
try:
login_datetime = datetime.fromisoformat(login_time)
if datetime.now() - login_datetime > timedelta(hours=24):
logger.info("Admin session expired, logging out")
self.logout_admin()
return False
except Exception as e:
logger.error(f"Error checking session expiry: {e}")
return authenticated
def login_admin(self, username, password):
"""Admin login"""
if self.authenticate_admin(username, password):
session['admin_authenticated'] = True
session['admin_username'] = username
session['admin_login_time'] = datetime.now().isoformat()
session.permanent = True # Make session permanent
logger.info(f"Admin login successful: {username}")
return True
else:
logger.warning(f"Admin login failed for username: {username}")
return False
def logout_admin(self):
"""Admin logout"""
session.pop('admin_authenticated', None)
session.pop('admin_username', None)
session.pop('admin_login_time', None)
def get_system_stats(self):
"""Get comprehensive system statistics"""
try:
conn = get_db_connection()
cursor = conn.cursor()
# User statistics
cursor.execute("SELECT COUNT(*) FROM users")
total_users = cursor.fetchone()[0]
cursor.execute("SELECT COUNT(*) FROM users WHERE created_at > datetime('now', '-7 days')")
new_users_week = cursor.fetchone()[0]
cursor.execute("SELECT COUNT(*) FROM users WHERE created_at > datetime('now', '-1 day')")
new_users_today = cursor.fetchone()[0]
# Activity statistics
cursor.execute("SELECT COUNT(*) FROM study_sessions")
total_sessions = cursor.fetchone()[0]
cursor.execute("SELECT COUNT(*) FROM flashcards")
total_flashcards = cursor.fetchone()[0]
cursor.execute("SELECT COUNT(*) FROM user_articles")
total_articles = cursor.fetchone()[0]
cursor.execute("SELECT COUNT(*) FROM study_plans")
total_study_plans = cursor.fetchone()[0]
# Token usage statistics
cursor.execute("SELECT SUM(tokens_used), COUNT(*) FROM token_usage")
token_stats = cursor.fetchone()
total_tokens = token_stats[0] if token_stats[0] else 0
total_api_calls = token_stats[1] if token_stats[1] else 0
# Calculate estimated costs
estimated_cost = self._calculate_estimated_cost(cursor)
# Recent activity
cursor.execute("""
SELECT u.email, s.created_at, s.activity_type
FROM study_sessions s
JOIN users u ON s.user_id = u.id
ORDER BY s.created_at DESC
LIMIT 10
""")
recent_activity = cursor.fetchall()
conn.close()
return {
'users': {
'total': total_users,
'new_week': new_users_week,
'new_today': new_users_today
},
'activity': {
'total_sessions': total_sessions,
'total_flashcards': total_flashcards,
'total_articles': total_articles,
'total_study_plans': total_study_plans
},
'api_usage': {
'total_tokens': total_tokens,
'total_calls': total_api_calls,
'estimated_cost': estimated_cost
},
'recent_activity': [
{
'user': activity[0],
'timestamp': activity[1],
'activity': activity[2]
} for activity in recent_activity
]
}
except Exception as e:
logger.error(f"Error getting system stats: {e}")
return {}
def _calculate_estimated_cost(self, cursor):
"""Calculate estimated API costs"""
try:
cursor.execute("""
SELECT api_provider, SUM(input_tokens), SUM(output_tokens)
FROM token_usage
GROUP BY api_provider
""")
usage_by_provider = cursor.fetchall()
total_cost = 0
for provider, input_tokens, output_tokens in usage_by_provider:
if provider in self.token_costs:
costs = self.token_costs[provider]
total_cost += (input_tokens * costs['input']) + (output_tokens * costs['output'])
return round(total_cost, 4)
except:
return 0
def get_all_users(self, page=1, per_page=20):
"""Get paginated list of all users"""
try:
conn = get_db_connection()
cursor = conn.cursor()
offset = (page - 1) * per_page
cursor.execute("""
SELECT u.id, u.email, u.created_at, u.email_confirmed, u.last_login,
COUNT(DISTINCT s.id) as session_count,
COUNT(DISTINCT f.id) as flashcard_count,
COUNT(DISTINCT a.id) as article_count
FROM users u
LEFT JOIN study_sessions s ON u.id = s.user_id
LEFT JOIN flashcards f ON u.id = f.user_id
LEFT JOIN user_articles a ON u.id = a.user_id
GROUP BY u.id
ORDER BY u.created_at DESC
LIMIT ? OFFSET ?
""", (per_page, offset))
users = cursor.fetchall()
# Get total count
cursor.execute("SELECT COUNT(*) FROM users")
total_users = cursor.fetchone()[0]
conn.close()
return {
'users': [
{
'id': user[0],
'email': user[1],
'created_at': user[2],
'email_confirmed': bool(user[3]),
'last_login': user[4],
'session_count': user[5],
'flashcard_count': user[6],
'article_count': user[7]
} for user in users
],
'total': total_users,
'page': page,
'per_page': per_page,
'total_pages': (total_users + per_page - 1) // per_page
}
except Exception as e:
logger.error(f"Error getting users: {e}")
return {'users': [], 'total': 0}
def delete_user(self, user_id):
"""Delete a user and all associated data"""
try:
conn = get_db_connection()
cursor = conn.cursor()
# Delete in order to respect foreign key constraints
tables = [
'study_plan_activities', 'study_plans', 'user_analytics',
'content_recommendations', 'user_interests', 'user_articles',
'study_sessions', 'flashcards', 'user_settings', 'users'
]
for table in tables:
cursor.execute(f"DELETE FROM {table} WHERE user_id = ?", (user_id,))
conn.commit()
conn.close()
return True
except Exception as e:
logger.error(f"Error deleting user {user_id}: {e}")
return False
def get_user_details(self, user_id):
"""Get detailed information about a specific user"""
try:
conn = get_db_connection()
cursor = conn.cursor()
# Basic user info
cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))
user = cursor.fetchone()
if not user:
return None
# User settings
cursor.execute("SELECT * FROM user_settings WHERE user_id = ?", (user_id,))
settings = cursor.fetchone()
# Recent activity
cursor.execute("""
SELECT activity_type, created_at, duration_minutes
FROM study_sessions
WHERE user_id = ?
ORDER BY created_at DESC
LIMIT 20
""", (user_id,))
recent_sessions = cursor.fetchall()
# Token usage
cursor.execute("""
SELECT api_provider, SUM(input_tokens), SUM(output_tokens), COUNT(*)
FROM token_usage
WHERE user_id = ?
GROUP BY api_provider
""", (user_id,))
token_usage = cursor.fetchall()
conn.close()
return {
'user': {
'id': user[0],
'email': user[1],
'created_at': user[2],
'email_confirmed': bool(user[3]),
'last_login': user[4]
},
'settings': dict(zip([col[0] for col in cursor.description], settings)) if settings else {},
'recent_sessions': [
{
'activity': session[0],
'timestamp': session[1],
'duration': session[2]
} for session in recent_sessions
],
'token_usage': [
{
'provider': usage[0],
'input_tokens': usage[1],
'output_tokens': usage[2],
'calls': usage[3]
} for usage in token_usage
]
}
except Exception as e:
logger.error(f"Error getting user details for {user_id}: {e}")
return None
def record_token_usage(self, user_id, api_provider, input_tokens, output_tokens, operation_type):
"""Record token usage for cost tracking"""
try:
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute("""
INSERT INTO token_usage
(user_id, api_provider, input_tokens, output_tokens, operation_type, created_at)
VALUES (?, ?, ?, ?, ?, ?)
""", (user_id, api_provider, input_tokens, output_tokens, operation_type, datetime.now().isoformat()))
conn.commit()
conn.close()
return True
except Exception as e:
logger.error(f"Error recording token usage: {e}")
return False
def get_database_schema(self):
"""Get database schema information"""
try:
conn = get_db_connection()
cursor = conn.cursor()
# Get all tables
cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
tables = cursor.fetchall()
schema_info = {}
for table in tables:
table_name = table[0]
# Get table info
cursor.execute(f"PRAGMA table_info({table_name})")
columns = cursor.fetchall()
# Get row count
cursor.execute(f"SELECT COUNT(*) FROM {table_name}")
row_count = cursor.fetchone()[0]
schema_info[table_name] = {
'columns': [
{
'name': col[1],
'type': col[2],
'not_null': bool(col[3]),
'primary_key': bool(col[5])
} for col in columns
],
'row_count': row_count
}
conn.close()
return schema_info
except Exception as e:
logger.error(f"Error getting database schema: {e}")
return {}
def get_system_health(self):
"""Get system health metrics"""
try:
import psutil
import os
# Memory usage
memory = psutil.virtual_memory()
# Disk usage
disk = psutil.disk_usage('/')
# Database size
db_path = 'data/englishhelper.db'
db_size = os.path.getsize(db_path) if os.path.exists(db_path) else 0
# Recent error logs (would implement proper logging)
recent_errors = self._get_recent_errors()
return {
'memory': {
'total': memory.total,
'used': memory.used,
'available': memory.available,
'percent': memory.percent
},
'disk': {
'total': disk.total,
'used': disk.used,
'free': disk.free,
'percent': disk.percent
},
'database': {
'size_bytes': db_size,
'size_mb': round(db_size / 1024 / 1024, 2)
},
'recent_errors': recent_errors,
'uptime': self._get_uptime()
}
except Exception as e:
logger.error(f"Error getting system health: {e}")
return {}
def _get_recent_errors(self):
"""Get recent error logs (simplified)"""
try:
# This would typically read from log files
# For now, return sample data
return [
{
'timestamp': '2024-10-11 14:30:00',
'level': 'ERROR',
'message': 'API rate limit exceeded for user 123',
'module': 'groq_client'
},
{
'timestamp': '2024-10-11 13:45:00',
'level': 'WARNING',
'message': 'High memory usage detected',
'module': 'system_monitor'
}
]
except:
return []
def _get_uptime(self):
"""Get system uptime"""
try:
import psutil
boot_time = psutil.boot_time()
uptime_seconds = datetime.now().timestamp() - boot_time
days = int(uptime_seconds // 86400)
hours = int((uptime_seconds % 86400) // 3600)
minutes = int((uptime_seconds % 3600) // 60)
return f"{days}d {hours}h {minutes}m"
except:
return "Unknown"
def check_system_alerts(self):
"""Check for system alerts and warnings"""
alerts = []
try:
# Check token usage limits
conn = get_db_connection()
cursor = conn.cursor()
# Check daily token usage
cursor.execute("""
SELECT SUM(tokens_used)
FROM token_usage
WHERE date(created_at) = date('now')
""")
daily_tokens = cursor.fetchone()[0] or 0
if daily_tokens > 100000: # Alert threshold
alerts.append({
'type': 'warning',
'message': f'High daily token usage: {daily_tokens:,} tokens',
'action': 'Monitor API costs'
})
# Check error rates
cursor.execute("""
SELECT COUNT(*) FROM token_usage
WHERE created_at > datetime('now', '-1 hour')
""")
hourly_requests = cursor.fetchone()[0] or 0
if hourly_requests > 500: # High load threshold
alerts.append({
'type': 'info',
'message': f'High API request rate: {hourly_requests} requests/hour',
'action': 'Monitor performance'
})
# Check database size
health = self.get_system_health()
if health.get('database', {}).get('size_mb', 0) > 100: # 100MB threshold
alerts.append({
'type': 'warning',
'message': f'Large database size: {health["database"]["size_mb"]}MB',
'action': 'Consider archiving old data'
})
conn.close()
return alerts
except Exception as e:
logger.error(f"Error checking system alerts: {e}")
return []
# Decorator for admin-only routes
def admin_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
is_authenticated = admin_manager.is_admin_logged_in()
# Enhanced logging for debugging
from flask import session, request
logger.info(f"Admin required check for {f.__name__}: authenticated={is_authenticated}")
logger.info(f"Session keys: {list(session.keys())}")
logger.info(f"Request URL: {request.url}")
if not is_authenticated:
logger.warning(f"Admin authentication failed for {f.__name__}")
return jsonify({
'error': 'Admin authentication required',
'authenticated': False,
'endpoint': f.__name__
}), 401
logger.info(f"Admin access granted to {f.__name__}")
return f(*args, **kwargs)
return decorated_function
# Global admin manager instance
admin_manager = AdminManager()