Merge pull request #38 from learnhouse/feat/init-activity

feat: init activity
This commit is contained in:
Badr B 2023-01-22 20:52:49 +01:00 committed by GitHub
commit 504128d4c9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 598 additions and 58 deletions

View file

@ -0,0 +1,124 @@
"use client";
import { getBackendUrl } from "@services/config";
import { getActivities } from "@services/courses/activity";
import { getOrganizationContextInfo } from "@services/orgs";
import { RequestBody } from "@services/utils/requests";
import React from "react";
import { styled } from "styled-components";
import useSWR from "swr";
function Activity(params: any) {
let orgslug = params.params.orgslug;
const [isLoading, setIsLoading] = React.useState(true);
const [activities, setActivities] = React.useState([]);
async function fetchActivities() {
setIsLoading(true);
const org = await getOrganizationContextInfo(orgslug);
const activities = await getActivities(org.org_id);
console.log(activities);
setActivities(activities);
setIsLoading(false);
}
React.useEffect(() => {
fetchActivities();
}, []);
return (
<ActivityLayout>
<h1>Activity</h1>
<br />
{isLoading ? (
<div>Loading...</div>
) : (
<div>
{activities.map((activity: any) => (
<ActivityBox key={activity.activity_id}>
<ActivityMetadata>
<ActivityThumbnail>
<img src={`${getBackendUrl()}content/uploads/img/${activity.course.thumbnail}`}></img>
</ActivityThumbnail>
<ActivityInfo>
<h2>Course</h2>
<h3>{activity.course.name}</h3>
</ActivityInfo>
</ActivityMetadata>
<ActivityProgress progress={activity.progression} />
</ActivityBox>
))}
</div>
)}
</ActivityLayout>
);
}
export default Activity;
const ActivityLayout = styled.div`
display: flex;
margin: 0 auto;
width: 1300px;
height: 100%;
flex-direction: column;
`;
const ActivityMetadata = styled.div`
display: flex;
flex-direction: row;
width: 100%;
height: 100%;
`;
const ActivityBox = styled.div`
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
margin-top: 20px;
margin-bottom: 20px;
padding: 15px;
border-radius: 7px;
box-shadow: 0px 13px 33px -13px rgba(0, 0, 0, 0.206);
background: #ffffff;
`;
const ActivityThumbnail = styled.div`
padding-right: 30px;
height: 100%;
border-radius: 7px 0px 0px 7px;
img {
width: 60px;
border-radius: 7px;
}
`;
const ActivityInfo = styled.div`
width: 100%;
height: 100%;
background: #ffffff;
border-radius: 0px 7px 7px 0px;
h2 {
font-size: 12px;
color: #2b2b2b;
padding: 0;
margin: 0;
}
h3 {
font-size: 23px;
color: #2b2b2b;
padding: 0;
margin: 0;
}
`;
const ActivityProgress = styled.div`
margin-top: 10px;
border-radius: 20px;
height: 10px;
width: ${(props: any) => props.progress + "%"};
background: #06a487;
`;

View file

