mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: implement author roles in course management
This commit is contained in:
parent
5f302106a9
commit
4ab8f52b09
11 changed files with 623 additions and 96 deletions
|
|
@ -4,6 +4,15 @@ from sqlmodel import Field, SQLModel
|
|||
from src.db.users import UserRead
|
||||
from src.db.trails import TrailRead
|
||||
from src.db.courses.chapters import ChapterRead
|
||||
from src.db.resource_authors import ResourceAuthorshipEnum, ResourceAuthorshipStatusEnum
|
||||
|
||||
|
||||
class AuthorWithRole(SQLModel):
|
||||
user: UserRead
|
||||
authorship: ResourceAuthorshipEnum
|
||||
authorship_status: ResourceAuthorshipStatusEnum
|
||||
creation_date: str
|
||||
update_date: str
|
||||
|
||||
|
||||
class CourseBase(SQLModel):
|
||||
|
|
@ -41,10 +50,11 @@ class CourseUpdate(CourseBase):
|
|||
public: Optional[bool]
|
||||
open_to_contributors: Optional[bool]
|
||||
|
||||
|
||||
class CourseRead(CourseBase):
|
||||
id: int
|
||||
org_id: int = Field(default=None, foreign_key="organization.id")
|
||||
authors: Optional[List[UserRead]]
|
||||
authors: List[AuthorWithRole]
|
||||
course_uuid: str
|
||||
creation_date: str
|
||||
update_date: str
|
||||
|
|
@ -58,7 +68,7 @@ class FullCourseRead(CourseBase):
|
|||
update_date: Optional[str]
|
||||
# Chapters, Activities
|
||||
chapters: List[ChapterRead]
|
||||
authors: List[UserRead]
|
||||
authors: List[AuthorWithRole]
|
||||
pass
|
||||
|
||||
|
||||
|
|
@ -68,7 +78,7 @@ class FullCourseReadWithTrail(CourseBase):
|
|||
creation_date: Optional[str]
|
||||
update_date: Optional[str]
|
||||
org_id: int = Field(default=None, foreign_key="organization.id")
|
||||
authors: List[UserRead]
|
||||
authors: List[AuthorWithRole]
|
||||
# Chapters, Activities
|
||||
chapters: List[ChapterRead]
|
||||
# Trail
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ class ResourceAuthor(SQLModel, table=True):
|
|||
user_id: int = Field(
|
||||
sa_column=Column(Integer, ForeignKey("user.id", ondelete="CASCADE"))
|
||||
)
|
||||
authorship: ResourceAuthorshipEnum = ResourceAuthorshipEnum.CREATOR
|
||||
authorship_status: ResourceAuthorshipStatusEnum = ResourceAuthorshipStatusEnum.ACTIVE
|
||||
authorship: ResourceAuthorshipEnum
|
||||
authorship_status: ResourceAuthorshipStatusEnum
|
||||
creation_date: str = ""
|
||||
update_date: str = ""
|
||||
|
|
|
|||
|
|
@ -294,7 +294,7 @@ async def api_get_course_contributors(
|
|||
return await get_course_contributors(request, course_uuid, current_user, db_session)
|
||||
|
||||
|
||||
@router.put("/{course_uuid}/contributors/{contributor_id}")
|
||||
@router.put("/{course_uuid}/contributors/{contributor_user_id}")
|
||||
async def api_update_course_contributor(
|
||||
request: Request,
|
||||
course_uuid: str,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from src.security.features_utils.usage import (
|
|||
increase_feature_usage,
|
||||
)
|
||||
from src.services.trail.trail import get_user_trail_with_orgid
|
||||
from src.db.resource_authors import ResourceAuthor, ResourceAuthorshipEnum
|
||||
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,
|
||||
|
|
@ -18,6 +18,7 @@ from src.db.courses.courses import (
|
|||
CourseRead,
|
||||
CourseUpdate,
|
||||
FullCourseReadWithTrail,
|
||||
AuthorWithRole,
|
||||
)
|
||||
from src.security.rbac.rbac import (
|
||||
authorization_verify_based_on_roles_and_authorship,
|
||||
|
|
@ -48,16 +49,28 @@ async def get_course(
|
|||
# RBAC check
|
||||
await rbac_check(request, course.course_uuid, current_user, "read", db_session)
|
||||
|
||||
# Get course authors
|
||||
# Get course authors with their roles
|
||||
authors_statement = (
|
||||
select(User)
|
||||
.join(ResourceAuthor)
|
||||
select(ResourceAuthor, User)
|
||||
.join(User, ResourceAuthor.user_id == User.id)
|
||||
.where(ResourceAuthor.resource_uuid == course.course_uuid)
|
||||
.order_by(
|
||||
ResourceAuthor.id.asc()
|
||||
)
|
||||
)
|
||||
authors = db_session.exec(authors_statement).all()
|
||||
author_results = db_session.exec(authors_statement).all()
|
||||
|
||||
# convert from User to UserRead
|
||||
authors = [UserRead.model_validate(author) for author in authors]
|
||||
# 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)
|
||||
|
||||
|
|
@ -82,16 +95,28 @@ async def get_course_by_id(
|
|||
# RBAC check
|
||||
await rbac_check(request, course.course_uuid, current_user, "read", db_session)
|
||||
|
||||
# Get course authors
|
||||
# Get course authors with their roles
|
||||
authors_statement = (
|
||||
select(User)
|
||||
.join(ResourceAuthor)
|
||||
select(ResourceAuthor, User)
|
||||
.join(User, ResourceAuthor.user_id == User.id)
|
||||
.where(ResourceAuthor.resource_uuid == course.course_uuid)
|
||||
.order_by(
|
||||
ResourceAuthor.id.asc()
|
||||
)
|
||||
)
|
||||
authors = db_session.exec(authors_statement).all()
|
||||
author_results = db_session.exec(authors_statement).all()
|
||||
|
||||
# convert from User to UserRead
|
||||
authors = [UserRead.model_validate(author) for author in authors]
|
||||
# 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)
|
||||
|
||||
|
|
@ -123,12 +148,15 @@ async def get_course_meta(
|
|||
# Start async tasks concurrently
|
||||
tasks = []
|
||||
|
||||
# Task 1: Get course authors
|
||||
# Task 1: Get course authors with their roles
|
||||
async def get_authors():
|
||||
authors_statement = (
|
||||
select(User)
|
||||
.join(ResourceAuthor)
|
||||
select(ResourceAuthor, User)
|
||||
.join(User, ResourceAuthor.user_id == User.id)
|
||||
.where(ResourceAuthor.resource_uuid == course.course_uuid)
|
||||
.order_by(
|
||||
ResourceAuthor.id.asc()
|
||||
)
|
||||
)
|
||||
return db_session.exec(authors_statement).all()
|
||||
|
||||
|
|
@ -153,10 +181,19 @@ async def get_course_meta(
|
|||
tasks.append(get_trail())
|
||||
|
||||
# Run all tasks concurrently
|
||||
authors_raw, chapters, trail = await asyncio.gather(*tasks)
|
||||
author_results, chapters, trail = await asyncio.gather(*tasks)
|
||||
|
||||
# Convert authors from User to UserRead
|
||||
authors = [UserRead.model_validate(author) for author in authors_raw]
|
||||
# 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
|
||||
course_read = CourseRead(**course.model_dump(), authors=authors)
|
||||
|
|
@ -167,6 +204,7 @@ async def get_course_meta(
|
|||
trail=trail,
|
||||
)
|
||||
|
||||
|
||||
async def get_courses_orgslug(
|
||||
request: Request,
|
||||
current_user: PublicUser | AnonymousUser,
|
||||
|
|
@ -225,6 +263,9 @@ async def get_courses_orgslug(
|
|||
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()
|
||||
|
|
@ -234,13 +275,23 @@ async def get_courses_orgslug(
|
|||
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(UserRead.model_validate(user))
|
||||
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(course)
|
||||
course_read.authors = course_authors.get(course.course_uuid, [])
|
||||
course_read = CourseRead(
|
||||
**course.model_dump(),
|
||||
authors=course_authors.get(course.course_uuid, [])
|
||||
)
|
||||
course_reads.append(course_read)
|
||||
|
||||
return course_reads
|
||||
|
|
@ -306,15 +357,31 @@ async def search_courses(
|
|||
# Fetch authors for each course
|
||||
course_reads = []
|
||||
for course in courses:
|
||||
authors_query = (
|
||||
select(User)
|
||||
.join(ResourceAuthor, ResourceAuthor.user_id == User.id) # type: ignore
|
||||
# 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()
|
||||
)
|
||||
)
|
||||
authors = db_session.exec(authors_query).all()
|
||||
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(course)
|
||||
course_read.authors = [UserRead.model_validate(author) for author in authors]
|
||||
course_read.authors = authors
|
||||
course_reads.append(course_read)
|
||||
|
||||
return course_reads
|
||||
|
|
@ -368,6 +435,7 @@ async def create_course(
|
|||
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()),
|
||||
)
|
||||
|
|
@ -377,20 +445,32 @@ async def create_course(
|
|||
db_session.commit()
|
||||
db_session.refresh(resource_author)
|
||||
|
||||
# Get course authors
|
||||
# Get course authors with their roles
|
||||
authors_statement = (
|
||||
select(User)
|
||||
.join(ResourceAuthor)
|
||||
select(ResourceAuthor, User)
|
||||
.join(User, ResourceAuthor.user_id == User.id)
|
||||
.where(ResourceAuthor.resource_uuid == course.course_uuid)
|
||||
.order_by(
|
||||
ResourceAuthor.id.asc()
|
||||
)
|
||||
)
|
||||
authors = db_session.exec(authors_statement).all()
|
||||
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)
|
||||
|
||||
# convert from User to UserRead
|
||||
authors = [UserRead.model_validate(author) for author in authors]
|
||||
|
||||
course = CourseRead(**course.model_dump(), authors=authors)
|
||||
|
||||
return CourseRead.model_validate(course)
|
||||
|
|
@ -444,16 +524,28 @@ async def update_course_thumbnail(
|
|||
db_session.commit()
|
||||
db_session.refresh(course)
|
||||
|
||||
# Get course authors
|
||||
# Get course authors with their roles
|
||||
authors_statement = (
|
||||
select(User)
|
||||
.join(ResourceAuthor)
|
||||
select(ResourceAuthor, User)
|
||||
.join(User, ResourceAuthor.user_id == User.id)
|
||||
.where(ResourceAuthor.resource_uuid == course.course_uuid)
|
||||
.order_by(
|
||||
ResourceAuthor.id.asc()
|
||||
)
|
||||
)
|
||||
authors = db_session.exec(authors_statement).all()
|
||||
author_results = db_session.exec(authors_statement).all()
|
||||
|
||||
# convert from User to UserRead
|
||||
authors = [UserRead.model_validate(author) for author in authors]
|
||||
# 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)
|
||||
|
||||
|
|
@ -491,16 +583,28 @@ async def update_course(
|
|||
db_session.commit()
|
||||
db_session.refresh(course)
|
||||
|
||||
# Get course authors
|
||||
# Get course authors with their roles
|
||||
authors_statement = (
|
||||
select(User)
|
||||
.join(ResourceAuthor)
|
||||
select(ResourceAuthor, User)
|
||||
.join(User, ResourceAuthor.user_id == User.id)
|
||||
.where(ResourceAuthor.resource_uuid == course.course_uuid)
|
||||
.order_by(
|
||||
ResourceAuthor.id.asc()
|
||||
)
|
||||
)
|
||||
authors = db_session.exec(authors_statement).all()
|
||||
author_results = db_session.exec(authors_statement).all()
|
||||
|
||||
# convert from User to UserRead
|
||||
authors = [UserRead.model_validate(author) for author in authors]
|
||||
# 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)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@ from fastapi import HTTPException, Request
|
|||
from sqlmodel import Session, select
|
||||
from sqlalchemy import text
|
||||
|
||||
from src.db.courses.courses import Course, CourseRead
|
||||
from src.db.courses.courses import Course, CourseRead, AuthorWithRole
|
||||
from src.db.organizations import Organization, OrganizationRead
|
||||
from src.db.users import User, UserRead
|
||||
from src.db.resource_authors import ResourceAuthor
|
||||
|
||||
|
||||
def _get_sort_expression(salt: str):
|
||||
|
|
@ -96,7 +98,27 @@ async def get_course_for_explore(
|
|||
detail="Course not found",
|
||||
)
|
||||
|
||||
return CourseRead.model_validate(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)
|
||||
)
|
||||
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
|
||||
]
|
||||
|
||||
return CourseRead(**course.model_dump(), authors=authors)
|
||||
|
||||
async def search_orgs_for_explore(
|
||||
request: Request,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from fastapi import HTTPException, Request
|
||||
from sqlmodel import Session, select
|
||||
from typing import Any
|
||||
from src.db.courses.courses import Course, CourseRead
|
||||
from typing import Any, List
|
||||
from src.db.courses.courses import Course, CourseRead, AuthorWithRole
|
||||
from src.db.payments.payments_courses import PaymentsCourse
|
||||
from src.db.payments.payments_users import PaymentsUser, PaymentStatusEnum, ProviderSpecificData
|
||||
from src.db.payments.payments_products import PaymentsProduct
|
||||
|
|
@ -231,19 +231,28 @@ async def get_owned_courses(
|
|||
# Get authors for each course and convert to CourseRead
|
||||
course_reads = []
|
||||
for course in unique_courses:
|
||||
# Get course authors
|
||||
# Get course authors with their roles
|
||||
authors_statement = (
|
||||
select(User)
|
||||
.join(ResourceAuthor)
|
||||
select(ResourceAuthor, User)
|
||||
.join(User, ResourceAuthor.user_id == User.id)
|
||||
.where(ResourceAuthor.resource_uuid == course.course_uuid)
|
||||
)
|
||||
authors = db_session.exec(authors_statement).all()
|
||||
author_results = db_session.exec(authors_statement).all()
|
||||
|
||||
# Convert authors to UserRead
|
||||
author_reads = [UserRead.model_validate(author) for author in authors]
|
||||
# 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 CourseRead object
|
||||
course_read = CourseRead(**course.model_dump(), authors=author_reads)
|
||||
course_read = CourseRead(**course.model_dump(), authors=authors)
|
||||
course_reads.append(course_read)
|
||||
|
||||
return course_reads
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue