mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
778 lines
25 KiB
Python
778 lines
25 KiB
Python
from typing import Literal, List
|
|
from uuid import uuid4
|
|
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
|
|
from src.security.features_utils.usage import (
|
|
check_limits_with_usage,
|
|
decrease_feature_usage,
|
|
increase_feature_usage,
|
|
)
|
|
from src.services.trail.trail import get_user_trail_with_orgid
|
|
from src.db.resource_authors import ResourceAuthor, ResourceAuthorshipEnum, ResourceAuthorshipStatusEnum
|
|
from src.db.users import PublicUser, AnonymousUser, User, UserRead
|
|
from src.db.courses.courses import (
|
|
Course,
|
|
CourseCreate,
|
|
CourseRead,
|
|
CourseUpdate,
|
|
FullCourseRead,
|
|
AuthorWithRole,
|
|
ThumbnailType,
|
|
)
|
|
from src.security.rbac.rbac import (
|
|
authorization_verify_based_on_roles_and_authorship,
|
|
authorization_verify_if_element_is_public,
|
|
authorization_verify_if_user_is_anon,
|
|
)
|
|
from src.services.courses.thumbnails import upload_thumbnail
|
|
from fastapi import HTTPException, Request, UploadFile
|
|
from datetime import datetime
|
|
import asyncio
|
|
|
|
|
|
async def get_course(
|
|
request: Request,
|
|
course_uuid: str,
|
|
current_user: PublicUser | AnonymousUser,
|
|
db_session: Session,
|
|
):
|
|
statement = select(Course).where(Course.course_uuid == course_uuid)
|
|
course = db_session.exec(statement).first()
|
|
|
|
if not course:
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail="Course not found",
|
|
)
|
|
|
|
# RBAC check
|
|
await rbac_check(request, course.course_uuid, current_user, "read", db_session)
|
|
|
|
# Get course authors with their roles
|
|
authors_statement = (
|
|
select(ResourceAuthor, User)
|
|
.join(User, ResourceAuthor.user_id == User.id)
|
|
.where(ResourceAuthor.resource_uuid == course.course_uuid)
|
|
.order_by(
|
|
ResourceAuthor.id.asc()
|
|
)
|
|
)
|
|
author_results = db_session.exec(authors_statement).all()
|
|
|
|
# Convert to AuthorWithRole objects
|
|
authors = [
|
|
AuthorWithRole(
|
|
user=UserRead.model_validate(user),
|
|
authorship=resource_author.authorship,
|
|
authorship_status=resource_author.authorship_status,
|
|
creation_date=resource_author.creation_date,
|
|
update_date=resource_author.update_date
|
|
)
|
|
for resource_author, user in author_results
|
|
]
|
|
|
|
course = CourseRead(**course.model_dump(), authors=authors)
|
|
|
|
return course
|
|
|
|
|
|
async def get_course_by_id(
|
|
request: Request,
|
|
course_id: str,
|
|
current_user: PublicUser | AnonymousUser,
|
|
db_session: Session,
|
|
):
|
|
statement = select(Course).where(Course.id == course_id)
|
|
course = db_session.exec(statement).first()
|
|
|
|
if not course:
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail="Course not found",
|
|
)
|
|
|
|
# RBAC check
|
|
await rbac_check(request, course.course_uuid, current_user, "read", db_session)
|
|
|
|
# Get course authors with their roles
|
|
authors_statement = (
|
|
select(ResourceAuthor, User)
|
|
.join(User, ResourceAuthor.user_id == User.id)
|
|
.where(ResourceAuthor.resource_uuid == course.course_uuid)
|
|
.order_by(
|
|
ResourceAuthor.id.asc()
|
|
)
|
|
)
|
|
author_results = db_session.exec(authors_statement).all()
|
|
|
|
# Convert to AuthorWithRole objects
|
|
authors = [
|
|
AuthorWithRole(
|
|
user=UserRead.model_validate(user),
|
|
authorship=resource_author.authorship,
|
|
authorship_status=resource_author.authorship_status,
|
|
creation_date=resource_author.creation_date,
|
|
update_date=resource_author.update_date
|
|
)
|
|
for resource_author, user in author_results
|
|
]
|
|
|
|
course = CourseRead(**course.model_dump(), authors=authors)
|
|
|
|
return course
|
|
|
|
|
|
async def get_course_meta(
|
|
request: Request,
|
|
course_uuid: str,
|
|
with_unpublished_activities: bool,
|
|
current_user: PublicUser | AnonymousUser,
|
|
db_session: Session,
|
|
) -> FullCourseRead:
|
|
# Avoid circular import
|
|
from src.services.courses.chapters import get_course_chapters
|
|
|
|
# Get course with authors in a single query using joins
|
|
course_statement = (
|
|
select(Course, ResourceAuthor, User)
|
|
.outerjoin(ResourceAuthor, ResourceAuthor.resource_uuid == Course.course_uuid) # type: ignore
|
|
.outerjoin(User, ResourceAuthor.user_id == User.id) # type: ignore
|
|
.where(Course.course_uuid == course_uuid)
|
|
.order_by(ResourceAuthor.id.asc()) # type: ignore
|
|
)
|
|
results = db_session.exec(course_statement).all()
|
|
|
|
if not results:
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail="Course not found",
|
|
)
|
|
|
|
# Extract course and authors from results
|
|
course = results[0][0] # First result's Course
|
|
author_results = [(ra, u) for _, ra, u in results if ra is not None and u is not None]
|
|
|
|
# RBAC check
|
|
await rbac_check(request, course.course_uuid, current_user, "read", db_session)
|
|
|
|
# Get course chapters
|
|
chapters = []
|
|
if course.id is not None:
|
|
chapters = await get_course_chapters(request, course.id, db_session, current_user, with_unpublished_activities)
|
|
|
|
# Convert to AuthorWithRole objects
|
|
authors = [
|
|
AuthorWithRole(
|
|
user=UserRead.model_validate(user),
|
|
authorship=resource_author.authorship,
|
|
authorship_status=resource_author.authorship_status,
|
|
creation_date=resource_author.creation_date,
|
|
update_date=resource_author.update_date
|
|
)
|
|
for resource_author, user in author_results
|
|
]
|
|
|
|
# Create course read model with chapters
|
|
course_read = FullCourseRead(
|
|
**course.model_dump(),
|
|
authors=authors,
|
|
chapters=chapters
|
|
)
|
|
|
|
return course_read
|
|
|
|
|
|
async def get_courses_orgslug(
|
|
request: Request,
|
|
current_user: PublicUser | AnonymousUser,
|
|
org_slug: 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)
|
|
)
|
|
|
|
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()
|
|
|
|
if not courses:
|
|
return []
|
|
|
|
# Get all course UUIDs
|
|
course_uuids = [course.course_uuid for course in courses]
|
|
|
|
# Fetch all authors for all courses in a single query
|
|
authors_query = (
|
|
select(ResourceAuthor, User)
|
|
.join(User, ResourceAuthor.user_id == User.id) # type: ignore
|
|
.where(ResourceAuthor.resource_uuid.in_(course_uuids)) # type: ignore
|
|
.order_by(
|
|
ResourceAuthor.id.asc()
|
|
)
|
|
)
|
|
|
|
author_results = db_session.exec(authors_query).all()
|
|
|
|
# Create a dictionary mapping course_uuid to list of authors
|
|
course_authors = {}
|
|
for resource_author, user in author_results:
|
|
if resource_author.resource_uuid not in course_authors:
|
|
course_authors[resource_author.resource_uuid] = []
|
|
course_authors[resource_author.resource_uuid].append(
|
|
AuthorWithRole(
|
|
user=UserRead.model_validate(user),
|
|
authorship=resource_author.authorship,
|
|
authorship_status=resource_author.authorship_status,
|
|
creation_date=resource_author.creation_date,
|
|
update_date=resource_author.update_date
|
|
)
|
|
)
|
|
|
|
# Create CourseRead objects with authors
|
|
course_reads = []
|
|
for course in courses:
|
|
course_read = CourseRead.model_validate({
|
|
"id": course.id or 0, # Ensure id is never None
|
|
"org_id": course.org_id,
|
|
"name": course.name,
|
|
"description": course.description or "",
|
|
"about": course.about or "",
|
|
"learnings": course.learnings or "",
|
|
"tags": course.tags or "",
|
|
"thumbnail_image": course.thumbnail_image or "",
|
|
"public": course.public,
|
|
"open_to_contributors": course.open_to_contributors,
|
|
"course_uuid": course.course_uuid,
|
|
"creation_date": course.creation_date,
|
|
"update_date": course.update_date,
|
|
"authors": course_authors.get(course.course_uuid, [])
|
|
})
|
|
course_reads.append(course_read)
|
|
|
|
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:
|
|
# Get course authors with their roles
|
|
authors_statement = (
|
|
select(ResourceAuthor, User)
|
|
.join(User, ResourceAuthor.user_id == User.id)
|
|
.where(ResourceAuthor.resource_uuid == course.course_uuid)
|
|
.order_by(
|
|
ResourceAuthor.id.asc()
|
|
)
|
|
)
|
|
author_results = db_session.exec(authors_statement).all()
|
|
|
|
# Convert to AuthorWithRole objects
|
|
authors = [
|
|
AuthorWithRole(
|
|
user=UserRead.model_validate(user),
|
|
authorship=resource_author.authorship,
|
|
authorship_status=resource_author.authorship_status,
|
|
creation_date=resource_author.creation_date,
|
|
update_date=resource_author.update_date
|
|
)
|
|
for resource_author, user in author_results
|
|
]
|
|
|
|
course_read = CourseRead.model_validate({
|
|
"id": course.id or 0, # Ensure id is never None
|
|
"org_id": course.org_id,
|
|
"name": course.name,
|
|
"description": course.description or "",
|
|
"about": course.about or "",
|
|
"learnings": course.learnings or "",
|
|
"tags": course.tags or "",
|
|
"thumbnail_image": course.thumbnail_image or "",
|
|
"public": course.public,
|
|
"open_to_contributors": course.open_to_contributors,
|
|
"course_uuid": course.course_uuid,
|
|
"creation_date": course.creation_date,
|
|
"update_date": course.update_date,
|
|
"authors": authors
|
|
})
|
|
course_reads.append(course_read)
|
|
|
|
return course_reads
|
|
|
|
|
|
async def create_course(
|
|
request: Request,
|
|
org_id: int,
|
|
course_object: CourseCreate,
|
|
current_user: PublicUser | AnonymousUser,
|
|
db_session: Session,
|
|
thumbnail_file: UploadFile | None = None,
|
|
thumbnail_type: ThumbnailType = ThumbnailType.IMAGE,
|
|
):
|
|
course = Course.model_validate(course_object)
|
|
|
|
# RBAC check
|
|
await rbac_check(request, "course_x", current_user, "create", db_session)
|
|
|
|
# Usage check
|
|
check_limits_with_usage("courses", org_id, db_session)
|
|
|
|
# Complete course object
|
|
course.org_id = course.org_id
|
|
|
|
# Get org uuid
|
|
org_statement = select(Organization).where(Organization.id == org_id)
|
|
org = db_session.exec(org_statement).first()
|
|
|
|
course.course_uuid = str(f"course_{uuid4()}")
|
|
course.creation_date = str(datetime.now())
|
|
course.update_date = str(datetime.now())
|
|
|
|
# Upload thumbnail
|
|
if thumbnail_file and thumbnail_file.filename:
|
|
name_in_disk = f"{course.course_uuid}_thumbnail_{uuid4()}.{thumbnail_file.filename.split('.')[-1]}"
|
|
await upload_thumbnail(
|
|
thumbnail_file, name_in_disk, org.org_uuid, course.course_uuid # type: ignore
|
|
)
|
|
if thumbnail_type == ThumbnailType.IMAGE:
|
|
course.thumbnail_image = name_in_disk
|
|
course.thumbnail_type = ThumbnailType.IMAGE
|
|
elif thumbnail_type == ThumbnailType.VIDEO:
|
|
course.thumbnail_video = name_in_disk
|
|
course.thumbnail_type = ThumbnailType.VIDEO
|
|
else:
|
|
course.thumbnail_image = ""
|
|
course.thumbnail_video = ""
|
|
course.thumbnail_type = ThumbnailType.IMAGE
|
|
|
|
# Insert course
|
|
db_session.add(course)
|
|
db_session.commit()
|
|
db_session.refresh(course)
|
|
|
|
# Make the user the creator of the course
|
|
resource_author = ResourceAuthor(
|
|
resource_uuid=course.course_uuid,
|
|
user_id=current_user.id,
|
|
authorship=ResourceAuthorshipEnum.CREATOR,
|
|
authorship_status=ResourceAuthorshipStatusEnum.ACTIVE,
|
|
creation_date=str(datetime.now()),
|
|
update_date=str(datetime.now()),
|
|
)
|
|
|
|
# Insert course author
|
|
db_session.add(resource_author)
|
|
db_session.commit()
|
|
db_session.refresh(resource_author)
|
|
|
|
# Get course authors with their roles
|
|
authors_statement = (
|
|
select(ResourceAuthor, User)
|
|
.join(User, ResourceAuthor.user_id == User.id)
|
|
.where(ResourceAuthor.resource_uuid == course.course_uuid)
|
|
.order_by(
|
|
ResourceAuthor.id.asc()
|
|
)
|
|
)
|
|
author_results = db_session.exec(authors_statement).all()
|
|
|
|
# Convert to AuthorWithRole objects
|
|
authors = [
|
|
AuthorWithRole(
|
|
user=UserRead.model_validate(user),
|
|
authorship=resource_author.authorship,
|
|
authorship_status=resource_author.authorship_status,
|
|
creation_date=resource_author.creation_date,
|
|
update_date=resource_author.update_date
|
|
)
|
|
for resource_author, user in author_results
|
|
]
|
|
|
|
# Feature usage
|
|
increase_feature_usage("courses", course.org_id, db_session)
|
|
|
|
course = CourseRead(**course.model_dump(), authors=authors)
|
|
|
|
return CourseRead.model_validate(course)
|
|
|
|
|
|
async def update_course_thumbnail(
|
|
request: Request,
|
|
course_uuid: str,
|
|
current_user: PublicUser | AnonymousUser,
|
|
db_session: Session,
|
|
thumbnail_file: UploadFile | None = None,
|
|
thumbnail_type: ThumbnailType = ThumbnailType.IMAGE,
|
|
):
|
|
statement = select(Course).where(Course.course_uuid == course_uuid)
|
|
course = db_session.exec(statement).first()
|
|
|
|
name_in_disk = None
|
|
|
|
if not course:
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail="Course not found",
|
|
)
|
|
|
|
# RBAC check
|
|
await rbac_check(request, course.course_uuid, current_user, "update", db_session)
|
|
|
|
# Get org uuid
|
|
org_statement = select(Organization).where(Organization.id == course.org_id)
|
|
org = db_session.exec(org_statement).first()
|
|
|
|
# Upload thumbnail
|
|
if thumbnail_file and thumbnail_file.filename:
|
|
name_in_disk = f"{course_uuid}_thumbnail_{uuid4()}.{thumbnail_file.filename.split('.')[-1]}"
|
|
await upload_thumbnail(
|
|
thumbnail_file, name_in_disk, org.org_uuid, course.course_uuid # type: ignore
|
|
)
|
|
|
|
# Update course
|
|
if name_in_disk:
|
|
if thumbnail_type == ThumbnailType.IMAGE:
|
|
course.thumbnail_image = name_in_disk
|
|
course.thumbnail_type = ThumbnailType.IMAGE if not course.thumbnail_video else ThumbnailType.BOTH
|
|
elif thumbnail_type == ThumbnailType.VIDEO:
|
|
course.thumbnail_video = name_in_disk
|
|
course.thumbnail_type = ThumbnailType.VIDEO if not course.thumbnail_image else ThumbnailType.BOTH
|
|
else:
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail="Issue with thumbnail upload",
|
|
)
|
|
|
|
# Complete the course object
|
|
course.update_date = str(datetime.now())
|
|
|
|
db_session.add(course)
|
|
db_session.commit()
|
|
db_session.refresh(course)
|
|
|
|
# Get course authors with their roles
|
|
authors_statement = (
|
|
select(ResourceAuthor, User)
|
|
.join(User, ResourceAuthor.user_id == User.id)
|
|
.where(ResourceAuthor.resource_uuid == course.course_uuid)
|
|
.order_by(
|
|
ResourceAuthor.id.asc()
|
|
)
|
|
)
|
|
author_results = db_session.exec(authors_statement).all()
|
|
|
|
# Convert to AuthorWithRole objects
|
|
authors = [
|
|
AuthorWithRole(
|
|
user=UserRead.model_validate(user),
|
|
authorship=resource_author.authorship,
|
|
authorship_status=resource_author.authorship_status,
|
|
creation_date=resource_author.creation_date,
|
|
update_date=resource_author.update_date
|
|
)
|
|
for resource_author, user in author_results
|
|
]
|
|
|
|
course = CourseRead(**course.model_dump(), authors=authors)
|
|
|
|
return course
|
|
|
|
|
|
async def update_course(
|
|
request: Request,
|
|
course_object: CourseUpdate,
|
|
course_uuid: str,
|
|
current_user: PublicUser | AnonymousUser,
|
|
db_session: Session,
|
|
):
|
|
statement = select(Course).where(Course.course_uuid == course_uuid)
|
|
course = db_session.exec(statement).first()
|
|
|
|
if not course:
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail="Course not found",
|
|
)
|
|
|
|
# RBAC check
|
|
await rbac_check(request, course.course_uuid, current_user, "update", db_session)
|
|
|
|
# Update only the fields that were passed in
|
|
for var, value in vars(course_object).items():
|
|
if value is not None:
|
|
setattr(course, var, value)
|
|
|
|
# Complete the course object
|
|
course.update_date = str(datetime.now())
|
|
|
|
db_session.add(course)
|
|
db_session.commit()
|
|
db_session.refresh(course)
|
|
|
|
# Get course authors with their roles
|
|
authors_statement = (
|
|
select(ResourceAuthor, User)
|
|
.join(User, ResourceAuthor.user_id == User.id)
|
|
.where(ResourceAuthor.resource_uuid == course.course_uuid)
|
|
.order_by(
|
|
ResourceAuthor.id.asc()
|
|
)
|
|
)
|
|
author_results = db_session.exec(authors_statement).all()
|
|
|
|
# Convert to AuthorWithRole objects
|
|
authors = [
|
|
AuthorWithRole(
|
|
user=UserRead.model_validate(user),
|
|
authorship=resource_author.authorship,
|
|
authorship_status=resource_author.authorship_status,
|
|
creation_date=resource_author.creation_date,
|
|
update_date=resource_author.update_date
|
|
)
|
|
for resource_author, user in author_results
|
|
]
|
|
|
|
course = CourseRead(**course.model_dump(), authors=authors)
|
|
|
|
return course
|
|
|
|
|
|
async def delete_course(
|
|
request: Request,
|
|
course_uuid: str,
|
|
current_user: PublicUser | AnonymousUser,
|
|
db_session: Session,
|
|
):
|
|
statement = select(Course).where(Course.course_uuid == course_uuid)
|
|
course = db_session.exec(statement).first()
|
|
|
|
if not course:
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail="Course not found",
|
|
)
|
|
|
|
# RBAC check
|
|
await rbac_check(request, course.course_uuid, current_user, "delete", db_session)
|
|
|
|
# Feature usage
|
|
decrease_feature_usage("courses", course.org_id, db_session)
|
|
|
|
db_session.delete(course)
|
|
db_session.commit()
|
|
|
|
return {"detail": "Course deleted"}
|
|
|
|
|
|
async def get_user_courses(
|
|
request: Request,
|
|
current_user: PublicUser | AnonymousUser,
|
|
user_id: int,
|
|
db_session: Session,
|
|
page: int = 1,
|
|
limit: int = 10,
|
|
) -> List[CourseRead]:
|
|
# Verify user is not anonymous
|
|
await authorization_verify_if_user_is_anon(current_user.id)
|
|
|
|
# Get all resource authors for the user
|
|
statement = select(ResourceAuthor).where(
|
|
and_(
|
|
ResourceAuthor.user_id == user_id,
|
|
ResourceAuthor.authorship_status == ResourceAuthorshipStatusEnum.ACTIVE
|
|
)
|
|
)
|
|
resource_authors = db_session.exec(statement).all()
|
|
|
|
# Extract course UUIDs from resource authors
|
|
course_uuids = [author.resource_uuid for author in resource_authors]
|
|
|
|
if not course_uuids:
|
|
return []
|
|
|
|
# Get courses with the extracted UUIDs
|
|
statement = select(Course).where(Course.course_uuid.in_(course_uuids))
|
|
|
|
# Apply pagination
|
|
statement = statement.offset((page - 1) * limit).limit(limit)
|
|
|
|
courses = db_session.exec(statement).all()
|
|
|
|
# Convert to CourseRead objects
|
|
result = []
|
|
for course in courses:
|
|
# Get authors for the course
|
|
authors_statement = select(ResourceAuthor).where(
|
|
ResourceAuthor.resource_uuid == course.course_uuid
|
|
)
|
|
authors = db_session.exec(authors_statement).all()
|
|
|
|
# Convert authors to AuthorWithRole objects
|
|
authors_with_role = []
|
|
for author in authors:
|
|
# Get user for the author
|
|
user_statement = select(User).where(User.id == author.user_id)
|
|
user = db_session.exec(user_statement).first()
|
|
|
|
if user:
|
|
authors_with_role.append(
|
|
AuthorWithRole(
|
|
user=UserRead.model_validate(user),
|
|
authorship=author.authorship,
|
|
authorship_status=author.authorship_status,
|
|
creation_date=author.creation_date,
|
|
update_date=author.update_date,
|
|
)
|
|
)
|
|
|
|
# Create CourseRead object
|
|
course_read = CourseRead.model_validate({
|
|
"id": course.id or 0, # Ensure id is never None
|
|
"org_id": course.org_id,
|
|
"name": course.name,
|
|
"description": course.description or "",
|
|
"about": course.about or "",
|
|
"learnings": course.learnings or "",
|
|
"tags": course.tags or "",
|
|
"thumbnail_image": course.thumbnail_image or "",
|
|
"public": course.public,
|
|
"open_to_contributors": course.open_to_contributors,
|
|
"course_uuid": course.course_uuid,
|
|
"creation_date": course.creation_date,
|
|
"update_date": course.update_date,
|
|
"authors": authors_with_role
|
|
})
|
|
|
|
result.append(course_read)
|
|
|
|
return result
|
|
|
|
|
|
## 🔒 RBAC Utils ##
|
|
|
|
|
|
async def rbac_check(
|
|
request: Request,
|
|
course_uuid: str,
|
|
current_user: PublicUser | AnonymousUser,
|
|
action: Literal["create", "read", "update", "delete"],
|
|
db_session: Session,
|
|
):
|
|
if action == "read":
|
|
if current_user.id == 0: # Anonymous user
|
|
res = await authorization_verify_if_element_is_public(
|
|
request, course_uuid, action, db_session
|
|
)
|
|
return res
|
|
else:
|
|
res = (
|
|
await authorization_verify_based_on_roles_and_authorship(
|
|
request, current_user.id, action, course_uuid, db_session
|
|
)
|
|
)
|
|
return res
|
|
else:
|
|
await authorization_verify_if_user_is_anon(current_user.id)
|
|
|
|
await authorization_verify_based_on_roles_and_authorship(
|
|
request,
|
|
current_user.id,
|
|
action,
|
|
course_uuid,
|
|
db_session,
|
|
)
|
|
|
|
|
|
## 🔒 RBAC Utils ##
|