@ -9,6 +9,8 @@ import Canva from "../../../../../../../components/LectureViews/DynamicCanva/Dyn
import styled from "styled-components";
import { getCourse, getCourseMetadata } from "../../../../../../../services/courses/courses";
import VideoLecture from "@components/LectureViews/Video/Video";
import { Check } from "lucide-react";
import { maskLectureAsComplete } from "@services/courses/activity";
function LecturePage(params: any) {
const router = useRouter();
@ -31,6 +33,11 @@ function LecturePage(params: any) {
setIsLoading(false);
}
async function markLectureAsCompleteFront() {
const activity = await maskLectureAsComplete("" + lectureid, courseid, lecture.lecture_id.replace("lecture_", ""));
fetchCourseData();
}
React.useEffect(() => {
if (lectureid) {
fetchLectureData();
@ -82,6 +89,25 @@ function LecturePage(params: any) {
{lecture.type == "dynamic" && <Canva content={lecture.content} lecture={lecture} />}
{/* todo : use apis & streams instead of this */}
{lecture.type == "video" && <VideoLecture course={course} lecture={lecture} />}
<ActivityMarkerWrapper>
{course.activity.lectures_marked_complete.includes("lecture_"+lectureid) && course.activity.status == "ongoing" ? (
<button style={{ backgroundColor: "green" }}>
<i>
<Check size={20}></Check>
</i>{" "}
Already completed
</button>
) : (
<button onClick={markLectureAsCompleteFront}>
{" "}
<i>
<Check size={20}></Check>
</i>{" "}
Mark as complete
</button>
)}
</ActivityMarkerWrapper>
</CourseContent>
</LectureLayout>
)}
@ -151,7 +177,52 @@ const LectureTopWrapper = styled.div`
const CourseContent = styled.div`
display: flex;
flex-direction: column;
background-color: white;
min-height: 600px;
`;
const ActivityMarkerWrapper = styled.div`
display: block;
width: 1300px;
justify-content: flex-end;
margin: 0 auto;
align-items: center;
button {
background-color: #151515;
border: none;
padding: 18px;
border-radius: 15px;
margin: 15px;
margin-left: 20px;
margin-top: 20px;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
margin: auto;
color: white;
font-weight: 700;
font-family: "DM Sans";
font-size: 16px;
letter-spacing: -0.05em;
box-shadow: 0px 13px 33px -13px rgba(0, 0, 0, 0.42);
i {
margin-right: 5px;
// center the icon
display: flex;
align-items: center;
justify-content: center;
}
&:hover {
background-color: #000000;
}
}
`;
export default LecturePage;

View file

@ -1,5 +1,6 @@
"use client";
import { EyeOpenIcon, Pencil2Icon } from "@radix-ui/react-icons";
import { closeActivity, createActivity } from "@services/courses/activity";
import Link from "next/link";
import { useRouter } from "next/navigation";
import React from "react";
@ -18,12 +19,26 @@ const CourseIdPage = (params: any) => {
async function fetchCourseInfo() {
const course = await getCourseMetadata("course_" + courseid);
setCourseInfo(course);
setIsLoading(false);
}
async function startActivity() {
const activity = await createActivity("course_" + courseid);
fetchCourseInfo();
}
async function quitActivity() {
let activity_id = courseInfo.activity.activity_id;
let org_id = courseInfo.activity.org_id;
console.log("activity", activity_id);
let activity = await closeActivity(activity_id, org_id);
console.log(activity);
fetchCourseInfo();
}
React.useEffect(() => {
if (courseid && orgslug) {
fetchCourseInfo();
@ -69,6 +84,8 @@ const CourseIdPage = (params: any) => {
<img src={`${getBackendUrl()}content/uploads/img/${courseInfo.course.thumbnail}`} alt="" />
</CourseThumbnailWrapper>
<CourseMetaWrapper>
<CourseMetaLeft>
<h2>Description</h2>
<BoxWrapper>
@ -104,6 +121,15 @@ const CourseIdPage = (params: any) => {
);
})}
</BoxWrapper>
</CourseMetaLeft>
<CourseMetaRight>
{courseInfo.activity.status == "ongoing" ? (
<button style={{backgroundColor:"red"}} onClick={quitActivity}>Quit Activity</button>
) : (
<button onClick={startActivity}>Start Activity</button>
)}
</CourseMetaRight>
</CourseMetaWrapper>
</CoursePageLayout>
)}
</>
@ -139,8 +165,6 @@ const CoursePageLayout = styled.div`
letter-spacing: -0.05em;
margin-bottom: 10px;
}
`;
const ChaptersWrapper = styled.div`
@ -175,7 +199,6 @@ const BoxWrapper = styled.div`
padding-top: 7px;
padding-left: 30px;
p {
font-family: "DM Sans";
font-style: normal;
@ -187,4 +210,45 @@ const BoxWrapper = styled.div`
}
`;
const CourseMetaWrapper = styled.div`
display: flex;
justify-content: space-between;
`;
const CourseMetaLeft = styled.div`
width: 80%;
`;
const CourseMetaRight = styled.div`
background: #ffffff;
box-shadow: 0px 4px 16px rgba(0, 0, 0, 0.03);
border-radius: 7px;
padding: 20px;
width: 30%;
display: flex;
height: 100%;
justify-content: center;
margin-left: 50px;
margin-top: 20px;
button {
width: 100%;
height: 50px;
background: #151515;
border-radius: 15px;
border: none;
color: white;
font-weight: 700;
font-family: "DM Sans";
font-size: 16px;
letter-spacing: -0.05em;
transition: all 0.2s ease;
box-shadow: 0px 13px 33px -13px rgba(0, 0, 0, 0.42);
&:hover {
cursor: pointer;
background: #000000;
}
}
`;
export default CourseIdPage;

View file

@ -7,10 +7,9 @@ import learnhouseLogo from "public/learnhouse_logo.png";
import Link from "next/link";
import Image from "next/image";
import { useRouter, useSearchParams } from "next/navigation";
import { headers } from 'next/headers';
export const Menu = ({orgslug } : any) => {
import { headers } from "next/headers";
export const Menu = ({ orgslug }: any) => {
return (
<GlobalHeader>
<LogoArea>
@ -30,12 +29,15 @@ export const Menu = ({orgslug } : any) => {
<MenuArea>
<ul>
<li>
<Link href={ "/courses"}>Courses</Link>
<Link href={"/courses"}>Courses</Link>
</li>
<li>
<Link href={ "/collections"}>Collections</Link>
<Link href={"/collections"}>Collections</Link>
</li>
<li>
{" "}
<Link href={"/activity"}>Activity</Link>
</li>
<li>Activity</li>
<li>More</li>
</ul>
</MenuArea>

View file

@ -24,6 +24,7 @@
"react-beautiful-dnd": "^13.1.1",
"react-dom": "^18.2.0",
"styled-components": "^6.0.0-beta.9",
"swr": "^2.0.1",
"y-indexeddb": "^9.0.9",
"y-webrtc": "^10.2.3",
"yjs": "^13.5.42"
@ -6731,6 +6732,20 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/swr": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/swr/-/swr-2.0.1.tgz",
"integrity": "sha512-6z4FpS9dKAay7axedlStsPahEw25nuMlVh4GHkuPpGptbmEEP8v/+kr0GkAE/7ErUs25U2VFOnZQz3AWfkmXdw==",
"dependencies": {
"use-sync-external-store": "^1.2.0"
},
"engines": {
"pnpm": "7"
},
"peerDependencies": {
"react": "^16.11.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/synckit": {
"version": "0.8.4",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.4.tgz",
@ -7014,6 +7029,14 @@
}
}
},
"node_modules/use-sync-external-store": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
"integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@ -11817,6 +11840,14 @@
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="
},
"swr": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/swr/-/swr-2.0.1.tgz",
"integrity": "sha512-6z4FpS9dKAay7axedlStsPahEw25nuMlVh4GHkuPpGptbmEEP8v/+kr0GkAE/7ErUs25U2VFOnZQz3AWfkmXdw==",
"requires": {
"use-sync-external-store": "^1.2.0"
}
},
"synckit": {
"version": "0.8.4",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.4.tgz",
@ -12008,6 +12039,12 @@
"tslib": "^2.0.0"
}
},
"use-sync-external-store": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
"integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
"requires": {}
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",

View file

@ -25,6 +25,7 @@
"react-beautiful-dnd": "^13.1.1",
"react-dom": "^18.2.0",
"styled-components": "^6.0.0-beta.9",
"swr": "^2.0.1",
"y-indexeddb": "^9.0.9",
"y-webrtc": "^10.2.3",
"yjs": "^13.5.42"

View file

@ -0,0 +1,40 @@
import { RequestBody } from "@services/utils/requests";
import { getAPIUrl } from "../config";
/*
This file includes only POST, PUT, DELETE requests
GET requests are called from the frontend using SWR (https://swr.vercel.app/)
*/
export async function createActivity(course_id: string) {
let data = {
course_id: course_id,
};
const result: any = await fetch(`${getAPIUrl()}activity/start`, RequestBody("POST", data))
.then((result) => result.json())
.catch((error) => console.log("error", error));
return result;
}
export async function closeActivity(org_id: string, activity_id: string) {
const result: any = await fetch(`${getAPIUrl()}activity/${org_id}/close_activity/${activity_id}"`, RequestBody("PATCH", null))
.then((result) => result.json())
.catch((error) => console.log("error", error));
return result;
}
export async function maskLectureAsComplete(org_id: string, course_id: string, lecture_id: string) {
const result: any = await fetch(`${getAPIUrl()}activity/${org_id}/add_lecture/${course_id}/${lecture_id}`, RequestBody("POST", null))
.then((result) => result.json())
.catch((error) => console.log("error", error));
return result;
}
// get all activities
export async function getActivities(org_id: string) {
const result: any = await fetch(`${getAPIUrl()}activity/${org_id}/activities`, RequestBody("GET", null))
.then((result) => result.json())
.catch((error) => console.log("error", error));
return result;
}

