diff --git a/config/config.py b/config/config.py index e94ae341..afe0a99a 100644 --- a/config/config.py +++ b/config/config.py @@ -1,3 +1,4 @@ +from typing import Optional from pydantic import BaseModel import os import yaml @@ -19,6 +20,7 @@ class DatabaseConfig(BaseModel): user: str password: str database_name: str + mongodb_connection_string: Optional[str] class LearnHouseConfig(BaseModel): @@ -54,6 +56,8 @@ def get_learnhouse_config() -> LearnHouseConfig: env_user = os.environ.get('LEARNHOUSE_DB_USER') env_password = os.environ.get('LEARNHOUSE_DB_PASSWORD') env_database_name = os.environ.get('LEARNHOUSE_DB_NAME') + env_mongodb_connection_string = os.environ.get( + 'LEARNHOUSE_MONGODB_CONNECTION_STRING') # Fill in values with YAML file if they are not provided site_name = env_site_name or yaml_config.get('site_name') @@ -80,6 +84,8 @@ def get_learnhouse_config() -> LearnHouseConfig: 'database_config', {}).get('password') database_name = env_database_name or yaml_config.get( 'database_config', {}).get('database_name') + mongodb_connection_string = env_mongodb_connection_string or yaml_config.get( + 'database_config', {}).get('mongodb_connection_string') # Create HostingConfig and DatabaseConfig objects hosting_config = HostingConfig( @@ -96,7 +102,8 @@ def get_learnhouse_config() -> LearnHouseConfig: port=int(db_port), user=user, password=password, - database_name=database_name + database_name=database_name, + mongodb_connection_string=mongodb_connection_string ) # Create LearnHouseConfig object diff --git a/config/config.yaml b/config/config.yaml index 65532180..d813e5e2 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -20,3 +20,4 @@ database_config: user: myuser password: mypassword database_name: mydatabase + mongodb_connection_string: mongodb://learnhouse:learnhouse@mongo:27017/ diff --git a/content/uploads/__init__.py b/content/uploads/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/front/app/_editor/course/[courseid]/activity/[activityid]/edit/page.tsx b/front/app/_editor/course/[courseid]/activity/[activityid]/edit/page.tsx index 778ec4f0..6539e6c6 100644 --- a/front/app/_editor/course/[courseid]/activity/[activityid]/edit/page.tsx +++ b/front/app/_editor/course/[courseid]/activity/[activityid]/edit/page.tsx @@ -9,7 +9,7 @@ import AuthProvider from "@components/Security/AuthProvider"; import EditorWrapper from "@components/Editor/EditorWrapper"; import useSWR, { mutate } from "swr"; import { getAPIUrl } from "@services/config/config"; -import { swrFetcher } from "@services/utils/requests"; +import { swrFetcher } from "@services/utils/ts/requests"; function EditActivity(params: any) { diff --git a/front/app/_orgs/[orgslug]/(withmenu)/collections/new/page.tsx b/front/app/_orgs/[orgslug]/(withmenu)/collections/new/page.tsx index da1f8355..5a190dec 100644 --- a/front/app/_orgs/[orgslug]/(withmenu)/collections/new/page.tsx +++ b/front/app/_orgs/[orgslug]/(withmenu)/collections/new/page.tsx @@ -5,7 +5,7 @@ import { Title } from "@components/UI/Elements/Styles/Title"; import { createCollection } from "@services/courses/collections"; import useSWR from "swr"; import { getAPIUrl } from "@services/config/config"; -import { swrFetcher } from "@services/utils/requests"; +import { swrFetcher } from "@services/utils/ts/requests"; import { getOrganizationContextInfo } from "@services/organizations/orgs"; function NewCollection(params : any) { diff --git a/front/app/_orgs/[orgslug]/(withmenu)/collections/page.tsx b/front/app/_orgs/[orgslug]/(withmenu)/collections/page.tsx index c74dd467..a6169647 100644 --- a/front/app/_orgs/[orgslug]/(withmenu)/collections/page.tsx +++ b/front/app/_orgs/[orgslug]/(withmenu)/collections/page.tsx @@ -5,7 +5,7 @@ import styled from "styled-components"; import { Title } from "@components/UI/Elements/Styles/Title"; import { deleteCollection } from "@services/courses/collections"; import { getAPIUrl, getBackendUrl, getUriWithOrg } from "@services/config/config"; -import { swrFetcher } from "@services/utils/requests"; +import { swrFetcher } from "@services/utils/ts/requests"; import useSWR, { mutate } from "swr"; function Collections(params: any) { diff --git a/front/app/_orgs/[orgslug]/(withmenu)/course/[courseid]/activity/[activityid]/page.tsx b/front/app/_orgs/[orgslug]/(withmenu)/course/[courseid]/activity/[activityid]/page.tsx index 480040f2..020c734d 100644 --- a/front/app/_orgs/[orgslug]/(withmenu)/course/[courseid]/activity/[activityid]/page.tsx +++ b/front/app/_orgs/[orgslug]/(withmenu)/course/[courseid]/activity/[activityid]/page.tsx @@ -10,7 +10,7 @@ import { getCourse } from "@services/courses/courses"; import VideoActivity from "@components/Pages/Activities/Video/Video"; import useSWR, { mutate } from "swr"; import { Check } from "lucide-react"; -import { swrFetcher } from "@services/utils/requests"; +import { swrFetcher } from "@services/utils/ts/requests"; import { markActivityAsComplete } from "@services/courses/activity"; function ActivityPage(params: any) { @@ -19,7 +19,7 @@ function ActivityPage(params: any) { const orgslug = params.params.orgslug; const { data: course, error: error_course } = useSWR(`${getAPIUrl()}courses/meta/course_${courseid}`, swrFetcher); - const { data: activity, error: error_activity } = useSWR(`${getAPIUrl()}trail/org_slug/${orgslug}/trail`, swrFetcher); + const { data: activity, error: error_activity } = useSWR(`${getAPIUrl()}activities/activity_${activityid}`, swrFetcher); async function markActivityAsCompleteFront() { @@ -31,7 +31,7 @@ function ActivityPage(params: any) { return ( <> {error_course &&

