init : more chapters

This commit is contained in:
swve 2023-11-20 19:53:39 +01:00
parent 4fcc8aa77c
commit d95497e804

View file

@ -1,7 +1,19 @@
from datetime import datetime
from typing import List, Literal
from typing import List
from uuid import uuid4
from pydantic import BaseModel
from sqlmodel import Session, select
from src import db
from src.db.course_chapters import CourseChapter
from src.db.activities import Activity, ActivityRead
from src.db.chapter_activities import ChapterActivity
from src.db.chapters import (
Chapter,
ChapterCreate,
ChapterRead,
ChapterUpdate,
ChapterUpdateOrder,
DepreceatedChaptersRead,
)
from src.security.auth import non_public_endpoint
from src.security.rbac.rbac import (
authorization_verify_based_on_roles,
@ -14,366 +26,454 @@ from src.services.users.users import PublicUser
from fastapi import HTTPException, status, Request
class Activity(BaseModel):
name: str
type: str
content: object
class ActivityInDB(Activity):
activity_id: str
course_id: str
coursechapter_id: str
org_id: str
creationDate: str
updateDate: str
class CourseChapter(BaseModel):
name: str
description: str
activities: list
class CourseChapterInDB(CourseChapter):
coursechapter_id: str
course_id: str
creationDate: str
updateDate: str
# Frontend
class CourseChapterMetaData(BaseModel):
chapterOrder: List[str]
chapters: dict
activities: object
#### Classes ####################################################
####################################################
# CRUD
####################################################
async def create_coursechapter(
async def create_chapter(
request: Request,
coursechapter_object: CourseChapter,
course_id: str,
chapter_object: ChapterCreate,
current_user: PublicUser,
):
courses = request.app.db["courses"]
users = request.app.db["users"]
# get course org_id and verify rights
await courses.find_one({"course_id": course_id})
user = await users.find_one({"user_id": current_user.user_id})
db_session: Session,
) -> ChapterRead:
chapter = Chapter.from_orm(chapter_object)
# generate coursechapter_id with uuid4
coursechapter_id = str(f"coursechapter_{uuid4()}")
# complete chapter object
chapter.course_id = chapter_object.course_id
chapter.chapter_uuid = f"chapter_{uuid4()}"
chapter.creation_date = str(datetime.now())
chapter.update_date = str(datetime.now())
hasRoleRights = await authorization_verify_based_on_roles(
request, current_user.user_id, "create", user["roles"], course_id
# Find the last chapter in the course and add it to the list
statement = (
select(CourseChapter)
.where(CourseChapter.course_id == chapter.course_id)
.order_by(CourseChapter.order)
)
course_chapters = db_session.exec(statement).all()
# get last chapter order
last_order = course_chapters[-1].order if course_chapters else 0
to_be_used_order = last_order + 1
# Add chapter to database
db_session.add(chapter)
db_session.commit()
db_session.refresh(chapter)
chapter = ChapterRead(**chapter.dict(), activities=[])
# Check if COurseChapter link exists
statement = (
select(CourseChapter)
.where(CourseChapter.chapter_id == chapter.id)
.where(CourseChapter.course_id == chapter.course_id)
.where(CourseChapter.order == to_be_used_order)
)
course_chapter = db_session.exec(statement).first()
if not course_chapter:
# Add CourseChapter link
course_chapter = CourseChapter(
course_id=chapter.course_id,
chapter_id=chapter.id,
org_id=chapter.org_id,
creation_date=str(datetime.now()),
update_date=str(datetime.now()),
order=to_be_used_order,
)
if not hasRoleRights:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Roles : Insufficient rights to perform this action",
)
# Insert CourseChapter link in DB
db_session.add(course_chapter)
db_session.commit()
coursechapter = CourseChapterInDB(
coursechapter_id=coursechapter_id,
creationDate=str(datetime.now()),
updateDate=str(datetime.now()),
course_id=course_id,
**coursechapter_object.dict(),
)
courses.update_one(
{"course_id": course_id},
{
"$addToSet": {
"chapters": coursechapter_id,
"chapters_content": coursechapter.dict(),
}
},
)
return coursechapter.dict()
return chapter
async def get_coursechapter(
request: Request, coursechapter_id: str, current_user: PublicUser
):
courses = request.app.db["courses"]
coursechapter = await courses.find_one(
{"chapters_content.coursechapter_id": coursechapter_id}
)
if coursechapter:
# verify course rights
await verify_rights(request, coursechapter["course_id"], current_user, "read")
coursechapter = CourseChapter(**coursechapter)
return coursechapter
else:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="CourseChapter does not exist"
)
async def update_coursechapter(
async def get_chapter(
request: Request,
coursechapter_object: CourseChapter,
coursechapter_id: str,
chapter_id: int,
current_user: PublicUser,
):
courses = request.app.db["courses"]
db_session: Session,
) -> ChapterRead:
statement = select(Chapter).where(Chapter.id == chapter_id)
chapter = db_session.exec(statement).first()
coursechapter = await courses.find_one(
{"chapters_content.coursechapter_id": coursechapter_id}
)
if coursechapter:
# verify course rights
await verify_rights(request, coursechapter["course_id"], current_user, "update")
coursechapter = CourseChapterInDB(
coursechapter_id=coursechapter_id,
creationDate=str(datetime.now()),
updateDate=str(datetime.now()),
course_id=coursechapter["course_id"],
**coursechapter_object.dict(),
)
courses.update_one(
{"chapters_content.coursechapter_id": coursechapter_id},
{"$set": {"chapters_content.$": coursechapter.dict()}},
)
return coursechapter
else:
if not chapter:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Coursechapter does not exist"
status_code=status.HTTP_409_CONFLICT, detail="Chapter does not exist"
)
async def delete_coursechapter(
request: Request, coursechapter_id: str, current_user: PublicUser
):
courses = request.app.db["courses"]
course = await courses.find_one(
{"chapters_content.coursechapter_id": coursechapter_id}
# Get activities for this chapter
statement = (
select(Activity)
.join(ChapterActivity, Activity.id == ChapterActivity.activity_id)
.where(ChapterActivity.chapter_id == chapter_id)
.distinct(Activity.id)
)
if course:
# verify course rights
await verify_rights(request, course["course_id"], current_user, "delete")
activities = db_session.exec(statement).all()
# Remove coursechapter from course
await courses.update_one(
{"course_id": course["course_id"]},
{"$pull": {"chapters": coursechapter_id}},
chapter = ChapterRead(
**chapter.dict(),
activities=[ActivityRead(**activity.dict()) for activity in activities],
)
await courses.update_one(
{"chapters_content.coursechapter_id": coursechapter_id},
{"$pull": {"chapters_content": {"coursechapter_id": coursechapter_id}}},
)
return chapter
return {"message": "Coursechapter deleted"}
else:
async def update_chapter(
request: Request,
chapter_object: ChapterUpdate,
current_user: PublicUser,
db_session: Session,
) -> ChapterRead:
statement = select(Chapter).where(Chapter.id == chapter_object.chapter_id)
chapter = db_session.exec(statement).first()
if not chapter:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist"
status_code=status.HTTP_409_CONFLICT, detail="Chapter does not exist"
)
# Update only the fields that were passed in
for var, value in vars(chapter_object).items():
if value is not None:
setattr(chapter, var, value)
chapter.update_date = str(datetime.now())
db_session.commit()
db_session.refresh(chapter)
chapter = ChapterRead(**chapter.dict())
return chapter
async def delete_chapter(
request: Request,
chapter_id: str,
current_user: PublicUser,
db_session: Session,
):
statement = select(Chapter).where(Chapter.id == chapter_id)
chapter = db_session.exec(statement).first()
if not chapter:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Chapter does not exist"
)
db_session.delete(chapter)
db_session.commit()
# Remove all linked activities
statement = select(ChapterActivity).where(ChapterActivity.chapter_id == chapter_id)
chapter_activities = db_session.exec(statement).all()
for chapter_activity in chapter_activities:
db_session.delete(chapter_activity)
db_session.commit()
return {"detail": "chapter deleted"}
####################################################
# Misc
####################################################
async def get_coursechapters(
request: Request, course_id: str, page: int = 1, limit: int = 10
):
courses = request.app.db["courses"]
async def get_course_chapters(
request: Request,
course_id: int,
db_session: Session,
page: int = 1,
limit: int = 10,
) -> List[ChapterRead]:
statement = select(Chapter).where(Chapter.course_id == course_id)
chapters = db_session.exec(statement).all()
course = await courses.find_one({"course_id": course_id})
if course:
course = Course(**course)
coursechapters = course.chapters_content
return coursechapters
async def get_coursechapters_meta(
request: Request, course_id: str, current_user: PublicUser
):
courses = request.app.db["courses"]
activities = request.app.db["activities"]
await non_public_endpoint(current_user)
await verify_rights(request, course_id, current_user, "read")
coursechapters = await courses.find_one(
{"course_id": course_id}, {"chapters": 1, "chapters_content": 1, "_id": 0}
)
coursechapters = coursechapters
if not coursechapters:
if not chapters:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist"
status_code=status.HTTP_409_CONFLICT, detail="Course do not have chapters"
)
# activities
coursechapter_activityIds_global = []
chapters = [ChapterRead(**chapter.dict(), activities=[]) for chapter in chapters]
# 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,
}
final = {
"chapters": chapters,
"chapterOrder": coursechapters["chapters"],
"activities": activities_list,
}
return final
async def update_coursechapters_meta(
request: Request,
course_id: str,
coursechapters_metadata: CourseChapterMetaData,
current_user: PublicUser,
):
courses = request.app.db["courses"]
await verify_rights(request, course_id, current_user, "update")
# update chapters in course
await courses.update_one(
{"course_id": course_id},
{"$set": {"chapters": coursechapters_metadata.chapterOrder}},
# Get activities for each chapter
for chapter in chapters:
statement = (
select(ChapterActivity)
.where(ChapterActivity.chapter_id == chapter.id)
.order_by(ChapterActivity.order)
.distinct(ChapterActivity.id, ChapterActivity.order)
)
chapter_activities = db_session.exec(statement).all()
if coursechapters_metadata.chapters is not None:
for (
coursechapter_id,
chapter_metadata,
) in coursechapters_metadata.chapters.items():
filter_query = {"chapters_content.coursechapter_id": coursechapter_id}
update_query = {
"$set": {
"chapters_content.$.activities": chapter_metadata["activityIds"]
}
}
result = await courses.update_one(filter_query, update_query)
if result.matched_count == 0:
# handle error when no documents are matched by the filter query
print(f"No documents found for course chapter ID {coursechapter_id}")
for chapter_activity in chapter_activities:
statement = (
select(Activity)
.where(Activity.id == chapter_activity.activity_id)
.distinct(Activity.id)
)
activity = db_session.exec(statement).first()
# update activities in coursechapters
activity = request.app.db["activities"]
if coursechapters_metadata.chapters is not None:
for (
coursechapter_id,
chapter_metadata,
) in coursechapters_metadata.chapters.items():
# Update coursechapter_id in activities
filter_query = {"activity_id": {"$in": chapter_metadata["activityIds"]}}
update_query = {"$set": {"coursechapter_id": coursechapter_id}}
if activity:
chapter.activities.append(ActivityRead(**activity.dict()))
result = await activity.update_many(filter_query, update_query)
if result.matched_count == 0:
# handle error when no documents are matched by the filter query
print(f"No documents found for course chapter ID {coursechapter_id}")
return {"detail": "coursechapters metadata updated"}
return chapters
#### Security ####################################################
async def verify_rights(
async def get_depreceated_course_chapters(
request: Request,
course_id: str,
course_id: int,
current_user: PublicUser,
action: Literal["read", "update", "delete"],
):
courses = request.app.db["courses"]
users = request.app.db["users"]
user = await users.find_one({"user_id": current_user.user_id})
course = await courses.find_one({"course_id": course_id})
db_session: Session,
) -> DepreceatedChaptersRead:
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"
)
if action == "read":
if current_user.user_id == "anonymous":
await authorization_verify_if_element_is_public(
request, course_id, current_user.user_id, action
# Get chapters that are linked to his course and order them by order, using the order field in the CourseChapter table
statement = (
select(Chapter)
.join(CourseChapter, Chapter.id == CourseChapter.chapter_id)
.where(CourseChapter.course_id == course_id)
.order_by(CourseChapter.order)
.group_by(Chapter.id, CourseChapter.order)
)
else:
users = request.app.db["users"]
user = await users.find_one({"user_id": current_user.user_id})
print("ded", statement)
chapters = db_session.exec(statement).all()
await authorization_verify_if_user_is_anon(current_user.user_id)
chapters = [ChapterRead(**chapter.dict(), activities=[]) for chapter in chapters]
await authorization_verify_based_on_roles_and_authorship(
request,
current_user.user_id,
action,
user["roles"],
course_id,
# Get activities for each chapter
for chapter in chapters:
statement = (
select(Activity)
.join(ChapterActivity, Activity.id == ChapterActivity.activity_id)
.where(ChapterActivity.chapter_id == chapter.id)
.order_by(ChapterActivity.order)
.distinct(Activity.id, ChapterActivity.order)
)
else:
users = request.app.db["users"]
user = await users.find_one({"user_id": current_user.user_id})
chapter_activities = db_session.exec(statement).all()
await authorization_verify_if_user_is_anon(current_user.user_id)
for chapter_activity in chapter_activities:
statement = (
select(Activity)
.join(ChapterActivity, Activity.id == ChapterActivity.activity_id)
.where(Activity.id == chapter_activity.id)
.distinct(Activity.id, ChapterActivity.order)
.order_by(ChapterActivity.order)
)
activity = db_session.exec(statement).first()
await authorization_verify_based_on_roles_and_authorship(
request,
current_user.user_id,
action,
user["roles"],
course_id,
if activity:
chapter.activities.append(ActivityRead(**activity.dict()))
# Get a list of chapter ids
chapter_order: List[str] = [str(chapter.id) for chapter in chapters]
# Get activities for each chapter
activities = []
for chapter_id in chapter_order:
# order by activity order
statement = (
select(Activity)
.join(ChapterActivity, Activity.id == ChapterActivity.activity_id)
.where(ChapterActivity.chapter_id == chapter_id)
.order_by(ChapterActivity.order)
.distinct(Activity.id, ChapterActivity.order)
)
chapter_activities = db_session.exec(statement).all()
activities.extend(chapter_activities)
result = DepreceatedChaptersRead(
chapter_order=chapter_order, chapters=chapters, activities=activities
)
return result
#### Security ####################################################
async def reorder_chapters_and_activities(
request: Request,
course_id: int,
chapters_order: ChapterUpdateOrder,
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"
)
###########
# Chapters
###########
# Delete CourseChapters that are not linked to chapter_id and activity_id and org_id and course_id
statement = (
select(CourseChapter)
.where(
CourseChapter.course_id == course_id, CourseChapter.org_id == course.org_id
)
.order_by(CourseChapter.order)
)
course_chapters = db_session.exec(statement).all()
chapter_ids_to_keep = [
chapter_order.chapter_id
for chapter_order in chapters_order.chapter_order_by_ids
]
for course_chapter in course_chapters:
if course_chapter.chapter_id not in chapter_ids_to_keep:
db_session.delete(course_chapter)
db_session.commit()
# Delete Chapters that are not in the list of chapters_order
statement = select(Chapter).where(Chapter.course_id == course_id)
chapters = db_session.exec(statement).all()
chapter_ids_to_keep = [
chapter_order.chapter_id
for chapter_order in chapters_order.chapter_order_by_ids
]
for chapter in chapters:
if chapter.id not in chapter_ids_to_keep:
db_session.delete(chapter)
db_session.commit()
# If links do not exists, create them
for chapter_order in chapters_order.chapter_order_by_ids:
statement = (
select(CourseChapter)
.where(
CourseChapter.chapter_id == chapter_order.chapter_id,
CourseChapter.course_id == course_id,
)
.order_by(CourseChapter.order)
)
course_chapter = db_session.exec(statement).first()
if not course_chapter:
# Add CourseChapter link
course_chapter = CourseChapter(
chapter_id=chapter_order.chapter_id,
course_id=course_id,
org_id=course.org_id,
creation_date=str(datetime.now()),
update_date=str(datetime.now()),
order=chapter_order.chapter_id,
)
# Insert CourseChapter link in DB
db_session.add(course_chapter)
db_session.commit()
# Update order of chapters
for chapter_order in chapters_order.chapter_order_by_ids:
statement = (
select(CourseChapter)
.where(
CourseChapter.chapter_id == chapter_order.chapter_id,
CourseChapter.course_id == course_id,
)
.order_by(CourseChapter.order)
)
course_chapter = db_session.exec(statement).first()
if course_chapter:
# Get the order from the index of the chapter_order_by_ids list
course_chapter.order = chapters_order.chapter_order_by_ids.index(
chapter_order
)
db_session.commit()
###########
# Activities
###########
# Delete ChapterActivities that are not linked to chapter_id and activity_id and org_id and course_id
statement = (
select(ChapterActivity)
.where(
ChapterActivity.course_id == course_id,
ChapterActivity.org_id == course.org_id,
)
.order_by(ChapterActivity.order)
)
chapter_activities = db_session.exec(statement).all()
activity_ids_to_keep = [
activity_order.activity_id
for chapter_order in chapters_order.chapter_order_by_ids
for activity_order in chapter_order.activities_order_by_ids
]
for chapter_activity in chapter_activities:
if chapter_activity.activity_id not in activity_ids_to_keep:
db_session.delete(chapter_activity)
db_session.commit()
# If links do not exists, create them
for chapter_order in chapters_order.chapter_order_by_ids:
for activity_order in chapter_order.activities_order_by_ids:
statement = (
select(ChapterActivity)
.where(
ChapterActivity.chapter_id == chapter_order.chapter_id,
ChapterActivity.activity_id == activity_order.activity_id,
)
.order_by(ChapterActivity.order)
)
chapter_activity = db_session.exec(statement).first()
if not chapter_activity:
# Add ChapterActivity link
chapter_activity = ChapterActivity(
chapter_id=chapter_order.chapter_id,
activity_id=activity_order.activity_id,
org_id=course.org_id,
course_id=course_id,
creation_date=str(datetime.now()),
update_date=str(datetime.now()),
order=activity_order.activity_id,
)
# Insert ChapterActivity link in DB
db_session.add(chapter_activity)
db_session.commit()
# Update order of activities
for chapter_order in chapters_order.chapter_order_by_ids:
for activity_order in chapter_order.activities_order_by_ids:
statement = (
select(ChapterActivity)
.where(
ChapterActivity.chapter_id == chapter_order.chapter_id,
ChapterActivity.activity_id == activity_order.activity_id,
)
.order_by(ChapterActivity.order)
)
chapter_activity = db_session.exec(statement).first()
if chapter_activity:
# Get the order from the index of the chapter_order_by_ids list
chapter_activity.order = chapter_order.activities_order_by_ids.index(
activity_order
)
db_session.commit()
return {"detail": "Chapters reordered"}