mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: init trails
This commit is contained in:
parent
b04fd64c92
commit
eca819b896
5 changed files with 495 additions and 253 deletions
|
|
@ -1,286 +1,362 @@
|
|||
from datetime import datetime
|
||||
import stat
|
||||
from typing import List, Literal, Optional
|
||||
from uuid import uuid4
|
||||
from fastapi import HTTPException, Request, status
|
||||
from pydantic import BaseModel
|
||||
from sqlmodel import Session, select
|
||||
from src.db.courses import Course
|
||||
from src.db.trail_runs import TrailRun, TrailRunCreate, TrailRunRead
|
||||
from src.db.trail_steps import TrailStep
|
||||
from src.db.trails import Trail, TrailCreate, TrailRead
|
||||
from src.db.users import PublicUser
|
||||
from src.services.orgs.schemas.orgs import PublicOrganization
|
||||
from src.services.courses.chapters import get_coursechapters_meta
|
||||
|
||||
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
|
||||
async def create_user_trail(
|
||||
request: Request,
|
||||
user: PublicUser,
|
||||
trail_object: TrailCreate,
|
||||
db_session: Session,
|
||||
) -> Trail:
|
||||
trails = request.app.db["trails"]
|
||||
statement = select(Trail).where(Trail.org_id == trail_object.org_id)
|
||||
trail = db_session.exec(statement).first()
|
||||
|
||||
# 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 trail:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Trail already exists",
|
||||
)
|
||||
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()}"
|
||||
trail = Trail.from_orm(trail_object)
|
||||
|
||||
trail.creation_date = str(datetime.now())
|
||||
trail.update_date = str(datetime.now())
|
||||
trail.org_id = trail_object.org_id
|
||||
trail.trail_uuid = str(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())
|
||||
db_session.add(trail)
|
||||
db_session.commit()
|
||||
db_session.refresh(trail)
|
||||
|
||||
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})
|
||||
async def get_user_trails(
|
||||
request: Request,
|
||||
user: PublicUser,
|
||||
db_session: Session,
|
||||
) -> TrailRead:
|
||||
statement = select(Trail).where(Trail.user_id == user.id)
|
||||
trail = db_session.exec(statement).first()
|
||||
|
||||
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
|
||||
)
|
||||
statement = select(TrailRun).where(TrailRun.trail_id == trail.id)
|
||||
trail_runs = db_session.exec(statement).all()
|
||||
|
||||
return Trail(**trail)
|
||||
trail_runs = [
|
||||
TrailRunRead(**trail_run.__dict__, steps=[]) for trail_run in trail_runs
|
||||
]
|
||||
|
||||
for trail_run in trail_runs:
|
||||
statement = select(TrailStep).where(TrailStep.trailrun_id == trail_run.id)
|
||||
trail_steps = db_session.exec(statement).all()
|
||||
|
||||
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"]
|
||||
trail_steps = [TrailStep(**trail_step.__dict__) for trail_step in trail_steps]
|
||||
trail_run.steps = trail_steps
|
||||
|
||||
# get org_id from orgslug
|
||||
org = await orgs.find_one({"slug": org_slug})
|
||||
for trail_step in trail_steps:
|
||||
statement = select(Course).where(Course.id == trail_step.course_id)
|
||||
course = db_session.exec(statement).first()
|
||||
trail_step.data = dict(course=course)
|
||||
|
||||
trail = await trails.find_one({"user_id": user.user_id, "org_id": org["org_id"]})
|
||||
|
||||
if not trail:
|
||||
return Trail(masked=False, courses=[])
|
||||
|
||||
course_ids = [course["course_id"] for course in trail["courses"]]
|
||||
|
||||
live_courses = await courses_mongo.find({"course_id": {"$in": course_ids}}).to_list(
|
||||
length=None
|
||||
trail_read = TrailRead(
|
||||
**trail.dict(),
|
||||
runs=trail_runs,
|
||||
)
|
||||
|
||||
for course in trail["courses"]:
|
||||
course_id = course["course_id"]
|
||||
return trail_read
|
||||
|
||||
if course_id not in [course["course_id"] for course in live_courses]:
|
||||
course["masked"] = True
|
||||
continue
|
||||
|
||||
chapters_meta = await get_coursechapters_meta(request, course_id, user)
|
||||
activities = chapters_meta["activities"]
|
||||
async def get_user_trail_with_orgid(
|
||||
request: Request, user: PublicUser, org_id: int, db_session: Session
|
||||
) -> TrailRead:
|
||||
statement = select(Trail).where(Trail.org_id == org_id, Trail.user_id == user.id)
|
||||
trail = db_session.exec(statement).first()
|
||||
|
||||
# get course object without _id
|
||||
course_object = await courses_mongo.find_one(
|
||||
{"course_id": course_id}, {"_id": 0}
|
||||
if not trail:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Trail not found"
|
||||
)
|
||||
|
||||
course["course_object"] = course_object
|
||||
num_activities = len(activities)
|
||||
statement = select(TrailRun).where(TrailRun.trail_id == trail.id)
|
||||
trail_runs = db_session.exec(statement).all()
|
||||
|
||||
num_completed_activities = len(course.get("activities_marked_complete", []))
|
||||
course["progress"] = (
|
||||
round((num_completed_activities / num_activities) * 100, 2)
|
||||
if num_activities > 0
|
||||
else 0
|
||||
)
|
||||
trail_runs = [
|
||||
TrailRunRead(**trail_run.__dict__, steps=[]) for trail_run in trail_runs
|
||||
]
|
||||
|
||||
return Trail(**trail)
|
||||
for trail_run in trail_runs:
|
||||
statement = select(TrailStep).where(TrailStep.trailrun_id == trail_run.id)
|
||||
trail_steps = db_session.exec(statement).all()
|
||||
|
||||
trail_steps = [TrailStep(**trail_step.__dict__) for trail_step in trail_steps]
|
||||
trail_run.steps = trail_steps
|
||||
|
||||
for trail_step in trail_steps:
|
||||
statement = select(Course).where(Course.id == trail_step.course_id)
|
||||
course = db_session.exec(statement).first()
|
||||
trail_step.data = dict(course=course)
|
||||
|
||||
trail_read = TrailRead(
|
||||
**trail.dict(),
|
||||
runs=trail_runs,
|
||||
)
|
||||
|
||||
return trail_read
|
||||
|
||||
|
||||
async def add_activity_to_trail(
|
||||
request: Request, user: PublicUser, course_id: str, org_slug: str, activity_id: str
|
||||
) -> Trail:
|
||||
trails = request.app.db["trails"]
|
||||
orgs = request.app.db["organizations"]
|
||||
courseid = "course_" + course_id
|
||||
activityid = "activity_" + activity_id
|
||||
request: Request,
|
||||
user: PublicUser,
|
||||
course_id: int,
|
||||
activity_id: int,
|
||||
db_session: Session,
|
||||
) -> TrailRead:
|
||||
|
||||
# check if run already exists
|
||||
statement = select(TrailRun).where(TrailRun.course_id == course_id)
|
||||
trailrun = db_session.exec(statement).first()
|
||||
|
||||
# get org_id from orgslug
|
||||
org = await orgs.find_one({"slug": org_slug})
|
||||
org_id = org["org_id"]
|
||||
if trailrun:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST, detail="TrailRun already exists"
|
||||
)
|
||||
|
||||
# find a trail with the user_id and course_id in the courses array
|
||||
trail = await trails.find_one(
|
||||
{"user_id": user.user_id, "courses.course_id": courseid, "org_id": org_id}
|
||||
statement = select(Course).where(Course.id == course_id)
|
||||
course = db_session.exec(statement).first()
|
||||
|
||||
if not course:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Course not found"
|
||||
)
|
||||
|
||||
statement = select(Trail).where(
|
||||
Trail.org_id == course.org_id, Trail.user_id == user.id
|
||||
)
|
||||
|
||||
if user.user_id == "anonymous":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Anonymous users cannot add activity to trail",
|
||||
)
|
||||
|
||||
if not trail:
|
||||
return Trail(masked=False, courses=[])
|
||||
|
||||
# if a trail has course_id in the courses array, then add the activity_id to the activities_marked_complete array
|
||||
for element in trail["courses"]:
|
||||
if element["course_id"] == courseid:
|
||||
if "activities_marked_complete" in element:
|
||||
# check if activity_id is already in the array
|
||||
if activityid not in element["activities_marked_complete"]:
|
||||
element["activities_marked_complete"].append(activityid)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Activity already marked complete",
|
||||
)
|
||||
else:
|
||||
element["activities_marked_complete"] = [activity_id]
|
||||
|
||||
# modify trail object
|
||||
await trails.replace_one({"trail_id": trail["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"]
|
||||
|
||||
if user.user_id == "anonymous":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Anonymous users cannot add activity to trail",
|
||||
)
|
||||
|
||||
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:
|
||||
trail_to_insert = TrailInDB(
|
||||
trail_id=f"trail_{uuid4()}",
|
||||
user_id=user.user_id,
|
||||
org_id=org["org_id"],
|
||||
courses=[],
|
||||
)
|
||||
trail_to_insert = await trails.insert_one(trail_to_insert.dict())
|
||||
|
||||
trail = await trails.find_one({"_id": trail_to_insert.inserted_id})
|
||||
|
||||
# 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"]
|
||||
|
||||
if user.user_id == "anonymous":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Anonymous users cannot add activity to trail",
|
||||
)
|
||||
|
||||
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"]})
|
||||
trail = db_session.exec(statement).first()
|
||||
|
||||
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
|
||||
statement = select(TrailRun).where(
|
||||
TrailRun.trail_id == trail.id, TrailRun.course_id == course.id
|
||||
)
|
||||
trailrun = db_session.exec(statement).first()
|
||||
|
||||
for element in trail["courses"]:
|
||||
if element["course_id"] == course_id:
|
||||
trail["courses"].remove(element)
|
||||
break
|
||||
if not trailrun:
|
||||
trailrun = TrailRun(
|
||||
trail_id=trail.id is not None,
|
||||
course_id=course.id is not None,
|
||||
org_id=course.org_id,
|
||||
user_id=user.id,
|
||||
creation_date=str(datetime.now()),
|
||||
update_date=str(datetime.now()),
|
||||
)
|
||||
db_session.add(trailrun)
|
||||
db_session.commit()
|
||||
db_session.refresh(trailrun)
|
||||
|
||||
await trails.replace_one({"trail_id": trail["trail_id"]}, trail)
|
||||
return Trail(**trail)
|
||||
statement = select(TrailStep).where(
|
||||
TrailStep.trailrun_id == trailrun.id, TrailStep.activity_id == activity_id
|
||||
)
|
||||
trailstep = db_session.exec(statement).first()
|
||||
|
||||
if not trailstep:
|
||||
trailstep = TrailStep(
|
||||
trailrun_id=trailrun.id is not None,
|
||||
activity_id=activity_id,
|
||||
course_id=course.id is not None,
|
||||
org_id=course.org_id,
|
||||
complete=False,
|
||||
teacher_verified=False,
|
||||
grade="",
|
||||
user_id=user.id,
|
||||
creation_date=str(datetime.now()),
|
||||
update_date=str(datetime.now()),
|
||||
)
|
||||
db_session.add(trailstep)
|
||||
db_session.commit()
|
||||
db_session.refresh(trailstep)
|
||||
|
||||
statement = select(TrailRun).where(TrailRun.trail_id == trail.id)
|
||||
trail_runs = db_session.exec(statement).all()
|
||||
|
||||
trail_runs = [
|
||||
TrailRunRead(**trail_run.__dict__, steps=[]) for trail_run in trail_runs
|
||||
]
|
||||
|
||||
for trail_run in trail_runs:
|
||||
statement = select(TrailStep).where(TrailStep.trailrun_id == trail_run.id)
|
||||
trail_steps = db_session.exec(statement).all()
|
||||
|
||||
trail_steps = [TrailStep(**trail_step.__dict__) for trail_step in trail_steps]
|
||||
trail_run.steps = trail_steps
|
||||
|
||||
for trail_step in trail_steps:
|
||||
statement = select(Course).where(Course.id == trail_step.course_id)
|
||||
course = db_session.exec(statement).first()
|
||||
trail_step.data = dict(course=course)
|
||||
|
||||
trail_read = TrailRead(
|
||||
**trail.dict(),
|
||||
runs=trail_runs,
|
||||
)
|
||||
|
||||
return trail_read
|
||||
|
||||
|
||||
async def add_course_to_trail(
|
||||
request: Request,
|
||||
user: PublicUser,
|
||||
course_id: str,
|
||||
db_session: Session,
|
||||
) -> TrailRead:
|
||||
|
||||
# check if run already exists
|
||||
statement = select(TrailRun).where(TrailRun.course_id == course_id)
|
||||
trailrun = db_session.exec(statement).first()
|
||||
|
||||
if trailrun:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST, detail="TrailRun already exists"
|
||||
)
|
||||
|
||||
statement = select(Course).where(Course.id == course_id)
|
||||
course = db_session.exec(statement).first()
|
||||
|
||||
if not course:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Course not found"
|
||||
)
|
||||
|
||||
statement = select(Trail).where(
|
||||
Trail.org_id == course.org_id, Trail.user_id == user.id
|
||||
)
|
||||
trail = db_session.exec(statement).first()
|
||||
|
||||
if not trail:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Trail not found"
|
||||
)
|
||||
|
||||
statement = select(TrailRun).where(
|
||||
TrailRun.trail_id == trail.id, TrailRun.course_id == course.id
|
||||
)
|
||||
trail_run = db_session.exec(statement).first()
|
||||
|
||||
if not trail_run:
|
||||
trail_run = TrailRun(
|
||||
trail_id=trail.id is not None,
|
||||
course_id=course.id is not None,
|
||||
org_id=course.org_id,
|
||||
user_id=user.id,
|
||||
creation_date=str(datetime.now()),
|
||||
update_date=str(datetime.now()),
|
||||
)
|
||||
db_session.add(trail_run)
|
||||
db_session.commit()
|
||||
db_session.refresh(trail_run)
|
||||
|
||||
statement = select(TrailRun).where(TrailRun.trail_id == trail.id)
|
||||
trail_runs = db_session.exec(statement).all()
|
||||
|
||||
trail_runs = [
|
||||
TrailRunRead(**trail_run.__dict__, steps=[]) for trail_run in trail_runs
|
||||
]
|
||||
|
||||
for trail_run in trail_runs:
|
||||
statement = select(TrailStep).where(TrailStep.trailrun_id == trail_run.id)
|
||||
trail_steps = db_session.exec(statement).all()
|
||||
|
||||
trail_steps = [TrailStep(**trail_step.__dict__) for trail_step in trail_steps]
|
||||
trail_run.steps = trail_steps
|
||||
|
||||
for trail_step in trail_steps:
|
||||
statement = select(Course).where(Course.id == trail_step.course_id)
|
||||
course = db_session.exec(statement).first()
|
||||
trail_step.data = dict(course=course)
|
||||
|
||||
trail_read = TrailRead(
|
||||
**trail.dict(),
|
||||
runs=trail_runs,
|
||||
)
|
||||
|
||||
return trail_read
|
||||
|
||||
|
||||
async def remove_course_from_trail(
|
||||
request: Request,
|
||||
user: PublicUser,
|
||||
course_id: str,
|
||||
db_session: Session,
|
||||
) -> TrailRead:
|
||||
statement = select(Course).where(Course.id == course_id)
|
||||
course = db_session.exec(statement).first()
|
||||
|
||||
if not course:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Course not found"
|
||||
)
|
||||
|
||||
statement = select(Trail).where(
|
||||
Trail.org_id == course.org_id, Trail.user_id == user.id
|
||||
)
|
||||
trail = db_session.exec(statement).first()
|
||||
|
||||
if not trail:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Trail not found"
|
||||
)
|
||||
|
||||
statement = select(TrailRun).where(
|
||||
TrailRun.trail_id == trail.id, TrailRun.course_id == course.id
|
||||
)
|
||||
trail_run = db_session.exec(statement).first()
|
||||
|
||||
if trail_run:
|
||||
db_session.delete(trail_run)
|
||||
db_session.commit()
|
||||
|
||||
statement = select(TrailRun).where(TrailRun.trail_id == trail.id)
|
||||
trail_runs = db_session.exec(statement).all()
|
||||
|
||||
trail_runs = [
|
||||
TrailRunRead(**trail_run.__dict__, steps=[]) for trail_run in trail_runs
|
||||
]
|
||||
for trail_run in trail_runs:
|
||||
statement = select(TrailStep).where(TrailStep.trailrun_id == trail_run.id)
|
||||
trail_steps = db_session.exec(statement).all()
|
||||
|
||||
trail_steps = [TrailStep(**trail_step.__dict__) for trail_step in trail_steps]
|
||||
trail_run.steps = trail_steps
|
||||
|
||||
for trail_step in trail_steps:
|
||||
statement = select(Course).where(Course.id == trail_step.course_id)
|
||||
course = db_session.exec(statement).first()
|
||||
trail_step.data = dict(course=course)
|
||||
|
||||
trail_read = TrailRead(
|
||||
**trail.dict(),
|
||||
runs=trail_runs,
|
||||
)
|
||||
|
||||
return trail_read
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue