diff --git a/apps/api/src/db/activities.py b/apps/api/src/db/activities.py index ca05a622..943c5a16 100644 --- a/apps/api/src/db/activities.py +++ b/apps/api/src/db/activities.py @@ -1,55 +1,69 @@ from typing import Literal, Optional +from click import Option from sqlalchemy import JSON, Column from sqlmodel import Field, Session, SQLModel, create_engine, select from enum import Enum class ActivityTypeEnum(str, Enum): - VIDEO = "VIDEO" - DOCUMENT = "DOCUMENT" - DYNAMIC = "DYNAMIC" - ASSESSMENT = "ASSESSMENT" - CUSTOM = "CUSTOM" + TYPE_VIDEO = "TYPE_VIDEO" + TYPE_DOCUMENT = "TYPE_DOCUMENT" + TYPE_DYNAMIC = "TYPE_DYNAMIC" + TYPE_ASSESSMENT = "TYPE_ASSESSMENT" + TYPE_CUSTOM = "TYPE_CUSTOM" class ActivitySubTypeEnum(str, Enum): # Dynamic - DYNAMIC_PAGE = "DYNAMIC_PAGE" + SUBTYPE_DYNAMIC_PAGE = "SUBTYPE_DYNAMIC_PAGE" # Video - VIDEO_YOUTUBE = "VIDEO_YOUTUBE" - VIDEO_HOSTED = "VIDEO_HOSTED" + SUBTYPE_VIDEO_YOUTUBE = "SUBTYPE_VIDEO_YOUTUBE" + SUBTYPE_VIDEO_HOSTED = "SUBTYPE_VIDEO_HOSTED" # Document - DOCUMENT_PDF = "DOCUMENT_PDF" - DOCUMENT_DOC = "DOCUMENT_GDOC" + SUBTYPE_DOCUMENT_PDF = "SUBTYPE_DOCUMENT_PDF" + SUBTYPE_DOCUMENT_DOC = "SUBTYPE_DOCUMENT_DOC" # Assessment - ASSESSMENT_QUIZ = "ASSESSMENT_QUIZ" + SUBTYPE_ASSESSMENT_QUIZ = "SUBTYPE_ASSESSMENT_QUIZ" # Custom - CUSTOM = "CUSTOM" + SUBTYPE_CUSTOM = "SUBTYPE_CUSTOM" class ActivityBase(SQLModel): name: str - activity_type: ActivityTypeEnum = ActivityTypeEnum.CUSTOM - activity_sub_type: ActivitySubTypeEnum = ActivitySubTypeEnum.CUSTOM - slug: str + activity_type: ActivityTypeEnum = ActivityTypeEnum.TYPE_CUSTOM + activity_sub_type: ActivitySubTypeEnum = ActivitySubTypeEnum.SUBTYPE_CUSTOM content: dict = Field(default={}, sa_column=Column(JSON)) published_version: int version: int - org_id: int = Field(default=None, foreign_key="organization.id") course_id: int = Field(default=None, foreign_key="course.id") class Activity(ActivityBase, table=True): id: Optional[int] = Field(default=None, primary_key=True) - activity_uuid: str - creation_date: str - update_date: str + org_id: int = Field(default=None, foreign_key="organization.id") + activity_uuid: str = "" + creation_date: str = "" + update_date: str = "" class ActivityCreate(ActivityBase): + order: int + org_id: int = Field(default=None, foreign_key="organization.id") + course_id: int = Field(default=None, foreign_key="course.id") + chapter_id : int pass +class ActivityUpdate(ActivityBase): + activity_id: int + name: Optional[str] + activity_type: Optional[ActivityTypeEnum] + activity_sub_type: Optional[ActivitySubTypeEnum] + content: dict = Field(default={}, sa_column=Column(JSON)) + published_version: Optional[int] + version: Optional[int] + + class ActivityRead(ActivityBase): id: int activity_uuid: str diff --git a/apps/api/src/db/organizations.py b/apps/api/src/db/organizations.py index a1170dab..6c8c2a1b 100644 --- a/apps/api/src/db/organizations.py +++ b/apps/api/src/db/organizations.py @@ -4,10 +4,10 @@ from sqlmodel import Field, SQLModel class OrganizationBase(SQLModel): name: str - description: Optional[str] = "" + description: Optional[str] slug: str email: str - logo_image: Optional[str] = "" + logo_image: Optional[str] class Organization(OrganizationBase, table=True): diff --git a/apps/api/src/db/roles.py b/apps/api/src/db/roles.py index 9e1227b9..10f1e917 100644 --- a/apps/api/src/db/roles.py +++ b/apps/api/src/db/roles.py @@ -5,21 +5,21 @@ from sqlmodel import Field, SQLModel class RoleTypeEnum(str, Enum): - ORGANIZATION = "ORGANIZATION" - ORGANIZATION_API_TOKEN = "ORGANIZATION_API_TOKEN" - GLOBAL = "GLOBAL" + TYPE_ORGANIZATION = "TYPE_ORGANIZATION" + TYPE_ORGANIZATION_API_TOKEN = "TYPE_ORGANIZATION_API_TOKEN" + TYPE_GLOBAL = "TYPE_GLOBAL" class RoleBase(SQLModel): - name: str - description: Optional[str] + name: str + description: Optional[str] rights: dict = Field(default={}, sa_column=Column(JSON)) class Role(RoleBase, table=True): id: Optional[int] = Field(default=None, primary_key=True) org_id: int = Field(default=None, foreign_key="organization.id") - role_type: RoleTypeEnum = RoleTypeEnum.ORGANIZATION + role_type: RoleTypeEnum = RoleTypeEnum.TYPE_GLOBAL role_uuid: str = "" creation_date: str = "" update_date: str = "" @@ -31,6 +31,6 @@ class RoleCreate(RoleBase): class RoleUpdate(SQLModel): role_id: int = Field(default=None, foreign_key="role.id") - name: Optional[str] - description: Optional[str] + name: Optional[str] + description: Optional[str] rights: Optional[dict] = Field(default={}, sa_column=Column(JSON)) diff --git a/apps/api/src/routers/courses/activities.py b/apps/api/src/routers/courses/activities.py index f04af1d7..6b62128c 100644 --- a/apps/api/src/routers/courses/activities.py +++ b/apps/api/src/routers/courses/activities.py @@ -1,4 +1,7 @@ from fastapi import APIRouter, Depends, UploadFile, Form, Request +from src.db.activities import ActivityCreate, ActivityUpdate +from src.db.users import PublicUser +from src.core.events.database import get_db_session from src.services.courses.activities.activities import ( Activity, create_activity, @@ -14,7 +17,6 @@ from src.services.courses.activities.video import ( create_external_video_activity, create_video_activity, ) -from src.services.users.schemas.users import PublicUser router = APIRouter() @@ -22,17 +24,14 @@ router = APIRouter() @router.post("/") async def api_create_activity( request: Request, - activity_object: Activity, - org_id: str, - coursechapter_id: str, + activity_object: ActivityCreate, current_user: PublicUser = Depends(get_current_user), + db_session=Depends(get_db_session), ): """ Create new activity """ - return await create_activity( - request, activity_object, org_id, coursechapter_id, current_user - ) + return await create_activity(request, activity_object, current_user, db_session) @router.get("/{activity_id}") @@ -40,11 +39,14 @@ async def api_get_activity( request: Request, activity_id: str, current_user: PublicUser = Depends(get_current_user), + db_session=Depends(get_db_session), ): """ Get single activity by activity_id """ - return await get_activity(request, activity_id, current_user=current_user) + return await get_activity( + request, activity_id, current_user=current_user, db_session=db_session + ) @router.get("/coursechapter/{coursechapter_id}") @@ -52,24 +54,25 @@ async def api_get_activities( request: Request, coursechapter_id: str, current_user: PublicUser = Depends(get_current_user), + db_session=Depends(get_db_session), ): """ Get CourseChapter activities """ - return await get_activities(request, coursechapter_id, current_user) + return await get_activities(request, coursechapter_id, current_user, db_session) -@router.put("/{activity_id}") +@router.put("/") async def api_update_activity( request: Request, - activity_object: Activity, - activity_id: str, + activity_object: ActivityUpdate, current_user: PublicUser = Depends(get_current_user), + db_session=Depends(get_db_session), ): """ Update activity by activity_id """ - return await update_activity(request, activity_object, activity_id, current_user) + return await update_activity(request, activity_object, current_user, db_session) @router.delete("/{activity_id}") @@ -77,11 +80,12 @@ async def api_delete_activity( request: Request, activity_id: str, current_user: PublicUser = Depends(get_current_user), + db_session=Depends(get_db_session), ): """ Delete activity by activity_id """ - return await delete_activity(request, activity_id, current_user) + return await delete_activity(request, activity_id, current_user, db_session) # Video activity @@ -91,15 +95,21 @@ async def api_delete_activity( async def api_create_video_activity( request: Request, name: str = Form(), - coursechapter_id: str = Form(), + chapter_id: str = Form(), current_user: PublicUser = Depends(get_current_user), video_file: UploadFile | None = None, + db_session=Depends(get_db_session), ): """ Create new activity """ return await create_video_activity( - request, name, coursechapter_id, current_user, video_file + request, + name, + chapter_id, + current_user, + db_session, + video_file, ) @@ -108,24 +118,28 @@ async def api_create_external_video_activity( request: Request, external_video: ExternalVideo, current_user: PublicUser = Depends(get_current_user), + db_session=Depends(get_db_session), ): """ Create new activity """ - return await create_external_video_activity(request, current_user, external_video) + return await create_external_video_activity( + request, current_user, external_video, db_session + ) @router.post("/documentpdf") async def api_create_documentpdf_activity( request: Request, name: str = Form(), - coursechapter_id: str = Form(), + chapter_id: str = Form(), current_user: PublicUser = Depends(get_current_user), pdf_file: UploadFile | None = None, + db_session=Depends(get_db_session), ): """ Create new activity """ return await create_documentpdf_activity( - request, name, coursechapter_id, current_user, pdf_file + request, name, chapter_id, current_user, db_session, pdf_file ) diff --git a/apps/api/src/services/courses/activities/activities.py b/apps/api/src/services/courses/activities/activities.py index 44ee1779..363b8884 100644 --- a/apps/api/src/services/courses/activities/activities.py +++ b/apps/api/src/services/courses/activities/activities.py @@ -1,35 +1,21 @@ +import stat from typing import Literal from pydantic import BaseModel +from sqlmodel import Session, select +from src.db.organizations import Organization +from src import db +from src.db.activities import ActivityCreate, Activity, ActivityRead, ActivityUpdate +from src.db.chapter_activities import ChapterActivity from src.security.rbac.rbac import ( authorization_verify_based_on_roles, authorization_verify_if_element_is_public, authorization_verify_if_user_is_anon, ) -from src.services.users.schemas.users import AnonymousUser, PublicUser +from src.db.users import AnonymousUser, PublicUser from fastapi import HTTPException, status, Request from uuid import uuid4 from datetime import datetime -#### Classes #################################################### - - -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 - - -#### Classes #################################################### - #################################################### # CRUD @@ -38,148 +24,130 @@ class ActivityInDB(Activity): async def create_activity( request: Request, - activity_object: Activity, - org_id: str, - coursechapter_id: str, + activity_object: ActivityCreate, current_user: PublicUser, + db_session: Session, ): - activities = request.app.db["activities"] - courses = request.app.db["courses"] - users = request.app.db["users"] + activity = Activity.from_orm(activity_object) - # get user - user = await users.find_one({"user_id": current_user.user_id}) + # CHeck if org exists + statement = select(Organization).where(Organization.id == activity_object.org_id) + org = db_session.exec(statement).first() - # generate activity_id - activity_id = str(f"activity_{uuid4()}") + if not org: + raise HTTPException( + status_code=404, + detail="Organization not found", + ) - # verify activity rights - await authorization_verify_based_on_roles( - request, - current_user.user_id, - "create", - user["roles"], - activity_id, + activity.activity_uuid = str(f"activity_{uuid4()}") + activity.creation_date = str(datetime.now()) + activity.update_date = str(datetime.now()) + + # Insert Activity in DB + db_session.add(activity) + db_session.commit() + db_session.refresh(activity) + + # Add activity to chapter + activity_chapter = ChapterActivity( + chapter_id=activity_object.chapter_id, + activity_id=activity.id is not None, + course_id=activity_object.course_id, + org_id=activity_object.org_id, + creation_date=str(datetime.now()), + update_date=str(datetime.now()), + order=activity_object.order, ) - # get course_id from activity - course = await courses.find_one({"chapters": coursechapter_id}) + # Insert ChapterActivity link in DB + db_session.add(activity_chapter) + db_session.commit() + db_session.refresh(activity_chapter) - # create activity - activity = ActivityInDB( - **activity_object.dict(), - creationDate=str(datetime.now()), - coursechapter_id=coursechapter_id, - updateDate=str(datetime.now()), - activity_id=activity_id, - org_id=org_id, - course_id=course["course_id"], - ) - await activities.insert_one(activity.dict()) - - # update chapter - await courses.update_one( - {"chapters_content.coursechapter_id": coursechapter_id}, - {"$addToSet": {"chapters_content.$.activities": activity_id}}, - ) - - return activity + return ActivityRead.from_orm(activity) -async def get_activity(request: Request, activity_id: str, current_user: PublicUser): - activities = request.app.db["activities"] - courses = request.app.db["courses"] - - activity = await activities.find_one({"activity_id": activity_id}) - - # get course_id from activity - coursechapter_id = activity["coursechapter_id"] - await courses.find_one({"chapters": coursechapter_id}) - - # verify course rights - await verify_rights(request, activity["course_id"], current_user, "read") +async def get_activity( + request: Request, + activity_id: str, + current_user: PublicUser, + db_session: Session, +): + statement = select(Activity).where(Activity.id == activity_id) + activity = db_session.exec(statement).first() if not activity: raise HTTPException( - status_code=status.HTTP_409_CONFLICT, detail="Course does not exist" + status_code=404, + detail="Activity not found", ) - activity = ActivityInDB(**activity) return activity async def update_activity( request: Request, - activity_object: Activity, - activity_id: str, + activity_object: ActivityUpdate, current_user: PublicUser, + db_session: Session, ): - activities = request.app.db["activities"] - - activity = await activities.find_one({"activity_id": activity_id}) - - # verify course rights - await verify_rights(request, activity_id, current_user, "update") - - if activity: - creationDate = activity["creationDate"] - - # get today's date - datetime_object = datetime.now() - - updated_course = ActivityInDB( - activity_id=activity_id, - coursechapter_id=activity["coursechapter_id"], - creationDate=creationDate, - updateDate=str(datetime_object), - course_id=activity["course_id"], - org_id=activity["org_id"], - **activity_object.dict(), - ) - - await activities.update_one( - {"activity_id": activity_id}, {"$set": updated_course.dict()} - ) - - return ActivityInDB(**updated_course.dict()) - - else: - raise HTTPException( - status_code=status.HTTP_409_CONFLICT, detail="activity does not exist" - ) - - -async def delete_activity(request: Request, activity_id: str, current_user: PublicUser): - activities = request.app.db["activities"] - - activity = await activities.find_one({"activity_id": activity_id}) - - # verify course rights - await verify_rights(request, activity_id, current_user, "delete") + statement = select(Activity).where(Activity.id == activity_object.activity_id) + activity = db_session.exec(statement).first() if not activity: raise HTTPException( - status_code=status.HTTP_409_CONFLICT, detail="activity does not exist" + status_code=404, + detail="Activity not found", ) - # Remove Activity - isDeleted = await activities.delete_one({"activity_id": activity_id}) + del activity_object.activity_id - # Remove Activity from chapter - courses = request.app.db["courses"] - isDeletedFromChapter = await courses.update_one( - {"chapters_content.activities": activity_id}, - {"$pull": {"chapters_content.$.activities": activity_id}}, - ) + # Update only the fields that were passed in + for var, value in vars(activity_object).items(): + if value is not None: + setattr(activity, var, value) - if isDeleted and isDeletedFromChapter: - return {"detail": "Activity deleted"} - else: + db_session.add(activity) + db_session.commit() + db_session.refresh(activity) + + return activity + + +async def delete_activity( + request: Request, + activity_id: str, + current_user: PublicUser, + db_session: Session, +): + statement = select(Activity).where(Activity.id == activity_id) + activity = db_session.exec(statement).first() + + if not activity: raise HTTPException( - status_code=status.HTTP_503_SERVICE_UNAVAILABLE, - detail="Unavailable database", + status_code=404, + detail="Activity not found", ) + # Delete activity from chapter + statement = select(ChapterActivity).where( + ChapterActivity.activity_id == activity_id + ) + activity_chapter = db_session.exec(statement).first() + + if not activity_chapter: + raise HTTPException( + status_code=404, + detail="Activity not found in chapter", + ) + + db_session.delete(activity_chapter) + db_session.delete(activity) + db_session.commit() + + return {"detail": "Activity deleted"} + #################################################### # Misc @@ -187,64 +155,20 @@ async def delete_activity(request: Request, activity_id: str, current_user: Publ async def get_activities( - request: Request, coursechapter_id: str, current_user: PublicUser + request: Request, + coursechapter_id: str, + current_user: PublicUser, + db_session: Session, ): - activities = request.app.db["activities"] - - activities = activities.find({"coursechapter_id": coursechapter_id}) + statement = select(ChapterActivity).where( + ChapterActivity.chapter_id == coursechapter_id + ) + activities = db_session.exec(statement).all() if not activities: raise HTTPException( - status_code=status.HTTP_409_CONFLICT, detail="Course does not exist" + status_code=404, + detail="No activities found", ) - activities = [ - ActivityInDB(**activity) for activity in await activities.to_list(length=100) - ] - return activities - - -#### Security #################################################### - - -async def verify_rights( - request: Request, - activity_id: str, # course_id in case of read - current_user: PublicUser | AnonymousUser, - action: Literal["create", "read", "update", "delete"], -): - if action == "read": - if current_user.user_id == "anonymous": - await authorization_verify_if_element_is_public( - request, activity_id, current_user.user_id, action - ) - else: - users = request.app.db["users"] - user = await users.find_one({"user_id": current_user.user_id}) - - await authorization_verify_if_user_is_anon(current_user.user_id) - - await authorization_verify_based_on_roles( - request, - current_user.user_id, - action, - user["roles"], - activity_id, - ) - else: - users = request.app.db["users"] - user = await users.find_one({"user_id": current_user.user_id}) - - await authorization_verify_if_user_is_anon(current_user.user_id) - - await authorization_verify_based_on_roles( - request, - current_user.user_id, - action, - user["roles"], - activity_id, - ) - - -#### Security #################################################### diff --git a/apps/api/src/services/courses/activities/pdf.py b/apps/api/src/services/courses/activities/pdf.py index 8919639b..8442d387 100644 --- a/apps/api/src/services/courses/activities/pdf.py +++ b/apps/api/src/services/courses/activities/pdf.py @@ -1,7 +1,16 @@ +from sqlmodel import Session, select +from src.db.chapters import Chapter +from src.db.activities import ( + Activity, + ActivityRead, + ActivitySubTypeEnum, + ActivityTypeEnum, +) +from src.db.chapter_activities import ChapterActivity +from src.db.course_chapters import CourseChapter +from src.db.users import PublicUser from src.security.rbac.rbac import authorization_verify_based_on_roles from src.services.courses.activities.uploads.pdfs import upload_pdf -from src.services.users.users import PublicUser -from src.services.courses.activities.activities import ActivityInDB from fastapi import HTTPException, status, UploadFile, Request from uuid import uuid4 from datetime import datetime @@ -10,26 +19,35 @@ from datetime import datetime async def create_documentpdf_activity( request: Request, name: str, - coursechapter_id: str, + chapter_id: str, current_user: PublicUser, + db_session: Session, pdf_file: UploadFile | None = None, ): - activities = request.app.db["activities"] - courses = request.app.db["courses"] - users = request.app.db["users"] + # get chapter_id + statement = select(Chapter).where(Chapter.id == chapter_id) + chapter = db_session.exec(statement).first() - # get user - user = await users.find_one({"user_id": current_user.user_id}) + if not chapter: + raise HTTPException( + status_code=404, + detail="Chapter not found", + ) - # generate activity_id - activity_id = str(f"activity_{uuid4()}") + statement = select(CourseChapter).where(CourseChapter.chapter_id == chapter_id) + coursechapter = db_session.exec(statement).first() - # get org_id from course - coursechapter = await courses.find_one( - {"chapters_content.coursechapter_id": coursechapter_id} - ) + if not coursechapter: + raise HTTPException( + status_code=404, + detail="CourseChapter not found", + ) - org_id = coursechapter["org_id"] + # get org_id + org_id = coursechapter.id + + # create activity uuid + activity_uuid = f"activity_{uuid4()}" # check if pdf_file is not None if not pdf_file: @@ -51,45 +69,48 @@ async def create_documentpdf_activity( status_code=status.HTTP_409_CONFLICT, detail="Pdf : No pdf file provided" ) - activity_object = ActivityInDB( - org_id=org_id, - activity_id=activity_id, - coursechapter_id=coursechapter_id, + # Create activity + activity = Activity( name=name, - type="documentpdf", - course_id=coursechapter["course_id"], + activity_type=ActivityTypeEnum.TYPE_DOCUMENT, + activity_sub_type=ActivitySubTypeEnum.SUBTYPE_DOCUMENT_PDF, content={ - "documentpdf": { - "filename": "documentpdf." + pdf_format, - "activity_id": activity_id, - } + "filename": "documentpdf." + pdf_format, + "activity_uuid": activity_uuid, }, - creationDate=str(datetime.now()), - updateDate=str(datetime.now()), + published_version=1, + version=1, + org_id=org_id is not None, + course_id=coursechapter.course_id, + activity_uuid=activity_uuid, + creation_date=str(datetime.now()), + update_date=str(datetime.now()), ) - await authorization_verify_based_on_roles( - request, - current_user.user_id, - "create", - user["roles"], - activity_id, - ) + # Insert Activity in DB + db_session.add(activity) + db_session.commit() + db_session.refresh(activity) - # create activity - activity = ActivityInDB(**activity_object.dict()) - await activities.insert_one(activity.dict()) + # Add activity to chapter + activity_chapter = ChapterActivity( + chapter_id=(int(chapter_id)), + activity_id=activity.id is not None, + course_id=coursechapter.course_id, + org_id=coursechapter.org_id, + creation_date=str(datetime.now()), + update_date=str(datetime.now()), + order=1, + ) # upload pdf if pdf_file: # get pdffile format - await upload_pdf(pdf_file, activity_id, org_id, coursechapter["course_id"]) + await upload_pdf(pdf_file, activity.id, org_id, coursechapter.course_id) - # todo : choose whether to update the chapter or not - # update chapter - await courses.update_one( - {"chapters_content.coursechapter_id": coursechapter_id}, - {"$addToSet": {"chapters_content.$.activities": activity_id}}, - ) + # Insert ChapterActivity link in DB + db_session.add(activity_chapter) + db_session.commit() + db_session.refresh(activity_chapter) - return activity + return ActivityRead.from_orm(activity) diff --git a/apps/api/src/services/courses/activities/video.py b/apps/api/src/services/courses/activities/video.py index 470e4ddc..924a68ad 100644 --- a/apps/api/src/services/courses/activities/video.py +++ b/apps/api/src/services/courses/activities/video.py @@ -1,12 +1,16 @@ from typing import Literal from pydantic import BaseModel +from sqlmodel import Session, select +from src.db.chapters import Chapter +from src.db.activities import Activity, ActivityRead, ActivitySubTypeEnum, ActivityTypeEnum +from src.db.chapter_activities import ChapterActivity +from src.db.course_chapters import CourseChapter +from src.db.users import PublicUser from src.security.rbac.rbac import ( authorization_verify_based_on_roles, ) from src.services.courses.activities.uploads.videos import upload_video -from src.services.users.users import PublicUser -from src.services.courses.activities.activities import ActivityInDB from fastapi import HTTPException, status, UploadFile, Request from uuid import uuid4 from datetime import datetime @@ -15,32 +19,32 @@ from datetime import datetime async def create_video_activity( request: Request, name: str, - coursechapter_id: str, + chapter_id: str, current_user: PublicUser, + db_session: Session, video_file: UploadFile | None = None, ): - activities = request.app.db["activities"] - courses = request.app.db["courses"] - users = request.app.db["users"] + # get chapter_id + statement = select(Chapter).where(Chapter.id == chapter_id) + chapter = db_session.exec(statement).first() - # get user - user = await users.find_one({"user_id": current_user.user_id}) + if not chapter: + raise HTTPException( + status_code=404, + detail="Chapter not found", + ) - # generate activity_id - activity_id = str(f"activity_{uuid4()}") - - # get org_id from course - coursechapter = await courses.find_one( - {"chapters_content.coursechapter_id": coursechapter_id} - ) + statement = select(CourseChapter).where(CourseChapter.chapter_id == chapter_id) + coursechapter = db_session.exec(statement).first() if not coursechapter: raise HTTPException( - status_code=status.HTTP_409_CONFLICT, - detail="CourseChapter : No coursechapter found", + status_code=404, + detail="CourseChapter not found", ) - org_id = coursechapter["org_id"] + # generate activity_uuid + activity_uuid = str(f"activity_{uuid4()}") # check if video_file is not None if not video_file: @@ -64,55 +68,57 @@ async def create_video_activity( detail="Video : No video file provided", ) - activity_object = ActivityInDB( - org_id=org_id, - activity_id=activity_id, - coursechapter_id=coursechapter_id, - course_id=coursechapter["course_id"], + activity_object = Activity( name=name, - type="video", + activity_type=ActivityTypeEnum.TYPE_VIDEO, + activity_sub_type=ActivitySubTypeEnum.SUBTYPE_VIDEO_HOSTED, + activity_uuid=activity_uuid, + published_version=1, content={ - "video": { - "filename": "video." + video_format, - "activity_id": activity_id, - } + "filename": "video." + video_format, + "activity_uuid": activity_uuid, }, - creationDate=str(datetime.now()), - updateDate=str(datetime.now()), - ) - - await authorization_verify_based_on_roles( - request, - current_user.user_id, - "create", - user["roles"], - activity_id, + version=1, + creation_date=str(datetime.now()), + update_date=str(datetime.now()), ) # create activity - activity = ActivityInDB(**activity_object.dict()) - await activities.insert_one(activity.dict()) + activity = Activity.from_orm(activity_object) + db_session.add(activity) + db_session.commit() + db_session.refresh(activity) # upload video if video_file: # get videofile format - await upload_video(video_file, activity_id, org_id, coursechapter["course_id"]) + await upload_video( + video_file, activity.id, coursechapter.org_id, coursechapter.course_id + ) - # todo : choose whether to update the chapter or not # update chapter - await courses.update_one( - {"chapters_content.coursechapter_id": coursechapter_id}, - {"$addToSet": {"chapters_content.$.activities": activity_id}}, + chapter_activity_object = ChapterActivity( + chapter_id=coursechapter.id is not None, + activity_id=activity.id is not None, + course_id=coursechapter.course_id, + org_id=coursechapter.org_id, + creation_date=str(datetime.now()), + update_date=str(datetime.now()), + order=1, ) - return activity + # Insert ChapterActivity link in DB + db_session.add(chapter_activity_object) + db_session.commit() + db_session.refresh(chapter_activity_object) + return ActivityRead.from_orm(activity) class ExternalVideo(BaseModel): name: str uri: str type: Literal["youtube", "vimeo"] - coursechapter_id: str + chapter_id: str class ExternalVideoInDB(BaseModel): @@ -123,65 +129,63 @@ async def create_external_video_activity( request: Request, current_user: PublicUser, data: ExternalVideo, + db_session: Session, ): - activities = request.app.db["activities"] - courses = request.app.db["courses"] - users = request.app.db["users"] + # get chapter_id + statement = select(Chapter).where(Chapter.id == data.chapter_id) + chapter = db_session.exec(statement).first() - # get user - user = await users.find_one({"user_id": current_user.user_id}) + if not chapter: + raise HTTPException( + status_code=404, + detail="Chapter not found", + ) - # generate activity_id - activity_id = str(f"activity_{uuid4()}") - - # get org_id from course - coursechapter = await courses.find_one( - {"chapters_content.coursechapter_id": data.coursechapter_id} - ) + statement = select(CourseChapter).where(CourseChapter.chapter_id == data.chapter_id) + coursechapter = db_session.exec(statement).first() if not coursechapter: raise HTTPException( - status_code=status.HTTP_409_CONFLICT, - detail="CourseChapter : No coursechapter found", + status_code=404, + detail="CourseChapter not found", ) - org_id = coursechapter["org_id"] + # generate activity_uuid + activity_uuid = str(f"activity_{uuid4()}") - activity_object = ActivityInDB( - org_id=org_id, - activity_id=activity_id, - coursechapter_id=data.coursechapter_id, + activity_object = Activity( name=data.name, - type="video", + activity_type=ActivityTypeEnum.TYPE_VIDEO, + activity_sub_type=ActivitySubTypeEnum.SUBTYPE_VIDEO_YOUTUBE, + activity_uuid=activity_uuid, + published_version=1, content={ - "external_video": { - "uri": data.uri, - "activity_id": activity_id, - "type": data.type, - } + "uri": data.uri, + "type": data.type, + "activity_uuid": activity_uuid, }, - course_id=coursechapter["course_id"], - creationDate=str(datetime.now()), - updateDate=str(datetime.now()), - ) - - await authorization_verify_based_on_roles( - request, - current_user.user_id, - "create", - user["roles"], - activity_id, + version=1, + creation_date=str(datetime.now()), + update_date=str(datetime.now()), ) # create activity - activity = ActivityInDB(**activity_object.dict()) - await activities.insert_one(activity.dict()) + activity = Activity.from_orm(activity_object) + db_session.add(activity) + db_session.commit() + db_session.refresh(activity) - # todo : choose whether to update the chapter or not # update chapter - await courses.update_one( - {"chapters_content.coursechapter_id": data.coursechapter_id}, - {"$addToSet": {"chapters_content.$.activities": activity_id}}, + chapter_activity_object = ChapterActivity( + chapter_id=coursechapter.id is not None, + activity_id=activity.id is not None, + creation_date=str(datetime.now()), + update_date=str(datetime.now()), + order=1, ) - return activity + # Insert ChapterActivity link in DB + db_session.add(chapter_activity_object) + db_session.commit() + + return ActivityRead.from_orm(activity) diff --git a/apps/api/src/services/courses/chapters.py b/apps/api/src/services/courses/chapters.py index 97184ecd..596b825a 100644 --- a/apps/api/src/services/courses/chapters.py +++ b/apps/api/src/services/courses/chapters.py @@ -10,11 +10,23 @@ from src.security.rbac.rbac import ( authorization_verify_if_user_is_anon, ) from src.services.courses.courses import Course -from src.services.courses.activities.activities import ActivityInDB 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 diff --git a/apps/api/src/services/courses/courses.py b/apps/api/src/services/courses/courses.py index 26fd2dd6..e12d3d06 100644 --- a/apps/api/src/services/courses/courses.py +++ b/apps/api/src/services/courses/courses.py @@ -12,7 +12,6 @@ from src.security.rbac.rbac import ( authorization_verify_if_element_is_public, authorization_verify_if_user_is_anon, ) -from src.services.courses.activities.activities import ActivityInDB from src.services.courses.thumbnails import upload_thumbnail from fastapi import HTTPException, Request, status, UploadFile from datetime import datetime