diff --git a/bot/callbacks/courses.py b/bot/callbacks/courses.py index 2ffadf6..f8d6bf0 100644 --- a/bot/callbacks/courses.py +++ b/bot/callbacks/courses.py @@ -84,7 +84,7 @@ def register(bot: TeleBot): def create_course(message, data): if Courses.createCourse(**data): - bot.send_message(message.chat.id, "✅ Teacher created!", + bot.send_message(message.chat.id, "✅ Course created!", reply_markup=startMarkup()) else: bot.send_message( @@ -132,7 +132,7 @@ def register(bot: TeleBot): new_value = message.text # Handle cancellation - if new_value.lower() == 'cancel' or new_value == f"{previous_value} (current)": + if new_value.lower() == 'cancel' or new_value == f"{previous_value}": msg = "Action cancelled." if new_value.lower( ) == 'cancel' else f"No changes made to {field}." bot.send_message(message.chat.id, msg, reply_markup=startMarkup()) diff --git a/bot/callbacks/init.py b/bot/callbacks/init.py index d834403..c7fa4d5 100644 --- a/bot/callbacks/init.py +++ b/bot/callbacks/init.py @@ -1,4 +1,4 @@ -from . import students, teachers, courses +from . import students, teachers, courses, tags from bot.handlers.start import startMarkup @@ -7,6 +7,7 @@ def register_all_callbacks(bot): students.register(bot) teachers.register(bot) courses.register(bot) + tags.register(bot) # Cancel is common among all @bot.callback_query_handler(func=lambda call: call.data == 'cancel') diff --git a/bot/callbacks/students.py b/bot/callbacks/students.py index 86c95aa..b464914 100644 --- a/bot/callbacks/students.py +++ b/bot/callbacks/students.py @@ -129,7 +129,7 @@ def register(bot: TeleBot): new_value = message.text # Handle cancellation - if new_value.lower() == 'cancel' or new_value == f"{previous_value} (current)": + if new_value.lower() == 'cancel' or new_value == f"{previous_value}": msg = "Action cancelled." if new_value.lower( ) == 'cancel' else f"No changes made to {field}." bot.send_message(message.chat.id, msg, reply_markup=startMarkup()) diff --git a/bot/callbacks/tags.py b/bot/callbacks/tags.py new file mode 100644 index 0000000..54abfe7 --- /dev/null +++ b/bot/callbacks/tags.py @@ -0,0 +1,156 @@ +from telebot import types, TeleBot +from database.models import Tags +from bot.handlers.start import startMarkup +from bot.utils.crud_helpers import create_entity_markup + +# TODO: Add Option for showing courses with specific tags. + + +def register(bot: TeleBot): + cancelMarkup = types.InlineKeyboardMarkup() + cancelMarkup.add(types.InlineKeyboardButton( + "Cancel", callback_data="cancel")) + + TAG_FIELDS = [ + ('name', "the tag's name"), + ('slug', "The slug for tag") + ] + EDITABLE_FIELDS = { + 'name': 1, + 'slug': 2 + } + + # Showing details of a Tag + @bot.callback_query_handler(func=lambda call: call.data.startswith('tag_')) + def show_teacher_details(call): + tag_id = call.data.split('_')[1] + tag = Tags.getTagById(tag_id) + + if tag: + details = f""" + 🔖 Tag Profile + + Name: {tag[1]} + slug: {tag[2]} + """ + details.strip() + markup = create_entity_markup("tag", tag_id) + + bot.send_message(call.message.chat.id, details, + reply_markup=markup, parse_mode="HTML") + else: + bot.send_message(call.message.chat.id, "Tag not found.") + + bot.answer_callback_query(call.id) + + # Creating a Tag Flow + @bot.callback_query_handler(func=lambda call: call.data == 'create_tag') + def start_tag_creation(call): + msg = bot.send_message(call.message.chat.id, + "Please enter following data: (enter any key to start)", + reply_markup=cancelMarkup) + bot.register_next_step_handler(msg, collect_field, {}, 0) + bot.answer_callback_query(call.id) + + def collect_field(message, data, step): + # Save previous field + if step > 0: + field_name = TAG_FIELDS[step - 1][0] + data[field_name] = message.text + + # Done collecting? + if step >= len(TAG_FIELDS): + show_confirmation(message, data) + return + + # Ask next question + field_name, prompt = TAG_FIELDS[step] + msg = bot.send_message(message.chat.id, f"Now enter {prompt}:", + reply_markup=cancelMarkup) + bot.register_next_step_handler(msg, collect_field, data, step + 1) + + def show_confirmation(message, data): + summary = "Is this correct? (enter any key to continue or cancel to exit)\n\n" + "\n".join( + f"{name.replace('_', ' ').title()}: {data[name]}" + for name, _ in TAG_FIELDS + ) + msg = bot.send_message(message.chat.id, summary, + reply_markup=cancelMarkup) + bot.register_next_step_handler(msg, create_tag, data) + + def create_tag(message, data): + if Tags.createTag(**data): + bot.send_message(message.chat.id, "✅ Tag created!", + reply_markup=startMarkup()) + else: + bot.send_message( + message.chat.id, "❌ Failed to create tag.", reply_markup=startMarkup()) + + # Editing a Tag Flow + + @bot.callback_query_handler(func=lambda call: call.data.startswith('edit_tag_')) + def start_tag_editing(call): + tag_id = call.data.split('_')[2] + tag = Tags.getTagById(tag_id) + + if not tag: + bot.send_message(call.message.chat.id, "Tag not found.") + bot.answer_callback_query(call.id) + return + + editMarkup = types.ReplyKeyboardMarkup( + resize_keyboard=True, one_time_keyboard=True) + for field in list(EDITABLE_FIELDS.keys()) + ['Cancel']: + editMarkup.add(types.KeyboardButton(field.capitalize())) + + msg = bot.send_message(call.message.chat.id, + "Please enter the field you want to edit: ", reply_markup=editMarkup) + + bot.register_next_step_handler( + msg, process_field_select, tag) + bot.answer_callback_query(call.id) + + def process_field_select(message, tag: tuple): + field = message.text.lower() + if field == 'cancel' or field not in EDITABLE_FIELDS: + msg = "Action cancelled." if field == 'cancel' else "Invalid field. Action cancelled." + bot.send_message(message.chat.id, msg, + reply_markup=startMarkup()) + return + + current_value = tag[EDITABLE_FIELDS[field]] + + msg = bot.send_message( + message.chat.id, f"Current value is: {current_value}.\n Please enter new value for {field}:", reply_markup=cancelMarkup) + bot.register_next_step_handler( + msg, process_value_edit, tag[0], field, current_value) + + def process_value_edit(message, tag_id, field, previous_value): + new_value = message.text + + # Handle cancellation + if new_value.lower() == 'cancel' or new_value == f"{previous_value}": + msg = "Action cancelled." if new_value.lower( + ) == 'cancel' else f"No changes made to {field}." + bot.send_message(message.chat.id, msg, reply_markup=startMarkup()) + return + + # Update teacher + if Tags.updateTag(tag_id, **{field: new_value}): + bot.send_message( + message.chat.id, f"✅ Tag's {field} updated successfully.", reply_markup=startMarkup()) + else: + bot.send_message( + message.chat.id, f"❌ Failed to update Tag's {field}.", reply_markup=startMarkup()) + + # Deleting a Teacher + @bot.callback_query_handler(func=lambda call: call.data.startswith('delete_tag_')) + def delete_tag(call): + tag_id = call.data.split('_')[2] + if (Tags.deleteTag(tag_id)): + bot.send_message(call.message.chat.id, "✅ Tag deleted.", + reply_markup=startMarkup()) + else: + bot.send_message(call.message.chat.id, "❌ Failed to delete Tag.", + reply_markup=startMarkup()) + bot.answer_callback_query(call.id) diff --git a/bot/callbacks/teachers.py b/bot/callbacks/teachers.py index cb2d9d2..9d9cc34 100644 --- a/bot/callbacks/teachers.py +++ b/bot/callbacks/teachers.py @@ -139,7 +139,7 @@ def register(bot: TeleBot): new_value = message.text # Handle cancellation - if new_value.lower() == 'cancel' or new_value == f"{previous_value} (current)": + if new_value.lower() == 'cancel' or new_value == f"{previous_value}": msg = "Action cancelled." if new_value.lower( ) == 'cancel' else f"No changes made to {field}." bot.send_message(message.chat.id, msg, reply_markup=startMarkup()) diff --git a/bot/handlers/init.py b/bot/handlers/init.py index fbe885c..c3ef3f6 100644 --- a/bot/handlers/init.py +++ b/bot/handlers/init.py @@ -1,4 +1,4 @@ -from . import start, students, teachers, courses, common +from . import start, students, teachers, courses, tags, common from bot.callbacks.init import register_all_callbacks @@ -9,6 +9,7 @@ def register_all_handlers(bot): students.register(bot) teachers.register(bot) courses.register(bot) + tags.register(bot) common.register(bot) # Must be last (catch-all) # Callback handlers diff --git a/bot/handlers/tags.py b/bot/handlers/tags.py new file mode 100644 index 0000000..be6042a --- /dev/null +++ b/bot/handlers/tags.py @@ -0,0 +1,31 @@ +import logging +from telebot import types +from database.models import Tags + +logger = logging.getLogger(__name__) + + +def register(bot): + @bot.message_handler(func=lambda message: message.text == "Show Tags") + def get_tags(message): + try: + data = Tags.getAllTags() + if data: + markup = types.InlineKeyboardMarkup(row_width=2) + + for row in data: + btn = types.InlineKeyboardButton( + f"{row[1]} | id#{row[0]}", callback_data=f"tag_{row[0]}") + markup.add(btn) + + # add button for creating a new tag + markup.add(types.InlineKeyboardButton( + "➕ Create New Tag", callback_data="create_tag")) + + bot.send_message( + message.chat.id, "Here is the data:", reply_markup=markup) + else: + bot.reply_to(message, "No data found.") + 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 73f45e7..e01ddc6 100644 --- a/database/models.py +++ b/database/models.py @@ -393,3 +393,132 @@ class Courses: finally: if conn: connection.release_db_connection(conn) + + +class Tags: + def getAllTags(): + conn = None + try: + conn = connection.get_db_connection() + if not conn: + return None + + cursor = conn.cursor() + cursor.execute("SELECT id, name, slug FROM tags") + 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 + + cursor = conn.cursor() + cursor.execute( + "SELECT id, name, slug FROM tags 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 + + cursor = conn.cursor() + cursor.execute( + "INSERT INTO tags (name, slug) VALUES (%s, %s)", + (name, slug) + ) + conn.commit() + cursor.close() + + 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): + 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() + cursor.execute( + "DELETE FROM tags WHERE id = %s", (tag_id,)) + conn.commit() + cursor.close() + + 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)