mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
wip: courses
This commit is contained in:
parent
afa8e4ea98
commit
ee07f49139
8 changed files with 334 additions and 378 deletions
|
|
@ -2,6 +2,10 @@ import json
|
|||
from typing import List, Literal, Optional
|
||||
from uuid import uuid4
|
||||
from pydantic import BaseModel
|
||||
from sqlmodel import Session, select
|
||||
from src.db.course_authors import CourseAuthor, CourseAuthorshipEnum
|
||||
from src.db.users import PublicUser, AnonymousUser
|
||||
from src.db.courses import Course, CourseCreate, CourseRead, CourseUpdate
|
||||
from src.security.rbac.rbac import (
|
||||
authorization_verify_based_on_roles,
|
||||
authorization_verify_based_on_roles_and_authorship,
|
||||
|
|
@ -10,320 +14,183 @@ from src.security.rbac.rbac import (
|
|||
)
|
||||
from src.services.courses.activities.activities import ActivityInDB
|
||||
from src.services.courses.thumbnails import upload_thumbnail
|
||||
from src.services.users.schemas.users import AnonymousUser
|
||||
from src.services.users.users import PublicUser
|
||||
from fastapi import HTTPException, Request, status, UploadFile
|
||||
from datetime import datetime
|
||||
|
||||
#### Classes ####################################################
|
||||
|
||||
|
||||
class Course(BaseModel):
|
||||
name: str
|
||||
mini_description: str
|
||||
description: str
|
||||
learnings: List[str]
|
||||
thumbnail: str
|
||||
public: bool
|
||||
chapters: List[str]
|
||||
chapters_content: Optional[List]
|
||||
org_id: str
|
||||
|
||||
|
||||
class CourseInDB(Course):
|
||||
course_id: str
|
||||
creationDate: str
|
||||
updateDate: str
|
||||
authors: List[str]
|
||||
|
||||
|
||||
# TODO : wow terrible, fix this
|
||||
# those models need to be available only in the chapters service
|
||||
class CourseChapter(BaseModel):
|
||||
name: str
|
||||
description: str
|
||||
activities: list
|
||||
|
||||
|
||||
class CourseChapterInDB(CourseChapter):
|
||||
coursechapter_id: str
|
||||
course_id: str
|
||||
creationDate: str
|
||||
updateDate: str
|
||||
|
||||
|
||||
#### Classes ####################################################
|
||||
|
||||
# TODO : Add courses photo & cover upload and delete
|
||||
|
||||
|
||||
####################################################
|
||||
# CRUD
|
||||
####################################################
|
||||
|
||||
|
||||
async def get_course(request: Request, course_id: str, current_user: PublicUser):
|
||||
courses = request.app.db["courses"]
|
||||
|
||||
course = await courses.find_one({"course_id": course_id})
|
||||
|
||||
# verify course rights
|
||||
await verify_rights(request, course_id, current_user, "read")
|
||||
async def get_course(
|
||||
request: Request, course_id: str, current_user: PublicUser, db_session: Session
|
||||
):
|
||||
statement = select(Course).where(Course.id == course_id)
|
||||
course = db_session.exec(statement).first()
|
||||
|
||||
if not course:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist"
|
||||
status_code=404,
|
||||
detail="Course not found",
|
||||
)
|
||||
|
||||
course = Course(**course)
|
||||
return course
|
||||
|
||||
|
||||
async def get_course_meta(request: Request, course_id: str, current_user: PublicUser):
|
||||
courses = request.app.db["courses"]
|
||||
trails = request.app.db["trails"]
|
||||
|
||||
course = await courses.find_one({"course_id": course_id})
|
||||
activities = request.app.db["activities"]
|
||||
|
||||
# verify course rights
|
||||
await verify_rights(request, course_id, current_user, "read")
|
||||
async def get_course_meta(
|
||||
request: Request, course_id: str, current_user: PublicUser, db_session: Session
|
||||
):
|
||||
course_statement = select(Course).where(Course.id == course_id)
|
||||
course = db_session.exec(course_statement).first()
|
||||
|
||||
if not course:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist"
|
||||
status_code=404,
|
||||
detail="Course not found",
|
||||
)
|
||||
|
||||
coursechapters = await courses.find_one(
|
||||
{"course_id": course_id}, {"chapters_content": 1, "_id": 0}
|
||||
)
|
||||
# todo : get course chapters
|
||||
# todo : get course activities
|
||||
# todo : get trail
|
||||
|
||||
# activities
|
||||
coursechapter_activityIds_global = []
|
||||
|
||||
# chapters
|
||||
chapters = {}
|
||||
if coursechapters["chapters_content"]:
|
||||
for coursechapter in coursechapters["chapters_content"]:
|
||||
coursechapter = CourseChapterInDB(**coursechapter)
|
||||
coursechapter_activityIds = []
|
||||
|
||||
for activity in coursechapter.activities:
|
||||
coursechapter_activityIds.append(activity)
|
||||
coursechapter_activityIds_global.append(activity)
|
||||
|
||||
chapters[coursechapter.coursechapter_id] = {
|
||||
"id": coursechapter.coursechapter_id,
|
||||
"name": coursechapter.name,
|
||||
"activityIds": coursechapter_activityIds,
|
||||
}
|
||||
|
||||
# activities
|
||||
activities_list = {}
|
||||
for activity in await activities.find(
|
||||
{"activity_id": {"$in": coursechapter_activityIds_global}}
|
||||
).to_list(length=100):
|
||||
activity = ActivityInDB(**activity)
|
||||
activities_list[activity.activity_id] = {
|
||||
"id": activity.activity_id,
|
||||
"name": activity.name,
|
||||
"type": activity.type,
|
||||
"content": activity.content,
|
||||
}
|
||||
|
||||
chapters_list_with_activities = []
|
||||
for chapter in chapters:
|
||||
chapters_list_with_activities.append(
|
||||
{
|
||||
"id": chapters[chapter]["id"],
|
||||
"name": chapters[chapter]["name"],
|
||||
"activities": [
|
||||
activities_list[activity]
|
||||
for activity in chapters[chapter]["activityIds"]
|
||||
],
|
||||
}
|
||||
)
|
||||
course = CourseInDB(**course)
|
||||
|
||||
# Get activity by user
|
||||
trail = await trails.find_one(
|
||||
{"courses.course_id": course_id, "user_id": current_user.user_id}
|
||||
)
|
||||
if trail:
|
||||
# get only the course where course_id == course_id
|
||||
trail_course = next(
|
||||
(course for course in trail["courses"] if course["course_id"] == course_id),
|
||||
None,
|
||||
)
|
||||
else:
|
||||
trail_course = ""
|
||||
|
||||
return {
|
||||
"course": course,
|
||||
"chapters": chapters_list_with_activities,
|
||||
"trail": trail_course,
|
||||
}
|
||||
return course
|
||||
|
||||
|
||||
async def create_course(
|
||||
request: Request,
|
||||
course_object: Course,
|
||||
org_id: str,
|
||||
course_object: CourseCreate,
|
||||
org_id: int,
|
||||
current_user: PublicUser,
|
||||
db_session: Session,
|
||||
thumbnail_file: UploadFile | None = None,
|
||||
):
|
||||
courses = request.app.db["courses"]
|
||||
users = request.app.db["users"]
|
||||
user = await users.find_one({"user_id": current_user.user_id})
|
||||
|
||||
# generate course_id with uuid4
|
||||
course_id = str(f"course_{uuid4()}")
|
||||
|
||||
# TODO(fix) : the implementation here is clearly not the best one (this entire function)
|
||||
course_object.org_id = org_id
|
||||
course_object.chapters_content = []
|
||||
|
||||
await authorization_verify_based_on_roles(
|
||||
request,
|
||||
current_user.user_id,
|
||||
"create",
|
||||
user["roles"],
|
||||
course_id,
|
||||
)
|
||||
course = Course.from_orm(course_object)
|
||||
|
||||
# Complete course object
|
||||
course.org_id = org_id
|
||||
course.course_uuid = str(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_id}_thumbnail_{uuid4()}.{thumbnail_file.filename.split('.')[-1]}"
|
||||
)
|
||||
await upload_thumbnail(
|
||||
thumbnail_file, name_in_disk, course_object.org_id, course_id
|
||||
)
|
||||
name_in_disk = f"{course.course_uuid}_thumbnail_{uuid4()}.{thumbnail_file.filename.split('.')[-1]}"
|
||||
await upload_thumbnail(thumbnail_file, name_in_disk, org_id, course.course_uuid)
|
||||
course_object.thumbnail = name_in_disk
|
||||
|
||||
course = CourseInDB(
|
||||
course_id=course_id,
|
||||
authors=[current_user.user_id],
|
||||
creationDate=str(datetime.now()),
|
||||
updateDate=str(datetime.now()),
|
||||
**course_object.dict(),
|
||||
# Insert course
|
||||
db_session.add(course)
|
||||
db_session.commit()
|
||||
db_session.refresh(course)
|
||||
|
||||
# Make the user the creator of the course
|
||||
course_author = CourseAuthor(
|
||||
course_id=course.id is not None,
|
||||
user_id=current_user.id,
|
||||
authorship=CourseAuthorshipEnum.CREATOR,
|
||||
creation_date=str(datetime.now()),
|
||||
update_date=str(datetime.now()),
|
||||
)
|
||||
|
||||
course_in_db = await courses.insert_one(course.dict())
|
||||
# Insert course author
|
||||
db_session.add(course_author)
|
||||
db_session.commit()
|
||||
db_session.refresh(course_author)
|
||||
|
||||
if not course_in_db:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Unavailable database",
|
||||
)
|
||||
|
||||
return course.dict()
|
||||
return CourseRead.from_orm(course)
|
||||
|
||||
|
||||
async def update_course_thumbnail(
|
||||
request: Request,
|
||||
course_id: str,
|
||||
current_user: PublicUser,
|
||||
db_session: Session,
|
||||
thumbnail_file: UploadFile | None = None,
|
||||
):
|
||||
courses = request.app.db["courses"]
|
||||
statement = select(Course).where(Course.id == course_id)
|
||||
course = db_session.exec(statement).first()
|
||||
|
||||
course = await courses.find_one({"course_id": course_id})
|
||||
|
||||
# verify course rights
|
||||
await verify_rights(request, course_id, current_user, "update")
|
||||
|
||||
# TODO(fix) : the implementation here is clearly not the best one
|
||||
if course:
|
||||
creationDate = course["creationDate"]
|
||||
authors = course["authors"]
|
||||
if thumbnail_file and thumbnail_file.filename:
|
||||
name_in_disk = f"{course_id}_thumbnail_{uuid4()}.{thumbnail_file.filename.split('.')[-1]}"
|
||||
course = Course(**course).copy(update={"thumbnail": name_in_disk})
|
||||
await upload_thumbnail(
|
||||
thumbnail_file, name_in_disk, course.org_id, course_id
|
||||
)
|
||||
|
||||
updated_course = CourseInDB(
|
||||
course_id=course_id,
|
||||
creationDate=creationDate,
|
||||
authors=authors,
|
||||
updateDate=str(datetime.now()),
|
||||
**course.dict(),
|
||||
)
|
||||
|
||||
await courses.update_one(
|
||||
{"course_id": course_id}, {"$set": updated_course.dict()}
|
||||
)
|
||||
|
||||
return CourseInDB(**updated_course.dict())
|
||||
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist"
|
||||
)
|
||||
|
||||
|
||||
async def update_course(
|
||||
request: Request, course_object: Course, course_id: str, current_user: PublicUser
|
||||
):
|
||||
courses = request.app.db["courses"]
|
||||
|
||||
course = await courses.find_one({"course_id": course_id})
|
||||
|
||||
# verify course rights
|
||||
await verify_rights(request, course_id, current_user, "update")
|
||||
|
||||
if course:
|
||||
creationDate = course["creationDate"]
|
||||
authors = course["authors"]
|
||||
|
||||
# get today's date
|
||||
datetime_object = datetime.now()
|
||||
|
||||
updated_course = CourseInDB(
|
||||
course_id=course_id,
|
||||
creationDate=creationDate,
|
||||
authors=authors,
|
||||
updateDate=str(datetime_object),
|
||||
**course_object.dict(),
|
||||
)
|
||||
|
||||
await courses.update_one(
|
||||
{"course_id": course_id}, {"$set": updated_course.dict()}
|
||||
)
|
||||
|
||||
return CourseInDB(**updated_course.dict())
|
||||
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist"
|
||||
)
|
||||
|
||||
|
||||
async def delete_course(request: Request, course_id: str, current_user: PublicUser):
|
||||
courses = request.app.db["courses"]
|
||||
|
||||
course = await courses.find_one({"course_id": course_id})
|
||||
|
||||
# verify course rights
|
||||
await verify_rights(request, course_id, current_user, "delete")
|
||||
name_in_disk = None
|
||||
|
||||
if not course:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist"
|
||||
status_code=404,
|
||||
detail="Course not found",
|
||||
)
|
||||
|
||||
isDeleted = await courses.delete_one({"course_id": course_id})
|
||||
# Upload thumbnail
|
||||
if thumbnail_file and thumbnail_file.filename:
|
||||
name_in_disk = (
|
||||
f"{course_id}_thumbnail_{uuid4()}.{thumbnail_file.filename.split('.')[-1]}"
|
||||
)
|
||||
await upload_thumbnail(
|
||||
thumbnail_file, name_in_disk, course.org_id, course.course_uuid
|
||||
)
|
||||
|
||||
if isDeleted:
|
||||
return {"detail": "Course deleted"}
|
||||
# Update course
|
||||
if name_in_disk:
|
||||
course.thumbnail_image = name_in_disk
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Unavailable database",
|
||||
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)
|
||||
|
||||
return course
|
||||
|
||||
|
||||
async def update_course(
|
||||
request: Request,
|
||||
course_object: CourseUpdate,
|
||||
current_user: PublicUser,
|
||||
db_session: Session,
|
||||
):
|
||||
statement = select(Course).where(Course.id == course_object.course_id)
|
||||
course = db_session.exec(statement).first()
|
||||
|
||||
if not course:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="Course not found",
|
||||
)
|
||||
|
||||
del course_object.course_id
|
||||
|
||||
# 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)
|
||||
|
||||
return course
|
||||
|
||||
|
||||
async def delete_course(
|
||||
request: Request, course_id: str, current_user: PublicUser, 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",
|
||||
)
|
||||
|
||||
db_session.delete(course)
|
||||
db_session.commit()
|
||||
|
||||
return {"detail": "Course deleted"}
|
||||
|
||||
|
||||
####################################################
|
||||
# Misc
|
||||
|
|
@ -349,7 +216,7 @@ async def get_courses_orgslug(
|
|||
)
|
||||
|
||||
# show only public courses if user is not logged in
|
||||
if current_user.user_id == "anonymous":
|
||||
if current_user.id == "anonymous":
|
||||
all_courses = (
|
||||
courses.find({"org_id": org["org_id"], "public": True})
|
||||
.sort("name", 1)
|
||||
|
|
@ -380,30 +247,30 @@ async def verify_rights(
|
|||
action: Literal["create", "read", "update", "delete"],
|
||||
):
|
||||
if action == "read":
|
||||
if current_user.user_id == "anonymous":
|
||||
if current_user.id == "anonymous":
|
||||
await authorization_verify_if_element_is_public(
|
||||
request, course_id, current_user.user_id, action
|
||||
request, course_id, str(current_user.id), action
|
||||
)
|
||||
else:
|
||||
users = request.app.db["users"]
|
||||
user = await users.find_one({"user_id": current_user.user_id})
|
||||
user = await users.find_one({"user_id": str(current_user.id)})
|
||||
|
||||
await authorization_verify_based_on_roles_and_authorship(
|
||||
request,
|
||||
current_user.user_id,
|
||||
str(current_user.id),
|
||||
action,
|
||||
user["roles"],
|
||||
course_id,
|
||||
)
|
||||
else:
|
||||
users = request.app.db["users"]
|
||||
user = await users.find_one({"user_id": current_user.user_id})
|
||||
user = await users.find_one({"user_id": str(current_user.id)})
|
||||
|
||||
await authorization_verify_if_user_is_anon(current_user.user_id)
|
||||
await authorization_verify_if_user_is_anon(str(current_user.id))
|
||||
|
||||
await authorization_verify_based_on_roles_and_authorship(
|
||||
request,
|
||||
current_user.user_id,
|
||||
str(current_user.id),
|
||||
action,
|
||||
user["roles"],
|
||||
course_id,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue