fix: refactor course chapters

This commit is contained in:
swve 2022-10-31 16:20:46 +01:00
parent 318bb221aa
commit 40496f7ced
6 changed files with 321 additions and 209 deletions

View file

@ -7,10 +7,10 @@ export const initialData = {
"element-5": { id: "element-5", content: "Fifth element" },
},
chapters: {
"chapter-1": { id: "chapter-1", title: "Chapter 1", elementIds: ["element-1", "element-2", "element-3", ] },
"chapter-1": { id: "chapter-1", title: "Chapter 1", elementIds: ["element-1", "element-2", "element-3"] },
"chapter-2": { id: "chapter-2", title: "Chapter 2", elementIds: ["element-4"] },
"chapter-3": { id: "chapter-3", title: "Chapter 3", elementIds: ["element-5"] },
},
chapterOrder: ["chapter-1", "chapter-2", "chapter-3"],
};

View file

@ -1,5 +1,5 @@
from fastapi import APIRouter
from src.routers import collections, courses, users, auth, houses, orgs, roles
from src.routers import chapters, collections, courses, users, auth, houses, orgs, roles
global_router = APIRouter(prefix="/api")
@ -12,5 +12,6 @@ global_router.include_router(houses.router, prefix="/houses", tags=["houses"])
global_router.include_router(orgs.router, prefix="/orgs", tags=["orgs"])
global_router.include_router(roles.router, prefix="/roles", tags=["roles"])
global_router.include_router(courses.router, prefix="/courses", tags=["courses"])
global_router.include_router(chapters.router, prefix="/chapters", tags=["chapters"])
global_router.include_router(collections.router, prefix="/collections", tags=["collections"])

64
src/routers/chapters.py Normal file
View file

@ -0,0 +1,64 @@
from fastapi import APIRouter, Depends, UploadFile, Form
from src.services.chapters import CourseChapter, CourseChapterMetaData, create_coursechapter, delete_coursechapter, get_coursechapter, get_coursechapters, get_coursechapters_meta, update_coursechapter, update_coursechapters_meta
from src.services.users import PublicUser
from src.services.auth import get_current_user
router = APIRouter()
@router.post("/")
async def api_create_coursechapter(coursechapter_object: CourseChapter, course_id: str, current_user: PublicUser = Depends(get_current_user)):
"""
Create new CourseChapter
"""
return await create_coursechapter(coursechapter_object, course_id, current_user)
@router.get("/{coursechapter_id}")
async def api_get_coursechapter(coursechapter_id: str, current_user: PublicUser = Depends(get_current_user)):
"""
Get single CourseChapter by coursechapter_id
"""
return await get_coursechapter(coursechapter_id, current_user=current_user)
@router.get("/meta/{course_id}")
async def api_get_coursechapter(course_id: str, current_user: PublicUser = Depends(get_current_user)):
"""
Get coursechapter metadata
"""
return await get_coursechapters_meta(course_id, current_user=current_user)
@router.put("/meta/{course_id}")
async def api_update_coursechapter_meta(course_id: str, coursechapters_metadata: CourseChapterMetaData, current_user: PublicUser = Depends(get_current_user)):
"""
Update coursechapter metadata
"""
return await update_coursechapters_meta(course_id, coursechapters_metadata, current_user=current_user)
@router.get("/{course_id}/page/{page}/limit/{limit}")
async def api_get_coursechapter_by(course_id: str, page: int, limit: int):
"""
Get CourseChapters by page and limit
"""
return await get_coursechapters(course_id, page, limit)
@router.put("/{coursechapter_id}")
async def api_update_coursechapter(coursechapter_object: CourseChapter, coursechapter_id: str, current_user: PublicUser = Depends(get_current_user)):
"""
Update CourseChapters by course_id
"""
return await update_coursechapter(coursechapter_object, coursechapter_id, current_user)
@router.delete("/{coursechapter_id}")
async def api_delete_coursechapter(coursechapter_id: str, current_user: PublicUser = Depends(get_current_user)):
"""
Delete CourseChapters by ID
"""
return await delete_coursechapter(coursechapter_id, current_user)

View file

