diff --git a/front/app/_orgs/[orgslug]/activity/page.tsx b/front/app/_orgs/[orgslug]/activity/page.tsx index 5228795c..5a9707f3 100644 --- a/front/app/_orgs/[orgslug]/activity/page.tsx +++ b/front/app/_orgs/[orgslug]/activity/page.tsx @@ -1,41 +1,25 @@ "use client"; -import { getBackendUrl } from "@services/config"; -import { getActivities } from "@services/courses/activity"; -import { getOrganizationContextInfo } from "@services/orgs"; -import { RequestBody } from "@services/utils/requests"; +import { getAPIUrl, getBackendUrl } from "@services/config"; +import { swrFetcher } from "@services/utils/requests"; import React from "react"; import { styled } from "styled-components"; import useSWR from "swr"; function Activity(params: any) { let orgslug = params.params.orgslug; - const [isLoading, setIsLoading] = React.useState(true); - const [activities, setActivities] = React.useState([]); - - async function fetchActivities() { - setIsLoading(true); - const org = await getOrganizationContextInfo(orgslug); - const activities = await getActivities(org.org_id); - console.log(activities); - - setActivities(activities); - setIsLoading(false); - } - React.useEffect(() => { - fetchActivities(); - }, []); + const { data: activities, error: error } = useSWR(`${getAPIUrl()}activity/org_slug/${orgslug}/activities`, swrFetcher); return (

Activity


- {isLoading ? ( + {error &&

Failed to load

} + {!activities ? (
Loading...
) : (
{activities.map((activity: any) => ( - diff --git a/front/app/_orgs/[orgslug]/collections/new/page.tsx b/front/app/_orgs/[orgslug]/collections/new/page.tsx index 0a1c0b50..7e766919 100644 --- a/front/app/_orgs/[orgslug]/collections/new/page.tsx +++ b/front/app/_orgs/[orgslug]/collections/new/page.tsx @@ -1,11 +1,12 @@ "use client"; import { useRouter } from "next/navigation"; import React from "react"; -import { Title } from "../../../../../components/UI/Elements/Styles/Title"; -import Layout from "../../../../../components/UI/Layout"; -import { getOrganizationContextInfo } from "../../../../../services/orgs"; -import { getOrgCourses } from "../../../../../services/courses/courses"; -import { createCollection } from "../../../../../services/collections"; +import { Title } from "@components/UI/Elements/Styles/Title"; +import { createCollection } from "@services/collections"; +import useSWR from "swr"; +import { getAPIUrl } from "@services/config"; +import { swrFetcher } from "@services/utils/requests"; +import { getOrganizationContextInfo } from "@services/orgs"; function NewCollection(params : any) { const orgslug = params.params.orgslug; @@ -13,19 +14,17 @@ function NewCollection(params : any) { const [org, setOrg] = React.useState({}) as any; const [description, setDescription] = React.useState(""); const [selectedCourses, setSelectedCourses] = React.useState([]) as any; - const [courses, setCourses] = React.useState([]) as any; - const [isLoading, setIsLoading] = React.useState(false); const router = useRouter(); - async function getCourses() { - - setIsLoading(true); - const org = await getOrganizationContextInfo(orgslug); - setOrg(org); - const courses = await getOrgCourses(org.org_id); - setCourses(courses); - setIsLoading(false); - } + const { data: courses, error: error } = useSWR(`${getAPIUrl()}courses/org_slug/${orgslug}/page/1/limit/10`, swrFetcher); + + React.useEffect(() => { + async function getOrg() { + const org = await getOrganizationContextInfo(orgslug); + setOrg(org); + } + getOrg(); + }, []); const handleNameChange = (event: React.ChangeEvent) => { setName(event.target.value); @@ -48,20 +47,13 @@ function NewCollection(params : any) { router.push("/org/" + orgslug + "/collections"); }; - React.useEffect(() => { - if (params.params.orgslug) { - getCourses(); - } - return () => {}; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [params.params.orgslug]); return ( <> Add new
- {isLoading ? ( + {!courses ? (

Loading...

) : (
diff --git a/front/app/_orgs/[orgslug]/collections/page.tsx b/front/app/_orgs/[orgslug]/collections/page.tsx index 3207daec..f2c11b26 100644 --- a/front/app/_orgs/[orgslug]/collections/page.tsx +++ b/front/app/_orgs/[orgslug]/collections/page.tsx @@ -1,48 +1,32 @@ "use client"; -import Layout from "../../../../components/UI/Layout"; import Link from "next/link"; -import { useRouter } from "next/router"; import React from "react"; import styled from "styled-components"; -import { Title } from "../../../../components/UI/Elements/Styles/Title"; -import { deleteCollection, getOrgCollections } from "../../../../services/collections"; -import { getOrganizationContextInfo } from "../../../../services/orgs"; -import { getBackendUrl } from "../../../../services/config"; +import { Title } from "@components/UI/Elements/Styles/Title"; +import { deleteCollection } from "@services/collections"; +import { getAPIUrl, getBackendUrl, getUriWithOrg } from "@services/config"; +import { swrFetcher } from "@services/utils/requests"; +import useSWR, { mutate } from "swr"; -function Collections(params:any) { +function Collections(params: any) { const orgslug = params.params.orgslug; - - const [isLoading, setIsLoading] = React.useState(true); - const [collections, setCollections] = React.useState([]); - - async function fetchCollections() { - setIsLoading(true); - const org = await getOrganizationContextInfo(orgslug); - const collections = await getOrgCollections(org.org_id); - setCollections(collections); - setIsLoading(false); - } + const { data: collections, error: error } = useSWR(`${getAPIUrl()}collections/page/1/limit/10`, swrFetcher); async function deleteCollectionAndFetch(collectionId: number) { - setIsLoading(true); await deleteCollection(collectionId); - await fetchCollections(); - setIsLoading(false); + mutate(`${getAPIUrl()}collections/page/1/limit/10`); } - React.useEffect(() => { - fetchCollections(); - }, []); - return ( <> {orgslug} Collections :{" "} - <Link href={"/collections/new"}> + <Link href={getUriWithOrg(orgslug, "/collections/new")}> <button>+</button> </Link>{" "} - {isLoading ? ( + {error &&

Failed to load

} + {!collections ? (
Loading...
) : (
diff --git a/front/app/_orgs/[orgslug]/course/[courseid]/edit/page.tsx b/front/app/_orgs/[orgslug]/course/[courseid]/edit/page.tsx index 66612aee..360beeab 100644 --- a/front/app/_orgs/[orgslug]/course/[courseid]/edit/page.tsx +++ b/front/app/_orgs/[orgslug]/course/[courseid]/edit/page.tsx @@ -2,17 +2,17 @@ import React from "react"; import { useState, useEffect } from "react"; import styled from "styled-components"; -import { Header } from "../../../../../../components/UI/Header"; -import Layout from "../../../../../../components/UI/Layout"; -import { Title } from "../../../../../../components/UI/Elements/Styles/Title"; +import { Header } from "@components/UI/Header"; +import Layout from "@components/UI/Layout"; +import { Title } from "@components/UI/Elements/Styles/Title"; import { DragDropContext, Droppable } from "react-beautiful-dnd"; -import { initialData, initialData2 } from "../../../../../../components/Drags/data"; -import Chapter from "../../../../../../components/Drags/Chapter"; -import { createChapter, deleteChapter, getCourseChaptersMetadata, updateChaptersMetadata } from "../../../../../../services/courses/chapters"; +import { initialData, initialData2 } from "@components/Drags/data"; +import Chapter from "@components/Drags/Chapter"; +import { createChapter, deleteChapter, getCourseChaptersMetadata, updateChaptersMetadata } from "@services/courses/chapters"; import { useRouter } from "next/navigation"; -import NewChapterModal from "../../../../../../components/Modals/CourseEdit/NewChapter"; -import NewLectureModal from "../../../../../../components/Modals/CourseEdit/NewLecture"; -import { createLecture, createFileLecture } from "../../../../../../services/courses/lectures"; +import NewChapterModal from "@components/Modals/CourseEdit/NewChapter"; +import NewLectureModal from "@components/Modals/CourseEdit/NewLecture"; +import { createLecture, createFileLecture } from "@services/courses/lectures"; function CourseEdit(params: any) { const router = useRouter(); diff --git a/front/app/_orgs/[orgslug]/course/[courseid]/lecture/[lectureid]/edit/page.tsx b/front/app/_orgs/[orgslug]/course/[courseid]/lecture/[lectureid]/edit/page.tsx index 384b6fb1..a73ce2ca 100644 --- a/front/app/_orgs/[orgslug]/course/[courseid]/lecture/[lectureid]/edit/page.tsx +++ b/front/app/_orgs/[orgslug]/course/[courseid]/lecture/[lectureid]/edit/page.tsx @@ -7,44 +7,24 @@ import { useRouter } from "next/navigation"; import { getLecture } from "../../../../../../../../services/courses/lectures"; import AuthProvider from "../../../../../../../../components/Security/AuthProvider"; import EditorWrapper from "../../../../../../../../components/Editor/EditorWrapper"; -import { getCourseMetadata } from "../../../../../../../../services/courses/courses"; +import useSWR, { mutate } from "swr"; +import { getAPIUrl } from "@services/config"; +import { swrFetcher } from "@services/utils/requests"; function EditLecture(params: any) { const router = useRouter(); const lectureid = params.params.lectureid; const courseid = params.params.courseid; - const [lecture, setLecture] = React.useState({}); - const [courseInfo, setCourseInfo] = React.useState({}) as any; - const [isLoading, setIsLoading] = React.useState(true); + const { data: courseInfo, error: error_course } = useSWR(`${getAPIUrl()}courses/meta/course_${courseid}`, swrFetcher); + const { data: lecture, error: error_lecture } = useSWR(`${getAPIUrl()}lectures/lecture_${lectureid}`, swrFetcher); - async function fetchLectureData() { - const lecture = await getLecture("lecture_" + lectureid); - setLecture(lecture); - } - async function fetchCourseInfo() { - const course = await getCourseMetadata("course_" + courseid); - setCourseInfo(course); - } - - async function fetchAllData() { - await fetchLectureData(); - await fetchCourseInfo(); - setIsLoading(false); - } - - React.useEffect(() => { - if (lectureid && courseid) { - fetchAllData(); - } - return () => {}; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [lectureid, courseid ]); + return ( - {isLoading ?
Loading...
: } + {!courseInfo || !lecture ?
Loading...
: }
); } diff --git a/front/app/_orgs/[orgslug]/course/[courseid]/lecture/[lectureid]/page.tsx b/front/app/_orgs/[orgslug]/course/[courseid]/lecture/[lectureid]/page.tsx index 1148b6da..b9ae1ff8 100644 --- a/front/app/_orgs/[orgslug]/course/[courseid]/lecture/[lectureid]/page.tsx +++ b/front/app/_orgs/[orgslug]/course/[courseid]/lecture/[lectureid]/page.tsx @@ -2,54 +2,38 @@ import { useRouter } from "next/navigation"; import Link from "next/link"; import React, { useMemo } from "react"; -import Layout from "../../../../../../../components/UI/Layout"; -import { getLecture } from "../../../../../../../services/courses/lectures"; -import { getBackendUrl } from "../../../../../../../services/config"; -import Canva from "../../../../../../../components/LectureViews/DynamicCanva/DynamicCanva"; +import Layout from "@components/UI/Layout"; +import { getLecture } from "@services/courses/lectures"; +import { getAPIUrl, getBackendUrl } from "@services/config"; +import Canva from "@components/LectureViews/DynamicCanva/DynamicCanva"; import styled from "styled-components"; -import { getCourse, getCourseMetadata } from "../../../../../../../services/courses/courses"; +import { getCourse } from "@services/courses/courses"; import VideoLecture from "@components/LectureViews/Video/Video"; +import useSWR, { mutate } from "swr"; import { Check } from "lucide-react"; import { maskLectureAsComplete } from "@services/courses/activity"; +import { swrFetcher } from "@services/utils/requests"; function LecturePage(params: any) { - const router = useRouter(); const lectureid = params.params.lectureid; const courseid = params.params.courseid; const orgslug = params.params.orgslug; - const [lecture, setLecture] = React.useState({}); - const [course, setCourse] = React.useState({}); - const [isLoading, setIsLoading] = React.useState(true); - async function fetchLectureData() { - setIsLoading(true); - const lecture = await getLecture("lecture_" + lectureid); - setLecture(lecture); - } + const { data: course, error: error_course } = useSWR(`${getAPIUrl()}courses/meta/course_${courseid}`, swrFetcher); + const { data: lecture, error: error_lecture } = useSWR(`${getAPIUrl()}lectures/lecture_${lectureid}`, swrFetcher); - async function fetchCourseData() { - const course = await getCourseMetadata("course_" + courseid); - setCourse(course); - setIsLoading(false); - } + console.log(course, lecture); async function markLectureAsCompleteFront() { const activity = await maskLectureAsComplete("" + lectureid, courseid, lecture.lecture_id.replace("lecture_", "")); - fetchCourseData(); + mutate(`${getAPIUrl()}lectures/lecture_${lectureid}`); + mutate(`${getAPIUrl()}courses/meta/course_${courseid}`); } - React.useEffect(() => { - if (lectureid) { - fetchLectureData(); - fetchCourseData(); - } - return () => {}; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [lectureid]); - return ( <> - {isLoading ? ( + {error_course &&

Failed to load

} + {!course || !lecture ? (
Loading...
) : ( @@ -85,30 +69,36 @@ function LecturePage(params: any) { })} - - {lecture.type == "dynamic" && } - {/* todo : use apis & streams instead of this */} - {lecture.type == "video" && } + {lecture ? ( + + {lecture.type == "dynamic" && } + {/* todo : use apis & streams instead of this */} + {lecture.type == "video" && } - - {course.activity.lectures_marked_complete.includes("lecture_"+lectureid) && course.activity.status == "ongoing" ? ( - - ) : ( - - )} - - + + {course.activity.lectures_marked_complete && + course.activity.lectures_marked_complete.includes("lecture_" + lectureid) && + course.activity.status == "ongoing" ? ( + + ) : ( + + )} + + + ) : ( +
Loading...
+ )}
)} diff --git a/front/app/_orgs/[orgslug]/course/[courseid]/page.tsx b/front/app/_orgs/[orgslug]/course/[courseid]/page.tsx index a4031e94..214b064d 100644 --- a/front/app/_orgs/[orgslug]/course/[courseid]/page.tsx +++ b/front/app/_orgs/[orgslug]/course/[courseid]/page.tsx @@ -2,67 +2,55 @@ import { EyeOpenIcon, Pencil2Icon } from "@radix-ui/react-icons"; import { closeActivity, createActivity } from "@services/courses/activity"; import Link from "next/link"; -import { useRouter } from "next/navigation"; import React from "react"; import styled from "styled-components"; -import Layout from "../../../../../components/UI/Layout"; -import { getAPIUrl, getBackendUrl } from "../../../../../services/config"; -import { getCourse, getCourseMetadata } from "../../../../../services/courses/courses"; +import { getAPIUrl, getBackendUrl } from "@services/config"; +import useSWR, { mutate } from "swr"; +import { swrFetcher } from "@services/utils/requests"; const CourseIdPage = (params: any) => { - const router = useRouter(); const courseid = params.params.courseid; const orgslug = params.params.orgslug; - - const [isLoading, setIsLoading] = React.useState(true); - const [courseInfo, setCourseInfo] = React.useState({}) as any; - - async function fetchCourseInfo() { - const course = await getCourseMetadata("course_" + courseid); - setCourseInfo(course); - setIsLoading(false); - } + const { data: course, error: error } = useSWR(`${getAPIUrl()}courses/meta/course_${courseid}`, swrFetcher); async function startActivity() { - const activity = await createActivity("course_" + courseid); - fetchCourseInfo(); + // Create activity + await createActivity("course_" + courseid); + + // Mutate course + mutate(`${getAPIUrl()}courses/meta/course_${courseid}`); } async function quitActivity() { - let activity_id = courseInfo.activity.activity_id; - let org_id = courseInfo.activity.org_id; - console.log("activity", activity_id); + // Get activity id and org id + let activity_id = course.activity.activity_id; + let org_id = course.activity.org_id; + // Close activity let activity = await closeActivity(activity_id, org_id); console.log(activity); - fetchCourseInfo(); + // Mutate course + mutate(`${getAPIUrl()}courses/meta/course_${courseid}`); } - React.useEffect(() => { - if (courseid && orgslug) { - fetchCourseInfo(); - } - return () => {}; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [courseid && orgslug]); - return ( <> - {isLoading ? ( + {error &&

Failed to load

} + {!course ? (
Loading...
) : (

Course

- {courseInfo.course.name}{" "} + {course.course.name}{" "} {" "}

- {courseInfo.chapters.map((chapter: any) => { + {course.chapters.map((chapter: any) => { return ( <> {chapter.lectures.map((lecture: any) => { @@ -81,7 +69,7 @@ const CourseIdPage = (params: any) => { - + @@ -89,18 +77,18 @@ const CourseIdPage = (params: any) => {

Description

-

{courseInfo.course.description}

+

{course.course.description}

What you will learn

-

{courseInfo.course.learnings == ![] ? "no data" : courseInfo.course.learnings}

+

{course.course.learnings == ![] ? "no data" : course.course.learnings}

Course Lessons

- {courseInfo.chapters.map((chapter: any) => { + {course.chapters.map((chapter: any) => { return ( <>

Chapter : {chapter.name}

@@ -123,8 +111,10 @@ const CourseIdPage = (params: any) => {
- {courseInfo.activity.status == "ongoing" ? ( - + {course.activity.status == "ongoing" ? ( + ) : ( )} diff --git a/front/app/_orgs/[orgslug]/courses/page.tsx b/front/app/_orgs/[orgslug]/courses/page.tsx index 1b678446..29cf16ea 100644 --- a/front/app/_orgs/[orgslug]/courses/page.tsx +++ b/front/app/_orgs/[orgslug]/courses/page.tsx @@ -3,32 +3,21 @@ import Link from "next/link"; import { useRouter } from "next/navigation"; import React from "react"; import styled from "styled-components"; -import { Header } from "../../../../components/UI/Header"; -import Layout from "../../../../components/UI/Layout"; import { Title } from "../../../../components/UI/Elements/Styles/Title"; -import { getBackendUrl } from "../../../../services/config"; -import { deleteCourseFromBackend, getOrgCourses } from "../../../../services/courses/courses"; -import { getOrganizationContextInfo } from "../../../../services/orgs"; +import { getAPIUrl, getBackendUrl } from "../../../../services/config"; +import { deleteCourseFromBackend } from "../../../../services/courses/courses"; +import useSWR, { mutate } from "swr"; +import { swrFetcher } from "@services/utils/requests"; const CoursesIndexPage = (params: any) => { const router = useRouter(); const orgslug = params.params.orgslug; - const [isLoading, setIsLoading] = React.useState(true); - const [orgInfo, setOrgInfo] = React.useState(null); - const [courses, setCourses] = React.useState([]); - - async function fetchCourses() { - const org = await getOrganizationContextInfo(orgslug); - const response = await getOrgCourses(org.org_id); - setCourses(response); - setIsLoading(false); - } + const { data: courses, error: error } = useSWR(`${getAPIUrl()}courses/org_slug/${orgslug}/page/1/limit/10`, swrFetcher); async function deleteCourses(course_id: any) { - const response = await deleteCourseFromBackend(course_id); - const newCourses = courses.filter((course: any) => course.course_id !== course_id); - setCourses(newCourses); + await deleteCourseFromBackend(course_id); + mutate(`${getAPIUrl()}courses/${orgslug}/page/1/limit/10`); } // function to remove "course_" from the course_id @@ -36,15 +25,6 @@ const CoursesIndexPage = (params: any) => { return course_id.replace("course_", ""); } - React.useEffect(() => { - if (orgslug) { - fetchCourses(); - if (courses.length > 0) { - setIsLoading(false); - } - } - }, [isLoading, orgslug]); - return ( <> @@ -55,7 +35,8 @@ const CoursesIndexPage = (params: any) => {
- {isLoading ? ( + {error &&

Failed to load

} + {!courses ? (
Loading...
) : (
diff --git a/front/app/_orgs/[orgslug]/layout.tsx b/front/app/_orgs/[orgslug]/layout.tsx index 523741f3..bf66f8aa 100644 --- a/front/app/_orgs/[orgslug]/layout.tsx +++ b/front/app/_orgs/[orgslug]/layout.tsx @@ -1,6 +1,6 @@ -import "../../../styles/globals.css"; -import { Menu } from "../../../components/UI/Elements/Menu"; -import AuthProvider from "../../../components/Security/AuthProvider"; +import "@styles/globals.css"; +import { Menu } from "@components/UI/Elements/Menu"; +import AuthProvider from "@components/Security/AuthProvider"; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( diff --git a/front/app/organizations/page.tsx b/front/app/organizations/page.tsx index 58c3886c..1d7571d5 100644 --- a/front/app/organizations/page.tsx +++ b/front/app/organizations/page.tsx @@ -3,64 +3,44 @@ import Link from "next/link"; import React from "react"; import Layout from "../../components/UI/Layout"; import { Title } from "../../components/UI/Elements/Styles/Title"; -import { deleteOrganizationFromBackend, getUserOrganizations } from "../../services/orgs"; +import { deleteOrganizationFromBackend } from "@services/orgs"; +import useSWR, { mutate } from "swr"; +import { swrFetcher } from "@services/utils/requests"; +import { getAPIUrl } from "@services/config"; const Organizations = () => { - const [userOrganizations, setUserOrganizations] = React.useState([]); - const [isLoading, setIsLoading] = React.useState(false); + const { data : organizations , error } = useSWR(`${getAPIUrl()}orgs/user/page/1/limit/10`, swrFetcher) - async function fetchUserOrganizations() { - const response = await getUserOrganizations(); - setUserOrganizations(response); - console.log(response); - setIsLoading(false); - } - - async function deleteOrganization(org_id:any) { + async function deleteOrganization(org_id: any) { const response = await deleteOrganizationFromBackend(org_id); - const newOrganizations = userOrganizations.filter((org:any) => org.org_id !== org_id); - setUserOrganizations(newOrganizations); + response && mutate(`${getAPIUrl()}orgs/user/page/1/limit/10`, organizations.filter((org: any) => org.org_id !== org_id)); } - - React.useEffect(() => { - setIsLoading(true); - fetchUserOrganizations(); - setIsLoading(false); - }, []); - - return ( - + <> Your Organizations{" "} <Link href={"/organizations/new"}> - <button>+</button> - </Link>
- {isLoading ? ( + {error &&

Failed to load

} + {!organizations ? (

Loading...

) : (
- {userOrganizations.map((org: any) => ( + {organizations.map((org: any) => (
-

{org.name}

- - +
))}
)} - -
+ ); }; diff --git a/front/components/UI/Elements/Menu.tsx b/front/components/UI/Elements/Menu.tsx index facbe583..151f3e9f 100644 --- a/front/components/UI/Elements/Menu.tsx +++ b/front/components/UI/Elements/Menu.tsx @@ -6,16 +6,21 @@ import learnhouseIcon from "public/learnhouse_icon.png"; import learnhouseLogo from "public/learnhouse_logo.png"; import Link from "next/link"; import Image from "next/image"; -import { useRouter, useSearchParams } from "next/navigation"; +import { useRouter, useSearchParams, usePathname } from "next/navigation"; import { headers } from "next/headers"; +import { getOrgFromUri, getUriWithOrg } from "@services/config"; -export const Menu = ({ orgslug }: any) => { +export const Menu = (params : any) => { + const router = useRouter(); + const pathname = usePathname(); + const orgslug = getOrgFromUri(pathname); + return ( - + @@ -29,14 +34,14 @@ export const Menu = ({ orgslug }: any) => {
  • - Courses + Courses
  • - Collections + Collections
  • {" "} - Activity + Activity
  • More
diff --git a/front/services/auth/auth.ts b/front/services/auth/auth.ts index c6a50845..68fa25e9 100644 --- a/front/services/auth/auth.ts +++ b/front/services/auth/auth.ts @@ -1,4 +1,4 @@ -import { getAPIUrl } from "../config"; +import { getAPIUrl } from "@services/config"; interface LoginAndGetTokenResponse { access_token: "string"; diff --git a/front/services/collections.ts b/front/services/collections.ts index fae3b7c2..96f8a87d 100644 --- a/front/services/collections.ts +++ b/front/services/collections.ts @@ -1,41 +1,9 @@ import { getAPIUrl } from "./config"; -export async function getOrgCollections(org_slug: any) { - const HeadersConfig = new Headers({ "Content-Type": "application/json" }); - - const requestOptions: any = { - method: "GET", - headers: HeadersConfig, - redirect: "follow", - credentials: "include", - }; - - return fetch(`${getAPIUrl()}collections/page/1/limit/10`, requestOptions) - .then((result) => result.json()) - .catch((error) => console.log("error", error)); -} - -export async function getCollection(collection_slug: any) { - const HeadersConfig = new Headers({ "Content-Type": "application/json" }); - - const requestOptions: any = { - method: "GET", - headers: HeadersConfig, - redirect: "follow", - credentials: "include", - }; - - return fetch( - `${getAPIUrl()}collections/${collection_slug}`, - requestOptions - ) - .then((result) => result.json()) - .catch((error) => console.log("error", error)); -} - - - - +/* + This file includes only POST, PUT, DELETE requests + GET requests are called from the frontend using SWR (https://swr.vercel.app/) +*/ export async function deleteCollection(collection_id: any) { const HeadersConfig = new Headers({ "Content-Type": "application/json" }); diff --git a/front/services/config.ts b/front/services/config.ts index 7b486965..dce3e900 100644 --- a/front/services/config.ts +++ b/front/services/config.ts @@ -4,3 +4,18 @@ const LEARNHOUSE_BACKEND_URL = "http://localhost:1338/"; export const getAPIUrl = () => LEARNHOUSE_API_URL; export const getBackendUrl = () => LEARNHOUSE_BACKEND_URL; + +export const getUriWithOrg = (orgslug: string, path: string) => { + return `http://localhost:3000/org/${orgslug}${path}`; +}; + +export const getOrgFromUri = (uri: any) => { + // if url contains /org + if (uri.includes("/org/")) { + let org = uri.match(/\/org\/([\w]+)/)[1]; + return org; + } + else { + return ""; + } +}; diff --git a/front/services/courses/activity.ts b/front/services/courses/activity.ts index e6fa9ebf..82e29a6e 100644 --- a/front/services/courses/activity.ts +++ b/front/services/courses/activity.ts @@ -1,5 +1,5 @@ import { RequestBody } from "@services/utils/requests"; -import { getAPIUrl } from "../config"; +import { getAPIUrl } from "@services/config"; /* This file includes only POST, PUT, DELETE requests @@ -30,11 +30,3 @@ export async function maskLectureAsComplete(org_id: string, course_id: string, l return result; } -// get all activities -export async function getActivities(org_id: string) { - const result: any = await fetch(`${getAPIUrl()}activity/${org_id}/activities`, RequestBody("GET", null)) - .then((result) => result.json()) - .catch((error) => console.log("error", error)); - return result; -} - diff --git a/front/services/courses/chapters.ts b/front/services/courses/chapters.ts index bb28ec1e..79819c95 100644 --- a/front/services/courses/chapters.ts +++ b/front/services/courses/chapters.ts @@ -1,5 +1,5 @@ import { initialData } from "../../components/Drags/data"; -import { getAPIUrl } from "../config"; +import { getAPIUrl } from "@services/config"; export async function getCourseChaptersMetadata(course_id: any) { const HeadersConfig = new Headers({ "Content-Type": "application/json" }); diff --git a/front/services/courses/courses.ts b/front/services/courses/courses.ts index 2bd0861e..d8e29f10 100644 --- a/front/services/courses/courses.ts +++ b/front/services/courses/courses.ts @@ -1,4 +1,9 @@ -import { getAPIUrl } from "../config"; +import { getAPIUrl } from "@services/config"; + +/* + This file includes only POST, PUT, DELETE requests + GET requests are called from the frontend using SWR (https://swr.vercel.app/) +*/ export async function getOrgCourses(org_id: number) { const HeadersConfig = new Headers({ "Content-Type": "application/json" }); @@ -31,22 +36,6 @@ export async function getCourse(course_id: string) { .catch((error) => console.log("error", error)); } -export async function getCourseMetadata(course_id: string) { - const HeadersConfig = new Headers({ "Content-Type": "application/json" }); - - const requestOptions: any = { - method: "GET", - headers: HeadersConfig, - redirect: "follow", - credentials: "include", - }; - - // todo : add course id to url - return fetch(`${getAPIUrl()}courses/meta/${course_id}`, requestOptions) - .then((result) => result.json()) - .catch((error) => console.log("error", error)); - -} export async function createNewCourse(org_id: string, course_body: any, thumbnail: any) { const HeadersConfig = new Headers(); diff --git a/front/services/courses/lectures.ts b/front/services/courses/lectures.ts index d5db065c..c46c351a 100644 --- a/front/services/courses/lectures.ts +++ b/front/services/courses/lectures.ts @@ -1,4 +1,4 @@ -import { getAPIUrl } from "../config"; +import { getAPIUrl } from "@services/config"; export async function createLecture(data: any, chapter_id: any) { data.content = {}; diff --git a/front/services/files/images.ts b/front/services/files/images.ts index b9645dab..e661469e 100644 --- a/front/services/files/images.ts +++ b/front/services/files/images.ts @@ -1,4 +1,4 @@ -import { getAPIUrl } from "../config"; +import { getAPIUrl } from "@services/config"; export async function uploadNewImageFile(file: any, lecture_id: string) { const HeadersConfig = new Headers(); diff --git a/front/services/files/video.ts b/front/services/files/video.ts index 0baec936..622e69c0 100644 --- a/front/services/files/video.ts +++ b/front/services/files/video.ts @@ -1,4 +1,4 @@ -import { getAPIUrl } from "../config"; +import { getAPIUrl } from "@services/config"; export async function uploadNewVideoFile(file: any, lecture_id: string) { const HeadersConfig = new Headers(); diff --git a/front/services/orgs.ts b/front/services/orgs.ts index a407048a..b7b945ff 100644 --- a/front/services/orgs.ts +++ b/front/services/orgs.ts @@ -1,19 +1,9 @@ -import { getAPIUrl } from "./config"; +import { getAPIUrl } from "@services/config"; -export async function getUserOrganizations() { - const HeadersConfig = new Headers({ "Content-Type": "application/json" }); - - const requestOptions: any = { - method: "GET", - headers: HeadersConfig, - redirect: "follow", - credentials: "include", - }; - - return fetch(`${getAPIUrl()}orgs/user/page/1/limit/10`, requestOptions) - .then((result) => result.json()) - .catch((error) => console.log("error", error)); -} +/* + This file includes only POST, PUT, DELETE requests + GET requests are called from the frontend using SWR (https://swr.vercel.app/) +*/ export async function createNewOrganization(body: any) { const HeadersConfig = new Headers({ "Content-Type": "application/json" }); @@ -31,8 +21,6 @@ export async function createNewOrganization(body: any) { .catch((error) => console.log("error", error)); } -// export async function getOrganizationData(org_id) {} - export async function deleteOrganizationFromBackend(org_id: any) { const HeadersConfig = new Headers({ "Content-Type": "application/json" }); @@ -48,7 +36,6 @@ export async function deleteOrganizationFromBackend(org_id: any) { .catch((error) => console.log("error", error)); } -// export async function updateOrganization(org_id) {} export async function getOrganizationContextInfo(org_slug : any){ const HeadersConfig = new Headers({ "Content-Type": "application/json" }); diff --git a/front/services/utils/requests.ts b/front/services/utils/requests.ts index df32b5e3..b81b4dfe 100644 --- a/front/services/utils/requests.ts +++ b/front/services/utils/requests.ts @@ -12,3 +12,31 @@ export const RequestBody = (method: string, data: any) => { return options; }; +export const swrFetcher = async (url: string, body: any) => { + + // Create the request options + let HeadersConfig = new Headers({ "Content-Type": "application/json" }); + let options: any = { + method: "GET", + headers: HeadersConfig, + redirect: "follow", + credentials: "include", + }; + + // If there is a body, add it to the request options + if (body) { + options.body = JSON.stringify(body); + } + + // Fetch the data + const res = await fetch(url, options); + + // If the response is not in the 200 range, throw an error + if (!res.ok) { + const error = new Error("An error occurred while fetching the data."); + throw error; + } + + // Return the data + return res.json(); +}; diff --git a/front/tsconfig.json b/front/tsconfig.json index a7ec212e..31ffef7d 100644 --- a/front/tsconfig.json +++ b/front/tsconfig.json @@ -24,6 +24,7 @@ "@components/*": ["components/*"], "@public/*": ["public/*"], "@images/*": ["public/img/*"], + "@styles/*": ["styles/*"], "@services/*": ["services/*"], "@editor/*": ["components/Editor/*"] } diff --git a/src/routers/activity.py b/src/routers/activity.py index edc5e000..34441401 100644 --- a/src/routers/activity.py +++ b/src/routers/activity.py @@ -1,6 +1,6 @@ from fastapi import APIRouter, Depends, Request from src.dependencies.auth import get_current_user -from src.services.activity import Activity, add_lecture_to_activity, close_activity, create_activity, get_user_activities +from src.services.activity import Activity, add_lecture_to_activity, close_activity, create_activity, get_user_activities, get_user_activities_orgslug router = APIRouter() @@ -16,13 +16,20 @@ async def api_start_activity(request: Request, activity_object: Activity, user=D # TODO : get activity by user_is and org_id and course_id -@router.get("/{org_id}/activities") -async def api_get_activity_by_userid(request: Request, org_id: str, user=Depends(get_current_user)): +@router.get("/org_id/{org_id}/activities") +async def api_get_activity_by_orgid(request: Request, org_id: str, user=Depends(get_current_user)): """ Get a user activities """ return await get_user_activities(request, user, org_id=org_id) +@router.get("/org_slug/{org_slug}/activities") +async def api_get_activity_by_orgslug(request: Request, org_slug: str, user=Depends(get_current_user)): + """ + Get a user activities using org slug + """ + return await get_user_activities_orgslug(request, user, org_slug=org_slug) + @router.post("/{org_id}/add_lecture/{course_id}/{lecture_id}") async def api_add_lecture_to_activity(request: Request, org_id: str, course_id: str, lecture_id: str, user=Depends(get_current_user)): diff --git a/src/routers/courses/courses.py b/src/routers/courses/courses.py index b3ddab1b..340cb735 100644 --- a/src/routers/courses/courses.py +++ b/src/routers/courses/courses.py @@ -1,7 +1,7 @@ from fastapi import APIRouter, Depends, UploadFile, Form, Request from src.dependencies.auth import get_current_user -from src.services.courses.courses import Course, create_course, get_course, get_course_meta, get_courses, update_course, delete_course, update_course_thumbnail +from src.services.courses.courses import Course, create_course, get_course, get_course_meta, get_courses, get_courses_orgslug, update_course, delete_course, update_course_thumbnail from src.services.users import PublicUser @@ -42,13 +42,20 @@ async def api_get_course_meta(request: Request,course_id: str, current_user: Pu return await get_course_meta(request, course_id, current_user=current_user) -@router.get("/{org_id}/page/{page}/limit/{limit}") +@router.get("/org_id/{org_id}/page/{page}/limit/{limit}") async def api_get_course_by(request: Request,page: int, limit: int, org_id: str): """ Get houses by page and limit """ return await get_courses(request,page, limit, org_id) +@router.get("/org_slug/{org_slug}/page/{page}/limit/{limit}") +async def api_get_course_by_orgslug(request: Request,page: int, limit: int, org_slug: str): + """ + Get houses by page and limit + """ + return await get_courses_orgslug(request,page, limit, org_slug) + @router.put("/{course_id}") async def api_update_course(request: Request,course_object: Course, course_id: str, current_user: PublicUser = Depends(get_current_user)): diff --git a/src/services/activity.py b/src/services/activity.py index afaf0d56..6ad810e9 100644 --- a/src/services/activity.py +++ b/src/services/activity.py @@ -89,6 +89,36 @@ async def get_user_activities(request: Request, user: PublicUser, org_id: str): return activities_metadata +async def get_user_activities_orgslug(request: Request, user: PublicUser, org_slug: str): + activities = request.app.db["activities"] + courses = request.app.db["courses"] + coursechapters = request.app.db["coursechapters"] + + activities_metadata = [] + + user_activities = activities.find( + {"user_id": user.user_id}, {'_id': 0}) + + if not user_activities: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="No activities found") + + for activity in user_activities: + # get number of lectures in the course + coursechapters = await get_coursechapters_meta(request, activity['course_id'], user) + + # calculate progression using the number of lectures marked complete and the total number of lectures + progression = round( + len(activity['lectures_marked_complete']) / len(coursechapters['lectures']) * 100, 2) + + course = courses.find_one({"course_id": activity['course_id']}, {'_id': 0}) + + # add progression to the activity + one_activity = {"course": course, "activitydata": activity, "progression": progression} + activities_metadata.append(one_activity) + + return activities_metadata + async def add_lecture_to_activity(request: Request, user: PublicUser, org_id: str, course_id: str, lecture_id: str): activities = request.app.db["activities"] diff --git a/src/services/courses/courses.py b/src/services/courses/courses.py index e5c95de4..52fdf9c4 100644 --- a/src/services/courses/courses.py +++ b/src/services/courses/courses.py @@ -258,6 +258,25 @@ async def get_courses(request: Request, page: int = 1, limit: int = 10, org_id: return [json.loads(json.dumps(course, default=str)) for course in all_courses] +async def get_courses_orgslug(request: Request, page: int = 1, limit: int = 10, org_slug: str | None = None): + courses = request.app.db["courses"] + orgs = request.app.db["organizations"] + # TODO : Get only courses that user is admin/has roles of + + # get org_id from slug + org = orgs.find_one({"slug": org_slug}) + + if not org: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail=f"Organization does not exist") + + # get all courses from database + all_courses = courses.find({"org_id": org['org_id']}).sort( + "name", 1).skip(10 * (page - 1)).limit(limit) + + return [json.loads(json.dumps(course, default=str)) for course in all_courses] + + #### Security ####################################################