diff --git a/bot/callbacks/categories.py b/bot/callbacks/categories.py index 5f4fd45..4e1e18d 100644 --- a/bot/callbacks/categories.py +++ b/bot/callbacks/categories.py @@ -3,7 +3,8 @@ from database.models import Categories from bot.handlers.start import startMarkup from bot.utils.crud_helpers import create_entity_markup -# TODO: Add Option for showing courses with specific tags. +# TODO: Add Option for showing parent categories when creating a new category. +# TODO: Show Some of the courses of a Category. def register(bot: TeleBot): diff --git a/bot/callbacks/teachers.py b/bot/callbacks/teachers.py index 9d9cc34..c71d1f0 100644 --- a/bot/callbacks/teachers.py +++ b/bot/callbacks/teachers.py @@ -41,7 +41,7 @@ def register(bot: TeleBot): @bot.callback_query_handler(func=lambda call: call.data.startswith('teacher_')) def show_teacher_details(call): teacher_id = call.data.split('_')[1] - teacher = Teachers.getTeachertById(teacher_id) + teacher = Teachers.getTeacherById(teacher_id) if teacher: details = format_teacher_info(teacher) @@ -101,7 +101,7 @@ def register(bot: TeleBot): @bot.callback_query_handler(func=lambda call: call.data.startswith('edit_teacher_')) def start_teacher_editing(call): teacher_id = call.data.split('_')[2] - teacher = Teachers.getTeachertById(teacher_id) + teacher = Teachers.getTeacherById(teacher_id) if not teacher: bot.send_message(call.message.chat.id, "Teacher not found.") diff --git a/bot/handlers/tags.py b/bot/handlers/tags.py index be6042a..29d22d5 100644 --- a/bot/handlers/tags.py +++ b/bot/handlers/tags.py @@ -1,11 +1,11 @@ import logging -from telebot import types +from telebot import types, TeleBot from database.models import Tags logger = logging.getLogger(__name__) -def register(bot): +def register(bot: TeleBot): @bot.message_handler(func=lambda message: message.text == "Show Tags") def get_tags(message): try: @@ -25,7 +25,11 @@ def register(bot): bot.send_message( message.chat.id, "Here is the data:", reply_markup=markup) else: - bot.reply_to(message, "No data found.") + markup = types.InlineKeyboardMarkup(row_width=2) + markup.add(types.InlineKeyboardButton( + "➕ Create New Tag", callback_data="create_tag")) + + bot.reply_to(message, "No data found.", markup=markup) except Exception as e: logger.error(f"Error in 'Show Tags' handler: {e}") bot.reply_to(message, "Sorry, an error occurred.") diff --git a/database/models.py b/database/models.py index dc713c8..40eda7b 100644 --- a/database/models.py +++ b/database/models.py @@ -4,8 +4,14 @@ import database.connection as connection logger = logging.getLogger(__name__) -class Students: - def getAllStudents(): +class BaseRepository: + """Base class for all repository classes""" + + table_name = None # Should be Overrided in subclasses + + @classmethod + def _execute_query(cls, query_func, operation_name): + """Execute a SELECT query""" conn = None try: conn = connection.get_db_connection() @@ -13,40 +19,21 @@ class Students: return None cursor = conn.cursor() - cursor.execute("SELECT id,username FROM students") - result = cursor.fetchall() + result = query_func(cursor) cursor.close() + return result - return result if result else None except Exception as e: - logger.error(f"Database query error in getAllStudents: {e}") + logger.error( + f"Database error in {cls.__name__}.{operation_name}: {e}") return None finally: if conn: connection.release_db_connection(conn) - def getStudentById(student_id): - conn = None - try: - conn = connection.get_db_connection() - if not conn: - return None - - cursor = conn.cursor() - cursor.execute( - "SELECT id,username, name, created_at, email, phone_number, last_seen, is_verfied, birthday FROM students WHERE id = %s", (student_id,)) - result = cursor.fetchall() - cursor.close() - - return result[0] if result else None - except Exception as e: - logger.error(f"Database query error in getStudentById: {e}") - return None - finally: - if conn: - connection.release_db_connection(conn) - - def createStudent(name, email, phone_number, password, username, birthday): + @classmethod + def _execute_mutation(cls, mutation_func, operation_name): + """Execute an INSERT, UPDATE, or DELETE""" conn = None try: conn = connection.get_db_connection() @@ -54,612 +41,298 @@ class Students: return False cursor = conn.cursor() + mutation_func(cursor, conn) + conn.commit() + cursor.close() + return True + + except Exception as e: + logger.error( + f"Database error in {cls.__name__}.{operation_name}: {e}") + return False + finally: + if conn: + connection.release_db_connection(conn) + + +class Students(BaseRepository): + table_name = "students" + + @classmethod + def getAllStudents(cls): + def query(cursor): cursor.execute( - "INSERT INTO students (name, email, phone_number, hashed_password, username, birthday) VALUES (%s, %s, %s, %s, %s, %s)", + f"SELECT id,username FROM {cls.table_name}") + result = cursor.fetchall() + return result if result else None + + return cls._execute_query(query, "getAllStudents") + + @classmethod + def getStudentById(cls, student_id): + def query(cursor): + cursor.execute( + f"SELECT id,username, name, created_at, email, phone_number, last_seen, is_verfied, birthday FROM {cls.table_name} WHERE id = %s", (student_id,)) + result = cursor.fetchall() + return result[0] if result else None + + return cls._execute_query(query, "getStudentById") + + @classmethod + def createStudent(cls, name, email, phone_number, password, username, birthday): + def mutation(cursor, conn): + cursor.execute( + f"INSERT INTO {cls.table_name} (name, email, phone_number, hashed_password, username, birthday) VALUES (%s, %s, %s, %s, %s, %s)", (name, email, phone_number, password, - username, birthday) - ) - conn.commit() - cursor.close() + username, birthday)) + return cls._execute_mutation(mutation, "createStudent") - return True - except Exception as e: - logger.error(f"Database query error in createStudent: {e}") - return False - finally: - if conn: - connection.release_db_connection(conn) - - def updateStudent(student_id, **fields): + @classmethod + def updateStudent(cls, student_id, **fields): if not fields: - return False # nothing to update - - conn = None - try: - conn = connection.get_db_connection() - if not conn: - return False - - cursor = conn.cursor() - - # Build dynamic SET clause - columns = [] - values = [] - - for key, value in fields.items(): - columns.append(f"{key} = %s" if key != - 'password' else "hashed_password = %s") - values.append(value) - - values.append(student_id) - - query = f""" - UPDATE students - SET {', '.join(columns)} - WHERE id = %s - """ - - cursor.execute(query, tuple(values)) - conn.commit() - cursor.close() - - return True - - except Exception as e: - logger.error(f"Database query error in updateStudent: {e}") return False - finally: - if conn: - connection.release_db_connection(conn) - - def deleteStudent(student_id): - conn = None - try: - conn = connection.get_db_connection() - if not conn: - return False - - cursor = conn.cursor() + def mutation(cursor, conn): + columns = [(f"{key} = %s" if key != 'password' else "hashed_password = %s") + for key in fields.keys()] + values = list(fields.values()) + [student_id] cursor.execute( - "DELETE FROM students WHERE id = %s", (student_id,)) - conn.commit() - cursor.close() + f"UPDATE {cls.table_name} SET {', '.join(columns)} WHERE id = %s", + tuple(values) + ) - return True - except Exception as e: - logger.error(f"Database query error in deleteStudent: {e}") - return False - finally: - if conn: - connection.release_db_connection(conn) + return cls._execute_mutation(mutation, "updateStudent") + + @classmethod + def deleteStudent(cls, student_id): + def mutation(cursor, conn): + cursor.execute( + f"DELETE FROM {cls.table_name} WHERE id = %s", (student_id,)) + + return cls._execute_mutation(mutation, "deleteStudent") -class Teachers: - def getAllTeachers(): - conn = None - try: - conn = connection.get_db_connection() - if not conn: - return None +class Teachers(BaseRepository): + table_name = "teachers" - cursor = conn.cursor() - cursor.execute("SELECT id,username FROM teachers") + @classmethod + def getAllTeachers(cls): + def query(cursor): + cursor.execute( + f"SELECT id,username FROM {cls.table_name}") result = cursor.fetchall() - cursor.close() - return result if result else None - except Exception as e: - logger.error(f"Database query error in getAllTeachers: {e}") - return None - finally: - if conn: - connection.release_db_connection(conn) - def getTeachertById(teacher_id): - conn = None - try: - conn = connection.get_db_connection() - if not conn: - return None + return cls._execute_query(query, "getAllTeachers") - cursor = conn.cursor() + @classmethod + def getTeacherById(cls, teacher_id): + def query(cursor): cursor.execute( - "SELECT id, username, name, created_at, email, phone_number, last_seen, is_verfied, birthday, about_me, job_title FROM teachers WHERE id = %s", (teacher_id, )) + f"SELECT id, username, name, created_at, email, phone_number, last_seen, is_verfied, birthday, about_me, job_title FROM {cls.table_name} WHERE id = %s", (teacher_id, )) result = cursor.fetchall() - cursor.close() - return result[0] if result else None - except Exception as e: - logger.error(f"Database query error in getTeacherById: {e}") - return None - finally: - if conn: - connection.release_db_connection(conn) - def createTeacher(name, email, phone_number, password, username, birthday, about_me, job_title): - conn = None - try: - conn = connection.get_db_connection() - if not conn: - return False + return cls._execute_query(query, "getTeacherById") - cursor = conn.cursor() + @classmethod + def createTeacher(cls, name, email, phone_number, password, username, birthday, about_me, job_title): + def mutation(cursor, conn): cursor.execute( - "INSERT INTO teachers (name, email, phone_number, hashed_password, username, birthday, about_me, job_title) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)", + f"INSERT INTO {cls.table_name} (name, email, phone_number, hashed_password, username, birthday, about_me, job_title) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)", (name, email, phone_number, password, - username, birthday, about_me, job_title) - ) - conn.commit() - cursor.close() + username, birthday, about_me, job_title)) + return cls._execute_mutation(mutation, "createTeacher") - return True - except Exception as e: - logger.error(f"Database query error in createTeacher: {e}") - return False - finally: - if conn: - connection.release_db_connection(conn) - - def updateTeacher(teacher_id, **fields): + @classmethod + def updateTeacher(cls, teacher_id, **fields): if not fields: - return False # nothing to update - - conn = None - try: - conn = connection.get_db_connection() - if not conn: - return False - - cursor = conn.cursor() - - # Build dynamic SET clause - columns = [] - values = [] - - for key, value in fields.items(): - columns.append(f"{key} = %s" if key != - 'password' else "hashed_password = %s") - values.append(value) - - values.append(teacher_id) - - query = f""" - UPDATE teachers - SET {', '.join(columns)} - WHERE id = %s - """ - - cursor.execute(query, tuple(values)) - conn.commit() - cursor.close() - - return True - - except Exception as e: - logger.error(f"Database query error in updateTeacher: {e}") return False - finally: - if conn: - connection.release_db_connection(conn) - - def deleteTeacher(teacher_id): - conn = None - try: - conn = connection.get_db_connection() - if not conn: - return False - - cursor = conn.cursor() + def mutation(cursor, conn): + columns = [(f"{key} = %s" if key != 'password' else "hashed_password = %s") + for key in fields.keys()] + values = list(fields.values()) + [teacher_id] cursor.execute( - "DELETE FROM teachers WHERE id = %s", (teacher_id,)) - conn.commit() - cursor.close() + f"UPDATE {cls.table_name} SET {', '.join(columns)} WHERE id = %s", + tuple(values) + ) - return True - except Exception as e: - logger.error(f"Database query error in deleteTeacher: {e}") - return False - finally: - if conn: - connection.release_db_connection(conn) + return cls._execute_mutation(mutation, "updateTeacher") + + @classmethod + def deleteTeacher(cls, teacher_id): + def mutation(cursor, conn): + cursor.execute( + f"DELETE FROM {cls.table_name} WHERE id = %s", (teacher_id,)) + + return cls._execute_mutation(mutation, "deleteTeacher") -class Courses: - def getAllCourses(): - conn = None - try: - conn = connection.get_db_connection() - if not conn: - return None +class Courses(BaseRepository): + table_name = "courses" - cursor = conn.cursor() - cursor.execute("SELECT id, name FROM courses") + @classmethod + def getAllCourses(cls): + def query(cursor): + cursor.execute( + f"SELECT id, name FROM {cls.table_name}") result = cursor.fetchall() - cursor.close() - return result if result else None - except Exception as e: - logger.error(f"Database query error in getAllCourses: {e}") - return None - finally: - if conn: - connection.release_db_connection(conn) - def getCourseById(course_id): - conn = None - try: - conn = connection.get_db_connection() - if not conn: - return None + return cls._execute_query(query, "getAllCourses") - cursor = conn.cursor() + @classmethod + def getCourseById(cls, course_id): + def query(cursor): cursor.execute( - "SELECT id, name, created_at, teacher_id, updated_at, description, difficulty, language FROM courses WHERE id = %s", (course_id, )) + f"SELECT id, name, created_at, teacher_id, updated_at, description, difficulty, language FROM {cls.table_name} WHERE id = %s", (course_id, )) result = cursor.fetchall() - cursor.close() - return result[0] if result else None - except Exception as e: - logger.error(f"Database query error in getCourseById: {e}") - return None - finally: - if conn: - connection.release_db_connection(conn) - def createCourse(name, teacher_id, description, language, difficulty): - conn = None - try: - conn = connection.get_db_connection() - if not conn: - return False + return cls._execute_query(query, "getCourseById") - cursor = conn.cursor() + @classmethod + def createCourse(cls, name, teacher_id, description, language, difficulty): + def mutation(cursor, conn): cursor.execute( - "INSERT INTO courses (name, teacher_id, description, language, difficulty) VALUES (%s, %s, %s, %s, %s)", - (name, teacher_id, description, language, difficulty) - ) - conn.commit() - cursor.close() + f"INSERT INTO {cls.table_name} (name, teacher_id, description, language, difficulty) VALUES (%s, %s, %s, %s, %s)", + (name, teacher_id, description, language, difficulty)) + return cls._execute_mutation(mutation, "createCourse") - return True - except Exception as e: - logger.error(f"Database query error in createCourse: {e}") - return False - finally: - if conn: - connection.release_db_connection(conn) - - def updateCourse(course_id, **fields): + @classmethod + def updateCourse(cls, course_id, **fields): if not fields: - return False # nothing to update - - conn = None - try: - conn = connection.get_db_connection() - if not conn: - return False - - cursor = conn.cursor() - - # Build dynamic SET clause - columns = [] - values = [] - - for key, value in fields.items(): - columns.append(f"{key} = %s") - values.append(value) - - values.append(course_id) - - query = f""" - UPDATE courses - SET {', '.join(columns)} - WHERE id = %s - """ - - cursor.execute(query, tuple(values)) - conn.commit() - cursor.close() - - return True - - except Exception as e: - logger.error(f"Database query error in updateCourse: {e}") return False - finally: - if conn: - connection.release_db_connection(conn) - - def deleteCourse(course_id): - conn = None - try: - conn = connection.get_db_connection() - if not conn: - return False - - cursor = conn.cursor() + def mutation(cursor, conn): + columns = [f"{key} = %s" for key in fields.keys()] + values = list(fields.values()) + [course_id] cursor.execute( - "DELETE FROM courses WHERE id = %s", (course_id,)) - conn.commit() - cursor.close() + f"UPDATE {cls.table_name} SET {', '.join(columns)} WHERE id = %s", + tuple(values) + ) - return True - except Exception as e: - logger.error(f"Database query error in deleteCourse: {e}") - return False - finally: - if conn: - connection.release_db_connection(conn) + return cls._execute_mutation(mutation, "updateCourse") + + @classmethod + def deleteCourse(cls, course_id): + def mutation(cursor, conn): + cursor.execute( + f"DELETE FROM {cls.table_name} WHERE id = %s", (course_id,)) + + return cls._execute_mutation(mutation, "deleteCourse") -class Tags: - def getAllTags(): - conn = None - try: - conn = connection.get_db_connection() - if not conn: - return None +class Tags(BaseRepository): + table_name = "tags" - cursor = conn.cursor() - cursor.execute("SELECT id, name, slug FROM tags") + @classmethod + def getAllTags(cls): + def query(cursor): + cursor.execute( + f"SELECT id, name, slug FROM {cls.table_name}") result = cursor.fetchall() - cursor.close() - return result if result else None - except Exception as e: - logger.error(f"Database query error in getAllTags: {e}") - return None - finally: - if conn: - connection.release_db_connection(conn) - def getTagById(tag_id): - conn = None - try: - conn = connection.get_db_connection() - if not conn: - return None + return cls._execute_query(query, "getAllTags") - cursor = conn.cursor() + @classmethod + def getTagById(cls, tag_id): + def query(cursor): cursor.execute( - "SELECT id, name, slug FROM tags WHERE id = %s", (tag_id, )) + f"SELECT id, name, slug FROM {cls.table_name} WHERE id = %s", (tag_id,)) result = cursor.fetchall() - cursor.close() - return result[0] if result else None - except Exception as e: - logger.error(f"Database query error in getTagById: {e}") - return None - finally: - if conn: - connection.release_db_connection(conn) - def createTag(name, slug): - conn = None - try: - conn = connection.get_db_connection() - if not conn: - return False + return cls._execute_query(query, "getTagById") - cursor = conn.cursor() + @classmethod + def createTag(cls, name, slug): + def mutation(cursor, conn): cursor.execute( - "INSERT INTO tags (name, slug) VALUES (%s, %s)", - (name, slug) - ) - conn.commit() - cursor.close() + f"INSERT INTO {cls.table_name} (name, slug) VALUES (%s, %s)", (name, slug)) + return cls._execute_mutation(mutation, "createTag") - return True - except Exception as e: - logger.error(f"Database query error in createTag: {e}") - return False - finally: - if conn: - connection.release_db_connection(conn) - - def updateTag(tag_id, **fields): + @classmethod + def updateTag(cls, tag_id, **fields): if not fields: - return False # nothing to update - - conn = None - try: - conn = connection.get_db_connection() - if not conn: - return False - - cursor = conn.cursor() - - # Build dynamic SET clause - columns = [] - values = [] - - for key, value in fields.items(): - columns.append(f"{key} = %s") - values.append(value) - - values.append(tag_id) - - query = f""" - UPDATE tags - SET {', '.join(columns)} - WHERE id = %s - """ - - cursor.execute(query, tuple(values)) - conn.commit() - cursor.close() - - return True - - except Exception as e: - logger.error(f"Database query error in updateTag: {e}") return False - finally: - if conn: - connection.release_db_connection(conn) - - def deleteTag(tag_id): - conn = None - try: - conn = connection.get_db_connection() - if not conn: - return False - - cursor = conn.cursor() + def mutation(cursor, conn): + columns = [f"{key} = %s" for key in fields.keys()] + values = list(fields.values()) + [tag_id] cursor.execute( - "DELETE FROM tags WHERE id = %s", (tag_id,)) - conn.commit() - cursor.close() + f"UPDATE {cls.table_name} SET {', '.join(columns)} WHERE id = %s", + tuple(values) + ) - return True - except Exception as e: - logger.error(f"Database query error in deleteTag: {e}") - return False - finally: - if conn: - connection.release_db_connection(conn) + return cls._execute_mutation(mutation, "updateTag") - -class Categories: - def getAllCategories(): - conn = None - try: - conn = connection.get_db_connection() - if not conn: - return None - - cursor = conn.cursor() + @classmethod + def deleteTag(cls, tag_id): + def mutation(cursor, conn): cursor.execute( - "SELECT id, name, description, parent_id FROM categories") + f"DELETE FROM {cls.table_name} WHERE id = %s", (tag_id,)) + + return cls._execute_mutation(mutation, "deleteTag") + + +class Categories(BaseRepository): + table_name = "categories" + + @classmethod + def getAllCategories(cls): + def query(cursor): + cursor.execute( + f"SELECT id, name, description, parent_id FROM {cls.table_name}") result = cursor.fetchall() - cursor.close() - return result if result else None - except Exception as e: - logger.error(f"Database query error in getAllCategories: {e}") - return None - finally: - if conn: - connection.release_db_connection(conn) - def getCategorieById(category_id): - conn = None - try: - conn = connection.get_db_connection() - if not conn: - return None + return cls._execute_query(query, "getAllCategories") - cursor = conn.cursor() - cursor.execute(""" - SELECT - c1.id, - c1.name, - c1.description, - COALESCE(c2.name, 'None') AS parent_category - FROM - categories c1 - LEFT JOIN categories c2 ON c2.id = c1.parent_id - WHERE - c1.id = %s; + @classmethod + def getCategorieById(cls, category_id): + def query(cursor): + cursor.execute(f""" + SELECT c1.id, c1.name, c1.description, + COALESCE(c2.name, 'None') AS parent_category + FROM {cls.table_name} c1 + LEFT JOIN {cls.table_name} c2 ON c2.id = c1.parent_id + WHERE c1.id = %s """, (category_id,)) - result = cursor.fetchall() - cursor.close() - return result[0] if result else None - except Exception as e: - logger.error(f"Database query error in getCategoryById: {e}") - return None - finally: - if conn: - connection.release_db_connection(conn) - def createCategory(name, description, parent_id=None): - conn = None - try: - conn = connection.get_db_connection() - if not conn: - return False + return cls._execute_query(query, "getCategoryById") - cursor = conn.cursor() + @classmethod + def createCategory(cls, name, description, parent_id=None): + def mutation(cursor, conn): cursor.execute( - "INSERT INTO categories (name, description, parent_id) VALUES (%s, %s, %s)", - (name, description, parent_id) - ) - conn.commit() - cursor.close() + f"INSERT INTO {cls.table_name} (name, description, parent_id) VALUES (%s, %s, %s)", (name, description, parent_id)) - return True - except Exception as e: - logger.error(f"Database query error in createCategory: {e}") - return False - finally: - if conn: - connection.release_db_connection(conn) + return cls._execute_mutation(mutation, "createCategory") - def updateCategory(category_id, **fields): + @classmethod + def updateCategory(cls, category_id, **fields): if not fields: - return False # nothing to update - - conn = None - try: - conn = connection.get_db_connection() - if not conn: - return False - - cursor = conn.cursor() - - # Build dynamic SET clause - columns = [] - values = [] - - for key, value in fields.items(): - columns.append(f"{key} = %s") - values.append(value) - - values.append(category_id) - - query = f""" - UPDATE categories - SET {', '.join(columns)} - WHERE id = %s - """ - - cursor.execute(query, tuple(values)) - conn.commit() - cursor.close() - - return True - - except Exception as e: - logger.error(f"Database query error in updateCategory: {e}") return False - finally: - if conn: - connection.release_db_connection(conn) - - def deleteCategory(category_id): - conn = None - try: - conn = connection.get_db_connection() - if not conn: - return False - - cursor = conn.cursor() + def mutation(cursor, conn): + columns = [f"{key} = %s" for key in fields.keys()] + values = list(fields.values()) + [category_id] cursor.execute( - "DELETE FROM categories WHERE id = %s", (category_id,)) - conn.commit() - cursor.close() + f"UPDATE {cls.table_name} SET {', '.join(columns)} WHERE id = %s", + tuple(values) + ) - return True - except Exception as e: - logger.error(f"Database query error in deleteCategory: {e}") - return False - finally: - if conn: - connection.release_db_connection(conn) + return cls._execute_mutation(mutation, "updateCategory") + + @classmethod + def deleteCategory(cls, category_id): + def mutation(cursor, conn): + cursor.execute( + f"DELETE FROM {cls.table_name} WHERE id = %s", (category_id,)) + + return cls._execute_mutation(mutation, "deleteCategory")