diff --git a/apps/api/src/db/courses/activities.py b/apps/api/src/db/courses/activities.py index d6d8f0d8..50ec31c8 100644 --- a/apps/api/src/db/courses/activities.py +++ b/apps/api/src/db/courses/activities.py @@ -67,6 +67,8 @@ class ActivityUpdate(ActivityBase): class ActivityRead(ActivityBase): id: int + org_id: int + course_id: int activity_uuid: str creation_date: str update_date: str diff --git a/apps/api/src/routers/courses/activities/activities.py b/apps/api/src/routers/courses/activities/activities.py index d7c69035..8ca5877a 100644 --- a/apps/api/src/routers/courses/activities/activities.py +++ b/apps/api/src/routers/courses/activities/activities.py @@ -7,6 +7,7 @@ from src.services.courses.activities.activities import ( create_activity, get_activity, get_activities, + get_activityby_id, update_activity, delete_activity, ) @@ -34,8 +35,22 @@ async def api_create_activity( return await create_activity(request, activity_object, current_user, db_session) -@router.get("/{activity_id}") +@router.get("/{activity_uuid}") async def api_get_activity( + request: Request, + activity_uuid: str, + current_user: PublicUser = Depends(get_current_user), + db_session=Depends(get_db_session), +) -> ActivityRead: + """ + Get single activity by activity_id + """ + return await get_activity( + request, activity_uuid, current_user=current_user, db_session=db_session + ) + +@router.get("/id/{activity_id}") +async def api_get_activityby_id( request: Request, activity_id: str, current_user: PublicUser = Depends(get_current_user), @@ -44,11 +59,10 @@ async def api_get_activity( """ Get single activity by activity_id """ - return await get_activity( + return await get_activityby_id( request, activity_id, current_user=current_user, db_session=db_session ) - - + @router.get("/chapter/{chapter_id}") async def api_get_chapter_activities( request: Request, diff --git a/apps/api/src/routers/courses/assignments.py b/apps/api/src/routers/courses/assignments.py index c9a9509f..8f564915 100644 --- a/apps/api/src/routers/courses/assignments.py +++ b/apps/api/src/routers/courses/assignments.py @@ -206,7 +206,7 @@ async def api_put_assignment_task_ref_file( ) -@router.delete("/{assignment_uuid}/tasks/{task_uuid}") +@router.delete("/{assignment_uuid}/tasks/{assignment_task_uuid}") async def api_delete_assignment_tasks( request: Request, assignment_task_uuid: str, diff --git a/apps/api/src/routers/courses/courses.py b/apps/api/src/routers/courses/courses.py index e81bf9ae..2afd99e4 100644 --- a/apps/api/src/routers/courses/courses.py +++ b/apps/api/src/routers/courses/courses.py @@ -18,13 +18,19 @@ from src.security.auth import get_current_user from src.services.courses.courses import ( create_course, get_course, + get_course_by_id, get_course_meta, get_courses_orgslug, update_course, delete_course, update_course_thumbnail, ) -from src.services.courses.updates import create_update, delete_update, get_updates_by_course_uuid, update_update +from src.services.courses.updates import ( + create_update, + delete_update, + get_updates_by_course_uuid, + update_update, +) router = APIRouter() @@ -93,6 +99,21 @@ async def api_get_course( ) +@router.get("/id/{course_id}") +async def api_get_course_by_id( + request: Request, + course_id: str, + db_session: Session = Depends(get_db_session), + current_user: PublicUser = Depends(get_current_user), +) -> CourseRead: + """ + Get single Course by id + """ + return await get_course_by_id( + request, course_id, current_user=current_user, db_session=db_session + ) + + @router.get("/{course_uuid}/meta") async def api_get_course_meta( request: Request, @@ -154,7 +175,8 @@ async def api_delete_course( return await delete_course(request, course_uuid, current_user, db_session) -@ router.get("/{course_uuid}/updates") + +@router.get("/{course_uuid}/updates") async def api_get_course_updates( request: Request, course_uuid: str, @@ -165,7 +187,10 @@ async def api_get_course_updates( Get Course Updates by course_uuid """ - return await get_updates_by_course_uuid(request, course_uuid, current_user, db_session) + return await get_updates_by_course_uuid( + request, course_uuid, current_user, db_session + ) + @router.post("/{course_uuid}/updates") async def api_create_course_update( @@ -183,6 +208,7 @@ async def api_create_course_update( request, course_uuid, update_object, current_user, db_session ) + @router.put("/{course_uuid}/update/{courseupdate_uuid}") async def api_update_course_update( request: Request, @@ -200,6 +226,7 @@ async def api_update_course_update( request, courseupdate_uuid, update_object, current_user, db_session ) + @router.delete("/{course_uuid}/update/{courseupdate_uuid}") async def api_delete_course_update( request: Request, @@ -213,4 +240,3 @@ async def api_delete_course_update( """ return await delete_update(request, courseupdate_uuid, current_user, db_session) - diff --git a/apps/api/src/services/courses/activities/activities.py b/apps/api/src/services/courses/activities/activities.py index f3a93d2c..8b5c7d03 100644 --- a/apps/api/src/services/courses/activities/activities.py +++ b/apps/api/src/services/courses/activities/activities.py @@ -116,6 +116,38 @@ async def get_activity( return activity +async def get_activityby_id( + 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=404, + detail="Activity not found", + ) + + # Get course from that activity + statement = select(Course).where(Course.id == activity.course_id) + course = db_session.exec(statement).first() + + if not course: + raise HTTPException( + status_code=404, + detail="Course not found", + ) + + # RBAC check + await rbac_check(request, course.course_uuid, current_user, "read", db_session) + + activity = ActivityRead.model_validate(activity) + + return activity + async def update_activity( request: Request, diff --git a/apps/api/src/services/courses/courses.py b/apps/api/src/services/courses/courses.py index b3d2ed33..744f77b4 100644 --- a/apps/api/src/services/courses/courses.py +++ b/apps/api/src/services/courses/courses.py @@ -58,6 +58,38 @@ async def get_course( return course +async def get_course_by_id( + request: Request, + course_id: str, + current_user: PublicUser | AnonymousUser, + 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", + ) + + # RBAC check + await rbac_check(request, course.course_uuid, current_user, "read", db_session) + + # Get course authors + authors_statement = ( + select(User) + .join(ResourceAuthor) + .where(ResourceAuthor.resource_uuid == course.course_uuid) + ) + authors = db_session.exec(authors_statement).all() + + # convert from User to UserRead + authors = [UserRead.model_validate(author) for author in authors] + + course = CourseRead(**course.model_dump(), authors=authors) + + return course async def get_course_meta( request: Request, diff --git a/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor.tsx b/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor.tsx index 818bffd0..85557ec4 100644 --- a/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor.tsx +++ b/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor.tsx @@ -2,33 +2,66 @@ import { useAssignments } from '@components/Contexts/Assignments/AssignmentContext'; import { useAssignmentsTask, useAssignmentsTaskDispatch } from '@components/Contexts/Assignments/AssignmentsTaskContext'; import { useLHSession } from '@components/Contexts/LHSessionContext'; +import { useOrg } from '@components/Contexts/OrgContext'; import FormLayout, { FormField, FormLabelAndMessage, Input, Textarea } from '@components/StyledElements/Form/Form'; import * as Form from '@radix-ui/react-form'; -import { getActivity } from '@services/courses/activities'; -import { updateAssignmentTask, updateReferenceFile } from '@services/courses/assignments'; +import { getAPIUrl } from '@services/config/config'; +import { getActivity, getActivityByID } from '@services/courses/activities'; +import { deleteAssignmentTask, updateAssignmentTask, updateReferenceFile } from '@services/courses/assignments'; import { getTaskRefFileDir } from '@services/media/media'; import { useFormik } from 'formik'; -import { ArrowBigUpDash, Cloud, File, GalleryVerticalEnd, Info, Loader, TentTree, Upload, UploadCloud } from 'lucide-react' +import { ArrowBigUpDash, Cloud, File, GalleryVerticalEnd, Info, Loader, TentTree, Trash, Upload, UploadCloud } from 'lucide-react' import Link from 'next/link'; import React, { use, useEffect } from 'react' import toast from 'react-hot-toast'; +import { mutate } from 'swr'; function AssignmentTaskEditor({ page }: any) { const [selectedSubPage, setSelectedSubPage] = React.useState(page) + const assignment = useAssignments() as any const assignmentTaskState = useAssignmentsTask() as any + const assignmentTaskStateHook = useAssignmentsTaskDispatch() as any + const session = useLHSession() as any; + const access_token = session?.data?.tokens?.access_token; + + async function deleteTaskUI() { + const res = await deleteAssignmentTask(assignmentTaskState.assignmentTask.assignment_task_uuid, assignment.assignment_object.assignment_uuid, access_token) + if (res) { + assignmentTaskStateHook({ + type: 'SET_MULTIPLE_STATES', + payload: { + selectedAssignmentTaskUUID: null, + assignmentTask: {}, + }, + }); + mutate(`${getAPIUrl()}assignments/${assignment.assignment_object.assignment_uuid}/tasks`) + toast.success('Task deleted successfully') + } else { + toast.error('Error deleting task, please retry later.') + } + } useEffect(() => { - console.log(assignmentTaskState) } - , [assignmentTaskState]) + , [assignmentTaskState,assignmentTaskStateHook]) return (
{assignmentTaskState.assignmentTask && Object.keys(assignmentTaskState.assignmentTask).length > 0 && (
-
- {assignmentTaskState?.assignmentTask.title} +
+
+ {assignmentTaskState?.assignmentTask.title} +
+
+
deleteTaskUI()} + className='flex px-2 py-1.5 cursor-pointer rounded-md space-x-2 items-center bg-gradient-to-bl text-red-800 bg-rose-100 border border-rose-600/10 shadow-rose-900/10 shadow-lg'> + +

Delete Task

+
+
{ + return getTaskRefFileDir( + org?.org_uuid, + assignment.course_object.course_uuid, + assignment.activity_object.activity_uuid, + assignment.assignment_object.assignment_uuid, + assignmentTaskState.assignmentTask.assignment_task_uuid, + assignmentTaskState.assignmentTask.reference_file + ) + } + const deleteReferenceFile = async () => { setIsLoading(true) const res = await updateReferenceFile( @@ -245,19 +292,16 @@ function UpdateTaskRef() { } async function getActivityUI() { - const res = await getActivity(assignment.assignment_object.activity_id, null, access_token) - console.log(res) + const res = await getActivityByID(assignment.assignment_object.activity_id, null, access_token) setActivity(res.data) } - + useEffect(() => { getActivityUI() - console.log(assignment.assignment_object.assignment_uuid) - console.log(assignmentTaskState.assignmentTask.assignment_task_uuid) } - , [assignmentTaskState]) + , [assignmentTaskState, org]) @@ -273,7 +317,7 @@ function UpdateTaskRef() { )}
- {assignmentTaskState.assignmentTask.reference_file && ( + {assignmentTaskState.assignmentTask.reference_file && !isLoading && (
@@ -283,10 +327,11 @@ function UpdateTaskRef() { {assignmentTaskState.assignmentTask.reference_file.split('.').pop()}
- Download + Download {/** */}
diff --git a/apps/web/components/Contexts/Assignments/AssignmentContext.tsx b/apps/web/components/Contexts/Assignments/AssignmentContext.tsx index f54844ee..8feb351b 100644 --- a/apps/web/components/Contexts/Assignments/AssignmentContext.tsx +++ b/apps/web/components/Contexts/Assignments/AssignmentContext.tsx @@ -10,7 +10,7 @@ export const AssignmentContext = createContext({}) export function AssignmentProvider({ children, assignment_uuid }: { children: React.ReactNode, assignment_uuid: string }) { const session = useLHSession() as any const accessToken = session?.data?.tokens?.access_token - const [assignmentsFull, setAssignmentsFull] = React.useState({ assignment_object: null, assignment_tasks: null }) + const [assignmentsFull, setAssignmentsFull] = React.useState({ assignment_object: null, assignment_tasks: null, course_object: null , activity_object: null}) const { data: assignment, error: assignmentError } = useSWR( `${getAPIUrl()}assignments/${assignment_uuid}`, @@ -22,15 +22,28 @@ export function AssignmentProvider({ children, assignment_uuid }: { children: Re (url) => swrFetcher(url, accessToken) ) + // Define a key for the course object based on assignment data + const course_id = assignment?.course_id + + const { data: course_object, error: courseObjectError } = useSWR( + course_id ? `${getAPIUrl()}courses/id/${course_id}` : null, + (url) => swrFetcher(url, accessToken) + ) + + const activity_id = assignment?.activity_id + + const { data: activity_object, error: activityObjectError } = useSWR( + activity_id ? `${getAPIUrl()}activities/id/${activity_id}` : null, + (url) => swrFetcher(url, accessToken) + ) + useEffect(() => { - setAssignmentsFull({ assignment_object: assignment, assignment_tasks: assignment_tasks }) - } - , [assignment, assignment_tasks]) + setAssignmentsFull({ assignment_object: assignment, assignment_tasks: assignment_tasks, course_object: course_object, activity_object: activity_object }) + }, [assignment, assignment_tasks, course_object, activity_object]) - if (assignmentError || assignmentTasksError) return
- - if (!assignment || !assignment_tasks) return
+ if (assignmentError || assignmentTasksError || courseObjectError) return
+ if (!assignment || !assignment_tasks || (course_id && !course_object)) return
return {children} } diff --git a/apps/web/components/Contexts/Assignments/AssignmentsTaskContext.tsx b/apps/web/components/Contexts/Assignments/AssignmentsTaskContext.tsx index 99d8adc2..32b69145 100644 --- a/apps/web/components/Contexts/Assignments/AssignmentsTaskContext.tsx +++ b/apps/web/components/Contexts/Assignments/AssignmentsTaskContext.tsx @@ -36,19 +36,19 @@ export function AssignmentsTaskProvider({ children }: { children: React.ReactNod async function fetchAssignmentTask(assignmentTaskUUID: string) { const res = await getAssignmentTask(assignmentTaskUUID, access_token); - + if (res.success) { dispatch({ type: 'setAssignmentTask', payload: res.data }); } } useEffect(() => { - + if (state.selectedAssignmentTaskUUID) { fetchAssignmentTask(state.selectedAssignmentTaskUUID); mutate(`${getAPIUrl()}assignments/${assignment.assignment_object?.assignment_uuid}/tasks`); } - }, [state.selectedAssignmentTaskUUID, state.reloadTrigger,assignment]); + }, [state.selectedAssignmentTaskUUID, state.reloadTrigger, assignment]); return ( @@ -78,11 +78,17 @@ export function useAssignmentsTaskDispatch() { function assignmentstaskReducer(state: State, action: Action): State { switch (action.type) { case 'setSelectedAssignmentTaskUUID': + console.log('st', action.payload) return { ...state, selectedAssignmentTaskUUID: action.payload }; case 'setAssignmentTask': return { ...state, assignmentTask: action.payload }; case 'reload': return { ...state, reloadTrigger: state.reloadTrigger + 1 }; + case 'SET_MULTIPLE_STATES': + return { + ...state, + ...action.payload, + }; default: return state; } diff --git a/apps/web/services/courses/activities.ts b/apps/web/services/courses/activities.ts index 1fa050f0..162780f7 100644 --- a/apps/web/services/courses/activities.ts +++ b/apps/web/services/courses/activities.ts @@ -74,12 +74,25 @@ export async function createExternalVideoActivity( } export async function getActivity( + activity_uuid: any, + next: any, + access_token: string +) { + const result = await fetch( + `${getAPIUrl()}activities/${activity_uuid}`, + RequestBodyWithAuthHeader('GET', null, next, access_token) + ) + const res = await result.json() + return res +} + +export async function getActivityByID( activity_id: any, next: any, access_token: string ) { const result = await fetch( - `${getAPIUrl()}activities/${activity_id}`, + `${getAPIUrl()}activities/id/${activity_id}`, RequestBodyWithAuthHeader('GET', null, next, access_token) ) const res = await result.json() diff --git a/apps/web/services/courses/assignments.ts b/apps/web/services/courses/assignments.ts index 1e7fc28e..a8a32523 100644 --- a/apps/web/services/courses/assignments.ts +++ b/apps/web/services/courses/assignments.ts @@ -92,6 +92,19 @@ export async function updateAssignmentTask( return res } +export async function deleteAssignmentTask( + assignmentTaskUUID: string, + assignmentUUID: string, + access_token: string +) { + const result: any = await fetch( + `${getAPIUrl()}assignments/${assignmentUUID}/tasks/${assignmentTaskUUID}`, + RequestBodyWithAuthHeader('DELETE', null, null, access_token) + ) + const res = await getResponseMetadata(result) + return res +} + export async function updateReferenceFile( file: any, assignmentTaskUUID: string, diff --git a/apps/web/services/courses/courses.ts b/apps/web/services/courses/courses.ts index 7c9313ed..a8b687fd 100644 --- a/apps/web/services/courses/courses.ts +++ b/apps/web/services/courses/courses.ts @@ -55,6 +55,15 @@ export async function getCourse(course_uuid: string, next: any, access_token:any return res } +export async function getCourseById(course_id: string, next: any, access_token:any) { + const result: any = await fetch( + `${getAPIUrl()}courses/id/${course_id}`, + RequestBodyWithAuthHeader('GET', null, next,access_token) + ) + const res = await errorHandling(result) + return res +} + export async function updateCourseThumbnail(course_uuid: any, thumbnail: any, access_token:any) { const formData = new FormData() formData.append('thumbnail', thumbnail) diff --git a/apps/web/services/media/media.ts b/apps/web/services/media/media.ts index 2b0185ee..67dd305d 100644 --- a/apps/web/services/media/media.ts +++ b/apps/web/services/media/media.ts @@ -47,30 +47,30 @@ export function getActivityBlockMediaDirectory( export function getTaskRefFileDir( orgUUID: string, - courseId: string, - activityId: string, + courseUUID: string, + activityUUID: string, assignmentUUID: string, assignmentTaskUUID: string, fileID : string ) { - let uri = `${getMediaUrl()}content/orgs/${orgUUID}/courses/${courseId}/activities/${activityId}/assignments/${assignmentUUID}/tasks/${assignmentTaskUUID}/${fileID}` + let uri = `${getMediaUrl()}content/orgs/${orgUUID}/courses/${courseUUID}/activities/${activityUUID}/assignments/${assignmentUUID}/tasks/${assignmentTaskUUID}/${fileID}` return uri } export function getActivityMediaDirectory( orgUUID: string, - courseId: string, - activityId: string, + courseUUID: string, + activityUUID: string, fileId: string, activityType: string ) { if (activityType == 'video') { - let uri = `${getMediaUrl()}content/orgs/${orgUUID}/courses/${courseId}/activities/${activityId}/video/${fileId}` + let uri = `${getMediaUrl()}content/orgs/${orgUUID}/courses/${courseUUID}/activities/${activityUUID}/video/${fileId}` return uri } if (activityType == 'documentpdf') { - let uri = `${getMediaUrl()}content/orgs/${orgUUID}/courses/${courseId}/activities/${activityId}/documentpdf/${fileId}` + let uri = `${getMediaUrl()}content/orgs/${orgUUID}/courses/${courseUUID}/activities/${activityUUID}/documentpdf/${fileId}` return uri } }