added some new features. (Showing student's reviews and courses, Show course reviews, Show Top Courses with a tag or in a category, skipping optional fields when creating entity)
This commit is contained in:
@@ -4,7 +4,6 @@ from bot.handlers.start import startMarkup
|
|||||||
from bot.utils.crud_helpers import create_entity_markup
|
from bot.utils.crud_helpers import create_entity_markup
|
||||||
|
|
||||||
# TODO: Add Option for showing parent categories when creating a new category.
|
# 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):
|
def register(bot: TeleBot):
|
||||||
@@ -14,8 +13,8 @@ def register(bot: TeleBot):
|
|||||||
|
|
||||||
CATEGORY_FIELDS = [
|
CATEGORY_FIELDS = [
|
||||||
('name', "the category's name"),
|
('name', "the category's name"),
|
||||||
('description', "The description for category"),
|
('description', "The description for category (<Optional>) ('s' to skip)"),
|
||||||
('parent_id', "The parent category id")
|
('parent_id', "The parent category id (<Optional>) ('s' to skip))")
|
||||||
]
|
]
|
||||||
EDITABLE_FIELDS = {
|
EDITABLE_FIELDS = {
|
||||||
'name': 1,
|
'name': 1,
|
||||||
@@ -27,7 +26,7 @@ def register(bot: TeleBot):
|
|||||||
@bot.callback_query_handler(func=lambda call: call.data.startswith('category_'))
|
@bot.callback_query_handler(func=lambda call: call.data.startswith('category_'))
|
||||||
def show_category_details(call):
|
def show_category_details(call):
|
||||||
category_id = call.data.split('_')[1]
|
category_id = call.data.split('_')[1]
|
||||||
category = Categories.getCategorieById(category_id)
|
category = Categories.getCategoryById(category_id)
|
||||||
|
|
||||||
if category:
|
if category:
|
||||||
details = f"""
|
details = f"""
|
||||||
@@ -39,6 +38,8 @@ def register(bot: TeleBot):
|
|||||||
"""
|
"""
|
||||||
details.strip()
|
details.strip()
|
||||||
markup = create_entity_markup("category", category_id, True)
|
markup = create_entity_markup("category", category_id, True)
|
||||||
|
markup.add(types.InlineKeyboardButton("📁 Show Top Courses",
|
||||||
|
callback_data=f"categoryCourses_{category_id}"))
|
||||||
|
|
||||||
bot.send_message(call.message.chat.id, details,
|
bot.send_message(call.message.chat.id, details,
|
||||||
reply_markup=markup, parse_mode="HTML")
|
reply_markup=markup, parse_mode="HTML")
|
||||||
@@ -60,7 +61,8 @@ def register(bot: TeleBot):
|
|||||||
# Save previous field
|
# Save previous field
|
||||||
if step > 0:
|
if step > 0:
|
||||||
field_name = CATEGORY_FIELDS[step - 1][0]
|
field_name = CATEGORY_FIELDS[step - 1][0]
|
||||||
data[field_name] = message.text
|
data[field_name] = None if (
|
||||||
|
(field_name in ['description', 'parent_id']) and message.text == 's') else message.text
|
||||||
|
|
||||||
# Done collecting?
|
# Done collecting?
|
||||||
if step >= len(CATEGORY_FIELDS):
|
if step >= len(CATEGORY_FIELDS):
|
||||||
@@ -99,7 +101,7 @@ def register(bot: TeleBot):
|
|||||||
return
|
return
|
||||||
|
|
||||||
category_id = call.data.split('_')[2]
|
category_id = call.data.split('_')[2]
|
||||||
category = Categories.getCategorieById(category_id)
|
category = Categories.getCategoryById(category_id)
|
||||||
|
|
||||||
if not category:
|
if not category:
|
||||||
bot.send_message(call.message.chat.id, "Category not found.")
|
bot.send_message(call.message.chat.id, "Category not found.")
|
||||||
@@ -167,3 +169,22 @@ def register(bot: TeleBot):
|
|||||||
bot.send_message(call.message.chat.id, "❌ Failed to delete Category.",
|
bot.send_message(call.message.chat.id, "❌ Failed to delete Category.",
|
||||||
reply_markup=startMarkup())
|
reply_markup=startMarkup())
|
||||||
bot.answer_callback_query(call.id)
|
bot.answer_callback_query(call.id)
|
||||||
|
|
||||||
|
# Show Top Courses in a Category
|
||||||
|
@bot.callback_query_handler(func=lambda call: call.data.startswith('categoryCourses_'))
|
||||||
|
def category_courses(call):
|
||||||
|
category_id = call.data.split('_')[1]
|
||||||
|
courses = Categories.getTopCoursesByCategory(category_id)
|
||||||
|
|
||||||
|
markup = types.InlineKeyboardMarkup()
|
||||||
|
if courses:
|
||||||
|
for course in courses:
|
||||||
|
markup.add(types.InlineKeyboardButton(
|
||||||
|
course[1], callback_data=f"course_{course[0]}"))
|
||||||
|
bot.send_message(
|
||||||
|
call.message.chat.id, "📚 Top Courses in this Category:", reply_markup=markup)
|
||||||
|
else:
|
||||||
|
bot.send_message(
|
||||||
|
call.message.chat.id, "There are no courses in this category.", reply_markup=markup)
|
||||||
|
|
||||||
|
bot.answer_callback_query(call.id)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from telebot import TeleBot, types
|
from telebot import TeleBot, types
|
||||||
from database.models import Courses, Admins
|
from database.models import Courses, Admins
|
||||||
from bot.utils.crud_helpers import create_entity_markup
|
from bot.utils.crud_helpers import create_entity_markup
|
||||||
from bot.utils.formatters import format_course_info
|
from bot.utils.formatters import format_course_info, format_course_review
|
||||||
from bot.handlers.start import startMarkup
|
from bot.handlers.start import startMarkup
|
||||||
|
|
||||||
# TODO: Add Option for selecting Teacher in a more user-friendly way (name instead of ID) + pagination for it.
|
# TODO: Add Option for selecting Teacher in a more user-friendly way (name instead of ID) + pagination for it.
|
||||||
@@ -16,9 +16,9 @@ def register(bot: TeleBot):
|
|||||||
COURSE_FIELDS = [
|
COURSE_FIELDS = [
|
||||||
('name', "the course's name"),
|
('name', "the course's name"),
|
||||||
('teacher_id', "The course's Teacher ID"),
|
('teacher_id', "The course's Teacher ID"),
|
||||||
('description', "description (<Optional>)"),
|
('description', "description (<Optional>) ('s' to skip)"),
|
||||||
('language', "language ('english', 'spanish', 'german', 'french', 'persian')"),
|
('language', "language ('english', 'spanish', 'german', 'french', 'persian')"),
|
||||||
('difficulty', "difficulty (<Optional>) ('beginner', 'intermediate', 'expert')"),
|
('difficulty', "difficulty (<Optional>) ('s' to skip) ('beginner', 'intermediate', 'expert')"),
|
||||||
]
|
]
|
||||||
EDITABLE_FIELDS = {
|
EDITABLE_FIELDS = {
|
||||||
'name': 1,
|
'name': 1,
|
||||||
@@ -35,12 +35,15 @@ def register(bot: TeleBot):
|
|||||||
|
|
||||||
if not course:
|
if not course:
|
||||||
bot.send_message(call.message.chat.id, "Course not found.")
|
bot.send_message(call.message.chat.id, "Course not found.")
|
||||||
|
bot.answer_callback_query(call.id)
|
||||||
return
|
return
|
||||||
|
|
||||||
details = format_course_info(course)
|
details = format_course_info(course)
|
||||||
markup = create_entity_markup("course", course_id, True)
|
markup = create_entity_markup("course", course_id, True)
|
||||||
markup.add(types.InlineKeyboardButton(
|
markup.add(types.InlineKeyboardButton(
|
||||||
f"👨🏫 Teacher's Info", callback_data=f"teacher_{course[3]}"))
|
f"👨🏫 Teacher's Info", callback_data=f"teacher_{course[3]}"))
|
||||||
|
markup.add(types.InlineKeyboardButton(
|
||||||
|
f" 📰 Course Reviews", callback_data=f"courseReviews_{course_id}"))
|
||||||
|
|
||||||
bot.send_message(call.message.chat.id, details,
|
bot.send_message(call.message.chat.id, details,
|
||||||
reply_markup=markup, parse_mode="HTML")
|
reply_markup=markup, parse_mode="HTML")
|
||||||
@@ -60,7 +63,8 @@ def register(bot: TeleBot):
|
|||||||
# Save previous field
|
# Save previous field
|
||||||
if step > 0:
|
if step > 0:
|
||||||
field_name = COURSE_FIELDS[step - 1][0]
|
field_name = COURSE_FIELDS[step - 1][0]
|
||||||
data[field_name] = message.text
|
data[field_name] = None if (
|
||||||
|
(field_name in ['description', 'difficulty']) and message.text == 's') else message.text
|
||||||
|
|
||||||
# Done collecting?
|
# Done collecting?
|
||||||
if step >= len(COURSE_FIELDS):
|
if step >= len(COURSE_FIELDS):
|
||||||
@@ -167,3 +171,19 @@ def register(bot: TeleBot):
|
|||||||
bot.send_message(call.message.chat.id, "❌ Failed to delete Course.",
|
bot.send_message(call.message.chat.id, "❌ Failed to delete Course.",
|
||||||
reply_markup=startMarkup())
|
reply_markup=startMarkup())
|
||||||
bot.answer_callback_query(call.id)
|
bot.answer_callback_query(call.id)
|
||||||
|
|
||||||
|
# Show Some of the course reviews
|
||||||
|
@bot.callback_query_handler(func=lambda call: call.data.startswith('courseReviews_'))
|
||||||
|
def show_course_reviews(call):
|
||||||
|
course_id = call.data.split('_')[1]
|
||||||
|
reviews = Courses.getCourseReviews(course_id)
|
||||||
|
|
||||||
|
if reviews:
|
||||||
|
for rev in reviews:
|
||||||
|
details = format_course_review(rev)
|
||||||
|
bot.send_message(call.message.chat.id,
|
||||||
|
details, parse_mode="HTML")
|
||||||
|
else:
|
||||||
|
bot.send_message(call.message.chat.id, "No reviews yet")
|
||||||
|
|
||||||
|
bot.answer_callback_query(call.id)
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
from . import students, teachers, courses, tags, categories
|
from . import students, teachers, courses, tags, categories
|
||||||
from bot.handlers.start import startMarkup
|
from bot.handlers.start import startMarkup
|
||||||
|
|
||||||
|
# TODO: Create a new entity for Reviews (Change The Corresponding codes from courses and students to use that)
|
||||||
|
# TODO: Add Option for handling all updates at once.
|
||||||
|
|
||||||
|
|
||||||
def register_all_callbacks(bot):
|
def register_all_callbacks(bot):
|
||||||
"""Register all callback handlers"""
|
"""Register all callback handlers"""
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from telebot import types, TeleBot
|
from telebot import types, TeleBot
|
||||||
from database.models import Students, Admins
|
from database.models import Students, Admins
|
||||||
from bot.utils.formatters import format_student_info
|
from bot.utils.formatters import format_course_review, format_student_info
|
||||||
from bot.utils.crud_helpers import create_entity_markup
|
from bot.utils.crud_helpers import create_entity_markup
|
||||||
from bot.handlers.start import startMarkup
|
from bot.handlers.start import startMarkup
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ def register(bot: TeleBot):
|
|||||||
STUDENT_FIELDS = [
|
STUDENT_FIELDS = [
|
||||||
('name', "the student's name"),
|
('name', "the student's name"),
|
||||||
('email', "email"),
|
('email', "email"),
|
||||||
('phone_number', "phone (<Optional>)"),
|
('phone_number', "phone (<Optional>) ('s' to skip)"),
|
||||||
('password', "password"),
|
('password', "password"),
|
||||||
('username', "username"),
|
('username', "username"),
|
||||||
('birthday', "birthday (YY/MM/DD)"),
|
('birthday', "birthday (YY/MM/DD)"),
|
||||||
@@ -41,6 +41,10 @@ def register(bot: TeleBot):
|
|||||||
if student:
|
if student:
|
||||||
details = format_student_info(student)
|
details = format_student_info(student)
|
||||||
markup = create_entity_markup("student", student_id, True)
|
markup = create_entity_markup("student", student_id, True)
|
||||||
|
markup.add(types.InlineKeyboardButton("📚 Show Courses 🔒",
|
||||||
|
callback_data=f"studentCourses_{student_id}"))
|
||||||
|
markup.add(types.InlineKeyboardButton("📰 Reviews By Student 🔒",
|
||||||
|
callback_data=f"studentReviews_{student_id}"))
|
||||||
|
|
||||||
bot.send_message(call.message.chat.id, details,
|
bot.send_message(call.message.chat.id, details,
|
||||||
reply_markup=markup, parse_mode="HTML")
|
reply_markup=markup, parse_mode="HTML")
|
||||||
@@ -67,7 +71,8 @@ def register(bot: TeleBot):
|
|||||||
# Save previous field
|
# Save previous field
|
||||||
if step > 0:
|
if step > 0:
|
||||||
field_name = STUDENT_FIELDS[step - 1][0]
|
field_name = STUDENT_FIELDS[step - 1][0]
|
||||||
data[field_name] = message.text
|
data[field_name] = None if (
|
||||||
|
(field_name == 'phone_number') and message.text == 's') else message.text
|
||||||
|
|
||||||
# Done collecting?
|
# Done collecting?
|
||||||
if step >= len(STUDENT_FIELDS):
|
if step >= len(STUDENT_FIELDS):
|
||||||
@@ -174,3 +179,50 @@ def register(bot: TeleBot):
|
|||||||
bot.send_message(call.message.chat.id, "❌ Failed to delete Student.",
|
bot.send_message(call.message.chat.id, "❌ Failed to delete Student.",
|
||||||
reply_markup=startMarkup())
|
reply_markup=startMarkup())
|
||||||
bot.answer_callback_query(call.id)
|
bot.answer_callback_query(call.id)
|
||||||
|
|
||||||
|
# Showing Courses a Students has enrolled in
|
||||||
|
@bot.callback_query_handler(func=lambda call: call.data.startswith("studentCourses_"))
|
||||||
|
def show_student_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
|
||||||
|
|
||||||
|
student_id = call.data.split('_')[1]
|
||||||
|
courses = Students.getStudentCourses(student_id)
|
||||||
|
|
||||||
|
if courses:
|
||||||
|
markup = types.InlineKeyboardMarkup(row_width=1)
|
||||||
|
for c in courses:
|
||||||
|
markup.add(types.InlineKeyboardButton(
|
||||||
|
f"{c[1][:26] + "..."} | id#{c[0]}", callback_data=f"course_{c[0]}")
|
||||||
|
)
|
||||||
|
|
||||||
|
bot.send_message(call.message.chat.id,
|
||||||
|
"Courses: ", reply_markup=markup)
|
||||||
|
else:
|
||||||
|
bot.send_message(call.message.chat.id, "No courses.")
|
||||||
|
|
||||||
|
bot.answer_callback_query(call.id)
|
||||||
|
|
||||||
|
# Showing Reviews a Students has written
|
||||||
|
@bot.callback_query_handler(func=lambda call: call.data.startswith("studentReviews_"))
|
||||||
|
def show_student_reviews(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]
|
||||||
|
reviews = Students.getStudentReviews(student_id)
|
||||||
|
|
||||||
|
if reviews:
|
||||||
|
for rev in reviews:
|
||||||
|
details = format_course_review(rev)
|
||||||
|
bot.send_message(call.message.chat.id,
|
||||||
|
details, parse_mode="HTML")
|
||||||
|
|
||||||
|
else:
|
||||||
|
bot.send_message(call.message.chat.id, "No reviews.")
|
||||||
|
|
||||||
|
bot.answer_callback_query(call.id)
|
||||||
|
|||||||
+21
-2
@@ -3,8 +3,6 @@ from database.models import Tags, Admins
|
|||||||
from bot.handlers.start import startMarkup
|
from bot.handlers.start import startMarkup
|
||||||
from bot.utils.crud_helpers import create_entity_markup
|
from bot.utils.crud_helpers import create_entity_markup
|
||||||
|
|
||||||
# TODO: Add Option for showing courses with specific tags.
|
|
||||||
|
|
||||||
|
|
||||||
def register(bot: TeleBot):
|
def register(bot: TeleBot):
|
||||||
cancelMarkup = types.InlineKeyboardMarkup()
|
cancelMarkup = types.InlineKeyboardMarkup()
|
||||||
@@ -35,6 +33,8 @@ def register(bot: TeleBot):
|
|||||||
"""
|
"""
|
||||||
details.strip()
|
details.strip()
|
||||||
markup = create_entity_markup("tag", tag_id, True)
|
markup = create_entity_markup("tag", tag_id, True)
|
||||||
|
markup.add(types.InlineKeyboardButton("📁 Show Top Courses",
|
||||||
|
callback_data=f"tagCourses_{tag_id}"))
|
||||||
|
|
||||||
bot.send_message(call.message.chat.id, details,
|
bot.send_message(call.message.chat.id, details,
|
||||||
reply_markup=markup, parse_mode="HTML")
|
reply_markup=markup, parse_mode="HTML")
|
||||||
@@ -163,3 +163,22 @@ def register(bot: TeleBot):
|
|||||||
bot.send_message(call.message.chat.id, "❌ Failed to delete Tag.",
|
bot.send_message(call.message.chat.id, "❌ Failed to delete Tag.",
|
||||||
reply_markup=startMarkup())
|
reply_markup=startMarkup())
|
||||||
bot.answer_callback_query(call.id)
|
bot.answer_callback_query(call.id)
|
||||||
|
|
||||||
|
# Show Top Courses with a Tag
|
||||||
|
@bot.callback_query_handler(func=lambda call: call.data.startswith('tagCourses_'))
|
||||||
|
def delete_tag(call):
|
||||||
|
tag_id = call.data.split('_')[1]
|
||||||
|
courses = Tags.getTopCoursesByTag(tag_id)
|
||||||
|
|
||||||
|
markup = types.InlineKeyboardMarkup()
|
||||||
|
if courses:
|
||||||
|
for course in courses:
|
||||||
|
markup.add(types.InlineKeyboardButton(
|
||||||
|
course[1], callback_data=f"course_{course[0]}"))
|
||||||
|
bot.send_message(
|
||||||
|
call.message.chat.id, "📚 Top Courses with this Tag:", reply_markup=markup)
|
||||||
|
else:
|
||||||
|
bot.send_message(
|
||||||
|
call.message.chat.id, "There are no courses with this Tag.", reply_markup=markup)
|
||||||
|
|
||||||
|
bot.answer_callback_query(call.id)
|
||||||
|
|||||||
@@ -4,11 +4,6 @@ from bot.utils.formatters import format_teacher_info
|
|||||||
from bot.handlers.start import startMarkup
|
from bot.handlers.start import startMarkup
|
||||||
from bot.utils.crud_helpers import create_entity_markup
|
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 Option for seeing courses taught by a teacher in Teacher Details.
|
|
||||||
|
|
||||||
|
|
||||||
def register(bot: TeleBot):
|
def register(bot: TeleBot):
|
||||||
cancelMarkup = types.InlineKeyboardMarkup()
|
cancelMarkup = types.InlineKeyboardMarkup()
|
||||||
@@ -18,7 +13,7 @@ def register(bot: TeleBot):
|
|||||||
TEACHER_FIELDS = [
|
TEACHER_FIELDS = [
|
||||||
('name', "the teacher's name"),
|
('name', "the teacher's name"),
|
||||||
('email', "email"),
|
('email', "email"),
|
||||||
('phone_number', "phone (<Optional>)"),
|
('phone_number', "phone (<Optional>) ('s' to skip)"),
|
||||||
('password', "password"),
|
('password', "password"),
|
||||||
('username', "username"),
|
('username', "username"),
|
||||||
('birthday', "birthday (YY/MM/DD)"),
|
('birthday', "birthday (YY/MM/DD)"),
|
||||||
@@ -71,7 +66,8 @@ def register(bot: TeleBot):
|
|||||||
# Save previous field
|
# Save previous field
|
||||||
if step > 0:
|
if step > 0:
|
||||||
field_name = TEACHER_FIELDS[step - 1][0]
|
field_name = TEACHER_FIELDS[step - 1][0]
|
||||||
data[field_name] = message.text
|
data[field_name] = None if (
|
||||||
|
(field_name == 'phone_number') and message.text == 's') else message.text
|
||||||
|
|
||||||
# Done collecting?
|
# Done collecting?
|
||||||
if step >= len(TEACHER_FIELDS):
|
if step >= len(TEACHER_FIELDS):
|
||||||
|
|||||||
@@ -6,6 +6,6 @@ def create_entity_markup(entity_name, enitity_id, is_auth_needed=False):
|
|||||||
markup.add(types.InlineKeyboardButton(
|
markup.add(types.InlineKeyboardButton(
|
||||||
f"✏️ Edit {entity_name} " + ("🔒" if is_auth_needed else ""), 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(
|
markup.add(types.InlineKeyboardButton(
|
||||||
f"🗑️ Delete {entity_name}" + ("🔒" if is_auth_needed else ""), 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
|
return markup
|
||||||
|
|||||||
+30
-2
@@ -115,11 +115,11 @@ def format_teacher_info(teacher):
|
|||||||
def format_course_info(course):
|
def format_course_info(course):
|
||||||
"""
|
"""
|
||||||
Format course data into a nice message
|
Format course data into a nice message
|
||||||
course is a tuple: (id, name, created_at, teacher_id, updated_at, description, difficulty, language)
|
course is a tuple: (id, name, created_at, teacher_id, updated_at, description, difficulty, language, avgerage_rate)
|
||||||
"""
|
"""
|
||||||
# print(course)
|
# print(course)
|
||||||
|
|
||||||
id, name, created_at, teacher_id, updated_at, description, difficulty, language = course
|
id, name, created_at, teacher_id, updated_at, description, difficulty, language, avgerage_rate = course
|
||||||
|
|
||||||
# Format dates nicely
|
# Format dates nicely
|
||||||
created_date = format_date(created_at)
|
created_date = format_date(created_at)
|
||||||
@@ -129,6 +129,7 @@ def format_course_info(course):
|
|||||||
<b>📚 Course Profile</b>
|
<b>📚 Course Profile</b>
|
||||||
|
|
||||||
<b>Name:</b> {name}
|
<b>Name:</b> {name}
|
||||||
|
<b>⭐️ Average Rating:</b> {avgerage_rate}
|
||||||
<b>Description:</b>
|
<b>Description:</b>
|
||||||
{description}
|
{description}
|
||||||
|
|
||||||
@@ -141,3 +142,30 @@ def format_course_info(course):
|
|||||||
<b>ID:</b> <code>{id}</code>
|
<b>ID:</b> <code>{id}</code>
|
||||||
"""
|
"""
|
||||||
return details.strip()
|
return details.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def format_course_review(review):
|
||||||
|
"""
|
||||||
|
Formate course review into a nice message
|
||||||
|
:param review: tuple(body: str, rate: int, student_name: str, course_name: str, created_at: date, updated_at: date)
|
||||||
|
"""
|
||||||
|
|
||||||
|
reviewBody, rate, student_name, course_name, created_at, updated_at = review
|
||||||
|
|
||||||
|
created_date = format_date(created_at)
|
||||||
|
updated_at = format_date(updated_at)
|
||||||
|
|
||||||
|
details = f"""
|
||||||
|
<b>✉️ Review</b>
|
||||||
|
|
||||||
|
<b>rate:</b> {'⭐️' * rate}
|
||||||
|
<b>Body:</b>\n{reviewBody}
|
||||||
|
|
||||||
|
<b>Course name:</b> {course_name}
|
||||||
|
<b>Student name:</b> {student_name}
|
||||||
|
|
||||||
|
<b>📅 Review Info</b>
|
||||||
|
<b>Created At:</b> {created_date}
|
||||||
|
<b>Last Updated At:</b> {updated_at}
|
||||||
|
"""
|
||||||
|
return details.strip()
|
||||||
|
|||||||
@@ -5,21 +5,18 @@ from psycopg2 import pool
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Connection pool for reusing connections (important for serverless!)
|
# Connection pool for reusing connections
|
||||||
_connection_pool = None
|
_connection_pool = None
|
||||||
|
|
||||||
|
|
||||||
def get_connection_pool():
|
def get_connection_pool():
|
||||||
"""Create a connection pool"""
|
"""Create a connection pool"""
|
||||||
global _connection_pool
|
global _connection_pool
|
||||||
if _connection_pool is None:
|
if _connection_pool is None:
|
||||||
try:
|
try:
|
||||||
# --- Production settings
|
DATABASE_URL = os.environ.get("DATABASE_URL")
|
||||||
# DATABASE_URL = os.environ.get("DATABASE_URL")
|
_connection_pool = psycopg2.pool.SimpleConnectionPool(
|
||||||
# _connection_pool = psycopg2.pool.SimpleConnectionPool(1, 5, DATABASE_URL);
|
1, 5, DATABASE_URL)
|
||||||
|
|
||||||
# --- Development settings
|
|
||||||
dev_url = "postgresql://postgres:123@localhost:5432/OLP"
|
|
||||||
_connection_pool = psycopg2.pool.SimpleConnectionPool(1, 5, dev_url);
|
|
||||||
|
|
||||||
logger.info("Database connection pool created")
|
logger.info("Database connection pool created")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -27,6 +24,7 @@ def get_connection_pool():
|
|||||||
return None
|
return None
|
||||||
return _connection_pool
|
return _connection_pool
|
||||||
|
|
||||||
|
|
||||||
def get_db_connection():
|
def get_db_connection():
|
||||||
"""Get a connection from the pool"""
|
"""Get a connection from the pool"""
|
||||||
try:
|
try:
|
||||||
@@ -39,6 +37,7 @@ def get_db_connection():
|
|||||||
logger.error(f"Database connection error: {e}")
|
logger.error(f"Database connection error: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def release_db_connection(conn):
|
def release_db_connection(conn):
|
||||||
"""Return connection to the pool"""
|
"""Return connection to the pool"""
|
||||||
try:
|
try:
|
||||||
|
|||||||
+141
-2
@@ -227,6 +227,44 @@ class Students(BaseRepository):
|
|||||||
|
|
||||||
return cls._execute_mutation(mutation, "deleteStudent")
|
return cls._execute_mutation(mutation, "deleteStudent")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def getStudentCourses(cls, student_id):
|
||||||
|
def query(cursor):
|
||||||
|
cursor.execute(
|
||||||
|
f"""
|
||||||
|
SELECT
|
||||||
|
c.id,
|
||||||
|
c.name
|
||||||
|
FROM courses c
|
||||||
|
JOIN course_enrolments ce ON ce.course_id = c.id
|
||||||
|
JOIN students s ON s.id = ce.student_id
|
||||||
|
WHERE s.id = %s
|
||||||
|
""", (student_id,)
|
||||||
|
)
|
||||||
|
result = cursor.fetchall()
|
||||||
|
return result if result else None
|
||||||
|
|
||||||
|
return cls._execute_query(query, 'getStudentCourses')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def getStudentReviews(cls, student_id):
|
||||||
|
def query(cursor):
|
||||||
|
cursor.execute(
|
||||||
|
f"""
|
||||||
|
SELECT r.body review, r.rate, s.name student_name, c.name course_name, r.created_at, r.updated_at
|
||||||
|
FROM reviews r
|
||||||
|
JOIN course_enrolments ce ON ce.id = r.enrolment_id
|
||||||
|
JOIN students s ON s.id = ce.student_id
|
||||||
|
JOIN courses c ON c.id = ce.course_id
|
||||||
|
WHERE s.id = %s
|
||||||
|
LIMIT (3)
|
||||||
|
""", (student_id,)
|
||||||
|
)
|
||||||
|
result = cursor.fetchall()
|
||||||
|
return result if result else None
|
||||||
|
|
||||||
|
return cls._execute_query(query, 'getStudentReviews')
|
||||||
|
|
||||||
|
|
||||||
class Teachers(BaseRepository):
|
class Teachers(BaseRepository):
|
||||||
table_name = "teachers"
|
table_name = "teachers"
|
||||||
@@ -302,7 +340,31 @@ class Courses(BaseRepository):
|
|||||||
def getCourseById(cls, course_id):
|
def getCourseById(cls, course_id):
|
||||||
def query(cursor):
|
def query(cursor):
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
f"SELECT id, name, created_at, teacher_id, updated_at, description, difficulty, language FROM {cls.table_name} WHERE id = %s", (course_id, ))
|
f"""
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
created_at,
|
||||||
|
teacher_id,
|
||||||
|
updated_at,
|
||||||
|
description,
|
||||||
|
difficulty,
|
||||||
|
language,
|
||||||
|
r.avg_rate
|
||||||
|
FROM
|
||||||
|
courses
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT
|
||||||
|
ce.course_id,
|
||||||
|
ROUND(AVG(r.rate), 2) avg_rate
|
||||||
|
FROM course_enrolments ce
|
||||||
|
JOIN reviews r ON r.enrolment_id = ce.id
|
||||||
|
GROUP BY ce.course_id
|
||||||
|
) r ON r.course_id = courses.id
|
||||||
|
WHERE
|
||||||
|
id = %s
|
||||||
|
|
||||||
|
""", (course_id, ))
|
||||||
result = cursor.fetchall()
|
result = cursor.fetchall()
|
||||||
return result[0] if result else None
|
return result[0] if result else None
|
||||||
|
|
||||||
@@ -339,6 +401,23 @@ class Courses(BaseRepository):
|
|||||||
|
|
||||||
return cls._execute_mutation(mutation, "deleteCourse")
|
return cls._execute_mutation(mutation, "deleteCourse")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def getCourseReviews(cls, course_id):
|
||||||
|
def query(cursor):
|
||||||
|
cursor.execute(f"""
|
||||||
|
SELECT r.body review, r.rate, s.name student_name, c.name course_name, r.created_at, r.updated_at
|
||||||
|
FROM reviews r
|
||||||
|
JOIN course_enrolments ce ON ce.id = r.enrolment_id
|
||||||
|
JOIN courses c ON c.id = ce.course_id
|
||||||
|
JOIN students s ON s.id = ce.student_id
|
||||||
|
WHERE c.id = %s
|
||||||
|
LIMIT (3)
|
||||||
|
""", (course_id,))
|
||||||
|
result = cursor.fetchall()
|
||||||
|
return result if result else None
|
||||||
|
|
||||||
|
return cls._execute_query(query, 'getCourseReviews')
|
||||||
|
|
||||||
|
|
||||||
class Tags(BaseRepository):
|
class Tags(BaseRepository):
|
||||||
table_name = "tags"
|
table_name = "tags"
|
||||||
@@ -393,6 +472,36 @@ class Tags(BaseRepository):
|
|||||||
|
|
||||||
return cls._execute_mutation(mutation, "deleteTag")
|
return cls._execute_mutation(mutation, "deleteTag")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def getTopCoursesByTag(cls, tag_id):
|
||||||
|
def query(cursor):
|
||||||
|
cursor.execute(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
cs.id,
|
||||||
|
cs.name course_name,
|
||||||
|
avg(r.rate) avg_rate
|
||||||
|
FROM
|
||||||
|
tags t
|
||||||
|
JOIN course_tags ct ON t.id = ct.tag_id
|
||||||
|
JOIN courses cs ON cs.id = ct.course_id
|
||||||
|
JOIN course_enrolments ce ON cs.id = ce.course_id
|
||||||
|
JOIN reviews r ON ce.id = r.enrolment_id
|
||||||
|
WHERE
|
||||||
|
t.id = %s
|
||||||
|
GROUP BY
|
||||||
|
cs.id,
|
||||||
|
cs.name
|
||||||
|
ORDER BY
|
||||||
|
avg_rate DESC
|
||||||
|
LIMIT
|
||||||
|
(5)
|
||||||
|
""", (tag_id, ))
|
||||||
|
result = cursor.fetchall()
|
||||||
|
return result if result else None
|
||||||
|
|
||||||
|
return cls._execute_query(query, "getTopCoursesByTag")
|
||||||
|
|
||||||
|
|
||||||
class Categories(BaseRepository):
|
class Categories(BaseRepository):
|
||||||
table_name = "categories"
|
table_name = "categories"
|
||||||
@@ -408,7 +517,7 @@ class Categories(BaseRepository):
|
|||||||
return cls._execute_query(query, "getAllCategories")
|
return cls._execute_query(query, "getAllCategories")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def getCategorieById(cls, category_id):
|
def getCategoryById(cls, category_id):
|
||||||
def query(cursor):
|
def query(cursor):
|
||||||
cursor.execute(f"""
|
cursor.execute(f"""
|
||||||
SELECT c1.id, c1.name, c1.description,
|
SELECT c1.id, c1.name, c1.description,
|
||||||
@@ -452,3 +561,33 @@ class Categories(BaseRepository):
|
|||||||
f"DELETE FROM {cls.table_name} WHERE id = %s", (category_id,))
|
f"DELETE FROM {cls.table_name} WHERE id = %s", (category_id,))
|
||||||
|
|
||||||
return cls._execute_mutation(mutation, "deleteCategory")
|
return cls._execute_mutation(mutation, "deleteCategory")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def getTopCoursesByCategory(cls, category_id):
|
||||||
|
def query(cursor):
|
||||||
|
cursor.execute(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
cs.id,
|
||||||
|
cs.name course_name,
|
||||||
|
avg(r.rate) avg_rate
|
||||||
|
FROM
|
||||||
|
categories c
|
||||||
|
JOIN course_categories cc ON c.id = cc.category_id
|
||||||
|
JOIN courses cs ON cc.course_id = cs.id
|
||||||
|
JOIN course_enrolments ce ON cs.id = ce.course_id
|
||||||
|
JOIN reviews r ON ce.id = r.enrolment_id
|
||||||
|
WHERE
|
||||||
|
c.id = %s
|
||||||
|
GROUP BY
|
||||||
|
cs.id,
|
||||||
|
cs.name
|
||||||
|
ORDER BY
|
||||||
|
avg_rate DESC
|
||||||
|
LIMIT
|
||||||
|
(5)
|
||||||
|
""", (category_id, ))
|
||||||
|
result = cursor.fetchall()
|
||||||
|
return result if result else None
|
||||||
|
|
||||||
|
return cls._execute_query(query, "getTopCoursesByCategory")
|
||||||
|
|||||||
Reference in New Issue
Block a user