diff --git a/bot/callbacks/categories.py b/bot/callbacks/categories.py
index 4e1e18d..6bc9d54 100644
--- a/bot/callbacks/categories.py
+++ b/bot/callbacks/categories.py
@@ -1,5 +1,5 @@
from telebot import types, TeleBot
-from database.models import Categories
+from database.models import Categories, Admins
from bot.handlers.start import startMarkup
from bot.utils.crud_helpers import create_entity_markup
@@ -38,7 +38,7 @@ def register(bot: TeleBot):
Description: \n{category[2]}
"""
details.strip()
- markup = create_entity_markup("category", category_id)
+ markup = create_entity_markup("category", category_id, True)
bot.send_message(call.message.chat.id, details,
reply_markup=markup, parse_mode="HTML")
@@ -91,9 +91,13 @@ def register(bot: TeleBot):
message.chat.id, "ā Failed to create category.", reply_markup=startMarkup())
# Editing a Category Flow
-
@bot.callback_query_handler(func=lambda call: call.data.startswith('edit_category_'))
def start_category_editing(call):
+ if not Admins.is_authenticated(call.from_user.id):
+ bot.answer_callback_query(call.id, "ā Unauthorized access!")
+ bot.send_message(call.message.chat.id, "Please /login first.")
+ return
+
category_id = call.data.split('_')[2]
category = Categories.getCategorieById(category_id)
@@ -150,6 +154,11 @@ def register(bot: TeleBot):
# Deleting a Category
@bot.callback_query_handler(func=lambda call: call.data.startswith('delete_category_'))
def delete_category(call):
+ if not Admins.is_authenticated(call.from_user.id):
+ bot.answer_callback_query(call.id, "ā Unauthorized access!")
+ bot.send_message(call.message.chat.id, "Please /login first.")
+ return
+
category_id = call.data.split('_')[2]
if (Categories.deleteCategory(category_id)):
bot.send_message(call.message.chat.id, "ā
Category deleted.",
diff --git a/bot/callbacks/courses.py b/bot/callbacks/courses.py
index f8d6bf0..fd802c4 100644
--- a/bot/callbacks/courses.py
+++ b/bot/callbacks/courses.py
@@ -1,5 +1,5 @@
from telebot import TeleBot, types
-from database.models import Courses
+from database.models import Courses, Admins
from bot.utils.crud_helpers import create_entity_markup
from bot.utils.formatters import format_course_info
from bot.handlers.start import startMarkup
@@ -38,7 +38,7 @@ def register(bot: TeleBot):
return
details = format_course_info(course)
- markup = create_entity_markup("course", course_id)
+ markup = create_entity_markup("course", course_id, True)
markup.add(types.InlineKeyboardButton(
f"šØāš« Teacher's Info", callback_data=f"teacher_{course[3]}"))
@@ -93,6 +93,11 @@ def register(bot: TeleBot):
# Editing a Course Flow
@bot.callback_query_handler(func=lambda call: call.data.startswith('edit_course_'))
def start_course_editing(call):
+ if not Admins.is_authenticated(call.from_user.id):
+ bot.answer_callback_query(call.id, "ā Unauthorized access!")
+ bot.send_message(call.message.chat.id, "Please /login first.")
+ return
+
course_id = call.data.split('_')[2]
course = Courses.getCourseById(course_id)
@@ -149,6 +154,11 @@ def register(bot: TeleBot):
# Deleting a Teacher
@bot.callback_query_handler(func=lambda call: call.data.startswith('delete_course_'))
def delete_course(call):
+ if not Admins.is_authenticated(call.from_user.id):
+ bot.answer_callback_query(call.id, "ā Unauthorized access!")
+ bot.send_message(call.message.chat.id, "Please /login first.")
+ return
+
course_id = call.data.split('_')[2]
if (Courses.deleteCourse(course_id)):
bot.send_message(call.message.chat.id, "ā
Course deleted.",
diff --git a/bot/callbacks/students.py b/bot/callbacks/students.py
index b464914..1baa0ed 100644
--- a/bot/callbacks/students.py
+++ b/bot/callbacks/students.py
@@ -1,5 +1,5 @@
from telebot import types, TeleBot
-from database.models import Students
+from database.models import Students, Admins
from bot.utils.formatters import format_student_info
from bot.utils.crud_helpers import create_entity_markup
from bot.handlers.start import startMarkup
@@ -30,12 +30,17 @@ def register(bot: TeleBot):
# Showing details of a Student
@bot.callback_query_handler(func=lambda call: call.data.startswith('student_'))
def show_student_details(call):
+ if not Admins.is_authenticated(call.from_user.id):
+ bot.answer_callback_query(call.id, "ā Unauthorized access!")
+ bot.send_message(call.message.chat.id, "Please /login first.")
+ return
+
student_id = call.data.split('_')[1]
student = Students.getStudentById(student_id)
if student:
details = format_student_info(student)
- markup = create_entity_markup("student", student_id)
+ markup = create_entity_markup("student", student_id, True)
bot.send_message(call.message.chat.id, details,
reply_markup=markup, parse_mode="HTML")
@@ -47,6 +52,11 @@ def register(bot: TeleBot):
# Creating a Student Flow
@bot.callback_query_handler(func=lambda call: call.data == 'create_student')
def create_student(call):
+ if not Admins.is_authenticated(call.from_user.id):
+ bot.answer_callback_query(call.id, "ā Unauthorized access!")
+ bot.send_message(call.message.chat.id, "Please /login first.")
+ return
+
msg = bot.send_message(call.message.chat.id,
"Please enter following data: (enter any key to start)",
reply_markup=cancelMarkup)
@@ -90,6 +100,11 @@ def register(bot: TeleBot):
# Editing a Student Flow
@bot.callback_query_handler(func=lambda call: call.data.startswith('edit_student_'))
def start_student_editing(call):
+ if not Admins.is_authenticated(call.from_user.id):
+ bot.answer_callback_query(call.id, "ā Unauthorized access!")
+ bot.send_message(call.message.chat.id, "Please /login first.")
+ return
+
student_id = call.data.split('_')[2]
student = Students.getStudentById(student_id)
@@ -146,6 +161,11 @@ def register(bot: TeleBot):
# Deleting a Teacher
@bot.callback_query_handler(func=lambda call: call.data.startswith('delete_student_'))
def delete_student(call):
+ if not Admins.is_authenticated(call.from_user.id):
+ bot.answer_callback_query(call.id, "ā Unauthorized access!")
+ bot.send_message(call.message.chat.id, "Please /login first.")
+ return
+
student_id = call.data.split('_')[2]
if (Students.deleteStudent(student_id)):
bot.send_message(call.message.chat.id, "ā
Student deleted.",
diff --git a/bot/callbacks/tags.py b/bot/callbacks/tags.py
index 70e2f57..5c9575e 100644
--- a/bot/callbacks/tags.py
+++ b/bot/callbacks/tags.py
@@ -1,5 +1,5 @@
from telebot import types, TeleBot
-from database.models import Tags
+from database.models import Tags, Admins
from bot.handlers.start import startMarkup
from bot.utils.crud_helpers import create_entity_markup
@@ -34,7 +34,7 @@ def register(bot: TeleBot):
slug: {tag[2]}
"""
details.strip()
- markup = create_entity_markup("tag", tag_id)
+ markup = create_entity_markup("tag", tag_id, True)
bot.send_message(call.message.chat.id, details,
reply_markup=markup, parse_mode="HTML")
@@ -87,9 +87,13 @@ def register(bot: TeleBot):
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):
+ if not Admins.is_authenticated(call.from_user.id):
+ bot.answer_callback_query(call.id, "ā Unauthorized access!")
+ bot.send_message(call.message.chat.id, "Please /login first.")
+ return
+
tag_id = call.data.split('_')[2]
tag = Tags.getTagById(tag_id)
@@ -146,6 +150,11 @@ def register(bot: TeleBot):
# Deleting a Tag
@bot.callback_query_handler(func=lambda call: call.data.startswith('delete_tag_'))
def delete_tag(call):
+ if not Admins.is_authenticated(call.from_user.id):
+ bot.answer_callback_query(call.id, "ā Unauthorized access!")
+ bot.send_message(call.message.chat.id, "Please /login first.")
+ return
+
tag_id = call.data.split('_')[2]
if (Tags.deleteTag(tag_id)):
bot.send_message(call.message.chat.id, "ā
Tag deleted.",
diff --git a/bot/callbacks/teachers.py b/bot/callbacks/teachers.py
index c71d1f0..63236dd 100644
--- a/bot/callbacks/teachers.py
+++ b/bot/callbacks/teachers.py
@@ -1,12 +1,11 @@
from telebot import types, TeleBot
-from database.models import Teachers
+from database.models import Teachers, Admins
from bot.utils.formatters import format_teacher_info
from bot.handlers.start import startMarkup
from bot.utils.crud_helpers import create_entity_markup
# TODO: Add Buttons for skipping Optional Fields (Add to all entities).
-# TODO: Add Option for handling all updates at once.
-# TODO: Add Authentication for sensitive actions and info.
+# TODO: Add Option for handling all updates at once.\
# TODO: Add Option for seeing courses taught by a teacher in Teacher Details.
@@ -45,7 +44,7 @@ def register(bot: TeleBot):
if teacher:
details = format_teacher_info(teacher)
- markup = create_entity_markup("teacher", teacher_id)
+ markup = create_entity_markup("teacher", teacher_id, True)
bot.send_message(call.message.chat.id, details,
reply_markup=markup, parse_mode="HTML")
@@ -57,6 +56,11 @@ def register(bot: TeleBot):
# Creating a Teacher Flow
@bot.callback_query_handler(func=lambda call: call.data == 'create_teacher')
def start_teacher_creation(call):
+ if not Admins.is_authenticated(call.from_user.id):
+ bot.answer_callback_query(call.id, "ā Unauthorized access!")
+ bot.send_message(call.message.chat.id, "Please /login first.")
+ return
+
msg = bot.send_message(call.message.chat.id,
"Please enter following data: (enter any key to start)",
reply_markup=cancelMarkup)
@@ -100,6 +104,11 @@ def register(bot: TeleBot):
# Editing a Teacher Flow
@bot.callback_query_handler(func=lambda call: call.data.startswith('edit_teacher_'))
def start_teacher_editing(call):
+ if not Admins.is_authenticated(call.from_user.id):
+ bot.answer_callback_query(call.id, "ā Unauthorized access!")
+ bot.send_message(call.message.chat.id, "Please /login first.")
+ return
+
teacher_id = call.data.split('_')[2]
teacher = Teachers.getTeacherById(teacher_id)
@@ -156,6 +165,11 @@ def register(bot: TeleBot):
# Deleting a Teacher
@bot.callback_query_handler(func=lambda call: call.data.startswith('delete_teacher_'))
def delete_teacher(call):
+ if not Admins.is_authenticated(call.from_user.id):
+ bot.answer_callback_query(call.id, "ā Unauthorized access!")
+ bot.send_message(call.message.chat.id, "Please /login first.")
+ return
+
teacher_id = call.data.split('_')[2]
if (Teachers.deleteTeacher(teacher_id)):
bot.send_message(call.message.chat.id, "ā
Teacher deleted.",
diff --git a/bot/handlers/categories.py b/bot/handlers/categories.py
index 4c7ac34..5bab5c2 100644
--- a/bot/handlers/categories.py
+++ b/bot/handlers/categories.py
@@ -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=1)
+ markup.add(types.InlineKeyboardButton(
+ "ā Create New Category", callback_data="create_category"))
+
+ bot.reply_to(message, "No data found.", reply_markup=markup)
except Exception as e:
logger.error(f"Error in 'Show Categories' handler: {e}")
bot.reply_to(message, "Sorry, an error occurred.")
diff --git a/bot/handlers/common.py b/bot/handlers/common.py
index 0071466..6229a4b 100644
--- a/bot/handlers/common.py
+++ b/bot/handlers/common.py
@@ -1,4 +1,9 @@
+from telebot import types
+from bot.handlers.start import startMessage
+
+
def register(bot):
@bot.message_handler(func=lambda message: True)
def handle_unknown(message):
- bot.reply_to(message, f"You said: {message.text}")
+ bot.reply_to(
+ message, f"You said: {message.text}. send /start to get started.")
diff --git a/bot/handlers/courses.py b/bot/handlers/courses.py
index c424c0e..b6be6ac 100644
--- a/bot/handlers/courses.py
+++ b/bot/handlers/courses.py
@@ -24,7 +24,10 @@ 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=1)
+ markup.add(types.InlineKeyboardButton(
+ "ā Create New Course", callback_data="create_course"))
+ bot.reply_to(message, "No data found.", reply_markup=markup)
except Exception as e:
logger.error(f"Error in (Show Courses) handler: {e}")
bot.reply_to(message, "Sorry, an error occurred.")
diff --git a/bot/handlers/init.py b/bot/handlers/init.py
index 50b2f80..124cde3 100644
--- a/bot/handlers/init.py
+++ b/bot/handlers/init.py
@@ -1,11 +1,25 @@
-from . import start, students, teachers, courses, tags, categories, common
+from . import start, students, teachers, courses, tags, categories, common, login
from bot.callbacks.init import register_all_callbacks
+from telebot import types
def register_all_handlers(bot):
"""Register all handlers and callbacks"""
+ # Set bot commands
+ commands = [
+ types.BotCommand(command="start", description="Start the bot"),
+ types.BotCommand(command="login", description="Login to your account"),
+ types.BotCommand(command="logout",
+ description="Logout of your account"),
+ types.BotCommand(command="session",
+ description="Info about your session"),
+ ]
+
+ bot.set_my_commands(commands)
+
# Message handlers
start.register(bot)
+ login.register(bot)
students.register(bot)
teachers.register(bot)
courses.register(bot)
diff --git a/bot/handlers/login.py b/bot/handlers/login.py
new file mode 100644
index 0000000..49f93b1
--- /dev/null
+++ b/bot/handlers/login.py
@@ -0,0 +1,110 @@
+from telebot import types, TeleBot
+from database.models import Admins
+from bot.handlers.start import startMarkup
+
+
+def register(bot: TeleBot):
+
+ @bot.message_handler(commands=['login'])
+ def start_login(message):
+ # Check if already authenticated
+ if Admins.is_authenticated(message.from_user.id):
+ session_info = Admins.get_session_info(message.from_user.id)
+ bot.send_message(
+ message.chat.id,
+ f"ā
You are already logged in!\n\n"
+ f"Session expires in: {session_info['time_remaining']}"
+ )
+ return
+
+ msg = bot.send_message(
+ message.chat.id,
+ "š Please enter your username:"
+ )
+ bot.register_next_step_handler(msg, get_password)
+
+ def get_password(message):
+ """Get username and ask for password"""
+ username = message.text.strip()
+
+ try:
+ bot.delete_message(message.chat.id, message.message_id)
+ except:
+ pass
+
+ msg = bot.send_message(
+ message.chat.id,
+ f"Username: {username}\n\nš Now enter your password:"
+ )
+ bot.register_next_step_handler(msg, verify_and_login, username)
+
+ def verify_and_login(message, username):
+ """Verify credentials and create session"""
+ password = message.text.strip()
+ telegram_id = message.from_user.id
+
+ try:
+ bot.delete_message(message.chat.id, message.message_id)
+ except:
+ pass
+
+ # Attempt login (this creates the session)
+ if Admins.login(username, password, telegram_id):
+ bot.send_message(
+ message.chat.id,
+ f"ā
Login successful!\n\n"
+ f"Welcome back, {username}!\n"
+ f"Session valid for: {Admins.SESSION_TIMEOUT_HOURS} hours\n"
+ f"Use /session for info about your session, or /logout to logout.",
+ reply_markup=startMarkup()
+ )
+ else:
+ bot.send_message(
+ message.chat.id,
+ "ā Invalid credentials or unauthorized Telegram account.\n\n"
+ "Please check your username and password.\nContact @HoseinA05 for support."
+ )
+
+ @bot.message_handler(commands=['logout'])
+ def logout(message):
+ """Logout user manually"""
+
+ if not Admins.is_authenticated(message.from_user.id):
+ bot.send_message(message.chat.id, "You are not logged in.")
+ return
+
+ if Admins.logout(message.from_user.id):
+ bot.send_message(
+ message.chat.id,
+ "ā
You have been logged out successfully.\n\n"
+ "Use /login to access admin features again."
+ )
+ else:
+ bot.send_message(
+ message.chat.id, "ā Logout failed. Please try again later.")
+
+ @bot.message_handler(commands=['session'])
+ def check_session(message):
+ """Check current session status"""
+ session_info = Admins.get_session_info(message.from_user.id)
+
+ if not session_info:
+ bot.send_message(
+ message.chat.id, "ā You don't have an admin account.")
+ return
+
+ if session_info['is_active']:
+ bot.send_message(
+ message.chat.id,
+ f"ā
Active Session\n\n"
+ f"Username: {session_info['username']}\n"
+ f"Last Login: {session_info['last_login']}\n"
+ f"Time Remaining: {session_info['time_remaining']}"
+ )
+ else:
+ bot.send_message(
+ message.chat.id,
+ f"ā±ļø Session Expired\n\n"
+ f"Username: {session_info['username']}\n"
+ f"Please use /login to continue."
+ )
diff --git a/bot/handlers/start.py b/bot/handlers/start.py
index 19ee62c..b1277bf 100644
--- a/bot/handlers/start.py
+++ b/bot/handlers/start.py
@@ -1,5 +1,4 @@
from telebot import types
-from bot.utils.auth import is_authenticated
# TODO: Add Pagincation to Showing rows of entities (To all entities)
@@ -7,16 +6,17 @@ from bot.utils.auth import is_authenticated
def register(bot):
@bot.message_handler(commands=["start"])
def start_handler(message):
- if is_authenticated(message.from_user.id):
- bot.send_message(
- message.chat.id, "authorized user! Welcome.")
- else:
- bot.send_message(
- message.chat.id, "Welcome! Use buttons to fetch from database.")
+ startMessage(bot, message=message)
- markup = startMarkup()
- bot.reply_to(message, "Use buttons to fetch from database.",
- reply_markup=markup)
+
+def startMessage(bot, message):
+ # if is_authenticated(message.from_user.id):
+ # bot.send_message(
+ # message.chat.id, "authorized user! Welcome.")
+
+ markup = startMarkup()
+ bot.reply_to(message, "Use buttons to fetch from database.",
+ reply_markup=markup)
def startMarkup():
diff --git a/bot/handlers/students.py b/bot/handlers/students.py
index 4d8e616..e9f53a9 100644
--- a/bot/handlers/students.py
+++ b/bot/handlers/students.py
@@ -1,13 +1,18 @@
import logging
-from telebot import types
-from database.models import Students
+from telebot import types, TeleBot
+from database.models import Students, Admins
logger = logging.getLogger(__name__)
-def register(bot):
+def register(bot: TeleBot):
@bot.message_handler(func=lambda message: message.text == "Show Students")
def get_students(message):
+ if not Admins.is_authenticated(message.from_user.id):
+ bot.send_message(
+ message.chat.id, "ā Unauthorized access!\nPlease /login first.")
+ return
+
try:
data = Students.getAllStudents()
if data:
@@ -20,12 +25,15 @@ def register(bot):
# add button for creating a new student
markup.add(types.InlineKeyboardButton(
- "ā Create New Student", callback_data="create_student"))
+ "ā Create New Student š", callback_data="create_student"))
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=1)
+ markup.add(types.InlineKeyboardButton(
+ "ā Create New Student š", callback_data="create_student"))
+ bot.reply_to(message, "No data found.", reply_markup=markup)
except Exception as e:
logger.error(f"Error in get_students handler: {e}")
bot.reply_to(message, "Sorry, an error occurred.")
diff --git a/bot/handlers/tags.py b/bot/handlers/tags.py
index 29d22d5..9093f3e 100644
--- a/bot/handlers/tags.py
+++ b/bot/handlers/tags.py
@@ -25,11 +25,11 @@ def register(bot: TeleBot):
bot.send_message(
message.chat.id, "Here is the data:", reply_markup=markup)
else:
- markup = types.InlineKeyboardMarkup(row_width=2)
+ markup = types.InlineKeyboardMarkup(row_width=1)
markup.add(types.InlineKeyboardButton(
"ā Create New Tag", callback_data="create_tag"))
- bot.reply_to(message, "No data found.", markup=markup)
+ bot.reply_to(message, "No data found.", reply_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/bot/handlers/teachers.py b/bot/handlers/teachers.py
index 2240869..5f1e15e 100644
--- a/bot/handlers/teachers.py
+++ b/bot/handlers/teachers.py
@@ -19,12 +19,15 @@ def register(bot):
markup.add(btn)
# add button for creating a new teacher
markup.add(types.InlineKeyboardButton(
- "ā Create New Teacher", callback_data="create_teacher"))
+ "ā Create New Teacher š", callback_data="create_teacher"))
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=1)
+ markup.add(types.InlineKeyboardButton(
+ "ā Create New Teacher š", callback_data="create_teacher"))
+ bot.reply_to(message, "No data found.", reply_markup=markup)
except Exception as e:
logger.error(f"Error in get_teachers handler: {e}")
bot.reply_to(message, "Sorry, an error occurred.")
diff --git a/bot/utils/auth.py b/bot/utils/auth.py
deleted file mode 100644
index 38165f1..0000000
--- a/bot/utils/auth.py
+++ /dev/null
@@ -1,4 +0,0 @@
-authenticated_users = {2032936226}
-
-def is_authenticated(user_id):
- return user_id in authenticated_users
\ No newline at end of file
diff --git a/bot/utils/crud_helpers.py b/bot/utils/crud_helpers.py
index 21a78f4..a12e1b2 100644
--- a/bot/utils/crud_helpers.py
+++ b/bot/utils/crud_helpers.py
@@ -1,11 +1,11 @@
from telebot import types
-def create_entity_markup(entity_name, enitity_id):
+def create_entity_markup(entity_name, enitity_id, is_auth_needed=False):
markup = types.InlineKeyboardMarkup()
markup.add(types.InlineKeyboardButton(
- f"āļø Edit {entity_name}", callback_data=f"edit_{entity_name}_{enitity_id}"))
+ f"āļø Edit {entity_name} " + ("š" if is_auth_needed else ""), callback_data=f"edit_{entity_name}_{enitity_id}"))
markup.add(types.InlineKeyboardButton(
- f"šļø Delete {entity_name}", callback_data=f"delete_{entity_name}_{enitity_id}"))
+ f"šļø Delete {entity_name}" + ("š" if is_auth_needed else ""), callback_data=f"delete_{entity_name}_{enitity_id}"))
return markup
diff --git a/database/models.py b/database/models.py
index 40eda7b..40ae1e2 100644
--- a/database/models.py
+++ b/database/models.py
@@ -1,6 +1,9 @@
import logging
import database.connection as connection
+import hashlib
+from datetime import datetime, timedelta
+
logger = logging.getLogger(__name__)
@@ -55,6 +58,119 @@ class BaseRepository:
connection.release_db_connection(conn)
+class Admins(BaseRepository):
+
+ SESSION_TIMEOUT_HOURS = 1
+
+ @staticmethod
+ def hash_password(password: str) -> str:
+ """Hash password using SHA256"""
+ return hashlib.sha256(password.encode()).hexdigest()
+
+ @staticmethod
+ def login(username: str, password: str, telegram_id: int) -> bool:
+ """
+ Verify credentials and create a session.
+ Returns True if login successful, False otherwise.
+ """
+
+ def query(cursor):
+ password_hash = Admins.hash_password(password)
+ cursor.execute(
+ "SELECT id FROM admins WHERE username = %s AND password_hash = %s AND telegram_id = %s",
+ (username, password_hash, telegram_id)
+ )
+ result = cursor.fetchone()
+ return result is not None
+
+ result = Admins._execute_query(query, "login")
+
+ if result:
+ # Create session by updating last_login and session_expires_at
+ def mutation(cursor, conn):
+ expires_at = datetime.now() + timedelta(hours=Admins.SESSION_TIMEOUT_HOURS)
+ cursor.execute(
+ """UPDATE admins
+ SET last_login = CURRENT_TIMESTAMP,
+ session_expires_at = %s
+ WHERE telegram_id = %s""",
+ (expires_at, telegram_id)
+ )
+
+ return Admins._execute_mutation(mutation, "login")
+
+ return False
+
+ @staticmethod
+ def is_authenticated(telegram_id: int) -> bool:
+ """Check if user has a valid active session"""
+ def query(cursor):
+ cursor.execute(
+ "SELECT session_expires_at FROM admins WHERE telegram_id = %s",
+ (telegram_id,)
+ )
+ result = cursor.fetchone()
+
+ if not result or result[0] is None:
+ return False
+
+ # Check if session has expired
+ expires_at = result[0]
+
+ if isinstance(expires_at, str):
+ expires_at = datetime.fromisoformat(expires_at)
+
+ return datetime.now() < expires_at
+
+ result = Admins._execute_query(query, "is_authenticated")
+ return result if result is not None else False
+
+ @staticmethod
+ def logout(telegram_id: int) -> bool:
+ """Logout admin by clearing session"""
+ def mutation(cursor, conn):
+ cursor.execute(
+ "UPDATE admins SET session_expires_at = NULL WHERE telegram_id = %s",
+ (telegram_id,)
+ )
+
+ return Admins._execute_mutation(mutation, "logout")
+
+ @staticmethod
+ def get_session_info(telegram_id: int) -> dict:
+ """Get session info for the admin"""
+ def query(cursor):
+ cursor.execute(
+ "SELECT username, last_login, session_expires_at FROM admins WHERE telegram_id = %s",
+ (telegram_id,)
+ )
+ result = cursor.fetchone()
+ return result
+
+ result = Admins._execute_query(query, "get_session_info")
+ if not result:
+ return None
+
+ session_info = {
+ 'username': result[0],
+ 'last_login': result[1],
+ 'expires_at': result[2],
+ 'is_active': False
+ }
+
+ if result[2]:
+ expires_at = result[2]
+
+ if isinstance(expires_at, str):
+ expires_at = datetime.fromisoformat(expires_at)
+
+ session_info['is_active'] = datetime.now() < expires_at
+ session_info['time_remaining'] = str(
+ expires_at - datetime.now()).split('.')[0]
+
+ return session_info
+
+
class Students(BaseRepository):
table_name = "students"
diff --git a/dev.py b/dev.py
index 4288e74..9e40d9d 100644
--- a/dev.py
+++ b/dev.py
@@ -7,6 +7,7 @@ load_dotenv()
BOT_TOKEN = os.environ.get("BOT_TOKEN")
+
def ensure_no_webhook():
"""Remove webhook before starting polling"""
response = requests.post(
@@ -15,13 +16,13 @@ def ensure_no_webhook():
if response.json()['ok']:
print("ā
Webhook removed, starting polling...")
return True
-
+
print("ā ļø Warning: Could not remove webhook")
return False
-
+
if __name__ == "__main__":
if ensure_no_webhook():
bot = get_bot()
print("š¤ Bot is polling locally...")
- bot.polling(non_stop=True)
\ No newline at end of file
+ bot.polling(non_stop=True)