View file

@ -0,0 +1,14 @@
export const RequestBody = (method: string, data: any) => {
let HeadersConfig = new Headers({ "Content-Type": "application/json" });
let options: any = {
method: method,
headers: HeadersConfig,
redirect: "follow",
credentials: "include",
};
if (data) {
options.body = JSON.stringify(data);
}
return options;
};

View file

@ -1,5 +1,5 @@
from fastapi import APIRouter
from src.routers import users, auth, houses, orgs, roles, files
from src.routers import activity, users, auth, houses, orgs, roles, files
from src.routers.courses import chapters, collections, courses,lectures
@ -17,4 +17,6 @@ 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"])
global_router.include_router(collections.router, prefix="/collections", tags=["collections"])
global_router.include_router(activity.router, prefix="/activity", tags=["activity"])

40
src/routers/activity.py Normal file
View file

@ -0,0 +1,40 @@
from fastapi import APIRouter, Depends, Request
from src.dependencies.auth import get_current_user
from src.services.activity import Activity, add_lecture_to_activity, close_activity, create_activity, get_user_activities
router = APIRouter()
@router.post("/start")
async def api_start_activity(request: Request, activity_object: Activity, user=Depends(get_current_user)):
"""
Start activity
"""
return await create_activity(request, user, activity_object)
# TODO : get activity by user_is and org_id and course_id
@router.get("/{org_id}/activities")
async def api_get_activity_by_userid(request: Request, org_id: str, user=Depends(get_current_user)):
"""
Get a user activities
"""
return await get_user_activities(request, user, org_id=org_id)
@router.post("/{org_id}/add_lecture/{course_id}/{lecture_id}")
async def api_add_lecture_to_activity(request: Request, org_id: str, course_id: str, lecture_id: str, user=Depends(get_current_user)):
"""
Add lecture to activity
"""
return await add_lecture_to_activity(request, user, org_id, course_id, lecture_id)
@router.patch("/{org_id}/close_activity/{activity_id}")
async def api_close_activity(request: Request, org_id: str, activity_id: str, user=Depends(get_current_user)):
"""
Close activity
"""
return await close_activity(request, user, org_id, activity_id)

