init commit

This commit is contained in:
2025-09-11 07:34:50 +09:00
commit 5ddc540f9e
36 changed files with 5103 additions and 0 deletions

374
src/database/database.py Normal file
View File

@@ -0,0 +1,374 @@
import aiosqlite
import logging
from typing import List, Dict, Optional, Tuple, Union
import json
class DatabaseManager:
def __init__(self, db_path: str):
self.db_path = db_path
async def init_database(self):
"""Инициализация базы данных и создание таблиц"""
async with aiosqlite.connect(self.db_path) as db:
# Таблица пользователей
await db.execute("""
CREATE TABLE IF NOT EXISTS users (
user_id INTEGER PRIMARY KEY,
username TEXT,
first_name TEXT,
last_name TEXT,
language_code TEXT DEFAULT 'ru',
registration_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_guest BOOLEAN DEFAULT TRUE,
total_questions INTEGER DEFAULT 0,
correct_answers INTEGER DEFAULT 0
)
""")
# Таблица тестов
await db.execute("""
CREATE TABLE IF NOT EXISTS tests (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT,
level INTEGER,
category TEXT,
csv_file TEXT,
created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_active BOOLEAN DEFAULT TRUE
)
""")
# Таблица вопросов
await db.execute("""
CREATE TABLE IF NOT EXISTS questions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
test_id INTEGER,
question TEXT NOT NULL,
option1 TEXT NOT NULL,
option2 TEXT NOT NULL,
option3 TEXT NOT NULL,
option4 TEXT NOT NULL,
correct_answer INTEGER NOT NULL,
FOREIGN KEY (test_id) REFERENCES tests (id)
)
""")
# Таблица результатов
await db.execute("""
CREATE TABLE IF NOT EXISTS results (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER,
test_id INTEGER,
mode TEXT, -- 'guest' or 'test'
questions_asked INTEGER,
correct_answers INTEGER,
total_time INTEGER,
start_time TIMESTAMP,
end_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
score REAL,
FOREIGN KEY (user_id) REFERENCES users (user_id),
FOREIGN KEY (test_id) REFERENCES tests (id)
)
""")
# Таблица активных сессий
await db.execute("""
CREATE TABLE IF NOT EXISTS active_sessions (
user_id INTEGER PRIMARY KEY,
test_id INTEGER,
current_question INTEGER DEFAULT 0,
correct_count INTEGER DEFAULT 0,
questions_data TEXT, -- JSON с вопросами сессии
start_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
mode TEXT,
FOREIGN KEY (user_id) REFERENCES users (user_id),
FOREIGN KEY (test_id) REFERENCES tests (id)
)
""")
await db.commit()
logging.info("Database initialized successfully")
async def register_user(self, user_id: int, username: Optional[str] = None,
first_name: Optional[str] = None, last_name: Optional[str] = None,
language_code: str = 'ru', is_guest: bool = True) -> bool:
"""Регистрация нового пользователя"""
try:
async with aiosqlite.connect(self.db_path) as db:
await db.execute("""
INSERT OR REPLACE INTO users
(user_id, username, first_name, last_name, language_code, is_guest)
VALUES (?, ?, ?, ?, ?, ?)
""", (user_id, username, first_name, last_name, language_code, is_guest))
await db.commit()
return True
except Exception as e:
logging.error(f"Error registering user {user_id}: {e}")
return False
async def get_user(self, user_id: int) -> Optional[Dict]:
"""Получение данных пользователя"""
try:
async with aiosqlite.connect(self.db_path) as db:
cursor = await db.execute(
"SELECT * FROM users WHERE user_id = ?", (user_id,)
)
row = await cursor.fetchone()
if row:
columns = [description[0] for description in cursor.description]
return dict(zip(columns, row))
return None
except Exception as e:
logging.error(f"Error getting user {user_id}: {e}")
return None
async def add_test(self, name: str, description: str, level: int,
category: str, csv_file: str) -> Optional[int]:
"""Добавление нового теста"""
try:
async with aiosqlite.connect(self.db_path) as db:
cursor = await db.execute("""
INSERT INTO tests (name, description, level, category, csv_file)
VALUES (?, ?, ?, ?, ?)
""", (name, description, level, category, csv_file))
await db.commit()
return cursor.lastrowid
except Exception as e:
logging.error(f"Error adding test: {e}")
return None
async def get_tests_by_category(self, category: Optional[str] = None) -> List[Dict]:
"""Получение тестов по категории"""
try:
async with aiosqlite.connect(self.db_path) as db:
if category:
cursor = await db.execute(
"SELECT * FROM tests WHERE category = ? AND is_active = TRUE ORDER BY level",
(category,)
)
else:
cursor = await db.execute(
"SELECT * FROM tests WHERE is_active = TRUE ORDER BY category, level"
)
rows = await cursor.fetchall()
columns = [description[0] for description in cursor.description]
return [dict(zip(columns, row)) for row in rows]
except Exception as e:
logging.error(f"Error getting tests: {e}")
return []
async def add_questions_to_test(self, test_id: int, questions: List[Dict]) -> bool:
"""Добавление вопросов к тесту"""
try:
async with aiosqlite.connect(self.db_path) as db:
for q in questions:
await db.execute("""
INSERT INTO questions
(test_id, question, option1, option2, option3, option4, correct_answer)
VALUES (?, ?, ?, ?, ?, ?, ?)
""", (test_id, q['question'], q['option1'], q['option2'],
q['option3'], q['option4'], q['correct_answer']))
await db.commit()
return True
except Exception as e:
logging.error(f"Error adding questions to test {test_id}: {e}")
return False
async def get_random_questions(self, test_id: int, count: int = 10) -> List[Dict]:
"""Получение случайных вопросов из теста"""
try:
async with aiosqlite.connect(self.db_path) as db:
cursor = await db.execute("""
SELECT * FROM questions WHERE test_id = ?
ORDER BY RANDOM() LIMIT ?
""", (test_id, count))
rows = await cursor.fetchall()
columns = [description[0] for description in cursor.description]
return [dict(zip(columns, row)) for row in rows]
except Exception as e:
logging.error(f"Error getting random questions: {e}")
return []
async def start_session(self, user_id: int, test_id: int,
questions: List[Dict], mode: str) -> bool:
"""Начало новой сессии викторины"""
try:
async with aiosqlite.connect(self.db_path) as db:
questions_json = json.dumps(questions)
await db.execute("""
INSERT OR REPLACE INTO active_sessions
(user_id, test_id, questions_data, mode)
VALUES (?, ?, ?, ?)
""", (user_id, test_id, questions_json, mode))
await db.commit()
return True
except Exception as e:
logging.error(f"Error starting session: {e}")
return False
async def get_active_session(self, user_id: int) -> Optional[Dict]:
"""Получение активной сессии пользователя"""
try:
async with aiosqlite.connect(self.db_path) as db:
cursor = await db.execute(
"SELECT * FROM active_sessions WHERE user_id = ?", (user_id,)
)
row = await cursor.fetchone()
if row:
columns = [description[0] for description in cursor.description]
session = dict(zip(columns, row))
session['questions_data'] = json.loads(session['questions_data'])
return session
return None
except Exception as e:
logging.error(f"Error getting active session: {e}")
return None
async def update_session_progress(self, user_id: int, question_num: int,
correct_count: int) -> bool:
"""Обновление прогресса сессии"""
try:
async with aiosqlite.connect(self.db_path) as db:
await db.execute("""
UPDATE active_sessions
SET current_question = ?, correct_count = ?
WHERE user_id = ?
""", (question_num, correct_count, user_id))
await db.commit()
return True
except Exception as e:
logging.error(f"Error updating session progress: {e}")
return False
async def update_session_questions(self, user_id: int, questions_data: list) -> bool:
"""Обновление данных вопросов в сессии (например, после перемешивания)"""
try:
async with aiosqlite.connect(self.db_path) as db:
questions_json = json.dumps(questions_data, ensure_ascii=False)
await db.execute("""
UPDATE active_sessions
SET questions_data = ?
WHERE user_id = ?
""", (questions_json, user_id))
await db.commit()
return True
except Exception as e:
logging.error(f"Error updating session questions: {e}")
return False
async def finish_session(self, user_id: int, score: float) -> bool:
"""Завершение сессии и сохранение результатов"""
try:
async with aiosqlite.connect(self.db_path) as db:
# Получаем данные сессии
session = await self.get_active_session(user_id)
if not session:
return False
# Сохраняем результат
await db.execute("""
INSERT INTO results
(user_id, test_id, mode, questions_asked, correct_answers, score)
VALUES (?, ?, ?, ?, ?, ?)
""", (user_id, session['test_id'], session['mode'],
len(session['questions_data']), session['correct_count'], score))
# Обновляем статистику пользователя
await db.execute("""
UPDATE users
SET total_questions = total_questions + ?,
correct_answers = correct_answers + ?
WHERE user_id = ?
""", (len(session['questions_data']), session['correct_count'], user_id))
# Удаляем активную сессию
await db.execute("DELETE FROM active_sessions WHERE user_id = ?", (user_id,))
await db.commit()
return True
except Exception as e:
logging.error(f"Error finishing session: {e}")
return False
async def get_user_stats(self, user_id: int) -> Optional[Dict]:
"""Получение статистики пользователя"""
try:
async with aiosqlite.connect(self.db_path) as db:
cursor = await db.execute("""
SELECT
u.total_questions,
u.correct_answers,
COUNT(r.id) as sessions_completed,
MAX(r.score) as best_score,
AVG(r.score) as average_score,
COUNT(CASE WHEN r.mode = 'guest' THEN 1 END) as guest_sessions,
COUNT(CASE WHEN r.mode = 'test' THEN 1 END) as test_sessions
FROM users u
LEFT JOIN results r ON u.user_id = r.user_id
WHERE u.user_id = ?
GROUP BY u.user_id
""", (user_id,))
row = await cursor.fetchone()
if row:
columns = [description[0] for description in cursor.description]
stats = dict(zip(columns, row))
return stats
return None
except Exception as e:
logging.error(f"Error getting user stats: {e}")
return None
async def get_recent_results(self, user_id: int, limit: int = 5) -> List[Dict]:
"""Получение последних результатов пользователя"""
try:
async with aiosqlite.connect(self.db_path) as db:
cursor = await db.execute("""
SELECT
r.mode,
r.questions_asked,
r.correct_answers,
r.score,
r.end_time,
t.name as test_name,
t.level
FROM results r
LEFT JOIN tests t ON r.test_id = t.id
WHERE r.user_id = ?
ORDER BY r.end_time DESC
LIMIT ?
""", (user_id, limit))
rows = await cursor.fetchall()
columns = [description[0] for description in cursor.description]
return [dict(zip(columns, row)) for row in rows]
except Exception as e:
logging.error(f"Error getting recent results: {e}")
return []
async def get_category_stats(self, user_id: int) -> List[Dict]:
"""Получение статистики по категориям"""
try:
async with aiosqlite.connect(self.db_path) as db:
cursor = await db.execute("""
SELECT
t.category,
COUNT(r.id) as attempts,
AVG(r.score) as avg_score,
MAX(r.score) as best_score,
SUM(r.questions_asked) as total_questions,
SUM(r.correct_answers) as correct_answers
FROM results r
JOIN tests t ON r.test_id = t.id
WHERE r.user_id = ?
GROUP BY t.category
ORDER BY attempts DESC
""", (user_id,))
rows = await cursor.fetchall()
columns = [description[0] for description in cursor.description]
return [dict(zip(columns, row)) for row in rows]
except Exception as e:
logging.error(f"Error getting category stats: {e}")
return []