Failed to load

} - {!course || !activity ? ( + {!course && !activity ? (
Loading...
) : ( @@ -95,9 +95,8 @@ function ActivityPage(params: any) { )} - ) : ( -
Loading...
- )} + ) : (
)} + {error_activity &&

Failed to load {error_activity.message}

}
)} diff --git a/front/app/_orgs/[orgslug]/(withmenu)/course/[courseid]/edit/page.tsx b/front/app/_orgs/[orgslug]/(withmenu)/course/[courseid]/edit/page.tsx index 45f0d10f..f2bd7c06 100644 --- a/front/app/_orgs/[orgslug]/(withmenu)/course/[courseid]/edit/page.tsx +++ b/front/app/_orgs/[orgslug]/(withmenu)/course/[courseid]/edit/page.tsx @@ -15,6 +15,7 @@ import { createActivity, createFileActivity } from "@services/courses/activities import { getOrganizationContextInfo } from "@services/organizations/orgs"; import Modal from "@components/UI/Modal/Modal"; import AuthProvider from "@components/Security/AuthProvider"; +import { denyAccessToUser } from "@services/utils/react/middlewares/views"; function CourseEdit(params: any) { @@ -38,9 +39,7 @@ function CourseEdit(params: any) { const courseChapters = await getCourseChaptersMetadata(courseid); setData(courseChapters); } catch (error: any) { - if (error.status === 401) { - router.push("/login"); - } + denyAccessToUser(error, router) } } diff --git a/front/app/_orgs/[orgslug]/(withmenu)/course/[courseid]/page.tsx b/front/app/_orgs/[orgslug]/(withmenu)/course/[courseid]/page.tsx index 0f2f3b4c..f098e2b6 100644 --- a/front/app/_orgs/[orgslug]/(withmenu)/course/[courseid]/page.tsx +++ b/front/app/_orgs/[orgslug]/(withmenu)/course/[courseid]/page.tsx @@ -1,21 +1,26 @@ "use client"; import { EyeOpenIcon, Pencil2Icon } from "@radix-ui/react-icons"; -import { removeCourse, startCourse } from "@services/courses/activity"; +import { removeCourse, startCourse } from "@services/courses/activity"; import Link from "next/link"; import React from "react"; import styled from "styled-components"; import { getAPIUrl, getBackendUrl, getUriWithOrg } from "@services/config/config"; import useSWR, { mutate } from "swr"; -import { swrFetcher } from "@services/utils/requests"; +import { swrFetcher } from "@services/utils/ts/requests"; +import { useRouter } from "next/navigation"; const CourseIdPage = (params: any) => { const courseid = params.params.courseid; const orgslug = params.params.orgslug; - const { data: course, error: error } = useSWR(`${getAPIUrl()}courses/meta/course_${courseid}`, swrFetcher); + const router = useRouter(); + + const { data: course, error: error } = useSWR(`${getAPIUrl()}courses/meta/course_${courseid}`, + (url: string, body: any) => swrFetcher(url, body, router) + ); async function startCourseUI() { // Create activity - await startCourse("course_" + courseid, orgslug); + await startCourse("course_" + courseid, orgslug); // Mutate course mutate(`${getAPIUrl()}courses/meta/course_${courseid}`); @@ -24,7 +29,7 @@ const CourseIdPage = (params: any) => { async function quitCourse() { // Close activity - let activity = await removeCourse("course_" + courseid, orgslug); + let activity = await removeCourse("course_" + courseid, orgslug); console.log(activity); // Mutate course @@ -42,7 +47,7 @@ const CourseIdPage = (params: any) => {

Course

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