134
src/services/activity.py Normal file
View file

@ -0,0 +1,134 @@
from cmath import log
from datetime import datetime
import json
from typing import List, Literal, Optional
from uuid import uuid4
from fastapi import HTTPException, Request, status
from pydantic import BaseModel
from src.services.courses.chapters import get_coursechapters_meta
from src.services.users import PublicUser
#### Classes ####################################################
class Activity(BaseModel):
course_id: str
status: Optional[Literal['ongoing', 'done', 'closed']] = 'ongoing'
masked: Optional[bool] = False
lectures_marked_complete: Optional[List[str]] = []
lectures_data: Optional[List[dict]] = []
class ActivityInDB(Activity):
activity_id: str
user_id: str
org_id: str
creationDate: str = datetime.now().isoformat()
updateDate: str = datetime.now().isoformat()
#### Classes ####################################################
async def create_activity(request: Request, user: PublicUser, activity_object: Activity):
activities = request.app.db["activities"]
# find if the user has already started the course
isActivityAlreadCreated = 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(
{"activity_id": isActivityAlreadCreated['activity_id']}, {"$set": activity_object.dict()})
return activity_object
else:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Activity already created")
# create activity id
activity_id = f"activity_{uuid4()}"
# create activity
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())
return activity
async def get_user_activities(request: Request, user: PublicUser, org_id: str):
activities = request.app.db["activities"]
courses = request.app.db["courses"]
coursechapters = request.app.db["coursechapters"]
activities_metadata = []
user_activities = activities.find(
{"user_id": user.user_id}, {'_id': 0})
if not user_activities:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="No activities found")
for activity in user_activities:
# get number of lectures in the course
coursechapters = await get_coursechapters_meta(request, activity['course_id'], user)
# calculate progression using the number of lectures marked complete and the total number of lectures
progression = round(
len(activity['lectures_marked_complete']) / len(coursechapters['lectures']) * 100, 2)
course = courses.find_one({"course_id": activity['course_id']}, {'_id': 0})
# add progression to the activity
one_activity = {"course": course, "activitydata": activity, "progression": progression}
activities_metadata.append(one_activity)
return activities_metadata
async def add_lecture_to_activity(request: Request, user: PublicUser, org_id: str, course_id: str, lecture_id: str):
activities = request.app.db["activities"]
course_id = f"course_{course_id}"
lecture_id = f"lecture_{lecture_id}"
activity = 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(
{"activity_id": activity['activity_id']}, {"$set": activity})
return activity
if not activity:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Activity not found")
else:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Lecture already marked complete")
async def close_activity(request: Request, user: PublicUser, activity_id: str, org_id: str,):
activities = request.app.db["activities"]
activity = activities.find_one(
{"activity_id": activity_id, "user_id": user.user_id})
if not activity:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Activity not found")
activity['status'] = 'closed'
activities.update_one(
{"activity_id": activity['activity_id']}, {"$set": activity})
activity = ActivityInDB(**activity)
return activity

