diff --git a/apps/api/src/db/activities.py b/apps/api/src/db/activities.py index 943c5a16..29d460cc 100644 --- a/apps/api/src/db/activities.py +++ b/apps/api/src/db/activities.py @@ -50,7 +50,7 @@ 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 + chapter_id: int pass diff --git a/apps/api/src/db/chapter_activities.py b/apps/api/src/db/chapter_activities.py index 73d20598..1e31b7c4 100644 --- a/apps/api/src/db/chapter_activities.py +++ b/apps/api/src/db/chapter_activities.py @@ -5,7 +5,7 @@ from enum import Enum class ChapterActivity(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) order: int - chapter_id: int = Field(default=None, foreign_key="chapter.id") + chapter_id: int = Field(default=None, foreign_key="chapter.id", ) activity_id: int = Field(default=None, foreign_key="activity.id") course_id : int = Field(default=None, foreign_key="course.id") org_id : int = Field(default=None, foreign_key="organization.id") diff --git a/apps/api/src/db/chapters.py b/apps/api/src/db/chapters.py index c7490c4a..7b80f564 100644 --- a/apps/api/src/db/chapters.py +++ b/apps/api/src/db/chapters.py @@ -1,5 +1,7 @@ -from typing import Optional +from typing import List, Optional +from pydantic import BaseModel from sqlmodel import Field, SQLModel +from src.db.activities import Activity, ActivityRead, ActivityUpdate class ChapterBase(SQLModel): @@ -7,24 +9,55 @@ class ChapterBase(SQLModel): description: Optional[str] = "" thumbnail_image: Optional[str] = "" org_id: int = Field(default=None, foreign_key="organization.id") + course_id: int = Field(default=None, foreign_key="course.id") creation_date: str update_date: str class Chapter(ChapterBase, table=True): id: Optional[int] = Field(default=None, primary_key=True) - chapter_uuid: str - creation_date: str - update_date: str + chapter_uuid: str = "" + creation_date: str = "" + update_date: str = "" class ChapterCreate(ChapterBase): + # referenced order here will be ignored and just used for validation + # used order will be the next available. pass +class ChapterUpdate(ChapterBase): + chapter_id: int + name: Optional[str] + description: Optional[str] + thumbnail_image: Optional[str] + + class ChapterRead(ChapterBase): id: int + activities: List[ActivityRead] chapter_uuid: str creation_date: str update_date: str pass + + +class ActivityOrder(BaseModel): + activity_id: int + + +class ChapterOrder(BaseModel): + chapter_id: int + activities_order_by_ids: List[ActivityOrder] + + +class ChapterUpdateOrder(BaseModel): + chapter_order_by_ids: List[ChapterOrder] + + +class DepreceatedChaptersRead(BaseModel): + chapter_order: list[str] + chapters: List[ChapterRead] + activities: List[ActivityRead] + pass diff --git a/apps/api/src/db/courses.py b/apps/api/src/db/courses.py index 0f2347ee..5774b28a 100644 --- a/apps/api/src/db/courses.py +++ b/apps/api/src/db/courses.py @@ -1,6 +1,8 @@ -from typing import Optional +from typing import List, Optional from sqlmodel import Field, SQLModel +from src.db.chapters import ChapterRead + class CourseBase(SQLModel): name: str @@ -41,3 +43,13 @@ class CourseRead(CourseBase): creation_date: str update_date: str pass + + +class FullCourseRead(CourseBase): + id: int + course_uuid: str + creation_date: str + update_date: str + # Chapters, Activities + chapters: List[ChapterRead] + pass diff --git a/apps/api/src/routers/courses/chapters.py b/apps/api/src/routers/courses/chapters.py index 213dcc55..263a93d7 100644 --- a/apps/api/src/routers/courses/chapters.py +++ b/apps/api/src/routers/courses/chapters.py @@ -1,6 +1,23 @@ +from typing import List from fastapi import APIRouter, Depends, Request +from src.core.events.database import get_db_session +from src.db.chapters import ( + ChapterCreate, + ChapterRead, + ChapterUpdate, + ChapterUpdateOrder, + DepreceatedChaptersRead, +) +from src.services.courses.chapters import ( + create_chapter, + delete_chapter, + get_chapter, + get_course_chapters, + get_depreceated_course_chapters, + reorder_chapters_and_activities, + update_chapter, +) -from src.services.courses.chapters import CourseChapter, CourseChapterMetaData, create_coursechapter, delete_coursechapter, get_coursechapter, get_coursechapters, get_coursechapters_meta, update_coursechapter, update_coursechapters_meta from src.services.users.users import PublicUser from src.security.auth import get_current_user @@ -8,57 +25,99 @@ router = APIRouter() @router.post("/") -async def api_create_coursechapter(request: Request,coursechapter_object: CourseChapter, course_id: str, current_user: PublicUser = Depends(get_current_user)): +async def api_create_coursechapter( + request: Request, + coursechapter_object: ChapterCreate, + current_user: PublicUser = Depends(get_current_user), + db_session=Depends(get_db_session), +) -> ChapterRead: """ - Create new CourseChapter + Create new Course Chapter """ - return await create_coursechapter(request, coursechapter_object, course_id, current_user) + return await create_chapter(request, coursechapter_object, current_user, db_session) -@router.get("/{coursechapter_id}") -async def api_get_coursechapter(request: Request,coursechapter_id: str, current_user: PublicUser = Depends(get_current_user)): +@router.get("/{chapter_id}") +async def api_get_coursechapter( + request: Request, + chapter_id: int, + current_user: PublicUser = Depends(get_current_user), + db_session=Depends(get_db_session), +) -> ChapterRead: """ - Get single CourseChapter by coursechapter_id + Get single CourseChapter by chapter_id """ - return await get_coursechapter(request, coursechapter_id, current_user=current_user) + return await get_chapter(request, chapter_id, current_user, db_session) @router.get("/meta/{course_id}") -async def api_get_coursechapter_meta(request: Request,course_id: str, current_user: PublicUser = Depends(get_current_user)): +async def api_get_chapter_meta( + request: Request, + course_id: int, + current_user: PublicUser = Depends(get_current_user), + db_session=Depends(get_db_session), +) -> DepreceatedChaptersRead: """ - Get coursechapter metadata + Get Chapters metadata """ - return await get_coursechapters_meta(request, course_id, current_user=current_user) + return await get_depreceated_course_chapters(request, course_id, current_user, db_session) -@router.put("/meta/{course_id}") -async def api_update_coursechapter_meta(request: Request,course_id: str, coursechapters_metadata: CourseChapterMetaData, current_user: PublicUser = Depends(get_current_user)): +@router.put("/order/{course_id}") +async def api_update_chapter_meta( + request: Request, + course_id: int, + order: ChapterUpdateOrder, + current_user: PublicUser = Depends(get_current_user), + db_session=Depends(get_db_session), +): """ - Update coursechapter metadata + Update Chapter metadata """ - return await update_coursechapters_meta(request, course_id, coursechapters_metadata, current_user=current_user) + return await reorder_chapters_and_activities( + request, course_id, order, current_user, db_session + ) @router.get("/{course_id}/page/{page}/limit/{limit}") -async def api_get_coursechapter_by(request: Request,course_id: str, page: int, limit: int): +async def api_get_chapter_by( + request: Request, + course_id: int, + page: int, + limit: int, + db_session=Depends(get_db_session), +) -> List[ChapterRead]: """ - Get CourseChapters by page and limit + Get Course Chapters by page and limit """ - return await get_coursechapters(request, course_id, page, limit) + return await get_course_chapters(request, course_id, db_session, page, limit) @router.put("/{coursechapter_id}") -async def api_update_coursechapter(request: Request,coursechapter_object: CourseChapter, coursechapter_id: str, current_user: PublicUser = Depends(get_current_user)): +async def api_update_coursechapter( + request: Request, + coursechapter_object: ChapterUpdate, + coursechapter_id: str, + current_user: PublicUser = Depends(get_current_user), + db_session=Depends(get_db_session), +) -> ChapterRead: """ Update CourseChapters by course_id """ - return await update_coursechapter(request, coursechapter_object, coursechapter_id, current_user) + return await update_chapter(request, coursechapter_object, current_user, db_session) @router.delete("/{coursechapter_id}") -async def api_delete_coursechapter(request: Request,coursechapter_id: str, current_user: PublicUser = Depends(get_current_user)): +async def api_delete_coursechapter( + request: Request, + coursechapter_id: str, + current_user: PublicUser = Depends(get_current_user), + db_session=Depends(get_db_session), +): """ Delete CourseChapters by ID """ - return await delete_coursechapter(request,coursechapter_id, current_user) + return await delete_chapter( + request, coursechapter_id, current_user, db_session + ) diff --git a/apps/api/src/routers/courses/courses.py b/apps/api/src/routers/courses/courses.py index c91ab48a..b496f376 100644 --- a/apps/api/src/routers/courses/courses.py +++ b/apps/api/src/routers/courses/courses.py @@ -47,7 +47,7 @@ async def api_create_course( tags=tags, ) return await create_course( - request, course, org_id, current_user, db_session, thumbnail + request, course, current_user, db_session, thumbnail ) diff --git a/apps/api/src/services/courses/activities/activities.py b/apps/api/src/services/courses/activities/activities.py index 363b8884..3035d84b 100644 --- a/apps/api/src/services/courses/activities/activities.py +++ b/apps/api/src/services/courses/activities/activities.py @@ -2,6 +2,7 @@ import stat from typing import Literal from pydantic import BaseModel from sqlmodel import Session, select +from src.db.chapters import Chapter from src.db.organizations import Organization from src import db from src.db.activities import ActivityCreate, Activity, ActivityRead, ActivityUpdate @@ -49,15 +50,26 @@ async def create_activity( db_session.commit() db_session.refresh(activity) + # Find the last activity in the Chapter and add it to the list + statement = ( + select(ChapterActivity) + .where(ChapterActivity.chapter_id == activity_object.chapter_id) + .order_by(ChapterActivity.order) + ) + chapter_activities = db_session.exec(statement).all() + + last_order = chapter_activities[-1].order if chapter_activities else 0 + to_be_used_order = last_order + 1 + # Add activity to chapter activity_chapter = ChapterActivity( chapter_id=activity_object.chapter_id, - activity_id=activity.id is not None, + activity_id=activity.id if activity.id else 0, 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, + order=to_be_used_order, ) # Insert ChapterActivity link in DB diff --git a/apps/api/src/services/courses/courses.py b/apps/api/src/services/courses/courses.py index e12d3d06..95a90426 100644 --- a/apps/api/src/services/courses/courses.py +++ b/apps/api/src/services/courses/courses.py @@ -54,7 +54,6 @@ async def get_course_meta( async def create_course( request: Request, course_object: CourseCreate, - org_id: int, current_user: PublicUser, db_session: Session, thumbnail_file: UploadFile | None = None, @@ -62,7 +61,7 @@ async def create_course( course = Course.from_orm(course_object) # Complete course object - course.org_id = org_id + course.org_id = course.org_id course.course_uuid = str(uuid4()) course.creation_date = str(datetime.now()) course.update_date = str(datetime.now()) @@ -70,7 +69,7 @@ async def create_course( # Upload thumbnail if thumbnail_file and thumbnail_file.filename: 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) + await upload_thumbnail(thumbnail_file, name_in_disk, course_object.org_id, course.course_uuid) course_object.thumbnail = name_in_disk # Insert course @@ -80,7 +79,7 @@ async def create_course( # Make the user the creator of the course course_author = CourseAuthor( - course_id=course.id is not None, + course_id=course.id if course.id else 0, user_id=current_user.id, authorship=CourseAuthorshipEnum.CREATOR, creation_date=str(datetime.now()),