feat: introduce trail api and depreciate old api

This commit is contained in:
swve 2023-03-26 18:46:03 +02:00
parent ecf4e4d6d9
commit e14ba02f97
13 changed files with 315 additions and 271 deletions

View file

@ -1,6 +1,6 @@
"use client"; "use client";
import { EyeOpenIcon, Pencil2Icon } from "@radix-ui/react-icons"; import { EyeOpenIcon, Pencil2Icon } from "@radix-ui/react-icons";
import { closeActivity, createActivity } from "@services/courses/activity"; import { removeCourse, startCourse } from "@services/courses/activity";
import Link from "next/link"; import Link from "next/link";
import React from "react"; import React from "react";
import styled from "styled-components"; import styled from "styled-components";
@ -13,21 +13,19 @@ const CourseIdPage = (params: any) => {
const orgslug = params.params.orgslug; const orgslug = params.params.orgslug;
const { data: course, error: error } = useSWR(`${getAPIUrl()}courses/meta/course_${courseid}`, swrFetcher); const { data: course, error: error } = useSWR(`${getAPIUrl()}courses/meta/course_${courseid}`, swrFetcher);
async function startActivity() { async function startCourseUI() {
// Create activity // Create activity
await createActivity("course_" + courseid); await startCourse("course_" + courseid, orgslug);
// Mutate course // Mutate course
mutate(`${getAPIUrl()}courses/meta/course_${courseid}`); mutate(`${getAPIUrl()}courses/meta/course_${courseid}`);
} }
async function quitActivity() { async function quitCourse() {
// Get activity id and org id
let activity_id = course.activity.activity_id;
let org_id = course.activity.org_id;
// Close activity // Close activity
let activity = await closeActivity(activity_id, org_id); let activity = await removeCourse("course_" + courseid, orgslug);
console.log(activity); console.log(activity);
// Mutate course // Mutate course
@ -111,12 +109,12 @@ const CourseIdPage = (params: any) => {
</BoxWrapper> </BoxWrapper>
</CourseMetaLeft> </CourseMetaLeft>
<CourseMetaRight> <CourseMetaRight>
{course.activity.status == "ongoing" ? ( {course.trail.status == "ongoing" ? (
<button style={{ backgroundColor: "red" }} onClick={quitActivity}> <button style={{ backgroundColor: "red" }} onClick={quitCourse}>
Quit Activity Quit Course
</button> </button>
) : ( ) : (
<button onClick={startActivity}>Start Activity</button> <button onClick={startCourseUI}>Start Course</button>
)} )}
</CourseMetaRight> </CourseMetaRight>
</CourseMetaWrapper> </CourseMetaWrapper>

View file

@ -7,7 +7,7 @@ export default function RootLayout({ children, params }: { children: React.React
return ( return (
<> <>
<AuthProvider> <AuthProvider>
<Menu></Menu> <Menu orgslug={params.orgslug}></Menu>
{children} {children}
</AuthProvider> </AuthProvider>
</> </>

View file

@ -5,42 +5,43 @@ import React from "react";
import { styled } from "styled-components"; import { styled } from "styled-components";
import useSWR from "swr"; import useSWR from "swr";
function Activity(params: any) { function Trail(params: any) {
let orgslug = params.params.orgslug; let orgslug = params.params.orgslug;
const { data: activities, error: error } = useSWR(`${getAPIUrl()}activity/org_slug/${orgslug}/activities`, swrFetcher); const { data: trail, error: error } = useSWR(`${getAPIUrl()}trail/org_slug/${orgslug}/trail`, swrFetcher);
return ( return (
<ActivityLayout> <TrailLayout>
<h1>Activity</h1> <h1>Trail</h1>
<br /> <br />
{error && <p>Failed to load</p>} {error && <p>Failed to load</p>}
{!activities ? ( {!trail ? (
<div>Loading...</div> <div>Loading...</div>
) : ( ) : (
<div> <div>
{activities.map((activity: any) => ( {trail.courses.map((course: any) => (
<ActivityBox key={activity.activity_id}> <TrailBox key={trail.trail_id}>
<ActivityMetadata> <TrailMetadata>
<ActivityThumbnail> <TrailThumbnail>
<img src={`${getBackendUrl()}content/uploads/img/${activity.course.thumbnail}`}></img> <img src={`${getBackendUrl()}content/uploads/img/${course.course_object.thumbnail}`}></img>
</ActivityThumbnail> </TrailThumbnail>
<ActivityInfo> <TrailInfo>
<h2>Course</h2> <h2>Course</h2>
<h3>{activity.course.name}</h3> <h3>{course.course_object.name}</h3>
</ActivityInfo> </TrailInfo>
</ActivityMetadata> </TrailMetadata>
<ActivityProgress progress={activity.progression} /> <TrailProgress progress={course.progress} />
</ActivityBox> </TrailBox>
))} ))}
</div> </div>
)} )}
</ActivityLayout> </TrailLayout>
); );
} }
export default Activity; export default Trail;
const ActivityLayout = styled.div` const TrailLayout = styled.div`
display: flex; display: flex;
margin: 0 auto; margin: 0 auto;
width: 1300px; width: 1300px;
@ -48,13 +49,13 @@ const ActivityLayout = styled.div`
flex-direction: column; flex-direction: column;
`; `;
const ActivityMetadata = styled.div` const TrailMetadata = styled.div`
display: flex; display: flex;
flex-direction: row; flex-direction: row;
width: 100%; width: 100%;
height: 100%; height: 100%;
`; `;
const ActivityBox = styled.div` const TrailBox = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
@ -67,7 +68,7 @@ const ActivityBox = styled.div`
background: #ffffff; background: #ffffff;
`; `;
const ActivityThumbnail = styled.div` const TrailThumbnail = styled.div`
padding-right: 30px; padding-right: 30px;
height: 100%; height: 100%;
border-radius: 7px 0px 0px 7px; border-radius: 7px 0px 0px 7px;
@ -78,7 +79,7 @@ const ActivityThumbnail = styled.div`
} }
`; `;
const ActivityInfo = styled.div` const TrailInfo = styled.div`
width: 100%; width: 100%;
height: 100%; height: 100%;
background: #ffffff; background: #ffffff;
@ -99,7 +100,7 @@ const ActivityInfo = styled.div`
} }
`; `;
const ActivityProgress = styled.div` const TrailProgress = styled.div`
margin-top: 10px; margin-top: 10px;
border-radius: 20px; border-radius: 20px;
height: 10px; height: 10px;

View file

@ -10,10 +10,11 @@ import { useRouter, useSearchParams, usePathname } from "next/navigation";
import { headers } from "next/headers"; import { headers } from "next/headers";
import { getOrgFromUri, getUriWithOrg } from "@services/config/config"; import { getOrgFromUri, getUriWithOrg } from "@services/config/config";
export const Menu = (params : any) => { export const Menu = (props : any ) => {
const router = useRouter(); const router = useRouter();
const pathname = usePathname(); const pathname = usePathname();
const orgslug = getOrgFromUri(); const orgslug = props.orgslug;
return ( return (
<GlobalHeader> <GlobalHeader>
@ -41,7 +42,7 @@ export const Menu = (params : any) => {
</li> </li>
<li> <li>
{" "} {" "}
<Link href={getUriWithOrg(orgslug, "/activity")}>Activity</Link> <Link href={getUriWithOrg(orgslug, "/trail")}>Trail</Link>
</li> </li>
<li>More</li> <li>More</li>
</ul> </ul>

View file

@ -21,7 +21,8 @@ export const getOrgFromUri = () => {
} else { } else {
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
const hostname = window.location.hostname; const hostname = window.location.hostname;
return hostname.replace(".localhost:3000", "");
return hostname.replace(".localhost", "");
} }
} }
}; };

View file

@ -6,18 +6,15 @@ import { getAPIUrl } from "@services/config/config";
GET requests are called from the frontend using SWR (https://swr.vercel.app/) GET requests are called from the frontend using SWR (https://swr.vercel.app/)
*/ */
export async function createActivity(course_id: string) { export async function startCourse(course_id: string, org_slug: string) {
let data = { const result: any = await fetch(`${getAPIUrl()}trail/${org_slug}/add_course/${course_id}`, RequestBody("POST", null))
course_id: course_id,
};
const result: any = await fetch(`${getAPIUrl()}activity/start`, RequestBody("POST", data))
.then((result) => result.json()) .then((result) => result.json())
.catch((error) => console.log("error", error)); .catch((error) => console.log("error", error));
return result; return result;
} }
export async function closeActivity(org_id: string, activity_id: string) { export async function removeCourse(course_id: string, org_slug: string) {
const result: any = await fetch(`${getAPIUrl()}activity/${org_id}/close_activity/${activity_id}"`, RequestBody("PATCH", null)) const result: any = await fetch(`${getAPIUrl()}trail/${org_slug}/remove_course/${course_id}`, RequestBody("POST", null))
.then((result) => result.json()) .then((result) => result.json())
.catch((error) => console.log("error", error)); .catch((error) => console.log("error", error));
return result; return result;
@ -29,4 +26,3 @@ export async function maskActivityAsComplete(org_id: string, course_id: string,
.catch((error) => console.log("error", error)); .catch((error) => console.log("error", error));
return result; return result;
} }

View file

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

View file

@ -1,47 +0,0 @@
from fastapi import APIRouter, Depends, Request
from src.security.auth import get_current_user
from src.services.activity import Activity, add_activity_to_activity, close_activity, create_activity, get_user_activities, get_user_activities_orgslug
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/{org_id}/activities")
async def api_get_activity_by_orgid(request: Request, org_id: str, user=Depends(get_current_user)):
"""
Get a user activities
"""
return await get_user_activities(request, user, org_id=org_id)
@router.get("/org_slug/{org_slug}/activities")
async def api_get_activity_by_orgslug(request: Request, org_slug: str, user=Depends(get_current_user)):
"""
Get a user activities using org slug
"""
return await get_user_activities_orgslug(request, user, org_slug=org_slug)
@router.post("/{org_id}/add_activity/{course_id}/{activity_id}")
async def api_add_activity_to_activity(request: Request, org_id: str, course_id: str, activity_id: str, user=Depends(get_current_user)):
"""
Add activity to activity
"""
return await add_activity_to_activity(request, user, org_id, course_id, activity_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)

47
src/routers/trail.py Normal file
View file

@ -0,0 +1,47 @@
from typing import Optional
from fastapi import APIRouter, Depends, Request
from src.security.auth import get_current_user
from src.services.trail import Trail, add_activity_to_trail, add_course_to_trail, create_trail, get_user_trail_with_orgslug, get_user_trail, remove_course_from_trail
router = APIRouter()
@router.post("/start")
async def api_start_trail(request: Request, trail_object: Trail, org_id: str, user=Depends(get_current_user)) -> Trail:
"""
Start trail
"""
return await create_trail(request, user, org_id, trail_object)
@router.get("/org_id/{org_id}/trail")
async def api_get_trail_by_orgid(request: Request, org_slug: str, user=Depends(get_current_user)):
"""
Get a user trails
"""
return await get_user_trail(request, user=user, org_slug=org_slug)
@router.get("/org_slug/{org_slug}/trail")
async def api_get_trail_by_orgslug(request: Request, org_slug: str, user=Depends(get_current_user)):
"""
Get a user trails using org slug
"""
return await get_user_trail_with_orgslug(request, user, org_slug=org_slug)
@router.post("/{org_slug}/add_course/{course_id}")
async def api_add_course_to_trail(request: Request, course_id: str, org_slug: str, user=Depends(get_current_user)):
"""
Add Course to trail
"""
return await add_course_to_trail(request, user, org_slug, course_id)
@router.post("/{org_slug}/remove_course/{course_id}")
async def api_remove_course_to_trail(request: Request, course_id: str, org_slug: str, user=Depends(get_current_user)):
"""
Remove Course from trail
"""
return await remove_course_from_trail(request, user, org_slug, course_id)

View file

@ -1,164 +0,0 @@
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.users import PublicUser
#### Classes ####################################################
class Activity(BaseModel):
course_id: str
status: Optional[Literal['ongoing', 'done', 'closed']] = 'ongoing'
masked: Optional[bool] = False
activities_marked_complete: Optional[List[str]] = []
activities_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 = 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'
await 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)
await 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 await user_activities.to_list(length=100):
# get number of activities in the course
coursechapters = await get_coursechapters_meta(request, activity['course_id'], user)
# calculate progression using the number of activities marked complete and the total number of activities
progression = round(
len(activity['activities_marked_complete']) / len(coursechapters['activities']) * 100, 2)
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}
activities_metadata.append(one_activity)
return activities_metadata
async def get_user_activities_orgslug(request: Request, user: PublicUser, org_slug: str):
activities = request.app.db["activities"]
courses = request.app.db["courses"]
coursechapters = request.app.db["coursechapters"]
activities_metadata = []
user_activities = activities.find(
{"user_id": user.user_id}, {'_id': 0})
if not user_activities:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="No activities found")
for activity in await user_activities.to_list(length=100):
# get number of activities in the course
coursechapters = await get_coursechapters_meta(request, activity['course_id'], user)
# calculate progression using the number of activities marked complete and the total number of activities
progression = round(
len(activity['activities_marked_complete']) / len(coursechapters['activities']) * 100, 2)
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}
activities_metadata.append(one_activity)
return activities_metadata
async def add_activity_to_activity(request: Request, user: PublicUser, org_id: str, course_id: str, activity_id: str):
activities = request.app.db["activities"]
course_id = f"course_{course_id}"
activity_id = f"activity_{activity_id}"
activity = await activities.find_one(
{"course_id": course_id,
"user_id": user.user_id
}, {'_id': 0})
if activity_id not in activity['activities_marked_complete']:
activity['activities_marked_complete'].append(str(activity_id))
await 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="Activity already marked complete")
async def close_activity(request: Request, user: PublicUser, activity_id: str, org_id: str,):
activities = request.app.db["activities"]
activity = await 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'
await activities.update_one(
{"activity_id": activity['activity_id']}, {"$set": activity})
activity = ActivityInDB(**activity)
return activity