View file

@ -152,7 +152,7 @@ async def get_coursechapters(request: Request,course_id: str, page: int = 1, lim
return [json.loads(json.dumps(coursechapter, default=str)) for coursechapter in all_coursechapters]
async def get_coursechapters_meta(request: Request,course_id: str, current_user: PublicUser):
async def get_coursechapters_meta(request: Request, course_id: str, current_user: PublicUser):
coursechapters = request.app.db["coursechapters"]
courses = request.app.db["courses"]
lectures = request.app.db["lectures"]
@ -206,7 +206,7 @@ async def update_coursechapters_meta(request: Request,course_id: str, coursechap
"$set": {"chapters": coursechapters_metadata.chapterOrder}})
# update lectures in coursechapters
# TODO : performance/optimization improvement
# 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

View file

@ -53,13 +53,13 @@ class CourseChapterInDB(CourseChapter):
# CRUD
####################################################
async def get_course(request: Request,course_id: str, current_user: PublicUser):
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})
# verify course rights
await verify_rights(request,course_id, current_user, "read")
await verify_rights(request, course_id, current_user, "read")
if not course:
raise HTTPException(
@ -69,14 +69,16 @@ async def get_course(request: Request,course_id: str, current_user: PublicUser):
return course
async def get_course_meta(request: Request,course_id: str, current_user: PublicUser):
async def get_course_meta(request: Request, course_id: str, current_user: PublicUser):
courses = request.app.db["courses"]
coursechapters = request.app.db["coursechapters"]
activities = request.app.db["activities"]
course = courses.find_one({"course_id": course_id})
lectures = request.app.db["lectures"]
# verify course rights
await verify_rights(request,course_id, current_user, "read")
await verify_rights(request, course_id, current_user, "read")
if not course:
raise HTTPException(
@ -115,13 +117,23 @@ async def get_course_meta(request: Request,course_id: str, current_user: PublicU
chapters_list_with_lectures.append(
{"id": chapters[chapter]["id"], "name": chapters[chapter]["name"], "lectures": [lectures_list[lecture] for lecture in chapters[chapter]["lectureIds"]]})
course = Course(**course)
# Get activity by user
activity = activities.find_one(
{"course_id": course_id, "user_id": current_user.user_id})
if activity:
activity = json.loads(json.dumps(activity, default=str))
else:
activity = ""
return {
"course": course,
"chapters": chapters_list_with_lectures,
"activity": activity
}
async def create_course(request: Request,course_object: Course, org_id: str, current_user: PublicUser, thumbnail_file: UploadFile | None = None):
async def create_course(request: Request, course_object: Course, org_id: str, current_user: PublicUser, thumbnail_file: UploadFile | None = None):
courses = request.app.db["courses"]
# generate course_id with uuid4
@ -152,7 +164,7 @@ async def create_course(request: Request,course_object: Course, org_id: str, cur
return course.dict()
async def update_course_thumbnail(request: Request,course_id: str, current_user: PublicUser, thumbnail_file: UploadFile | None = None):
async def update_course_thumbnail(request: Request, course_id: str, current_user: PublicUser, thumbnail_file: UploadFile | None = None):
# verify course rights
await verify_rights(request, course_id, current_user, "update")
@ -182,7 +194,7 @@ async def update_course_thumbnail(request: Request,course_id: str, current_user:
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist")
async def update_course(request: Request,course_object: Course, course_id: str, current_user: PublicUser):
async def update_course(request: Request, course_object: Course, course_id: str, current_user: PublicUser):
# verify course rights
await verify_rights(request, course_id, current_user, "update")
@ -211,7 +223,7 @@ async def update_course(request: Request,course_object: Course, course_id: str,
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist")
async def delete_course(request: Request,course_id: str, current_user: PublicUser):
async def delete_course(request: Request, course_id: str, current_user: PublicUser):
# verify course rights
await verify_rights(request, course_id, current_user, "delete")
@ -237,7 +249,7 @@ async def delete_course(request: Request,course_id: str, current_user: PublicUse
####################################################
async def get_courses(request: Request,page: int = 1, limit: int = 10, org_id: str | None = None):
async def get_courses(request: Request, page: int = 1, limit: int = 10, org_id: str | None = None):
courses = request.app.db["courses"]
# TODO : Get only courses that user is admin/has roles of
# get all courses from database
@ -250,7 +262,7 @@ async def get_courses(request: Request,page: int = 1, limit: int = 10, org_id: s
#### Security ####################################################
async def verify_rights(request: Request,course_id: str, current_user: PublicUser, action: str):
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})

View file

@ -135,7 +135,6 @@ async def delete_org(request: Request, org_id: str, current_user: PublicUser):
async def get_orgs_by_user(request: Request, user_id: str, page: int = 1, limit: int = 10):
orgs = request.app.db["organizations"]
print(user_id)
# 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)
@ -155,7 +154,7 @@ async def verify_org_rights(request: Request, org_id: str, current_user: Public
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist")
isOwner = current_user.user_id in org["owners"]
hasRoleRights = await verify_user_rights_with_roles(action, current_user.user_id, org_id)
hasRoleRights = await verify_user_rights_with_roles(request, action, current_user.user_id, org_id)
if not hasRoleRights and not isOwner:
raise HTTPException(