@ -1,23 +1,25 @@
from fastapi import APIRouter, Depends, File, UploadFile, Form
from fastapi import APIRouter, Depends, UploadFile, Form
from src.services.auth import get_current_user
from src.services.courses import Course, CourseChapter, create_course, create_coursechapter, delete_coursechapter, get_course, get_coursechapter, get_coursechapters, get_courses, update_course, delete_course, update_course_thumbnail, update_coursechapter
from src.services.users import PublicUser, User
from src.services.courses import Course, create_course, get_course, get_courses, update_course, delete_course, update_course_thumbnail
from src.services.users import PublicUser
router = APIRouter()
@router.post("/")
async def api_create_course(org_id :str , name : str = Form(), mini_description : str = Form() , description :str = Form(), public : bool = Form(), current_user: PublicUser = Depends(get_current_user) , thumbnail: UploadFile | None = None):
async def api_create_course(org_id: str, name: str = Form(), mini_description: str = Form(), description: str = Form(), public: bool = Form(), current_user: PublicUser = Depends(get_current_user), thumbnail: UploadFile | None = None):
"""
Create new Course
"""
course = Course(name=name, mini_description=mini_description, description=description, org_id=org_id, public=public , thumbnail="" , chapters=[], learnings=[])
return await create_course(course, org_id , current_user, thumbnail)
course = Course(name=name, mini_description=mini_description, description=description,
org_id=org_id, public=public, thumbnail="", chapters=[], learnings=[])
return await create_course(course, org_id, current_user, thumbnail)
@router.put("/thumbnail/{course_id}")
async def api_create_course_thumbnail(course_id : str, thumbnail: UploadFile | None = None, current_user: PublicUser = Depends(get_current_user)):
async def api_create_course_thumbnail(course_id: str, thumbnail: UploadFile | None = None, current_user: PublicUser = Depends(get_current_user)):
"""
Update new Course Thumbnail
"""
@ -29,7 +31,7 @@ async def api_get_course(course_id: str, current_user: PublicUser = Depends(get
"""
Get single Course by course_id
"""
return await get_course(course_id,current_user=current_user)
return await get_course(course_id, current_user=current_user)
@router.get("/{org_id}/page/{page}/limit/{limit}")
@ -54,47 +56,4 @@ async def api_delete_course(course_id: str, current_user: PublicUser = Depends(g
Delete Course by ID
"""
return await delete_course(course_id, current_user)
# CoursesChapters
@router.post("/chapters/")
async def api_create_coursechapter(coursechapter_object: CourseChapter, course_id: str, current_user: PublicUser = Depends(get_current_user)):
"""
Create new CourseChapter
"""
return await create_coursechapter(coursechapter_object, course_id, current_user)
@router.get("/chapters/{coursechapter_id}")
async def api_get_coursechapter(coursechapter_id: str, current_user: PublicUser = Depends(get_current_user)):
"""
Get single CourseChapter by coursechapter_id
"""
return await get_coursechapter(coursechapter_id, current_user=current_user)
@router.get("/chapters/{course_id}/page/{page}/limit/{limit}")
async def api_get_coursechapter_by(course_id: str, page: int, limit: int):
"""
Get CourseChapters by page and limit
"""
return await get_coursechapters(course_id, page, limit)
@router.put("/chapters/{coursechapter_id}")
async def api_update_coursechapter(coursechapter_object: CourseChapter, coursechapter_id: str, current_user: PublicUser = Depends(get_current_user)):
"""
Update CourseChapters by course_id
"""
return await update_coursechapter(coursechapter_object, coursechapter_id, current_user)
@router.delete("/chapters/{coursechapter_id}")
async def api_delete_coursechapter(coursechapter_id: str, current_user: PublicUser = Depends(get_current_user)):
"""
Delete CourseChapters by ID
"""
return await delete_coursechapter(coursechapter_id, current_user)
return await delete_course(course_id, current_user)

220
src/services/chapters.py Normal file
View file

@ -0,0 +1,220 @@
from cmath import log
from datetime import datetime
import json
from typing import List
from uuid import uuid4
from pydantic import BaseModel
from src.services.courses import Course, CourseInDB
from src.services.database import create_config_collection, check_database, create_database, learnhouseDB, learnhouseDB
from src.services.security import verify_user_rights_with_roles
from src.services.users import PublicUser
from fastapi import FastAPI, HTTPException, status, Request, Response, BackgroundTasks, UploadFile, File
class CourseElement(BaseModel):
element_id: str
content: str
content_type: str
class CourseChapter(BaseModel):
name: str
description: str
elements: List[CourseElement]
class CourseChapterInDB(CourseChapter):
coursechapter_id: str
course_id: str
creationDate: str
updateDate: str
# Frontend
class CourseChapterMetaData(BaseModel):
chapterOrder: List[str]
chapters: List
# CoursesChapters
async def create_coursechapter(coursechapter_object: CourseChapter, course_id: str, current_user: PublicUser):
await check_database()
coursechapters = learnhouseDB["coursechapters"]
courses = learnhouseDB["courses"]
# generate coursechapter_id with uuid4
coursechapter_id = str(f"coursechapter_{uuid4()}")
hasRoleRights = await verify_user_rights_with_roles("create", current_user.user_id, coursechapter_id)
if not hasRoleRights:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Roles : Insufficient rights to perform this action")
coursechapter = CourseChapterInDB(coursechapter_id=coursechapter_id, creationDate=str(
datetime.now()), updateDate=str(datetime.now()), course_id=course_id, **coursechapter_object.dict())
coursechapter_in_db = coursechapters.insert_one(coursechapter.dict())
courses.update_one({"course_id": course_id}, {
"$addToSet": {"chapters": coursechapter_id}})
if not coursechapter_in_db:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Unavailable database")
return coursechapter.dict()
async def get_coursechapter(coursechapter_id: str, current_user: PublicUser):
await check_database()
coursechapters = learnhouseDB["coursechapters"]
coursechapter = coursechapters.find_one(
{"coursechapter_id": coursechapter_id})
if coursechapter:
# verify course rights
await verify_rights(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 get_coursechapters_meta(course_id: str, current_user: PublicUser):
await check_database()
coursechapters = learnhouseDB["coursechapters"]
courses = learnhouseDB["courses"]
coursechapters = coursechapters.find(
{"course_id": course_id}).sort("name", 1)
course = courses.find_one({"course_id": course_id})
course = Course(**course)
# chapters
chapters = [json.loads(json.dumps(chapter, default=str))
for chapter in coursechapters]
final = {
"chapters": chapters,
"chapterOrder": course.chapters
}
return final
async def update_coursechapters_meta(course_id: str, coursechapters_metadata: CourseChapterMetaData, current_user: PublicUser):
await check_database()
coursechapters = learnhouseDB["coursechapters"]
courses = learnhouseDB["courses"]
course = courses.find_one({"course_id": course_id})
course = Course(**course)
# update chapters in course
courseInDB = courses.update_one({"course_id": course_id}, {
"$set": {"chapters": coursechapters_metadata.chapterOrder}})
# TODO : update chapters in coursechapters
return {courseInDB}
async def update_coursechapter(coursechapter_object: CourseChapter, coursechapter_id: str, current_user: PublicUser):
await check_database()
coursechapters = learnhouseDB["coursechapters"]
coursechapter = coursechapters.find_one(
{"coursechapter_id": coursechapter_id})
if coursechapter:
# verify course rights
await verify_rights(coursechapter["course_id"], current_user, "update")
creationDate = coursechapter["creationDate"]
# get today's date
datetime_object = datetime.now()
updated_coursechapter = CourseChapterInDB(
coursechapter_id=coursechapter_id, creationDate=creationDate, course_id=coursechapter["course_id"], updateDate=str(datetime_object), **coursechapter_object.dict())
coursechapters.update_one({"coursechapter_id": coursechapter_id}, {
"$set": updated_coursechapter.dict()})
return CourseChapterInDB(**updated_coursechapter.dict())
else:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Coursechapter does not exist")
async def delete_coursechapter(coursechapter_id: str, current_user: PublicUser):
await check_database()
coursechapters = learnhouseDB["coursechapters"]
coursechapter = coursechapters.find_one(
{"coursechapter_id": coursechapter_id})
if coursechapter:
# verify course rights
await verify_rights(coursechapter["course_id"], current_user, "delete")
isDeleted = coursechapters.delete_one(
{"coursechapter_id": coursechapter_id})
# TODO : delete coursechapter from course using $pull https://www.mongodb.com/docs/v4.2/reference/operator/update/pull/
if isDeleted:
return {"detail": "coursechapter deleted"}
else:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Unavailable database")
else:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist")
async def get_coursechapters(course_id: str, page: int = 1, limit: int = 10):
await check_database()
courses = learnhouseDB["coursechapters"]
# TODO : Get only courses that user is admin/has roles of
# get all courses from database
all_coursechapters = courses.find({"course_id": course_id}).sort(
"name", 1).skip(10 * (page - 1)).limit(limit)
return [json.loads(json.dumps(coursechapter, default=str)) for coursechapter in all_coursechapters]
#### Security ####################################################
async def verify_rights(course_id: str, current_user: PublicUser, action: str):
await check_database()
courses = learnhouseDB["courses"]
course = courses.find_one({"course_id": course_id})
if not course:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail=f"Course/CourseChapter does not exist")
hasRoleRights = await verify_user_rights_with_roles(action, current_user.user_id, course_id)
isAuthor = current_user.user_id in course["authors"]
if not hasRoleRights and not isAuthor:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="Roles/Ownership : Insufficient rights to perform this action")
return True
#### Security ####################################################

View file

@ -18,7 +18,7 @@ class Course(BaseModel):
mini_description: str
description: str
learnings: List[str]
thumbnail : str
thumbnail: str
public: bool
chapters: List[str]
org_id: str
@ -33,23 +33,6 @@ class CourseInDB(Course):
#####
class CourseElement(BaseModel):
element_id: str
content: str
content_type: str
class CourseChapter(BaseModel):
name: str
description: str
elements: List[CourseElement]
class CourseChapterInDB(CourseChapter):
coursechapter_id: str
course_id: str
creationDate: str
updateDate: str
#### Classes ####################################################
@ -59,7 +42,7 @@ class CourseChapterInDB(CourseChapter):
# Courses
async def get_course(course_id: str , current_user: PublicUser):
async def get_course(course_id: str, current_user: PublicUser):
await check_database()
courses = learnhouseDB["courses"]
@ -76,27 +59,26 @@ async def get_course(course_id: str , current_user: PublicUser):
return course
async def create_course(course_object: Course, org_id : str , current_user: PublicUser, thumbnail_file: UploadFile | None = None):
async def create_course(course_object: Course, org_id: str, current_user: PublicUser, thumbnail_file: UploadFile | None = None):
await check_database()
courses = learnhouseDB["courses"]
# 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
hasRoleRights = await verify_user_rights_with_roles("create", current_user.user_id, course_id)
if not hasRoleRights:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Roles : Insufficient rights to perform this action")
if thumbnail_file:
name_in_disk = f"{course_id}_thumbnail_{uuid4()}.{thumbnail_file.filename.split('.')[-1]}"
await upload_thumbnail(thumbnail_file, name_in_disk)
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())
@ -108,12 +90,13 @@ async def create_course(course_object: Course, org_id : str , current_user: Pu
return course.dict()
async def update_course_thumbnail(course_id: str , current_user: PublicUser, thumbnail_file: UploadFile | None = None):
async def update_course_thumbnail(course_id: str, current_user: PublicUser, thumbnail_file: UploadFile | None = None):
await check_database()
# verify course rights
await verify_rights(course_id, current_user, "update")
courses = learnhouseDB["courses"]
course = courses.find_one({"course_id": course_id})
@ -121,19 +104,18 @@ async def update_course_thumbnail(course_id: str , current_user: PublicUser, thu
if course:
creationDate = course["creationDate"]
authors = course["authors"]
if thumbnail_file:
if thumbnail_file:
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)
updated_course = CourseInDB(course_id=course_id, creationDate=creationDate, authors=authors, updateDate=str(datetime.now()) , **course.dict())
await upload_thumbnail(thumbnail_file, name_in_disk)
updated_course = CourseInDB(course_id=course_id, creationDate=creationDate,
authors=authors, updateDate=str(datetime.now()), **course.dict())
courses.update_one({"course_id": course_id}, {
"$set": updated_course.dict()})
"$set": updated_course.dict()})
return CourseInDB(**updated_course.dict())
else:
raise HTTPException(
@ -145,7 +127,7 @@ async def update_course(course_object: Course, course_id: str, current_user: Pub
# verify course rights
await verify_rights(course_id, current_user, "update")
courses = learnhouseDB["courses"]
course = courses.find_one({"course_id": course_id})
@ -156,12 +138,12 @@ async def update_course(course_object: Course, course_id: str, current_user: Pub
# 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())
courses.update_one({"course_id": course_id}, {
"$set": updated_course.dict()})
"$set": updated_course.dict()})
return CourseInDB(**updated_course.dict())
@ -169,8 +151,6 @@ async def update_course(course_object: Course, course_id: str, current_user: Pub
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist")
async def delete_course(course_id: str, current_user: PublicUser):
await check_database()
@ -195,129 +175,17 @@ async def delete_course(course_id: str, current_user: PublicUser):
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Unavailable database")
async def get_courses(page: int = 1, limit: int = 10 , org_id : str | None = None):
async def get_courses(page: int = 1, limit: int = 10, org_id: str | None = None):
await check_database()
courses = learnhouseDB["courses"]
# TODO : Get only courses that user is admin/has roles of
# get all courses from database
all_courses = courses.find({"org_id": org_id}).sort("name", 1).skip(10 * (page - 1)).limit(limit)
all_courses = courses.find({"org_id": org_id}).sort(
"name", 1).skip(10 * (page - 1)).limit(limit)
return [json.loads(json.dumps(course, default=str)) for course in all_courses]
# CoursesChapters
async def create_coursechapter(coursechapter_object: CourseChapter, course_id: str, current_user: PublicUser):
await check_database()
coursechapters = learnhouseDB["coursechapters"]
# generate coursechapter_id with uuid4
coursechapter_id = str(f"coursechapter_{uuid4()}")
hasRoleRights = await verify_user_rights_with_roles("create", current_user.user_id, coursechapter_id)
if not hasRoleRights:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Roles : Insufficient rights to perform this action")
coursechapter = CourseChapterInDB(coursechapter_id=coursechapter_id, creationDate=str(
datetime.now()), updateDate=str(datetime.now()), course_id=course_id, **coursechapter_object.dict())
coursechapter_in_db = coursechapters.insert_one(coursechapter.dict())
if not coursechapter_in_db:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Unavailable database")
return coursechapter.dict()
async def get_coursechapter(coursechapter_id: str, current_user: PublicUser):
await check_database()
coursechapters = learnhouseDB["coursechapters"]
coursechapter = coursechapters.find_one(
{"coursechapter_id": coursechapter_id})
if coursechapter:
# verify course rights
await verify_rights(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(coursechapter_object: CourseChapter, coursechapter_id: str, current_user: PublicUser):
await check_database()
coursechapters = learnhouseDB["coursechapters"]
coursechapter = coursechapters.find_one(
{"coursechapter_id": coursechapter_id})
if coursechapter:
# verify course rights
await verify_rights(coursechapter["course_id"], current_user, "update")
creationDate = coursechapter["creationDate"]
# get today's date
datetime_object = datetime.now()
updated_coursechapter = CourseChapterInDB(
coursechapter_id=coursechapter_id, creationDate=creationDate, course_id=coursechapter["course_id"], updateDate=str(datetime_object), **coursechapter_object.dict())
coursechapters.update_one({"coursechapter_id": coursechapter_id}, {
"$set": updated_coursechapter.dict()})
return CourseChapterInDB(**updated_coursechapter.dict())
else:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Coursechapter does not exist")
async def delete_coursechapter(coursechapter_id: str, current_user: PublicUser):
await check_database()
coursechapters = learnhouseDB["coursechapters"]
coursechapter = coursechapters.find_one(
{"coursechapter_id": coursechapter_id})
if coursechapter:
# verify course rights
await verify_rights(coursechapter["course_id"], current_user, "delete")
isDeleted = coursechapters.delete_one(
{"coursechapter_id": coursechapter_id})
if isDeleted:
return {"detail": "coursechapter deleted"}
else:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Unavailable database")
else:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist")
async def get_coursechapters(course_id: str, page: int = 1, limit: int = 10):
await check_database()
courses = learnhouseDB["coursechapters"]
# TODO : Get only courses that user is admin/has roles of
# get all courses from database
all_coursechapters = courses.find({"course_id": course_id}).sort(
"name", 1).skip(10 * (page - 1)).limit(limit)
return [json.loads(json.dumps(coursechapter, default=str)) for coursechapter in all_coursechapters]
#### Security ####################################################