View file

@ -72,7 +72,7 @@ async def get_course(request: Request, course_id: str, current_user: PublicUser)
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"] courses = request.app.db["courses"]
coursechapters = request.app.db["coursechapters"] coursechapters = request.app.db["coursechapters"]
activities = request.app.db["activities"] trails = request.app.db["trails"]
course = await courses.find_one({"course_id": course_id}) course = await courses.find_one({"course_id": course_id})
activities = request.app.db["activities"] activities = request.app.db["activities"]
@ -119,17 +119,20 @@ async def get_course_meta(request: Request, course_id: str, current_user: Public
course = Course(**course) course = Course(**course)
# Get activity by user # Get activity by user
activity = await activities.find_one( trail = await trails.find_one(
{"course_id": course_id, "user_id": current_user.user_id}) {"courses.course_id": course_id, "user_id": current_user.user_id})
if activity: print(trail)
activity = json.loads(json.dumps(activity, default=str)) if trail:
# get only the course where course_id == course_id
trail_course = next(
(course for course in trail["courses"] if course["course_id"] == course_id), None)
else: else:
activity = "" trail_course = ""
return { return {
"course": course, "course": course,
"chapters": chapters_list_with_activities, "chapters": chapters_list_with_activities,
"activity": activity "trail": trail_course
} }

View file

@ -30,6 +30,9 @@ class PublicOrganization(Organization):
slug: str slug: str
org_id: str org_id: str
def __getitem__(self, item):
return getattr(self, item)
#### Classes #################################################### #### Classes ####################################################

205
src/services/trail.py Normal file
View file

@ -0,0 +1,205 @@
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.orgs import PublicOrganization
from src.services.users.users import PublicUser
#### Classes ####################################################
class ActivityData(BaseModel):
activity_id: str
activity_type: str
data: Optional[dict]
class TrailCourse(BaseModel):
course_id: str
elements_type: Optional[Literal['course']] = 'course'
status: Optional[Literal['ongoing', 'done', 'closed']] = 'ongoing'
course_object: dict
masked: Optional[bool] = False
activities_marked_complete: Optional[List[str]]
activities_data: Optional[List[ActivityData]]
progress: Optional[int]
class Trail(BaseModel):
status: Optional[Literal['ongoing', 'done', 'closed']] = 'ongoing'
masked: Optional[bool] = False
courses: Optional[List[TrailCourse]]
class TrailInDB(Trail):
trail_id: str
org_id: str
user_id: str
creationDate: str = datetime.now().isoformat()
updateDate: str = datetime.now().isoformat()
#### Classes ####################################################
async def create_trail(request: Request, user: PublicUser, org_id: str, trail_object: Trail) -> Trail:
trails = request.app.db["trails"]
# get list of courses
if trail_object.courses:
courses = trail_object.courses
# get course ids
course_ids = [course.course_id for course in courses]
# find if the user has already started the course
existing_trail = await trails.find_one({'user_id': user.user_id, 'courses.course_id': {'$in': course_ids}})
if existing_trail:
# update the status of the element with the matching course_id to "ongoing"
for element in existing_trail['courses']:
if element['course_id'] in course_ids:
element['status'] = 'ongoing'
# update the existing trail in the database
await trails.replace_one({'trail_id': existing_trail['trail_id']}, existing_trail)
# create trail id
trail_id = f"trail_{uuid4()}"
# create trail
trail = TrailInDB(**trail_object.dict(), trail_id=trail_id,
user_id=user.user_id, org_id=org_id)
await trails.insert_one(trail.dict())
return trail
async def get_user_trail(request: Request, org_slug: str, user: PublicUser) -> Trail:
trails = request.app.db["trails"]
trail = await trails.find_one({"user_id": user.user_id})
if not trail:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Trail not found")
for element in trail["courses"]:
course_id = element["course_id"]
chapters_meta = await get_coursechapters_meta(request, course_id, user)
activities = chapters_meta["activities"]
num_activities = len(activities)
num_completed_activities = len(
element.get("activities_marked_complete", []))
element["progress"] = round(
(num_completed_activities / num_activities) * 100, 2) if num_activities > 0 else 0
return Trail(**trail)
async def get_user_trail_with_orgslug(request: Request, user: PublicUser, org_slug: str) -> Trail:
trails = request.app.db["trails"]
orgs = request.app.db["organizations"]
courses_mongo = request.app.db["courses"]
# get org_id from orgslug
org = await orgs.find_one({"slug": org_slug})
trail = await trails.find_one({"user_id": user.user_id, "org_id": org["org_id"]})
if not trail:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Trail not found")
for courses in trail["courses"]:
course_id = courses["course_id"]
chapters_meta = await get_coursechapters_meta(request, course_id, user)
activities = chapters_meta["activities"]
# get course object without _id
course_object = await courses_mongo.find_one({"course_id": course_id}, {"_id": 0})
courses["course_object"] = course_object
num_activities = len(activities)
num_completed_activities = len(
courses.get("activities_marked_complete", []))
courses["progress"] = round(
(num_completed_activities / num_activities) * 100, 2) if num_activities > 0 else 0
return Trail(**trail)
async def add_activity_to_trail(request: Request, user: PublicUser, trail_id: str, course_id: str, activity_id: str) -> Trail:
trails = request.app.db["trails"]
trail = await trails.find_one({"trail_id": trail_id, "user_id": user.user_id})
if not trail:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Trail not found")
for element in trail["courses"]:
if element["course_id"] == course_id:
if activity_id not in element["activities_marked_complete"]:
element["activities_marked_complete"].append(activity_id)
break
await trails.replace_one({"trail_id": trail_id}, trail)
return Trail(**trail)
async def add_course_to_trail(request: Request, user: PublicUser, orgslug: str, course_id: str) -> Trail:
trails = request.app.db["trails"]
orgs = request.app.db["organizations"]
org = await orgs.find_one({"slug": orgslug})
org = PublicOrganization(**org)
trail = await trails.find_one(
{"user_id": user.user_id, "org_id": org["org_id"]})
if not trail:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Trail not found")
# check if course is already present in the trail
for element in trail["courses"]:
if element["course_id"] == course_id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail="Course already present in the trail")
updated_trail = TrailCourse(course_id=course_id, activities_data=[
], activities_marked_complete=[], progress=0, course_object={}, status="ongoing", masked=False)
trail["courses"].append(updated_trail.dict())
await trails.replace_one({"trail_id": trail['trail_id']}, trail)
return Trail(**trail)
async def remove_course_from_trail(request: Request, user: PublicUser, orgslug: str, course_id: str) -> Trail:
trails = request.app.db["trails"]
orgs = request.app.db["organizations"]
org = await orgs.find_one({"slug": orgslug})
org = PublicOrganization(**org)
print(org)
trail = await trails.find_one(
{"user_id": user.user_id, "org_id": org["org_id"]})
if not trail:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Trail not found")
# check if course is already present in the trail
for element in trail["courses"]:
if element["course_id"] == course_id:
trail["courses"].remove(element)
break
await trails.replace_one({"trail_id": trail['trail_id']}, trail)
return Trail(**trail)