@@ -53,7 +58,7 @@ const CourseIdPage = (params: any) => { {chapter.activities.map((activity: any) => { return ( <> - + {" "} @@ -94,7 +99,7 @@ const CourseIdPage = (params: any) => { <>

Activity {activity.name} - + {" "}

diff --git a/front/app/_orgs/[orgslug]/(withmenu)/courses/page.tsx b/front/app/_orgs/[orgslug]/(withmenu)/courses/page.tsx index 86706ad1..d7bfa2cd 100644 --- a/front/app/_orgs/[orgslug]/(withmenu)/courses/page.tsx +++ b/front/app/_orgs/[orgslug]/(withmenu)/courses/page.tsx @@ -7,7 +7,7 @@ import { Title } from "@components/UI/Elements/Styles/Title"; import { getAPIUrl, getBackendUrl, getSelfHostedOption, getUriWithOrg } from "@services/config/config"; import { deleteCourseFromBackend } from "@services/courses/courses"; import useSWR, { mutate } from "swr"; -import { swrFetcher } from "@services/utils/requests"; +import { swrFetcher } from "@services/utils/ts/requests"; import { Edit2, Trash } from "lucide-react"; const CoursesIndexPage = (params: any) => { diff --git a/front/app/_orgs/[orgslug]/(withmenu)/trail/page.tsx b/front/app/_orgs/[orgslug]/(withmenu)/trail/page.tsx index 2d86dacb..f965dce1 100644 --- a/front/app/_orgs/[orgslug]/(withmenu)/trail/page.tsx +++ b/front/app/_orgs/[orgslug]/(withmenu)/trail/page.tsx @@ -1,6 +1,6 @@ "use client"; import { getAPIUrl, getBackendUrl } from "@services/config/config"; -import { swrFetcher } from "@services/utils/requests"; +import { swrFetcher } from "@services/utils/ts/requests"; import React from "react"; import { styled } from "styled-components"; import useSWR from "swr"; diff --git a/front/app/_orgs/[orgslug]/settings/organization/general/page.tsx b/front/app/_orgs/[orgslug]/settings/organization/general/page.tsx index 4ecd1249..048f4fc2 100644 --- a/front/app/_orgs/[orgslug]/settings/organization/general/page.tsx +++ b/front/app/_orgs/[orgslug]/settings/organization/general/page.tsx @@ -1,7 +1,7 @@ "use client"; import React from 'react' import useSWR, { mutate } from "swr"; -import { swrFetcher } from "@services/utils/requests"; +import { swrFetcher } from "@services/utils/ts/requests"; import { getAPIUrl } from '@services/config/config'; import { Field, Form, Formik } from 'formik'; import { updateOrganization } from '@services/settings/org'; diff --git a/front/app/organizations/page.tsx b/front/app/organizations/page.tsx index ba286b4c..312ec33b 100644 --- a/front/app/organizations/page.tsx +++ b/front/app/organizations/page.tsx @@ -4,7 +4,7 @@ import React from "react"; import { Title } from "../../components/UI/Elements/Styles/Title"; import { deleteOrganizationFromBackend } from "@services/organizations/orgs"; import useSWR, { mutate } from "swr"; -import { swrFetcher } from "@services/utils/requests"; +import { swrFetcher } from "@services/utils/ts/requests"; import { getAPIUrl, getUriWithOrg } from "@services/config/config"; import AuthProvider from "@components/Security/AuthProvider"; diff --git a/front/middleware.ts b/front/middleware.ts index 56fd95cd..baeb1761 100644 --- a/front/middleware.ts +++ b/front/middleware.ts @@ -1,4 +1,4 @@ -import { getDefaultOrg, getSelfHostedOption } from "@services/config/config"; + import { NextRequest, NextResponse } from "next/server"; export const config = { @@ -15,13 +15,16 @@ export const config = { ], }; -export default function middleware(req: NextRequest) { - const url = req.nextUrl; - const isSelfHosted = getSelfHostedOption(); - const hostname = req.headers.get("host") || "learnhouse.app"; - let currentHost = hostname.replace(".localhost:3000", ""); - if (!isSelfHosted && currentHost === "localhost:3000" && !url.pathname.startsWith("/organizations")) { +export default function middleware(req: NextRequest) { + const LEARNHOUSE_DOMAIN = process.env.NEXT_PUBLIC_LEARNHOUSE_DOMAIN; + const url = req.nextUrl; + const isSelfHosted = process.env.NEXT_PUBLIC_LEARNHOUSE_SELF_HOSTED === "true" ? true : false + const hostname = req.headers.get("host") || "learnhouse.app"; + const defaultOrg = isSelfHosted ? process.env.NEXT_PUBLIC_LEARNHOUSE_DEFAULT_ORG : null; + let currentHost = hostname.replace(`.${LEARNHOUSE_DOMAIN}`, ""); + + if (!isSelfHosted && currentHost === LEARNHOUSE_DOMAIN && !url.pathname.startsWith("/organizations")) { // Redirect to error page if not self-hosted and on localhost const errorUrl = "/error"; return NextResponse.redirect(errorUrl, { status: 302 }); @@ -42,7 +45,7 @@ export default function middleware(req: NextRequest) { } if (isSelfHosted) { - currentHost = getDefaultOrg() || currentHost; + currentHost = defaultOrg || currentHost; } url.pathname = `/_orgs/${currentHost}${url.pathname}`; diff --git a/front/package.json b/front/package.json index eea65ed2..fe1bceca 100644 --- a/front/package.json +++ b/front/package.json @@ -24,7 +24,7 @@ "formik": "^2.2.9", "framer-motion": "^7.3.6", "lucide-react": "^0.104.1", - "next": "^13.2.0", + "next": "^13.2.4", "react": "^18.2.0", "react-beautiful-dnd": "^13.1.1", "react-dom": "^18.2.0", diff --git a/front/services/blocks/Image/images.ts b/front/services/blocks/Image/images.ts index bb93582b..0429b6dd 100644 --- a/front/services/blocks/Image/images.ts +++ b/front/services/blocks/Image/images.ts @@ -1,5 +1,5 @@ import { getAPIUrl } from "@services/config/config"; -import { RequestBody, RequestBodyForm } from "@services/utils/requests"; +import { RequestBody, RequestBodyForm } from "@services/utils/ts/requests"; export async function uploadNewImageFile(file: any, activity_id: string) { // Send file thumbnail as form data diff --git a/front/services/blocks/Pdf/pdf.ts b/front/services/blocks/Pdf/pdf.ts index 2bff8fbe..fc9b4e5e 100644 --- a/front/services/blocks/Pdf/pdf.ts +++ b/front/services/blocks/Pdf/pdf.ts @@ -1,5 +1,5 @@ import { getAPIUrl } from "@services/config/config"; -import { RequestBody, RequestBodyForm } from "@services/utils/requests"; +import { RequestBody, RequestBodyForm } from "@services/utils/ts/requests"; export async function uploadNewPDFFile(file: any, activity_id: string) { // Send file thumbnail as form data diff --git a/front/services/blocks/Quiz/quiz.ts b/front/services/blocks/Quiz/quiz.ts index 706bbf00..0adb85dd 100644 --- a/front/services/blocks/Quiz/quiz.ts +++ b/front/services/blocks/Quiz/quiz.ts @@ -1,5 +1,5 @@ import { getAPIUrl } from "@services/config/config"; -import { RequestBody, RequestBodyForm } from "@services/utils/requests"; +import { RequestBody, RequestBodyForm } from "@services/utils/ts/requests"; export async function submitQuizBlock(activity_id: string, data: any) { diff --git a/front/services/blocks/Video/video.ts b/front/services/blocks/Video/video.ts index a2c45388..bf7142b4 100644 --- a/front/services/blocks/Video/video.ts +++ b/front/services/blocks/Video/video.ts @@ -1,5 +1,5 @@ import { getAPIUrl } from "@services/config/config"; -import { RequestBody, RequestBodyForm } from "@services/utils/requests"; +import { RequestBody, RequestBodyForm } from "@services/utils/ts/requests"; export async function uploadNewVideoFile(file: any, activity_id: string) { // Send file thumbnail as form data diff --git a/front/services/config/config.ts b/front/services/config/config.ts index 8e9a63a6..ed5ee7e4 100644 --- a/front/services/config/config.ts +++ b/front/services/config/config.ts @@ -1,5 +1,7 @@ -const LEARNHOUSE_API_URL = "http://localhost:1338/api/"; -const LEARNHOUSE_BACKEND_URL = "http://localhost:1338/"; +const LEARNHOUSE_HTTP_PROTOCOL = process.env.NEXT_PUBLIC_LEARNHOUSE_HTTPS === "true" ? "https://" : "http://"; +const LEARNHOUSE_API_URL = `${process.env.NEXT_PUBLIC_LEARNHOUSE_API_URL}`; +const LEARNHOUSE_BACKEND_URL = `${process.env.NEXT_PUBLIC_LEARNHOUSE_BACKEND_URL}`; +export const LEARNHOUSE_DOMAIN = process.env.NEXT_PUBLIC_LEARNHOUSE_DOMAIN; export const getAPIUrl = () => LEARNHOUSE_API_URL; export const getBackendUrl = () => LEARNHOUSE_BACKEND_URL; @@ -7,11 +9,10 @@ export const getSelfHostedOption = () => (process.env.NEXT_PUBLIC_LEARNHOUSE_SEL export const getUriWithOrg = (orgslug: string, path: string) => { const selfHosted = getSelfHostedOption(); - if (selfHosted) { - return `http://localhost:3000${path}`; + return `${LEARNHOUSE_DOMAIN}${path}`; } - return `http://${orgslug}.localhost:3000${path}`; + return `${LEARNHOUSE_HTTP_PROTOCOL}${orgslug}.${LEARNHOUSE_DOMAIN}${path}`; }; export const getOrgFromUri = () => { @@ -21,8 +22,8 @@ export const getOrgFromUri = () => { } else { if (typeof window !== "undefined") { const hostname = window.location.hostname; - - return hostname.replace(".localhost", ""); + + return hostname.replace(`.${LEARNHOUSE_DOMAIN}`, ""); } } }; diff --git a/front/services/courses/activities.ts b/front/services/courses/activities.ts index 9ee811d6..fa378281 100644 --- a/front/services/courses/activities.ts +++ b/front/services/courses/activities.ts @@ -1,19 +1,14 @@ import { getAPIUrl } from "@services/config/config"; -import { RequestBody, RequestBodyForm } from "@services/utils/requests"; +import { RequestBody, RequestBodyForm } from "@services/utils/ts/requests"; export async function createActivity(data: any, chapter_id: any, org_id: any) { data.content = {}; // remove chapter_id from data delete data.chapterId; - - const result: any = await fetch(`${getAPIUrl()}activities/?coursechapter_id=${chapter_id}&org_id=${org_id}`, RequestBody("POST", data)) - .then((result) => result.json()) - .catch((error) => console.log("error", error)); - - console.log("result", result); - - return result; + const result = await fetch(`${getAPIUrl()}activities/?coursechapter_id=${chapter_id}&org_id=${org_id}`, RequestBody("POST", data)); + const res = await result.json(); + return res; } export async function createFileActivity(file: File, type: string, data: any, chapter_id: any) { @@ -29,27 +24,19 @@ export async function createFileActivity(file: File, type: string, data: any, ch endpoint = `${getAPIUrl()}activities/video`; } - const result: any = await fetch(endpoint, RequestBodyForm("POST", formData)) - .then((result) => result.json()) - .catch((error) => console.log("error", error)); - - console.log("result", result); - - return result; + const result: any = await fetch(endpoint, RequestBodyForm("POST", formData)); + const res = await result.json(); + return res; } export async function getActivity(activity_id: any) { - const result: any = await fetch(`${getAPIUrl()}activities/${activity_id}`, RequestBody("GET", null)) - .then((result) => result.json()) - .catch((error) => console.log("error", error)); - - return result; + const result = await fetch(`${getAPIUrl()}activities/${activity_id}`, RequestBody("GET", null)); + const res = await result.json(); + return res; } export async function updateActivity(data: any, activity_id: any) { - const result: any = await fetch(`${getAPIUrl()}activities/${activity_id}`, RequestBody("PUT", data)) - .then((result) => result.json()) - .catch((error) => console.log("error", error)); - - return result; + const result = await fetch(`${getAPIUrl()}activities/${activity_id}`, RequestBody("PUT", data)); + const res = await result.json(); + return res; } diff --git a/front/services/courses/activity.ts b/front/services/courses/activity.ts index d2fcda94..aec4ec45 100644 --- a/front/services/courses/activity.ts +++ b/front/services/courses/activity.ts @@ -1,4 +1,4 @@ -import { RequestBody } from "@services/utils/requests"; +import { RequestBody, errorHandling } from "@services/utils/ts/requests"; import { getAPIUrl } from "@services/config/config"; /* @@ -8,21 +8,18 @@ import { getAPIUrl } from "@services/config/config"; export async function startCourse(course_id: string, org_slug: string) { const result: any = await fetch(`${getAPIUrl()}trail/org_slug/${org_slug}/add_course/${course_id}`, RequestBody("POST", null)) - .then((result) => result.json()) - .catch((error) => console.log("error", error)); - return result; + const res = await errorHandling(result); + return res; } export async function removeCourse(course_id: string, org_slug: string) { const result: any = await fetch(`${getAPIUrl()}trail/org_slug/${org_slug}/remove_course/${course_id}`, RequestBody("POST", null)) - .then((result) => result.json()) - .catch((error) => console.log("error", error)); - return result; + const res = await errorHandling(result); + return res; } export async function markActivityAsComplete(org_slug: string, course_id: string, activity_id: string) { const result: any = await fetch(`${getAPIUrl()}trail/org_slug/${org_slug}/add_activity/course_id/${course_id}/activity_id/${activity_id}`, RequestBody("POST", null)) - .then((result) => result.json()) - .catch((error) => console.log("error", error)); - return result; + const res = await errorHandling(result); + return res; } diff --git a/front/services/courses/chapters.ts b/front/services/courses/chapters.ts index 8bdc85e2..ddf2c521 100644 --- a/front/services/courses/chapters.ts +++ b/front/services/courses/chapters.ts @@ -1,6 +1,5 @@ -import { initialData } from "../../components/Pages/CourseEdit/Draggables/data"; import { getAPIUrl } from "@services/config/config"; -import { RequestBody } from "@services/utils/requests"; +import { RequestBody, errorHandling } from "@services/utils/ts/requests"; /* This file includes only POST, PUT, DELETE requests @@ -9,38 +8,26 @@ import { RequestBody } from "@services/utils/requests"; //TODO : depreciate this function export async function getCourseChaptersMetadata(course_id: any) { - const response = await fetch(`${getAPIUrl()}chapters/meta/course_${course_id}`, RequestBody("GET", null)); - - if (!response.ok) { - const error: any = new Error(`Error ${response.status}: ${response.statusText}`, {}); - error.status = response.status; - throw error; - } - - const data = await response.json(); - return data; + const result = await fetch(`${getAPIUrl()}chapters/meta/course_${course_id}`, RequestBody("GET", null)); + const res = await errorHandling(result); + return res; } export async function updateChaptersMetadata(course_id: any, data: any) { - const result: any = await fetch(`${getAPIUrl()}chapters/meta/course_${course_id}`, RequestBody("PUT", data)) - .then((result) => result.json()) - .catch((error) => console.log("error", error)); - - return result; + const result: any = await fetch(`${getAPIUrl()}chapters/meta/course_${course_id}`, RequestBody("PUT", data)); + const res = await errorHandling(result); + return res; } export async function createChapter(data: any, course_id: any) { - const result: any = await fetch(`${getAPIUrl()}chapters/?course_id=course_${course_id}`, RequestBody("POST", data)) - .then((result) => result.json()) - .catch((error) => console.log("error", error)); + const result: any = await fetch(`${getAPIUrl()}chapters/?course_id=course_${course_id}`, RequestBody("POST", data)); + const res = await errorHandling(result); - return result; + return res; } export async function deleteChapter(coursechapter_id: any) { - const result: any = await fetch(`${getAPIUrl()}chapters/${coursechapter_id}`, RequestBody("DELETE", null)) - .then((result) => result.json()) - .catch((error) => console.log("error", error)); - - return result; + const result: any = await fetch(`${getAPIUrl()}chapters/${coursechapter_id}`, RequestBody("DELETE", null)); + const res = await errorHandling(result); + return res; } diff --git a/front/services/courses/collections.ts b/front/services/courses/collections.ts index 2f213f0b..09ca1610 100644 --- a/front/services/courses/collections.ts +++ b/front/services/courses/collections.ts @@ -1,5 +1,5 @@ import { getAPIUrl } from "../config/config"; -import { RequestBody } from "../utils/requests"; +import { RequestBody, errorHandling } from "@services/utils/ts/requests"; /* This file includes only POST, PUT, DELETE requests @@ -7,14 +7,14 @@ import { RequestBody } from "../utils/requests"; */ export async function deleteCollection(collection_id: any) { - return fetch(`${getAPIUrl()}collections/${collection_id}`, RequestBody("DELETE", null)) - .then((result) => result.json()) - .catch((error) => console.log("error", error)); + const result: any = await fetch(`${getAPIUrl()}collections/${collection_id}`, RequestBody("DELETE", null)); + const res = await errorHandling(result); + return res; } // Create a new collection export async function createCollection(collection: any) { - return fetch(`${getAPIUrl()}collections/`, RequestBody("POST", collection)) - .then((result) => result.json()) - .catch((error) => console.log("error", error)); + const result: any = await fetch(`${getAPIUrl()}collections/`, RequestBody("POST", collection)); + const res = await errorHandling(result); + return res; } diff --git a/front/services/courses/courses.ts b/front/services/courses/courses.ts index d7307e70..13486429 100644 --- a/front/services/courses/courses.ts +++ b/front/services/courses/courses.ts @@ -1,5 +1,5 @@ import { getAPIUrl } from "@services/config/config"; -import { RequestBody, RequestBodyForm } from "@services/utils/requests"; +import { RequestBody, RequestBodyForm, errorHandling } from "@services/utils/ts/requests"; /* This file includes only POST, PUT, DELETE requests @@ -7,23 +7,18 @@ import { RequestBody, RequestBodyForm } from "@services/utils/requests"; */ export async function getOrgCourses(org_id: number) { - - - return fetch(`${getAPIUrl()}courses/${org_id}/page/1/limit/10`, RequestBody("GET", null)) - .then((result) => result.json()) - .catch((error) => console.log("error", error)); + const result: any = await fetch(`${getAPIUrl()}courses/${org_id}/page/1/limit/10`, RequestBody("GET", null)); + const res = await errorHandling(result); + return res; } export async function getCourse(course_id: string) { - // todo : add course id to url - return fetch(`${getAPIUrl()}courses/${course_id}`, RequestBody("GET", null)) - .then((result) => result.json()) - .catch((error) => console.log("error", error)); + const result: any = await fetch(`${getAPIUrl()}courses/${course_id}`, RequestBody("GET", null)); + const res = await errorHandling(result); + return res; } - export async function createNewCourse(org_id: string, course_body: any, thumbnail: any) { - // Send file thumbnail as form data const formData = new FormData(); formData.append("thumbnail", thumbnail); @@ -32,16 +27,13 @@ export async function createNewCourse(org_id: string, course_body: any, thumbnai formData.append("mini_description", "course_body.mini_description"); formData.append("public", "true"); - - return fetch(`${getAPIUrl()}courses/?org_id=${org_id}`, RequestBodyForm("POST", formData)) - .then((result) => result.json()) - .catch((error) => console.log("error", error)); + const result = await fetch(`${getAPIUrl()}courses/?org_id=${org_id}`, RequestBodyForm("POST", formData)); + const res = await errorHandling(result); + return res; } export async function deleteCourseFromBackend(course_id: any) { - - - return fetch(`${getAPIUrl()}courses/${course_id}`, RequestBody("DELETE", null)) - .then((result) => result.json()) - .catch((error) => console.log("error", error)); + const result: any = await fetch(`${getAPIUrl()}courses/${course_id}`, RequestBody("DELETE", null)); + const res = await errorHandling(result); + return res; } diff --git a/front/services/organizations/orgs.ts b/front/services/organizations/orgs.ts index ddd6fde4..2718df70 100644 --- a/front/services/organizations/orgs.ts +++ b/front/services/organizations/orgs.ts @@ -1,5 +1,5 @@ import { getAPIUrl } from "@services/config/config"; -import { RequestBody } from "../utils/requests"; +import { RequestBody, errorHandling } from "@services/utils/ts/requests"; /* This file includes only POST, PUT, DELETE requests @@ -7,19 +7,19 @@ import { RequestBody } from "../utils/requests"; */ export async function createNewOrganization(body: any) { - return fetch(`${getAPIUrl()}orgs/`, RequestBody("POST", body)) - .then((result) => result.json()) - .catch((error) => console.log("error", error)); + const result = await fetch(`${getAPIUrl()}orgs/`, RequestBody("POST", body)); + const res = await errorHandling(result); + return res; } export async function deleteOrganizationFromBackend(org_id: any) { - return fetch(`${getAPIUrl()}orgs/${org_id}`, RequestBody("DELETE", null)) - .then((result) => result.json()) - .catch((error) => console.log("error", error)); + const result = await fetch(`${getAPIUrl()}orgs/${org_id}`, RequestBody("DELETE", null)); + const res = await errorHandling(result); + return res; } export async function getOrganizationContextInfo(org_slug: any) { - return fetch(`${getAPIUrl()}orgs/slug/${org_slug}`, RequestBody("GET", null)) - .then((result) => result.json()) - .catch((error) => console.log("error", error)); + const result = await fetch(`${getAPIUrl()}orgs/slug/${org_slug}`, RequestBody("GET", null)); + const res = await errorHandling(result); + return res; } diff --git a/front/services/settings/org.ts b/front/services/settings/org.ts index 10842ca2..1f4a0fd8 100644 --- a/front/services/settings/org.ts +++ b/front/services/settings/org.ts @@ -1,15 +1,13 @@ import { getAPIUrl } from "@services/config/config"; -import { RequestBody } from "@services/utils/requests"; +import { RequestBody, errorHandling } from "@services/utils/ts/requests"; /* This file includes only POST, PUT, DELETE requests GET requests are called from the frontend using SWR (https://swr.vercel.app/) */ -export async function updateOrganization(org_id : string, data: any) { - const result: any = await fetch(`${getAPIUrl()}orgs/` + org_id, RequestBody("PUT", data)) - .then((result) => result.json()) - .catch((error) => console.log("error", error)); - - return result; +export async function updateOrganization(org_id: string, data: any) { + const result: any = await fetch(`${getAPIUrl()}orgs/` + org_id, RequestBody("PUT", data)); + const res = await errorHandling(result); + return res; } diff --git a/front/services/settings/password.ts b/front/services/settings/password.ts index b4c13bb3..a93483f3 100644 --- a/front/services/settings/password.ts +++ b/front/services/settings/password.ts @@ -1,5 +1,5 @@ import { getAPIUrl } from "@services/config/config"; -import { RequestBody } from "@services/utils/requests"; +import { RequestBody, errorHandling } from "@services/utils/ts/requests"; /* This file includes only POST, PUT, DELETE requests @@ -8,8 +8,6 @@ import { RequestBody } from "@services/utils/requests"; export async function updatePassword(user_id : string, data: any) { const result: any = await fetch(`${getAPIUrl()}users/password/user_id/` + user_id, RequestBody("PUT", data)) - .then((result) => result.json()) - .catch((error) => console.log("error", error)); - - return result; + const res = await errorHandling(result); + return res; } diff --git a/front/services/settings/profile.ts b/front/services/settings/profile.ts index bb1a048a..06f0667e 100644 --- a/front/services/settings/profile.ts +++ b/front/services/settings/profile.ts @@ -1,5 +1,5 @@ import { getAPIUrl } from "@services/config/config"; -import { RequestBody } from "@services/utils/requests"; +import { RequestBody, errorHandling } from "@services/utils/ts/requests"; /* This file includes only POST, PUT, DELETE requests @@ -8,8 +8,6 @@ import { RequestBody } from "@services/utils/requests"; export async function updateProfile(data: any) { const result: any = await fetch(`${getAPIUrl()}users/user_id/` + data.user_id, RequestBody("PUT", data)) - .then((result) => result.json()) - .catch((error) => console.log("error", error)); - - return result; + const res = await errorHandling(result); + return res; } diff --git a/front/services/utils/react/middlewares/views.ts b/front/services/utils/react/middlewares/views.ts new file mode 100644 index 00000000..6ab82348 --- /dev/null +++ b/front/services/utils/react/middlewares/views.ts @@ -0,0 +1,14 @@ +import { AppRouterInstance } from "next/dist/shared/lib/app-router-context"; +import { NextRouter } from "next/router"; + +export const denyAccessToUser = (error : any, router: AppRouterInstance) => { + if (error.status === 401) { + router.push("/login"); + } + + if (error.status === 403) { + router.push("/login"); + // TODO : add a message to the user to tell him he is not allowed to access this page, route to /error + } + +} \ No newline at end of file diff --git a/front/services/utils/requests.ts b/front/services/utils/ts/requests.ts similarity index 61% rename from front/services/utils/requests.ts rename to front/services/utils/ts/requests.ts index 3f4e6466..802f92ec 100644 --- a/front/services/utils/requests.ts +++ b/front/services/utils/ts/requests.ts @@ -1,3 +1,6 @@ +import { AppRouterInstance } from "next/dist/shared/lib/app-router-context"; +import { denyAccessToUser } from "../react/middlewares/views"; + export const RequestBody = (method: string, data: any) => { let HeadersConfig = new Headers({ "Content-Type": "application/json" }); let options: any = { @@ -24,7 +27,7 @@ export const RequestBodyForm = (method: string, data: any) => { return options; }; -export const swrFetcher = async (url: string, body: any) => { +export const swrFetcher = async (url: string, body: any, router?: AppRouterInstance) => { // Create the request options let HeadersConfig = new Headers({ "Content-Type": "application/json" }); let options: any = { @@ -39,15 +42,26 @@ export const swrFetcher = async (url: string, body: any) => { options.body = JSON.stringify(body); } - // Fetch the data - const res = await fetch(url, options); + try { + // Fetch the data + const request = await fetch(url, options); + let res = errorHandling(request); - // 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."); + // Return the data + return res; + } catch (error: any) { + if (router) { + denyAccessToUser(error, router); + } throw error; } +}; - // Return the data +export const errorHandling = (res: any) => { + if (!res.ok) { + const error: any = new Error(`Error ${res.status}: ${res.statusText}`, {}); + error.status = res.status; + throw error; + } return res.json(); }; diff --git a/src/core/events/database.py b/src/core/events/database.py index 56913242..174575a6 100644 --- a/src/core/events/database.py +++ b/src/core/events/database.py @@ -2,17 +2,20 @@ import logging from fastapi import FastAPI import motor.motor_asyncio -async def connect_to_db(app: FastAPI) : + +async def connect_to_db(app: FastAPI): logging.info("Connecting to database...") try: - app.mongodb_client = motor.motor_asyncio.AsyncIOMotorClient("mongodb://learnhouse:learnhouse@mongo:27017/") # type: ignore - app.db = app.mongodb_client["learnhouse"] # type: ignore + app.mongodb_client = motor.motor_asyncio.AsyncIOMotorClient( # type: ignore + app.learnhouse_config.database_config.mongodb_connection_string) # type: ignore + app.db = app.mongodb_client["learnhouse"] # type: ignore logging.info("Connected to database!") except Exception as e: logging.error("Failed to connect to database!") logging.error(e) + async def close_database(app: FastAPI): - app.mongodb_client.close() # type: ignore + app.mongodb_client.close() # type: ignore logging.info("LearnHouse has been shut down.") - return app \ No newline at end of file + return app diff --git a/src/core/events/events.py b/src/core/events/events.py index 27dd830b..c7afb00f 100644 --- a/src/core/events/events.py +++ b/src/core/events/events.py @@ -1,11 +1,16 @@ from typing import Callable from fastapi import FastAPI +from config.config import LearnHouseConfig, get_learnhouse_config from src.core.events.database import close_database, connect_to_db from src.core.events.logs import create_logs_dir def startup_app(app: FastAPI) -> Callable: async def start_app() -> None: + # Get LearnHouse Config + learnhouse_config: LearnHouseConfig = get_learnhouse_config() + app.learnhouse_config = learnhouse_config # type: ignore + # Connect to database await connect_to_db(app) diff --git a/src/services/courses/courses.py b/src/services/courses/courses.py index d8dee31a..55b12d67 100644 --- a/src/services/courses/courses.py +++ b/src/services/courses/courses.py @@ -288,7 +288,7 @@ async def verify_rights(request: Request, course_id: str, current_user: PublicUs course = await courses.find_one({"course_id": course_id}) - if current_user.user_id == "anonymous" and course["public"] == True: + if current_user.user_id == "anonymous" and course["public"] == True and action == "read": return True if not course: diff --git a/src/services/mocks/initial.py b/src/services/mocks/initial.py index a4e206ca..ea443c1d 100644 --- a/src/services/mocks/initial.py +++ b/src/services/mocks/initial.py @@ -1,3 +1,4 @@ +import os import requests from datetime import datetime from fileinput import filename @@ -183,7 +184,13 @@ async def create_initial_data(request: Request): name_in_disk = f"test_mock{course_id}.jpeg" image = requests.get( - "https://source.unsplash.com/random/800x600") + "https://source.unsplash.com/random/800x600/?img=1") + + # check if folder exists and create it if not + if not os.path.exists(f"content/uploads/img"): + + os.makedirs(f"content/uploads/img") + with open(f"content/uploads/img/{name_in_disk}", "wb") as f: f.write(image.content)