diff --git a/front/components/Editor/Editor.tsx b/front/components/Editor/Editor.tsx index da0e0da5..4bf794d5 100644 --- a/front/components/Editor/Editor.tsx +++ b/front/components/Editor/Editor.tsx @@ -22,6 +22,7 @@ import { Save } from "lucide-react"; import MathEquationBlock from "./Extensions/MathEquation/MathEquationBlock"; import PDFBlockComponent from "./Extensions/PDF/PDFBlockComponent"; import PDFBlock from "./Extensions/PDF/PDFBlock"; +import QuizBlock from "./Extensions/Quiz/QuizBlock"; interface Editor { content: string; @@ -65,6 +66,10 @@ function Editor(props: Editor) { editable: true, lecture: props.lecture, }), + QuizBlock.configure({ + editable: true, + lecture: props.lecture, + }), Youtube.configure({ controls: true, modestBranding: true, @@ -114,7 +119,7 @@ function Editor(props: Editor) { - + @@ -279,8 +284,8 @@ export const EditorContentWrapper = styled.div` padding-left: 20px; padding-right: 20px; padding-bottom: 20px; + padding-top: 20px; - padding-top: 1px; &:focus { outline: none !important; outline-style: none !important; diff --git a/front/components/Editor/Extensions/Image/ImageBlockComponent.tsx b/front/components/Editor/Extensions/Image/ImageBlockComponent.tsx index 6d1a9a5d..950d4740 100644 --- a/front/components/Editor/Extensions/Image/ImageBlockComponent.tsx +++ b/front/components/Editor/Extensions/Image/ImageBlockComponent.tsx @@ -41,7 +41,7 @@ function ImageBlockComponent(props: any) { {fileObject && ( { + let modifiedQuestions = [...questions]; + modifiedQuestions.splice(index, 1); + setQuestions(modifiedQuestions); + console.log(questions); + + // remove the answers from the answers array + let modifiedAnswers = [...answers]; + modifiedAnswers = modifiedAnswers.filter((answer: any) => answer.question_id !== questions[index].question_id); + setAnswers(modifiedAnswers); + }; + + const onQuestionChange = (e: any, index: number) => { + let modifiedQuestions = [...questions]; + modifiedQuestions[index].question_value = e.target.value; + setQuestions(modifiedQuestions); + }; + + const addOption = (question_id: string) => { + // find the question index from the question_id and add the option to that question index + let modifiedQuestions = [...questions]; + let questionIndex = modifiedQuestions.findIndex((question: any) => question.question_id === question_id); + modifiedQuestions[questionIndex].options.push({ + option_id: "option_" + uuidv4(), + option_data: "", + option_type: "text", + }); + setQuestions(modifiedQuestions); + }; + + const deleteOption = (question_id: string, option_id: string) => { + // find the option index from the option_id and delete the option from that option index + let modifiedQuestions = [...questions]; + let questionIndex = modifiedQuestions.findIndex((question: any) => question.question_id === question_id); + let optionIndex = modifiedQuestions[questionIndex].options.findIndex((option: any) => option.option_id === option_id); + modifiedQuestions[questionIndex].options.splice(optionIndex, 1); + setQuestions(modifiedQuestions); + + // remove the answer from the answers array + let answerIndex = answers.findIndex((answer: any) => answer.option_id === option_id); + if (answerIndex !== -1) { + let modifiedAnswers = [...answers]; + modifiedAnswers.splice(answerIndex, 1); + setAnswers(modifiedAnswers); + } + }; + + const markOptionAsCorrect = (question_id: string, option_id: string) => { + // find the option index from the option_id and mark the option as correct + let answer = { + question_id: question_id, + option_id: option_id, + }; + setAnswers([...answers, answer]); + console.log(answers); + }; + + const saveQuiz = async () => { + // save the questions and answers to the backend + console.log("saving quiz"); + console.log(questions); + console.log(answers); + try { + let res = await submitQuizBlock(props.extension.options.lecture.lecture_id, {questions : questions , answers : answers}) + console.log(res.block_id); + props.updateAttributes({ + quizId: { + value : res.block_id + }, + }); + + } + catch (error) { + console.log(error); + } + + + }; + + const onOptionChange = (e: any, questionIndex: number, optionIndex: number) => { + let modifiedQuestions = [...questions]; + modifiedQuestions[questionIndex].options[optionIndex].option_data = e.target.value; + setQuestions(modifiedQuestions); + }; + + React.useEffect(() => { + // fetch the questions and options from the backend + console.log("fetching questions"); + console.log(questions); + console.log(answers); + }, [questions, answers]); + + return ( + + + Questions +
+ {questions.map((question: any, qIndex: number) => ( + <> +
+ Question : onQuestionChange(e, qIndex)} /> + +
+ Answers :
+ {question.options.map((option: any, oIndex: number) => ( + <> +
+ onOptionChange(e, qIndex, oIndex)} /> + + + + // check if checkbox is checked or not + // if checked then add the answer to the answers array + // if unchecked then remove the answer from the answers array + e.target.checked ? markOptionAsCorrect(question.question_id, option.option_id) : null + } + /> +
+ + ))} + + + ))} +
+
+ ); +} + +const QuizBlockWrapper = styled.div` + background-color: #0000001d; + border-radius: 5px; + padding: 20px; + height: 100%; +`; +export default ImageBlockComponent; diff --git a/front/components/Editor/Toolbar/ToolbarButtons.tsx b/front/components/Editor/Toolbar/ToolbarButtons.tsx index 17af1615..4116e2a5 100644 --- a/front/components/Editor/Toolbar/ToolbarButtons.tsx +++ b/front/components/Editor/Toolbar/ToolbarButtons.tsx @@ -1,8 +1,8 @@ import styled from "styled-components"; import { FontBoldIcon, FontItalicIcon, StrikethroughIcon, ArrowLeftIcon, ArrowRightIcon, OpacityIcon } from "@radix-ui/react-icons"; -import { AlertCircle, AlertTriangle, FileText, ImagePlus, Info, Sigma, Video, Youtube } from "lucide-react"; +import { AlertCircle, AlertTriangle, FileText, GraduationCap, ImagePlus, Info, Sigma, Video, Youtube } from "lucide-react"; -export const ToolbarButtons = ({ editor }: any) => { +export const ToolbarButtons = ({ editor, props }: any) => { if (!editor) { return null; } @@ -116,6 +116,19 @@ export const ToolbarButtons = ({ editor }: any) => { > + + editor + .chain() + .focus() + .insertContent({ + type: "blockQuiz", + }) + .run() + } + > + + ); }; diff --git a/front/components/Modals/Modal.tsx b/front/components/Modals/Modal.tsx index 4c54d4ee..7923df5b 100644 --- a/front/components/Modals/Modal.tsx +++ b/front/components/Modals/Modal.tsx @@ -34,6 +34,9 @@ const Overlay = styled.div` width: 100%; height: 100%; z-index: 100; + background-color: #00000029; + backdrop-filter: blur(1px); + `; const Content = styled.div` diff --git a/front/package-lock.json b/front/package-lock.json index db8ab2bd..d23c0f84 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -26,6 +26,7 @@ "react-katex": "^3.0.1", "styled-components": "^6.0.0-beta.9", "swr": "^2.0.1", + "uuid": "^9.0.0", "y-indexeddb": "^9.0.9", "y-webrtc": "^10.2.3", "yjs": "^13.5.42" @@ -37,6 +38,7 @@ "@types/react-dom": "18.0.6", "@types/react-katex": "^3.0.0", "@types/styled-components": "^5.1.26", + "@types/uuid": "^9.0.0", "autoprefixer": "^10.4.12", "eslint": "8.23.1", "eslint-config-next": "^13.0.6", @@ -3058,6 +3060,12 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q==", + "dev": true + }, "node_modules/@typescript-eslint/parser": { "version": "5.46.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.46.1.tgz", @@ -7088,6 +7096,14 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/w3c-keyname": { "version": "2.2.6", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz", @@ -9309,6 +9325,12 @@ "csstype": "^3.0.2" } }, + "@types/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q==", + "dev": true + }, "@typescript-eslint/parser": { "version": "5.46.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.46.1.tgz", @@ -12128,6 +12150,11 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" + }, "w3c-keyname": { "version": "2.2.6", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz", diff --git a/front/package.json b/front/package.json index 325ba9c8..fd630495 100644 --- a/front/package.json +++ b/front/package.json @@ -27,6 +27,7 @@ "react-katex": "^3.0.1", "styled-components": "^6.0.0-beta.9", "swr": "^2.0.1", + "uuid": "^9.0.0", "y-indexeddb": "^9.0.9", "y-webrtc": "^10.2.3", "yjs": "^13.5.42" @@ -38,6 +39,7 @@ "@types/react-dom": "18.0.6", "@types/react-katex": "^3.0.0", "@types/styled-components": "^5.1.26", + "@types/uuid": "^9.0.0", "autoprefixer": "^10.4.12", "eslint": "8.23.1", "eslint-config-next": "^13.0.6", diff --git a/front/services/blocks/Quiz/quiz.ts b/front/services/blocks/Quiz/quiz.ts new file mode 100644 index 00000000..e881c624 --- /dev/null +++ b/front/services/blocks/Quiz/quiz.ts @@ -0,0 +1,10 @@ +import { getAPIUrl } from "@services/config"; +import { RequestBody, RequestBodyForm } from "@services/utils/requests"; + + +export async function submitQuizBlock(lecture_id: string, data: any) { + const result: any = await fetch(`${getAPIUrl()}blocks/quiz/${lecture_id}"`, RequestBody("POST", data)) + .then((result) => result.json()) + .catch((error) => console.log("error", error)); + return result; +} \ No newline at end of file diff --git a/front/services/files/documents.ts b/front/services/files/documents.ts index 1faa3546..c0f04d61 100644 --- a/front/services/files/documents.ts +++ b/front/services/files/documents.ts @@ -7,14 +7,14 @@ export async function uploadNewPDFFile(file: any, lecture_id: string) { formData.append("file_object", file); formData.append("lecture_id", lecture_id); - return fetch(`${getAPIUrl()}files/document`, RequestBodyForm("POST", formData)) + return fetch(`${getAPIUrl()}blocks/document`, RequestBodyForm("POST", formData)) .then((result) => result.json()) .catch((error) => console.log("error", error)); } export async function getPDFFile(file_id: string) { // todo : add course id to url - return fetch(`${getAPIUrl()}files/document?file_id=${file_id}`, RequestBody("GET", null)) + return fetch(`${getAPIUrl()}blocks/document?file_id=${file_id}`, RequestBody("GET", null)) .then((result) => result.json()) .catch((error) => console.log("error", error)); } diff --git a/front/services/files/images.ts b/front/services/files/images.ts index 35f449f4..157a74fa 100644 --- a/front/services/files/images.ts +++ b/front/services/files/images.ts @@ -7,14 +7,14 @@ export async function uploadNewImageFile(file: any, lecture_id: string) { formData.append("file_object", file); formData.append("lecture_id", lecture_id); - return fetch(`${getAPIUrl()}files/picture`, RequestBodyForm("POST", formData)) + return fetch(`${getAPIUrl()}blocks/image`, RequestBodyForm("POST", formData)) .then((result) => result.json()) .catch((error) => console.log("error", error)); } export async function getImageFile(file_id: string) { // todo : add course id to url - return fetch(`${getAPIUrl()}files/picture?file_id=${file_id}`, RequestBody("GET", null)) + return fetch(`${getAPIUrl()}blocks/image?file_id=${file_id}`, RequestBody("GET", null)) .then((result) => result.json()) .catch((error) => console.log("error", error)); } diff --git a/front/services/files/video.ts b/front/services/files/video.ts index eaa10e22..3fadc936 100644 --- a/front/services/files/video.ts +++ b/front/services/files/video.ts @@ -7,13 +7,13 @@ export async function uploadNewVideoFile(file: any, lecture_id: string) { formData.append("file_object", file); formData.append("lecture_id", lecture_id); - return fetch(`${getAPIUrl()}files/video`, RequestBodyForm("POST", formData)) + return fetch(`${getAPIUrl()}blocks/video`, RequestBodyForm("POST", formData)) .then((result) => result.json()) .catch((error) => console.log("error", error)); } export async function getVideoFile(file_id: string) { - return fetch(`${getAPIUrl()}files/video?file_id=${file_id}`, RequestBody("GET", null)) + return fetch(`${getAPIUrl()}blocks/video?file_id=${file_id}`, RequestBody("GET", null)) .then((result) => result.json()) .catch((error) => console.log("error", error)); } diff --git a/requirements.txt b/requirements.txt index 6881d583..d1f1bd3a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,8 @@ -fastapi==0.89.1 +fastapi==0.92.0 pydantic>=1.8.0,<2.0.0 uvicorn==0.20.0 -pymongo==4.1.1 +pymongo==4.3.3 +motor==3.1.1 python-multipart python-jose passlib diff --git a/src/core/events/database.py b/src/core/events/database.py index 22a6038c..56913242 100644 --- a/src/core/events/database.py +++ b/src/core/events/database.py @@ -1,11 +1,11 @@ import logging from fastapi import FastAPI -import pymongo +import motor.motor_asyncio async def connect_to_db(app: FastAPI) : logging.info("Connecting to database...") try: - app.mongodb_client = pymongo.MongoClient("mongodb://learnhouse:learnhouse@mongo:27017/") # type: ignore + app.mongodb_client = motor.motor_asyncio.AsyncIOMotorClient("mongodb://learnhouse:learnhouse@mongo:27017/") # type: ignore app.db = app.mongodb_client["learnhouse"] # type: ignore logging.info("Connected to database!") except Exception as e: diff --git a/src/main.py b/src/main.py index 6a41e764..6361f91b 100644 --- a/src/main.py +++ b/src/main.py @@ -1,5 +1,5 @@ from fastapi import APIRouter -from src.routers import activity, users, auth, houses, orgs, roles, files +from src.routers import activity, blocks, users, auth, houses, orgs, roles from src.routers.courses import chapters, collections, courses,lectures @@ -12,7 +12,7 @@ global_router.include_router(auth.router, prefix="/auth", tags=["auth"]) global_router.include_router(houses.router, prefix="/houses", tags=["houses"]) global_router.include_router(orgs.router, prefix="/orgs", tags=["orgs"]) global_router.include_router(roles.router, prefix="/roles", tags=["roles"]) -global_router.include_router(files.router, prefix="/files", tags=["files"]) +global_router.include_router(blocks.router, prefix="/blocks", tags=["blocks"]) global_router.include_router(courses.router, prefix="/courses", tags=["courses"]) global_router.include_router(chapters.router, prefix="/chapters", tags=["chapters"]) global_router.include_router(lectures.router, prefix="/lectures", tags=["lectures"]) diff --git a/src/routers/blocks.py b/src/routers/blocks.py new file mode 100644 index 00000000..b9b68707 --- /dev/null +++ b/src/routers/blocks.py @@ -0,0 +1,80 @@ +from fastapi import APIRouter, Depends, UploadFile, Form, Request +from src.dependencies.auth import get_current_user +from fastapi import HTTPException, status, UploadFile +from src.services.blocks.imageBlock.images import create_image_file, get_image_file +from src.services.blocks.videoBlock.videos import create_video_file, get_video_file +from src.services.blocks.pdfBlock.documents import create_document_file, get_document_file +from src.services.blocks.quizBlock.quizBlock import create_quiz_block, get_quiz_block_answers, get_quiz_block_options, quizBlock +from src.services.users import PublicUser + +router = APIRouter() + + +@router.post("/image") +async def api_create_image_file_block(request: Request, file_object: UploadFile, lecture_id: str = Form(), current_user: PublicUser = Depends(get_current_user)): + """ + Create new image file + """ + return await create_image_file(request, file_object, lecture_id) + + +@router.get("/image") +async def api_get_image_file_block(request: Request, file_id: str, current_user: PublicUser = Depends(get_current_user)): + """ + Get image file + """ + return await get_image_file(request, file_id, current_user) + +@router.post("/video") +async def api_create_video_file_block(request: Request, file_object: UploadFile, lecture_id: str = Form(), current_user: PublicUser = Depends(get_current_user)): + """ + Create new video file + """ + return await create_video_file(request, file_object, lecture_id) + +@router.get("/video") +async def api_get_video_file_block(request: Request, file_id: str, current_user: PublicUser = Depends(get_current_user)): + """ + Get video file + """ + return await get_video_file(request, file_id, current_user) + + +@router.post("/document") +async def api_create_document_file_block(request: Request, file_object: UploadFile, lecture_id: str = Form(), current_user: PublicUser = Depends(get_current_user)): + """ + Create new document file + """ + return await create_document_file(request, file_object, lecture_id) + + +@router.get("/document") +async def api_get_document_file_block(request: Request, file_id: str, current_user: PublicUser = Depends(get_current_user)): + """ + Get document file + """ + return await get_document_file(request, file_id, current_user) + + +@router.post("/quiz/{lecture_id}") +async def api_create_quiz_block(request: Request, quiz_block: quizBlock, lecture_id: str, current_user: PublicUser = Depends(get_current_user)): + """ + Create new document file + """ + return await create_quiz_block(request, quiz_block, lecture_id, current_user) + + +@router.get("/quiz/options") +async def api_get_quiz_options(request: Request, block_id: str, current_user: PublicUser = Depends(get_current_user)): + """ + Get quiz options + """ + return await get_quiz_block_options(request, block_id, current_user) + + +@router.get("/quiz/answers") +async def api_get_quiz_answers(request: Request, block_id: str, current_user: PublicUser = Depends(get_current_user)): + """ + Get quiz answers + """ + return await get_quiz_block_answers(request, block_id, current_user) diff --git a/src/routers/files.py b/src/routers/files.py deleted file mode 100644 index d520c196..00000000 --- a/src/routers/files.py +++ /dev/null @@ -1,58 +0,0 @@ -from fastapi import APIRouter, Depends, UploadFile, Form, Request -from src.dependencies.auth import get_current_user -from fastapi import HTTPException, status, UploadFile -from src.services.files.documents import create_document_file, get_document_file - -from src.services.files.pictures import create_picture_file, get_picture_file -from src.services.files.videos import create_video_file, get_video_file -from src.services.users import PublicUser - -router = APIRouter() - - -@router.post("/picture") -async def api_create_picture_file(request: Request, file_object: UploadFile, lecture_id: str = Form(), current_user: PublicUser = Depends(get_current_user)): - """ - Create new picture file - """ - return await create_picture_file(request, file_object, lecture_id) - - -@router.post("/video") -async def api_create_video_file(request: Request, file_object: UploadFile, lecture_id: str = Form(), current_user: PublicUser = Depends(get_current_user)): - """ - Create new video file - """ - return await create_video_file(request, file_object, lecture_id) - - -@router.get("/picture") -async def api_get_picture_file(request: Request, file_id: str, current_user: PublicUser = Depends(get_current_user)): - """ - Get picture file - """ - return await get_picture_file(request, file_id, current_user) - - -@router.get("/video") -async def api_get_video_file(request: Request, file_id: str, current_user: PublicUser = Depends(get_current_user)): - """ - Get video file - """ - return await get_video_file(request, file_id, current_user) - - -@router.get("/document") -async def api_get_document_file(request: Request, file_id: str, current_user: PublicUser = Depends(get_current_user)): - """ - Get document file - """ - return await get_document_file(request, file_id, current_user) - - -@router.post("/document") -async def api_create_document_file(request: Request, file_object: UploadFile, lecture_id: str = Form(), current_user: PublicUser = Depends(get_current_user)): - """ - Create new document file - """ - return await create_document_file(request, file_object, lecture_id) diff --git a/src/services/activity.py b/src/services/activity.py index 6ad810e9..ae2ece70 100644 --- a/src/services/activity.py +++ b/src/services/activity.py @@ -35,13 +35,13 @@ async def create_activity(request: Request, user: PublicUser, activity_object: A activities = request.app.db["activities"] # find if the user has already started the course - isActivityAlreadCreated = activities.find_one( + isActivityAlreadCreated = await activities.find_one( {"course_id": activity_object.course_id, "user_id": user.user_id}) if isActivityAlreadCreated: if isActivityAlreadCreated['status'] == 'closed': activity_object.status = 'ongoing' - activities.update_one( + await activities.update_one( {"activity_id": isActivityAlreadCreated['activity_id']}, {"$set": activity_object.dict()}) return activity_object else: @@ -54,7 +54,7 @@ async def create_activity(request: Request, user: PublicUser, activity_object: A activity = ActivityInDB(**activity_object.dict(),activity_id=activity_id, user_id=user.user_id, org_id=activity_object.course_id) - activities.insert_one(activity.dict()) + await activities.insert_one(activity.dict()) return activity @@ -73,7 +73,7 @@ async def get_user_activities(request: Request, user: PublicUser, org_id: str): raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="No activities found") - for activity in user_activities: + for activity in await user_activities.to_list(length=100): # get number of lectures in the course coursechapters = await get_coursechapters_meta(request, activity['course_id'], user) @@ -81,7 +81,7 @@ async def get_user_activities(request: Request, user: PublicUser, org_id: str): progression = round( len(activity['lectures_marked_complete']) / len(coursechapters['lectures']) * 100, 2) - course = courses.find_one({"course_id": activity['course_id']}, {'_id': 0}) + course = await courses.find_one({"course_id": activity['course_id']}, {'_id': 0}) # add progression to the activity one_activity = {"course": course, "activitydata": activity, "progression": progression} @@ -103,7 +103,7 @@ async def get_user_activities_orgslug(request: Request, user: PublicUser, org_sl raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="No activities found") - for activity in user_activities: + for activity in await user_activities.to_list(length=100): # get number of lectures in the course coursechapters = await get_coursechapters_meta(request, activity['course_id'], user) @@ -111,7 +111,7 @@ async def get_user_activities_orgslug(request: Request, user: PublicUser, org_sl progression = round( len(activity['lectures_marked_complete']) / len(coursechapters['lectures']) * 100, 2) - course = courses.find_one({"course_id": activity['course_id']}, {'_id': 0}) + course = await courses.find_one({"course_id": activity['course_id']}, {'_id': 0}) # add progression to the activity one_activity = {"course": course, "activitydata": activity, "progression": progression} @@ -125,14 +125,14 @@ async def add_lecture_to_activity(request: Request, user: PublicUser, org_id: st course_id = f"course_{course_id}" lecture_id = f"lecture_{lecture_id}" - activity = activities.find_one( + activity = await activities.find_one( {"course_id": course_id, "user_id": user.user_id }, {'_id': 0}) if lecture_id not in activity['lectures_marked_complete']: activity['lectures_marked_complete'].append(str(lecture_id)) - activities.update_one( + await activities.update_one( {"activity_id": activity['activity_id']}, {"$set": activity}) return activity @@ -147,7 +147,7 @@ async def add_lecture_to_activity(request: Request, user: PublicUser, org_id: st async def close_activity(request: Request, user: PublicUser, activity_id: str, org_id: str,): activities = request.app.db["activities"] - activity = activities.find_one( + activity = await activities.find_one( {"activity_id": activity_id, "user_id": user.user_id}) if not activity: @@ -156,7 +156,7 @@ async def close_activity(request: Request, user: PublicUser, activity_id: str, activity['status'] = 'closed' - activities.update_one( + await activities.update_one( {"activity_id": activity['activity_id']}, {"$set": activity}) activity = ActivityInDB(**activity) diff --git a/src/services/blocks/blocks.py b/src/services/blocks/blocks.py new file mode 100644 index 00000000..d146e633 --- /dev/null +++ b/src/services/blocks/blocks.py @@ -0,0 +1,12 @@ + +from typing import Any, List, Literal +from uuid import uuid4 +from fastapi import Request +from pydantic import BaseModel + + +class Block(BaseModel): + block_id: str + lecture_id: str + block_type: Literal["quizBlock", "videoBlock", "pdfBlock"] + block_data: Any diff --git a/src/services/files/pictures.py b/src/services/blocks/imageBlock/images.py similarity index 68% rename from src/services/files/pictures.py rename to src/services/blocks/imageBlock/images.py index 7bbf890b..be8104b9 100644 --- a/src/services/files/pictures.py +++ b/src/services/blocks/imageBlock/images.py @@ -16,31 +16,31 @@ class PhotoFile(BaseModel): lecture_id: str -async def create_picture_file(request: Request,picture_file: UploadFile, lecture_id: str): +async def create_image_file(request: Request,image_file: UploadFile, lecture_id: str): photos = request.app.db["files"] # generate file_id file_id = str(f"file_{uuid4()}") # get file format - file_format = picture_file.filename.split(".")[-1] + file_format = image_file.filename.split(".")[-1] # validate file format if file_format not in ["jpg", "jpeg", "png", "gif"]: raise HTTPException( - status_code=status.HTTP_409_CONFLICT, detail="Picture file format not supported") + status_code=status.HTTP_409_CONFLICT, detail="Image file format not supported") # create file - file = await picture_file.read() + file = await image_file.read() # get file size file_size = len(file) # get file type - file_type = picture_file.content_type + file_type = image_file.content_type # get file name - file_name = picture_file.filename + file_name = image_file.filename # create file object uploadable_file = PhotoFile( @@ -53,16 +53,16 @@ async def create_picture_file(request: Request,picture_file: UploadFile, lecture ) # create folder for lecture - if not os.path.exists(f"content/uploads/files/pictures/{lecture_id}"): - os.mkdir(f"content/uploads/files/pictures/{lecture_id}") + if not os.path.exists(f"content/uploads/files/images/{lecture_id}"): + os.mkdir(f"content/uploads/files/images/{lecture_id}") # upload file to server - with open(f"content/uploads/files/pictures/{lecture_id}/{file_id}.{file_format}", 'wb') as f: + with open(f"content/uploads/files/images/{lecture_id}/{file_id}.{file_format}", 'wb') as f: f.write(file) f.close() # insert file object into database - photo_file_in_db = photos.insert_one(uploadable_file.dict()) + photo_file_in_db = await photos.insert_one(uploadable_file.dict()) if not photo_file_in_db: raise HTTPException( @@ -71,10 +71,10 @@ async def create_picture_file(request: Request,picture_file: UploadFile, lecture return uploadable_file -async def get_picture_object(request: Request,file_id: str): +async def get_image_object(request: Request,file_id: str): photos = request.app.db["files"] - photo_file = photos.find_one({"file_id": file_id}) + photo_file = await photos.find_one({"file_id": file_id}) if photo_file: photo_file = PhotoFile(**photo_file) @@ -85,10 +85,10 @@ async def get_picture_object(request: Request,file_id: str): status_code=status.HTTP_409_CONFLICT, detail="Photo file does not exist") -async def get_picture_file(request: Request,file_id: str, current_user: PublicUser): +async def get_image_file(request: Request,file_id: str, current_user: PublicUser): photos = request.app.db["files"] - photo_file = photos.find_one({"file_id": file_id}) + photo_file = await photos.find_one({"file_id": file_id}) # TODO : check if user has access to file @@ -104,7 +104,7 @@ async def get_picture_file(request: Request,file_id: str, current_user: PublicUs file_format = photo_file.file_format lecture_id = photo_file.lecture_id file = open( - f"content/uploads/files/pictures/{lecture_id}/{file_id}.{file_format}", 'rb') + f"content/uploads/files/images/{lecture_id}/{file_id}.{file_format}", 'rb') return StreamingResponse(file, media_type=photo_file.file_type) else: diff --git a/src/services/files/documents.py b/src/services/blocks/pdfBlock/documents.py similarity index 92% rename from src/services/files/documents.py rename to src/services/blocks/pdfBlock/documents.py index 9fbdf718..0f061ecd 100644 --- a/src/services/files/documents.py +++ b/src/services/blocks/pdfBlock/documents.py @@ -62,7 +62,7 @@ async def create_document_file(request: Request, document_file: UploadFile, lect f.close() # insert file object into database - document_file_in_db = documents.insert_one(uploadable_file.dict()) + document_file_in_db = await documents.insert_one(uploadable_file.dict()) if not document_file_in_db: raise HTTPException( @@ -74,7 +74,7 @@ async def create_document_file(request: Request, document_file: UploadFile, lect async def get_document_object(request: Request, file_id: str): documents = request.app.db["files"] - document_file = documents.find_one({"file_id": file_id}) + document_file = await documents.find_one({"file_id": file_id}) if document_file: document_file = DocumentFile(**document_file) @@ -88,14 +88,14 @@ async def get_document_object(request: Request, file_id: str): async def get_document_file(request: Request, file_id: str, current_user: PublicUser): documents = request.app.db["files"] - document_file = documents.find_one({"file_id": file_id}) + document_file = await documents.find_one({"file_id": file_id}) # TODO : check if user has access to file if document_file: # check media type - if document_file.format not in ["jpg", "jpeg", "png", "gif"]: + if document_file.format not in ["pdf"]: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="Document file format not supported") diff --git a/src/services/blocks/quizBlock/quizBlock.py b/src/services/blocks/quizBlock/quizBlock.py new file mode 100644 index 00000000..d1f0cecb --- /dev/null +++ b/src/services/blocks/quizBlock/quizBlock.py @@ -0,0 +1,60 @@ +from typing import List, Literal +from uuid import uuid4 +from fastapi import Request +from pydantic import BaseModel +from src.services.blocks.blocks import Block +from src.services.users import PublicUser + + +class option(BaseModel): + option_id: str + option_type: Literal["text", "image"] + option_data: str + + +class answer(BaseModel): + question_id: str + option_id: str + + +class question(BaseModel): + question_id: str + question_value:str + options: List[option] + + +class quizBlock(BaseModel): + questions: List[question] + answers: List[answer] + + +async def create_quiz_block(request: Request, quizBlock: quizBlock, lecture_id: str, user: PublicUser): + blocks = request.app.db["blocks"] + block_id = str(f"block_{uuid4()}") + + # create block + block = Block(block_id=block_id, lecture_id=lecture_id, + block_type="quizBlock", block_data=quizBlock) + + # insert block + await blocks.insert_one(block.dict()) + + return block + + +async def get_quiz_block_options(request: Request, block_id: str, user: PublicUser): + blocks = request.app.db["blocks"] + # find block but get only the options + block = await blocks.find_one({"block_id": block_id, }, { + "_id": 0, "block_data.answers": 0}) + + return block + +async def get_quiz_block_answers(request: Request, block_id: str, user: PublicUser): + blocks = request.app.db["blocks"] + + # find block but get only the answers + block = await blocks.find_one({"block_id": block_id, }, { + "_id": 0, "block_data.questions": 0}) + + return block diff --git a/src/services/files/videos.py b/src/services/blocks/videoBlock/videos.py similarity index 94% rename from src/services/files/videos.py rename to src/services/blocks/videoBlock/videos.py index dd194c1d..b703013f 100644 --- a/src/services/files/videos.py +++ b/src/services/blocks/videoBlock/videos.py @@ -62,7 +62,7 @@ async def create_video_file(request: Request,video_file: UploadFile, lecture_id: f.close() # insert file object into database - video_file_in_db = files.insert_one(uploadable_file.dict()) + video_file_in_db = await files.insert_one(uploadable_file.dict()) if not video_file_in_db: raise HTTPException( @@ -74,7 +74,7 @@ async def create_video_file(request: Request,video_file: UploadFile, lecture_id: async def get_video_object(request: Request,file_id: str, current_user: PublicUser): photos = request.app.db["files"] - video_file = photos.find_one({"file_id": file_id}) + video_file = await photos.find_one({"file_id": file_id}) if video_file: video_file = VideoFile(**video_file) @@ -88,7 +88,7 @@ async def get_video_object(request: Request,file_id: str, current_user: PublicUs async def get_video_file(request: Request,file_id: str, current_user: PublicUser): photos = request.app.db["files"] - video_file = photos.find_one({"file_id": file_id}) + video_file = await photos.find_one({"file_id": file_id}) # TODO : check if user has access to file diff --git a/src/services/courses/chapters.py b/src/services/courses/chapters.py index 651bc58e..bda0aeff 100644 --- a/src/services/courses/chapters.py +++ b/src/services/courses/chapters.py @@ -26,7 +26,7 @@ class CourseChapterInDB(CourseChapter): # Frontend class CourseChapterMetaData(BaseModel): chapterOrder: List[str] - chapters: object + chapters: dict lectures: object #### Classes #################################################### @@ -52,7 +52,7 @@ async def create_coursechapter(request: Request,coursechapter_object: CourseChap coursechapter = CourseChapterInDB(coursechapter_id=coursechapter_id, creationDate=str( datetime.now()), updateDate=str(datetime.now()), course_id=course_id, **coursechapter_object.dict()) - coursechapter_in_db = coursechapters.insert_one(coursechapter.dict()) + coursechapter_in_db = await coursechapters.insert_one(coursechapter.dict()) courses.update_one({"course_id": course_id}, { "$addToSet": {"chapters": coursechapter_id}}) @@ -66,7 +66,7 @@ async def create_coursechapter(request: Request,coursechapter_object: CourseChap async def get_coursechapter(request: Request,coursechapter_id: str, current_user: PublicUser): coursechapters = request.app.db["coursechapters"] - coursechapter = coursechapters.find_one( + coursechapter = await coursechapters.find_one( {"coursechapter_id": coursechapter_id}) if coursechapter: @@ -84,7 +84,7 @@ async def get_coursechapter(request: Request,coursechapter_id: str, current_user async def update_coursechapter(request: Request,coursechapter_object: CourseChapter, coursechapter_id: str, current_user: PublicUser): coursechapters = request.app.db["coursechapters"] - coursechapter = coursechapters.find_one( + coursechapter = await coursechapters.find_one( {"coursechapter_id": coursechapter_id}) if coursechapter: @@ -98,7 +98,7 @@ async def update_coursechapter(request: Request,coursechapter_object: CourseChap updated_coursechapter = CourseChapterInDB( coursechapter_id=coursechapter_id, creationDate=creationDate, course_id=coursechapter["course_id"], updateDate=str(datetime_object), **coursechapter_object.dict()) - coursechapters.update_one({"coursechapter_id": coursechapter_id}, { + await coursechapters.update_one({"coursechapter_id": coursechapter_id}, { "$set": updated_coursechapter.dict()}) return CourseChapterInDB(**updated_coursechapter.dict()) @@ -113,18 +113,18 @@ async def delete_coursechapter(request: Request,coursechapter_id: str, current_ coursechapters = request.app.db["coursechapters"] courses = request.app.db["courses"] - coursechapter = coursechapters.find_one( + coursechapter = await coursechapters.find_one( {"coursechapter_id": coursechapter_id}) if coursechapter: # verify course rights await verify_rights(request, coursechapter["course_id"], current_user, "delete") - isDeleted = coursechapters.delete_one( + isDeleted = await coursechapters.delete_one( {"coursechapter_id": coursechapter_id}) # Remove coursechapter from course - courses.update_one({"course_id": coursechapter["course_id"]}, { + await courses.update_one({"course_id": coursechapter["course_id"]}, { "$pull": {"chapters": coursechapter_id}}) if isDeleted: @@ -149,7 +149,7 @@ async def get_coursechapters(request: Request,course_id: str, page: int = 1, lim all_coursechapters = courses.find({"course_id": course_id}).sort( "name", 1).skip(10 * (page - 1)).limit(limit) - return [json.loads(json.dumps(coursechapter, default=str)) for coursechapter in all_coursechapters] + return [json.loads(json.dumps(coursechapter, default=str)) for coursechapter in await all_coursechapters.to_list(length=100)] async def get_coursechapters_meta(request: Request, course_id: str, current_user: PublicUser): @@ -160,7 +160,7 @@ async def get_coursechapters_meta(request: Request, course_id: str, current_user coursechapters = coursechapters.find( {"course_id": course_id}).sort("name", 1) - course = courses.find_one({"course_id": course_id}) + course = await courses.find_one({"course_id": course_id}) course = Course(**course) # type: ignore # lectures @@ -168,7 +168,7 @@ async def get_coursechapters_meta(request: Request, course_id: str, current_user # chapters chapters = {} - for coursechapter in coursechapters: + for coursechapter in await coursechapters.to_list(length=100): coursechapter = CourseChapterInDB(**coursechapter) coursechapter_lectureIds = [] @@ -182,7 +182,7 @@ async def get_coursechapters_meta(request: Request, course_id: str, current_user # lectures lectures_list = {} - for lecture in lectures.find({"lecture_id": {"$in": coursechapter_lectureIds_global}}): + for lecture in await lectures.find({"lecture_id": {"$in": coursechapter_lectureIds_global}}).to_list(length=100): lecture = LectureInDB(**lecture) lectures_list[lecture.lecture_id] = { "id": lecture.lecture_id, "name": lecture.name, "type": lecture.type, "content": lecture.content @@ -202,14 +202,18 @@ async def update_coursechapters_meta(request: Request,course_id: str, coursechap courses = request.app.db["courses"] # update chapters in course - courseInDB = courses.update_one({"course_id": course_id}, { + courseInDB = await courses.update_one({"course_id": course_id}, { "$set": {"chapters": coursechapters_metadata.chapterOrder}}) - # update lectures in coursechapters - # TODO : performance/optimization improvement, this does not work anyway. - for coursechapter in coursechapters_metadata.chapters.__dict__.items(): - coursechapters.update_one({"coursechapter_id": coursechapter}, { - "$set": {"lectures": coursechapters_metadata.chapters[coursechapter]["lectureIds"]}}) # type: ignore + if coursechapters_metadata.chapters is not None: + for coursechapter_id, chapter_metadata in coursechapters_metadata.chapters.items(): + filter_query = {"coursechapter_id": coursechapter_id} + update_query = {"$set": {"lectures": chapter_metadata["lectureIds"]}} + result = await coursechapters.update_one(filter_query, update_query) + if result.matched_count == 0: + # handle error when no documents are matched by the filter query + print(f"No documents found for course chapter ID {coursechapter_id}") + return {"detail": "coursechapters metadata updated"} @@ -219,7 +223,7 @@ async def update_coursechapters_meta(request: Request,course_id: str, coursechap async def verify_rights(request: Request,course_id: str, current_user: PublicUser, action: str): courses = request.app.db["courses"] - course = courses.find_one({"course_id": course_id}) + course = await courses.find_one({"course_id": course_id}) if not course: raise HTTPException( diff --git a/src/services/courses/collections.py b/src/services/courses/collections.py index 6c719819..3996c91b 100644 --- a/src/services/courses/collections.py +++ b/src/services/courses/collections.py @@ -30,7 +30,7 @@ class CollectionInDB(Collection): async def get_collection(request: Request,collection_id: str, current_user: PublicUser): collections = request.app.db["collections"] - collection = collections.find_one({"collection_id": collection_id}) + collection = await collections.find_one({"collection_id": collection_id}) # verify collection rights await verify_collection_rights(request, collection_id, current_user, "read") @@ -47,7 +47,7 @@ async def create_collection(request: Request,collection_object: Collection, curr collections = request.app.db["collections"] # find if collection already exists using name - isCollectionNameAvailable = collections.find_one( + isCollectionNameAvailable = await collections.find_one( {"name": collection_object.name}) # TODO @@ -63,7 +63,7 @@ async def create_collection(request: Request,collection_object: Collection, curr collection = CollectionInDB( collection_id=collection_id, **collection_object.dict()) - collection_in_db = collections.insert_one(collection.dict()) + collection_in_db = await collections.insert_one(collection.dict()) if not collection_in_db: raise HTTPException( @@ -79,7 +79,7 @@ async def update_collection(request: Request,collection_object: Collection, coll collections = request.app.db["collections"] - collection = collections.find_one({"collection_id": collection_id}) + collection = await collections.find_one({"collection_id": collection_id}) if not collection: raise HTTPException( @@ -88,7 +88,7 @@ async def update_collection(request: Request,collection_object: Collection, coll updated_collection = CollectionInDB( collection_id=collection_id, **collection_object.dict()) - collections.update_one({"collection_id": collection_id}, { + await collections.update_one({"collection_id": collection_id}, { "$set": updated_collection.dict()}) return Collection(**updated_collection.dict()) @@ -100,13 +100,13 @@ async def delete_collection(request: Request,collection_id: str, current_user: P collections = request.app.db["collections"] - collection = collections.find_one({"collection_id": collection_id}) + collection = await collections.find_one({"collection_id": collection_id}) if not collection: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="Collection does not exist") - isDeleted = collections.delete_one({"collection_id": collection_id}) + isDeleted = await collections.delete_one({"collection_id": collection_id}) if isDeleted: return {"detail": "collection deleted"} @@ -129,7 +129,7 @@ async def get_collections(request: Request,page: int = 1, limit: int = 10): # create list of collections and include courses in each collection collections_list = [] - for collection in all_collections: + for collection in await all_collections.to_list(length=100): collection = CollectionInDB(**collection) collections_list.append(collection) @@ -140,7 +140,7 @@ async def get_collections(request: Request,page: int = 1, limit: int = 10): collection.courses = courses.find( {"course_id": {"$in": collection_courses}}, {'_id': 0}) - collection.courses = [course for course in collection.courses] + collection.courses = [course for course in await collection.courses.to_list(length=100)] return collections_list @@ -150,7 +150,7 @@ async def get_collections(request: Request,page: int = 1, limit: int = 10): async def verify_collection_rights(request: Request,collection_id: str, current_user: PublicUser, action: str): collections = request.app.db["collections"] - collection = collections.find_one({"collection_id": collection_id}) + collection = await collections.find_one({"collection_id": collection_id}) if not collection and action != "create": raise HTTPException( diff --git a/src/services/courses/courses.py b/src/services/courses/courses.py index 52fdf9c4..265dcf85 100644 --- a/src/services/courses/courses.py +++ b/src/services/courses/courses.py @@ -56,7 +56,7 @@ class CourseChapterInDB(CourseChapter): async def get_course(request: Request, course_id: str, current_user: PublicUser): courses = request.app.db["courses"] - course = courses.find_one({"course_id": course_id}) + course = await courses.find_one({"course_id": course_id}) # verify course rights await verify_rights(request, course_id, current_user, "read") @@ -74,7 +74,7 @@ async def get_course_meta(request: Request, course_id: str, current_user: Public coursechapters = request.app.db["coursechapters"] activities = request.app.db["activities"] - course = courses.find_one({"course_id": course_id}) + course = await courses.find_one({"course_id": course_id}) lectures = request.app.db["lectures"] # verify course rights @@ -92,7 +92,7 @@ async def get_course_meta(request: Request, course_id: str, current_user: Public # chapters chapters = {} - for coursechapter in coursechapters: + for coursechapter in await coursechapters.to_list(length=100): coursechapter = CourseChapterInDB(**coursechapter) coursechapter_lectureIds = [] @@ -106,7 +106,7 @@ async def get_course_meta(request: Request, course_id: str, current_user: Public # lectures lectures_list = {} - for lecture in lectures.find({"lecture_id": {"$in": coursechapter_lectureIds_global}}): + for lecture in await lectures.find({"lecture_id": {"$in": coursechapter_lectureIds_global}}).to_list(length=100): lecture = LectureInDB(**lecture) lectures_list[lecture.lecture_id] = { "id": lecture.lecture_id, "name": lecture.name, "type": lecture.type, "content": lecture.content @@ -119,7 +119,7 @@ async def get_course_meta(request: Request, course_id: str, current_user: Public course = Course(**course) # Get activity by user - activity = activities.find_one( + activity = await activities.find_one( {"course_id": course_id, "user_id": current_user.user_id}) if activity: activity = json.loads(json.dumps(activity, default=str)) @@ -155,7 +155,7 @@ async def create_course(request: Request, course_object: Course, org_id: str, cu course = CourseInDB(course_id=course_id, authors=[ current_user.user_id], creationDate=str(datetime.now()), updateDate=str(datetime.now()), **course_object.dict()) - course_in_db = courses.insert_one(course.dict()) + course_in_db = await courses.insert_one(course.dict()) if not course_in_db: raise HTTPException( @@ -171,7 +171,7 @@ async def update_course_thumbnail(request: Request, course_id: str, current_user courses = request.app.db["courses"] - course = courses.find_one({"course_id": course_id}) + course = await courses.find_one({"course_id": course_id}) # TODO(fix) : the implementation here is clearly not the best one if course: creationDate = course["creationDate"] @@ -184,7 +184,7 @@ async def update_course_thumbnail(request: Request, course_id: str, current_user updated_course = CourseInDB(course_id=course_id, creationDate=creationDate, authors=authors, updateDate=str(datetime.now()), **course.dict()) - courses.update_one({"course_id": course_id}, { + await courses.update_one({"course_id": course_id}, { "$set": updated_course.dict()}) return CourseInDB(**updated_course.dict()) @@ -201,7 +201,7 @@ async def update_course(request: Request, course_object: Course, course_id: str, courses = request.app.db["courses"] - course = courses.find_one({"course_id": course_id}) + course = await courses.find_one({"course_id": course_id}) if course: creationDate = course["creationDate"] @@ -213,7 +213,7 @@ async def update_course(request: Request, course_object: Course, course_id: str, updated_course = CourseInDB( course_id=course_id, creationDate=creationDate, authors=authors, updateDate=str(datetime_object), **course_object.dict()) - courses.update_one({"course_id": course_id}, { + await courses.update_one({"course_id": course_id}, { "$set": updated_course.dict()}) return CourseInDB(**updated_course.dict()) @@ -230,13 +230,13 @@ async def delete_course(request: Request, course_id: str, current_user: PublicUs courses = request.app.db["courses"] - course = courses.find_one({"course_id": course_id}) + course = await courses.find_one({"course_id": course_id}) if not course: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="Course does not exist") - isDeleted = courses.delete_one({"course_id": course_id}) + isDeleted = await courses.delete_one({"course_id": course_id}) if isDeleted: return {"detail": "Course deleted"} @@ -256,7 +256,7 @@ async def get_courses(request: Request, page: int = 1, limit: int = 10, org_id: all_courses = courses.find({"org_id": org_id}).sort( "name", 1).skip(10 * (page - 1)).limit(limit) - return [json.loads(json.dumps(course, default=str)) for course in all_courses] + return [json.loads(json.dumps(course, default=str)) for course in await all_courses.to_list(length=100)] async def get_courses_orgslug(request: Request, page: int = 1, limit: int = 10, org_slug: str | None = None): courses = request.app.db["courses"] @@ -264,7 +264,7 @@ async def get_courses_orgslug(request: Request, page: int = 1, limit: int = 10, # TODO : Get only courses that user is admin/has roles of # get org_id from slug - org = orgs.find_one({"slug": org_slug}) + org = await orgs.find_one({"slug": org_slug}) if not org: raise HTTPException( @@ -274,7 +274,7 @@ async def get_courses_orgslug(request: Request, page: int = 1, limit: int = 10, 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] + return [json.loads(json.dumps(course, default=str)) for course in await all_courses.to_list(length=100)] @@ -284,7 +284,7 @@ async def get_courses_orgslug(request: Request, page: int = 1, limit: int = 10, async def verify_rights(request: Request, course_id: str, current_user: PublicUser, action: str): courses = request.app.db["courses"] - course = courses.find_one({"course_id": course_id}) + course = await courses.find_one({"course_id": course_id}) if not course: raise HTTPException( diff --git a/src/services/courses/lectures/lectures.py b/src/services/courses/lectures/lectures.py index ad43f8a8..51d58fb4 100644 --- a/src/services/courses/lectures/lectures.py +++ b/src/services/courses/lectures/lectures.py @@ -44,10 +44,10 @@ async def create_lecture(request: Request,lecture_object: Lecture, coursechapter # create lecture lecture = LectureInDB(**lecture_object.dict(), creationDate=str( datetime.now()), coursechapter_id=coursechapter_id, updateDate=str(datetime.now()), lecture_id=lecture_id) - lectures.insert_one(lecture.dict()) + await lectures.insert_one(lecture.dict()) # update chapter - coursechapters.update_one({"coursechapter_id": coursechapter_id}, { + await coursechapters.update_one({"coursechapter_id": coursechapter_id}, { "$addToSet": {"lectures": lecture_id}}) return lecture @@ -56,7 +56,7 @@ async def create_lecture(request: Request,lecture_object: Lecture, coursechapter async def get_lecture(request: Request,lecture_id: str, current_user: PublicUser): lectures = request.app.db["lectures"] - lecture = lectures.find_one({"lecture_id": lecture_id}) + lecture = await lectures.find_one({"lecture_id": lecture_id}) # verify course rights hasRoleRights = await verify_user_rights_with_roles(request,"read", current_user.user_id, lecture_id) @@ -80,7 +80,7 @@ async def update_lecture(request: Request,lecture_object: Lecture, lecture_id: s lectures = request.app.db["lectures"] - lecture = lectures.find_one({"lecture_id": lecture_id}) + lecture = await lectures.find_one({"lecture_id": lecture_id}) if lecture: creationDate = lecture["creationDate"] @@ -91,7 +91,7 @@ async def update_lecture(request: Request,lecture_object: Lecture, lecture_id: s updated_course = LectureInDB( lecture_id=lecture_id, coursechapter_id=lecture["coursechapter_id"], creationDate=creationDate, updateDate=str(datetime_object), **lecture_object.dict()) - lectures.update_one({"lecture_id": lecture_id}, { + await lectures.update_one({"lecture_id": lecture_id}, { "$set": updated_course.dict()}) return LectureInDB(**updated_course.dict()) @@ -108,13 +108,13 @@ async def delete_lecture(request: Request,lecture_id: str, current_user: PublicU lectures = request.app.db["lectures"] - lecture = lectures.find_one({"lecture_id": lecture_id}) + lecture = await lectures.find_one({"lecture_id": lecture_id}) if not lecture: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="lecture does not exist") - isDeleted = lectures.delete_one({"lecture_id": lecture_id}) + isDeleted = await lectures.delete_one({"lecture_id": lecture_id}) if isDeleted: return {"detail": "lecture deleted"} @@ -137,11 +137,8 @@ async def get_lectures(request: Request,coursechapter_id: str, current_user: Pub if not lectures: raise HTTPException( - status_code=status.HTTP_409_CONFLICT, detail="No lectures found") + status_code=status.HTTP_409_CONFLICT, detail="Course does not exist") + + lectures = [LectureInDB(**lecture) for lecture in await lectures.to_list(length=100)] - lectures_list = [] - - for lecture in lectures: - lectures_list.append(Lecture(**lecture)) - - return lectures_list + return lectures diff --git a/src/services/courses/lectures/video.py b/src/services/courses/lectures/video.py index de1d9821..b2c0dd0b 100644 --- a/src/services/courses/lectures/video.py +++ b/src/services/courses/lectures/video.py @@ -44,7 +44,7 @@ async def create_video_lecture(request: Request,name: str, coursechapter_id: st # create lecture lecture = LectureInDB(**lecture_object.dict()) - lectures.insert_one(lecture.dict()) + await lectures.insert_one(lecture.dict()) # upload video if video_file: @@ -55,7 +55,7 @@ async def create_video_lecture(request: Request,name: str, coursechapter_id: st # todo : choose whether to update the chapter or not # update chapter - coursechapters.update_one({"coursechapter_id": coursechapter_id}, { + await coursechapters.update_one({"coursechapter_id": coursechapter_id}, { "$addToSet": {"lectures": lecture_id}}) return lecture diff --git a/src/services/houses.py b/src/services/houses.py index 75ebd90d..9acd1b96 100644 --- a/src/services/houses.py +++ b/src/services/houses.py @@ -131,7 +131,7 @@ async def get_houses(request: Request,page: int = 1, limit: int = 10): # get all houses from database all_houses = houses.find().sort("name", 1).skip(10 * (page - 1)).limit(limit) - return [json.loads(json.dumps(house, default=str)) for house in all_houses] + return [json.loads(json.dumps(house, default=str)) for house in await all_houses.to_list(length=limit)] #### Security #################################################### diff --git a/src/services/mocks/initial.py b/src/services/mocks/initial.py index ba8744b7..0e91f423 100644 --- a/src/services/mocks/initial.py +++ b/src/services/mocks/initial.py @@ -26,7 +26,7 @@ async def create_initial_data(request: Request): ######################################## database_users = request.app.db["users"] - database_users.delete_many({}) + await database_users.delete_many({}) users = [] admin_user = UserWithPassword( @@ -53,7 +53,7 @@ async def create_initial_data(request: Request): # find admin user users = request.app.db["users"] - admin_user = users.find_one({"username": "admin"}) + admin_user = await users.find_one({"username": "admin"}) if admin_user: admin_user = UserInDB(**admin_user) @@ -65,7 +65,7 @@ async def create_initial_data(request: Request): ######################################## database_orgs = request.app.db["organizations"] - database_orgs.delete_many({}) + await database_orgs.delete_many({}) organizations = [] for i in range(0, 5): @@ -85,7 +85,7 @@ async def create_initial_data(request: Request): ######################################## database_roles = request.app.db["roles"] - database_roles.delete_many({}) + await database_roles.delete_many({}) roles = [] admin_role = Role( @@ -117,14 +117,14 @@ async def create_initial_data(request: Request): database_courses = request.app.db["courses"] database_chapters = request.app.db["coursechapters"] - database_courses.delete_many({}) - database_chapters.delete_many({}) + await database_courses.delete_many({}) + await database_chapters.delete_many({}) courses = [] orgs = request.app.db["organizations"] - if orgs.count_documents({}) > 0: - for org in orgs.find(): + if await orgs.count_documents({}) > 0: + for org in await orgs.find().to_list(length=100): for i in range(0, 5): # get image in BinaryIO format from unsplash and save it to disk @@ -161,7 +161,7 @@ async def create_initial_data(request: Request): course.thumbnail = name_in_disk course = CourseInDB(**course.dict()) - course_in_db = courses.insert_one(course.dict()) + course_in_db = await courses.insert_one(course.dict()) # create chapters for i in range(0, 5): diff --git a/src/services/orgs.py b/src/services/orgs.py index fe07f291..1e45853d 100644 --- a/src/services/orgs.py +++ b/src/services/orgs.py @@ -37,7 +37,7 @@ class PublicOrganization(Organization): async def get_organization(request: Request, org_id: str): orgs = request.app.db["organizations"] - org = orgs.find_one({"org_id": org_id}) + org = await orgs.find_one({"org_id": org_id}) if not org: raise HTTPException( @@ -50,7 +50,7 @@ async def get_organization(request: Request, org_id: str): async def get_organization_by_slug(request: Request, org_slug: str): orgs = request.app.db["organizations"] - org = orgs.find_one({"slug": org_slug}) + org = await orgs.find_one({"slug": org_slug}) if not org: raise HTTPException( @@ -64,7 +64,7 @@ async def create_org(request: Request, org_object: Organization, current_user: P orgs = request.app.db["organizations"] # find if org already exists using name - isOrgAvailable = orgs.find_one({"slug": org_object.slug}) + isOrgAvailable = await orgs.find_one({"slug": org_object.slug}) if isOrgAvailable: raise HTTPException( @@ -77,7 +77,7 @@ async def create_org(request: Request, org_object: Organization, current_user: P current_user.user_id], admins=[ current_user.user_id], **org_object.dict()) - org_in_db = orgs.insert_one(org.dict()) + org_in_db = await orgs.insert_one(org.dict()) if not org_in_db: raise HTTPException( @@ -93,7 +93,7 @@ async def update_org(request: Request, org_object: Organization, org_id: str, cu orgs = request.app.db["organizations"] - org = orgs.find_one({"org_id": org_id}) + org = await orgs.find_one({"org_id": org_id}) if org: owners = org["owners"] @@ -106,7 +106,7 @@ async def update_org(request: Request, org_object: Organization, org_id: str, cu updated_org = OrganizationInDB( org_id=org_id, owners=owners, admins=admins, **org_object.dict()) - orgs.update_one({"org_id": org_id}, {"$set": updated_org.dict()}) + await orgs.update_one({"org_id": org_id}, {"$set": updated_org.dict()}) return Organization(**updated_org.dict()) @@ -117,13 +117,13 @@ async def delete_org(request: Request, org_id: str, current_user: PublicUser): orgs = request.app.db["organizations"] - org = orgs.find_one({"org_id": org_id}) + org = await orgs.find_one({"org_id": org_id}) if not org: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist") - isDeleted = orgs.delete_one({"org_id": org_id}) + isDeleted = await orgs.delete_one({"org_id": org_id}) if isDeleted: return {"detail": "Org deleted"} @@ -137,9 +137,9 @@ async def get_orgs_by_user(request: Request, user_id: str, page: int = 1, limit: # find all orgs where user_id is in owners or admins arrays all_orgs = orgs.find({"$or": [{"owners": user_id}, {"admins": user_id}]}).sort( - "name", 1).skip(10 * (page - 1)).limit(limit) + "name", 1).skip(10 * (page - 1)).limit(100) - return [json.loads(json.dumps(org, default=str)) for org in all_orgs] + return [json.loads(json.dumps(org, default=str)) for org in await all_orgs.to_list(length=100)] #### Security #################################################### @@ -147,7 +147,7 @@ async def get_orgs_by_user(request: Request, user_id: str, page: int = 1, limit: async def verify_org_rights(request: Request, org_id: str, current_user: PublicUser, action: str,): orgs = request.app.db["organizations"] - org = orgs.find_one({"org_id": org_id}) + org = await orgs.find_one({"org_id": org_id}) if not org: raise HTTPException( diff --git a/src/services/roles.py b/src/services/roles.py index 53642002..eb170627 100644 --- a/src/services/roles.py +++ b/src/services/roles.py @@ -47,7 +47,7 @@ class RoleInDB(Role): async def get_role(request: Request,role_id: str): roles = request.app.db["roles"] - role = roles.find_one({"role_id": role_id}) + role = await roles.find_one({"role_id": role_id}) if not role: raise HTTPException( @@ -61,7 +61,7 @@ async def create_role(request: Request,role_object: Role, current_user: PublicUs roles = request.app.db["roles"] # find if house already exists using name - isRoleAvailable = roles.find_one({"name": role_object.name}) + isRoleAvailable = await roles.find_one({"name": role_object.name}) if isRoleAvailable: raise HTTPException( @@ -75,7 +75,7 @@ async def create_role(request: Request,role_object: Role, current_user: PublicUs role = RoleInDB(role_id=role_id, creationDate=str(datetime.now()), updateDate=str(datetime.now()), **role_object.dict()) - role_in_db = roles.insert_one(role.dict()) + role_in_db = await roles.insert_one(role.dict()) if not role_in_db: raise HTTPException( @@ -91,7 +91,7 @@ async def update_role(request: Request,role_object: Role, role_id: str, current_ roles = request.app.db["roles"] - role = roles.find_one({"role_id": role_id}) + role = await roles.find_one({"role_id": role_id}) if not role: raise HTTPException( @@ -100,7 +100,7 @@ async def update_role(request: Request,role_object: Role, role_id: str, current_ updated_role = RoleInDB( role_id=role_id, updateDate=str(datetime.now()), creationDate=role["creationDate"], **role_object.dict()) - roles.update_one({"role_id": role_id}, {"$set": updated_role.dict()}) + await roles.update_one({"role_id": role_id}, {"$set": updated_role.dict()}) return RoleInDB(**updated_role.dict()) @@ -112,13 +112,13 @@ async def delete_role(request: Request,role_id: str, current_user: PublicUser): roles = request.app.db["roles"] - role = roles.find_one({"role_id": role_id}) + role = await roles.find_one({"role_id": role_id}) if not role: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="Role does not exist") - isDeleted = roles.delete_one({"role_id": role_id}) + isDeleted = await roles.delete_one({"role_id": role_id}) if isDeleted: return {"detail": "Role deleted"} @@ -133,7 +133,7 @@ async def get_roles(request: Request,page: int = 1, limit: int = 10): # get all roles from database all_roles = roles.find().sort("name", 1).skip(10 * (page - 1)).limit(limit) - return [json.loads(json.dumps(role, default=str)) for role in all_roles] + return [json.loads(json.dumps(role, default=str)) for role in await all_roles.to_list(length=limit)] #### Security #################################################### @@ -141,7 +141,7 @@ async def get_roles(request: Request,page: int = 1, limit: int = 10): async def verify_user_permissions_on_roles(request: Request,action: str, current_user: PublicUser): users = request.app.db["users"] - user = users.find_one({"user_id": current_user.user_id}) + user = await users.find_one({"user_id": current_user.user_id}) if not user: raise HTTPException( diff --git a/src/services/security.py b/src/services/security.py index c9ae871b..57b9583b 100644 --- a/src/services/security.py +++ b/src/services/security.py @@ -42,7 +42,7 @@ async def verify_user_rights_with_roles(request: Request,action: str, user_id: s # Info: permission actions are: read, create, delete, update - for role in user_roles_cursor: + for role in await user_roles_cursor.to_list(length=100): user_roles.append(role) for role in user_roles: diff --git a/src/services/users.py b/src/services/users.py index 57f2829e..36bc8fe6 100644 --- a/src/services/users.py +++ b/src/services/users.py @@ -57,7 +57,7 @@ class Role(BaseModel): async def get_user(request: Request, username: str): users = request.app.db["users"] - user = users.find_one({"username": username}) + user = await users.find_one({"username": username}) if not user: raise HTTPException( @@ -71,7 +71,7 @@ async def get_profile_metadata(request: Request, user): users = request.app.db["users"] roles = request.app.db["roles"] - user = users.find_one({"user_id": user['user_id']}) + user = await users.find_one({"user_id": user['user_id']}) if not user: raise HTTPException( @@ -81,7 +81,7 @@ async def get_profile_metadata(request: Request, user): user_roles = roles.find({"linked_users": user['user_id']}) user_roles_list = [] - for role in user_roles: + for role in await user_roles.to_list(length=100): print(role) user_roles_list.append(Role(**role)) @@ -94,7 +94,7 @@ async def get_profile_metadata(request: Request, user): async def get_user_by_userid(request: Request, user_id: str): users = request.app.db["users"] - user = users.find_one({"user_id": user_id}) + user = await users.find_one({"user_id": user_id}) if not user: raise HTTPException( @@ -107,7 +107,7 @@ async def get_user_by_userid(request: Request, user_id: str): async def security_get_user(request: Request, email: str): users = request.app.db["users"] - user = users.find_one({"email": email}) + user = await users.find_one({"email": email}) if not user: raise HTTPException( @@ -119,7 +119,7 @@ async def security_get_user(request: Request, email: str): async def get_userid_by_username(request: Request, username: str): users = request.app.db["users"] - user = users.find_one({"username": username}) + user = await users.find_one({"username": username}) if not user: raise HTTPException( @@ -131,8 +131,8 @@ async def get_userid_by_username(request: Request, username: str): async def update_user(request: Request, user_id: str, user_object: UserWithPassword): users = request.app.db["users"] - isUserExists = users.find_one({"user_id": user_id}) - isUsernameAvailable = users.find_one({"username": user_object.username}) + isUserExists = await users.find_one({"user_id": user_id}) + isUsernameAvailable = await users.find_one({"username": user_object.username}) if not isUserExists: raise HTTPException( @@ -153,7 +153,7 @@ async def update_user(request: Request, user_id: str, user_object: UserWithPassw async def delete_user(request: Request, user_id: str): users = request.app.db["users"] - isUserAvailable = users.find_one({"user_id": user_id}) + isUserAvailable = await users.find_one({"user_id": user_id}) if not isUserAvailable: raise HTTPException( @@ -167,7 +167,7 @@ async def delete_user(request: Request, user_id: str): async def create_user(request: Request, user_object: UserWithPassword): users = request.app.db["users"] - isUserAvailable = users.find_one({"username": user_object.username}) + isUserAvailable = await users.find_one({"username": user_object.username}) if isUserAvailable: raise HTTPException( @@ -184,6 +184,6 @@ async def create_user(request: Request, user_object: UserWithPassword): user = UserInDB(user_id=user_id, creationDate=str(datetime.now()), updateDate=str(datetime.now()), **user_object.dict()) - user_in_db = users.insert_one(user.dict()) + user_in_db = await users.insert_one(user.dict()) return User(**user.dict())