feat: add course search functionality with advanced filtering backend

This commit is contained in:
swve 2025-02-18 15:54:48 +01:00
parent 35c2fbc0f7
commit 01132c3745
2 changed files with 94 additions and 1 deletions

View file

@ -24,6 +24,7 @@ from src.services.courses.courses import (
update_course,
delete_course,
update_course_thumbnail,
search_courses,
)
from src.services.courses.updates import (
create_update,
@ -146,6 +147,24 @@ async def api_get_course_by_orgslug(
)
@router.get("/org_slug/{org_slug}/search")
async def api_search_courses(
request: Request,
org_slug: str,
query: str,
page: int = 1,
limit: int = 10,
db_session: Session = Depends(get_db_session),
current_user: PublicUser = Depends(get_current_user),
) -> List[CourseRead]:
"""
Search courses by title and description
"""
return await search_courses(
request, current_user, org_slug, query, db_session, page, limit
)
@router.put("/{course_uuid}")
async def api_update_course(
request: Request,

View file

@ -1,6 +1,6 @@
from typing import Literal, List
from uuid import uuid4
from sqlmodel import Session, select, or_, and_
from sqlmodel import Session, select, or_, and_, text
from src.db.usergroup_resources import UserGroupResource
from src.db.usergroup_user import UserGroupUser
from src.db.organizations import Organization
@ -214,6 +214,80 @@ async def get_courses_orgslug(
return course_reads
async def search_courses(
request: Request,
current_user: PublicUser | AnonymousUser,
org_slug: str,
search_query: str,
db_session: Session,
page: int = 1,
limit: int = 10,
) -> List[CourseRead]:
offset = (page - 1) * limit
# Base query
query = (
select(Course)
.join(Organization)
.where(Organization.slug == org_slug)
.where(
or_(
text(f"LOWER(course.name) LIKE LOWER('%{search_query}%')"),
text(f"LOWER(course.description) LIKE LOWER('%{search_query}%')"),
text(f"LOWER(course.about) LIKE LOWER('%{search_query}%')"),
text(f"LOWER(course.learnings) LIKE LOWER('%{search_query}%')"),
text(f"LOWER(course.tags) LIKE LOWER('%{search_query}%')")
)
)
)
if isinstance(current_user, AnonymousUser):
# For anonymous users, only show public courses
query = query.where(Course.public == True)
else:
# For authenticated users, show:
# 1. Public courses
# 2. Courses not in any UserGroup
# 3. Courses in UserGroups where the user is a member
# 4. Courses where the user is a resource author
query = (
query
.outerjoin(UserGroupResource, UserGroupResource.resource_uuid == Course.course_uuid) # type: ignore
.outerjoin(UserGroupUser, and_(
UserGroupUser.usergroup_id == UserGroupResource.usergroup_id,
UserGroupUser.user_id == current_user.id
))
.outerjoin(ResourceAuthor, ResourceAuthor.resource_uuid == Course.course_uuid) # type: ignore
.where(or_(
Course.public == True,
UserGroupResource.resource_uuid == None, # Courses not in any UserGroup # noqa: E711
UserGroupUser.user_id == current_user.id, # Courses in UserGroups where user is a member
ResourceAuthor.user_id == current_user.id # Courses where user is a resource author
))
)
# Apply pagination
query = query.offset(offset).limit(limit).distinct()
courses = db_session.exec(query).all()
# Fetch authors for each course
course_reads = []
for course in courses:
authors_query = (
select(User)
.join(ResourceAuthor, ResourceAuthor.user_id == User.id) # type: ignore
.where(ResourceAuthor.resource_uuid == course.course_uuid)
)
authors = db_session.exec(authors_query).all()
course_read = CourseRead.model_validate(course)
course_read.authors = [UserRead.model_validate(author) for author in authors]
course_reads.append(course_read)
return course_reads
async def create_course(
request: Request,
org_id: int,