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
53
apps/api/src/db/trail_runs.py
Normal file
53
apps/api/src/db/trail_runs.py
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
from typing import Optional
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from sqlalchemy import JSON, Column
|
||||||
|
from sqlmodel import Field, SQLModel
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
from src.db.trail_steps import TrailStep
|
||||||
|
|
||||||
|
|
||||||
|
class TrailRunEnum(str, Enum):
|
||||||
|
RUN_TYPE_COURSE = "RUN_TYPE_COURSE"
|
||||||
|
|
||||||
|
|
||||||
|
class StatusEnum(str, Enum):
|
||||||
|
STATUS_IN_PROGRESS = "STATUS_IN_PROGRESS"
|
||||||
|
STATUS_COMPLETED = "STATUS_COMPLETED"
|
||||||
|
STATUS_PAUSED = "STATUS_PAUSED"
|
||||||
|
STATUS_CANCELLED = "STATUS_CANCELLED"
|
||||||
|
|
||||||
|
|
||||||
|
class TrailRun(SQLModel, table=True):
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
data: dict = Field(default={}, sa_column=Column(JSON))
|
||||||
|
status: StatusEnum = StatusEnum.STATUS_IN_PROGRESS
|
||||||
|
# foreign keys
|
||||||
|
trail_id: int = Field(default=None, foreign_key="trail.id")
|
||||||
|
course_id: int = Field(default=None, foreign_key="course.id")
|
||||||
|
org_id: int = Field(default=None, foreign_key="organization.id")
|
||||||
|
user_id: int = Field(default=None, foreign_key="user.id")
|
||||||
|
# timestamps
|
||||||
|
creation_date: str
|
||||||
|
update_date: str
|
||||||
|
|
||||||
|
|
||||||
|
class TrailRunCreate(TrailRun):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# trick because Lists are not supported in SQLModel (runs: list[TrailStep] )
|
||||||
|
class TrailRunRead(BaseModel):
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
data: dict = Field(default={}, sa_column=Column(JSON))
|
||||||
|
status: StatusEnum = StatusEnum.STATUS_IN_PROGRESS
|
||||||
|
# foreign keys
|
||||||
|
trail_id: int = Field(default=None, foreign_key="trail.id")
|
||||||
|
course_id: int = Field(default=None, foreign_key="course.id")
|
||||||
|
org_id: int = Field(default=None, foreign_key="organization.id")
|
||||||
|
user_id: int = Field(default=None, foreign_key="user.id")
|
||||||
|
# timestamps
|
||||||
|
creation_date: str
|
||||||
|
update_date: str
|
||||||
|
steps: list[TrailStep]
|
||||||
|
pass
|
||||||
32
apps/api/src/db/trail_steps.py
Normal file
32
apps/api/src/db/trail_steps.py
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
from enum import Enum
|
||||||
|
from typing import Optional
|
||||||
|
from sqlalchemy import JSON, Column
|
||||||
|
from sqlmodel import Field, SQLModel
|
||||||
|
|
||||||
|
|
||||||
|
class TrailStepTypeEnum(str, Enum):
|
||||||
|
STEP_TYPE_READABLE_ACTIVITY = "STEP_TYPE_READABLE_ACTIVITY"
|
||||||
|
STEP_TYPE_ASSIGNMENT_ACTIVITY = "STEP_TYPE_ASSIGNMENT_ACTIVITY"
|
||||||
|
STEP_TYPE_CUSTOM_ACTIVITY = "STEP_TYPE_CUSTOM_ACTIVITY"
|
||||||
|
|
||||||
|
|
||||||
|
class TrailStep(SQLModel, table=True):
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
complete: bool
|
||||||
|
teacher_verified: bool
|
||||||
|
grade: str
|
||||||
|
data: dict = Field(default={}, sa_column=Column(JSON))
|
||||||
|
# foreign keys
|
||||||
|
trailrun_id: int = Field(default=None, foreign_key="trailrun.id")
|
||||||
|
trail_id: int = Field(default=None, foreign_key="trail.id")
|
||||||
|
activity_id: int = Field(default=None, foreign_key="activity.id")
|
||||||
|
course_id: int = Field(default=None, foreign_key="course.id")
|
||||||
|
org_id: int = Field(default=None, foreign_key="organization.id")
|
||||||
|
user_id: int = Field(default=None, foreign_key="user.id")
|
||||||
|
# timestamps
|
||||||
|
creation_date: str
|
||||||
|
update_date: str
|
||||||
|
|
||||||
|
|
||||||
|
# note : prepare assignments support
|
||||||
|
# an assignment object will be linked to a trail step object in the future
|
||||||
34
apps/api/src/db/trails.py
Normal file
34
apps/api/src/db/trails.py
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
from typing import Optional
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from sqlmodel import Field, SQLModel
|
||||||
|
from enum import Enum
|
||||||
|
from src.db.trail_runs import TrailRun, TrailRunRead
|
||||||
|
|
||||||
|
from src.db.trail_steps import TrailStep
|
||||||
|
|
||||||
|
|
||||||
|
class TrailBase(SQLModel):
|
||||||
|
org_id: int = Field(default=None, foreign_key="organization.id")
|
||||||
|
user_id: int = Field(default=None, foreign_key="user.id")
|
||||||
|
|
||||||
|
|
||||||
|
class Trail(TrailBase, table=True):
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
trail_uuid: str = ""
|
||||||
|
creation_date: str = ""
|
||||||
|
update_date: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
class TrailCreate(TrailBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# trick because Lists are not supported in SQLModel (runs: list[TrailRun] )
|
||||||
|
class TrailRead(BaseModel):
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
trail_uuid: str
|
||||||
|
org_id: int = Field(default=None, foreign_key="organization.id")
|
||||||
|
user_id: int = Field(default=None, foreign_key="user.id")
|
||||||
|
creation_date: str
|
||||||
|
update_date: str
|
||||||
|
runs: list[TrailRunRead]
|
||||||
|
|
@ -1,56 +1,103 @@
|
||||||
from fastapi import APIRouter, Depends, Request
|
from fastapi import APIRouter, Depends, Request
|
||||||
|
from src.core.events.database import get_db_session
|
||||||
|
from src.db.trails import TrailCreate, TrailRead
|
||||||
from src.security.auth import get_current_user
|
from src.security.auth import get_current_user
|
||||||
from src.services.trail.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
|
from src.services.trail.trail import (
|
||||||
|
Trail,
|
||||||
|
add_activity_to_trail,
|
||||||
|
add_course_to_trail,
|
||||||
|
create_user_trail,
|
||||||
|
get_user_trails,
|
||||||
|
get_user_trail_with_orgid,
|
||||||
|
remove_course_from_trail,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/start")
|
@router.post("/start")
|
||||||
async def api_start_trail(request: Request, trail_object: Trail, org_id: str, user=Depends(get_current_user)) -> Trail:
|
async def api_start_trail(
|
||||||
|
request: Request,
|
||||||
|
trail_object: TrailCreate,
|
||||||
|
user=Depends(get_current_user),
|
||||||
|
db_session=Depends(get_db_session),
|
||||||
|
) -> Trail:
|
||||||
"""
|
"""
|
||||||
Start trail
|
Start trail
|
||||||
"""
|
"""
|
||||||
return await create_trail(request, user, org_id, trail_object)
|
return await create_user_trail(request, user, trail_object, db_session)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/org_id/{org_id}/trail")
|
@router.get("/")
|
||||||
async def api_get_trail_by_orgid(request: Request, org_slug: str, user=Depends(get_current_user)):
|
async def api_get_user_trail(
|
||||||
|
request: Request,
|
||||||
|
user=Depends(get_current_user),
|
||||||
|
db_session=Depends(get_db_session),
|
||||||
|
) -> TrailRead:
|
||||||
"""
|
"""
|
||||||
Get a user trails
|
Get a user trails
|
||||||
"""
|
"""
|
||||||
return await get_user_trail(request, user=user, org_slug=org_slug)
|
return await get_user_trails(
|
||||||
|
request, user=user, db_session=db_session
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/org_slug/{org_slug}/trail")
|
@router.get("/org_slug/{org_id}/trail")
|
||||||
async def api_get_trail_by_orgslug(request: Request, org_slug: str, user=Depends(get_current_user)):
|
async def api_get_trail_by_org_id(
|
||||||
|
request: Request,
|
||||||
|
org_id: int,
|
||||||
|
user=Depends(get_current_user),
|
||||||
|
db_session=Depends(get_db_session),
|
||||||
|
) -> TrailRead:
|
||||||
"""
|
"""
|
||||||
Get a user trails using org slug
|
Get a user trails using org slug
|
||||||
"""
|
"""
|
||||||
return await get_user_trail_with_orgslug(request, user, org_slug=org_slug)
|
return await get_user_trail_with_orgid(
|
||||||
|
request, user, org_id=org_id, db_session=db_session
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Courses in trail
|
# Courses in trail
|
||||||
|
|
||||||
|
|
||||||
@router.post("/org_slug/{org_slug}/add_course/{course_id}")
|
@router.post("/add_course/{course_id}")
|
||||||
async def api_add_course_to_trail(request: Request, course_id: str, org_slug: str, user=Depends(get_current_user)):
|
async def api_add_course_to_trail(
|
||||||
|
request: Request,
|
||||||
|
course_id: str,
|
||||||
|
user=Depends(get_current_user),
|
||||||
|
db_session=Depends(get_db_session),
|
||||||
|
) -> TrailRead:
|
||||||
"""
|
"""
|
||||||
Add Course to trail
|
Add Course to trail
|
||||||
"""
|
"""
|
||||||
return await add_course_to_trail(request, user, org_slug, course_id)
|
return await add_course_to_trail(request, user, course_id, db_session)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/org_slug/{org_slug}/remove_course/{course_id}")
|
@router.post("/remove_course/{course_id}")
|
||||||
async def api_remove_course_to_trail(request: Request, course_id: str, org_slug: str, user=Depends(get_current_user)):
|
async def api_remove_course_to_trail(
|
||||||
|
request: Request,
|
||||||
|
course_id: str,
|
||||||
|
user=Depends(get_current_user),
|
||||||
|
db_session=Depends(get_db_session),
|
||||||
|
) -> TrailRead:
|
||||||
"""
|
"""
|
||||||
Remove Course from trail
|
Remove Course from trail
|
||||||
"""
|
"""
|
||||||
return await remove_course_from_trail(request, user, org_slug, course_id)
|
return await remove_course_from_trail(request, user, course_id, db_session)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/org_slug/{org_slug}/add_activity/course_id/{course_id}/activity_id/{activity_id}")
|
@router.post("/add_activity/course_id/{course_id}/activity_id/{activity_id}")
|
||||||
async def api_add_activity_to_trail(request: Request, activity_id: str, course_id: str, org_slug: str, user=Depends(get_current_user)):
|
async def api_add_activity_to_trail(
|
||||||
|
request: Request,
|
||||||
|
activity_id: int,
|
||||||
|
course_id: int,
|
||||||
|
user=Depends(get_current_user),
|
||||||
|
db_session=Depends(get_db_session),
|
||||||
|
) -> TrailRead:
|
||||||
"""
|
"""
|
||||||
Add Course to trail
|
Add Course to trail
|
||||||
"""
|
"""
|
||||||
return await add_activity_to_trail(request, user, course_id, org_slug, activity_id)
|
return await add_activity_to_trail(
|
||||||
|
request, user, course_id, activity_id, db_session
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,286 +1,362 @@
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import stat
|
||||||
from typing import List, Literal, Optional
|
from typing import List, Literal, Optional
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from fastapi import HTTPException, Request, status
|
from fastapi import HTTPException, Request, status
|
||||||
from pydantic import BaseModel
|
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.orgs.schemas.orgs import PublicOrganization
|
||||||
from src.services.courses.chapters import get_coursechapters_meta
|
from src.services.courses.chapters import get_coursechapters_meta
|
||||||
|
|
||||||
from src.services.users.users import PublicUser
|
|
||||||
|
|
||||||
#### Classes ####################################################
|
async def create_user_trail(
|
||||||
|
request: Request,
|
||||||
|
user: PublicUser,
|
||||||
class ActivityData(BaseModel):
|
trail_object: TrailCreate,
|
||||||
activity_id: str
|
db_session: Session,
|
||||||
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:
|
) -> 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:
|
||||||
if trail_object.courses:
|
raise HTTPException(
|
||||||
courses = trail_object.courses
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
# get course ids
|
detail="Trail already exists",
|
||||||
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 = Trail.from_orm(trail_object)
|
||||||
trail_id = f"trail_{uuid4()}"
|
|
||||||
|
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
|
# create trail
|
||||||
trail = TrailInDB(
|
db_session.add(trail)
|
||||||
**trail_object.dict(), trail_id=trail_id, user_id=user.user_id, org_id=org_id
|
db_session.commit()
|
||||||
)
|
db_session.refresh(trail)
|
||||||
|
|
||||||
await trails.insert_one(trail.dict())
|
|
||||||
|
|
||||||
return trail
|
return trail
|
||||||
|
|
||||||
|
|
||||||
async def get_user_trail(request: Request, org_slug: str, user: PublicUser) -> Trail:
|
async def get_user_trails(
|
||||||
trails = request.app.db["trails"]
|
request: Request,
|
||||||
trail = await trails.find_one({"user_id": user.user_id})
|
user: PublicUser,
|
||||||
|
db_session: Session,
|
||||||
|
) -> TrailRead:
|
||||||
|
statement = select(Trail).where(Trail.user_id == user.id)
|
||||||
|
trail = db_session.exec(statement).first()
|
||||||
|
|
||||||
if not trail:
|
if not trail:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND, detail="Trail not found"
|
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", []))
|
statement = select(TrailRun).where(TrailRun.trail_id == trail.id)
|
||||||
element["progress"] = (
|
trail_runs = db_session.exec(statement).all()
|
||||||
round((num_completed_activities / num_activities) * 100, 2)
|
|
||||||
if num_activities > 0
|
trail_runs = [
|
||||||
else 0
|
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(**trail)
|
return trail_read
|
||||||
|
|
||||||
|
|
||||||
async def get_user_trail_with_orgslug(
|
async def get_user_trail_with_orgid(
|
||||||
request: Request, user: PublicUser, org_slug: str
|
request: Request, user: PublicUser, org_id: int, db_session: Session
|
||||||
) -> Trail:
|
) -> TrailRead:
|
||||||
trails = request.app.db["trails"]
|
statement = select(Trail).where(Trail.org_id == org_id, Trail.user_id == user.id)
|
||||||
orgs = request.app.db["organizations"]
|
trail = db_session.exec(statement).first()
|
||||||
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:
|
if not trail:
|
||||||
return Trail(masked=False, courses=[])
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND, detail="Trail not found"
|
||||||
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
|
|
||||||
)
|
)
|
||||||
|
|
||||||
for course in trail["courses"]:
|
statement = select(TrailRun).where(TrailRun.trail_id == trail.id)
|
||||||
course_id = course["course_id"]
|
trail_runs = db_session.exec(statement).all()
|
||||||
|
|
||||||
if course_id not in [course["course_id"] for course in live_courses]:
|
trail_runs = [
|
||||||
course["masked"] = True
|
TrailRunRead(**trail_run.__dict__, steps=[]) for trail_run in trail_runs
|
||||||
continue
|
]
|
||||||
|
|
||||||
chapters_meta = await get_coursechapters_meta(request, course_id, user)
|
for trail_run in trail_runs:
|
||||||
activities = chapters_meta["activities"]
|
statement = select(TrailStep).where(TrailStep.trailrun_id == trail_run.id)
|
||||||
|
trail_steps = db_session.exec(statement).all()
|
||||||
|
|
||||||
# get course object without _id
|
trail_steps = [TrailStep(**trail_step.__dict__) for trail_step in trail_steps]
|
||||||
course_object = await courses_mongo.find_one(
|
trail_run.steps = trail_steps
|
||||||
{"course_id": course_id}, {"_id": 0}
|
|
||||||
|
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,
|
||||||
)
|
)
|
||||||
|
|
||||||
course["course_object"] = course_object
|
return trail_read
|
||||||
num_activities = len(activities)
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
return Trail(**trail)
|
|
||||||
|
|
||||||
|
|
||||||
async def add_activity_to_trail(
|
async def add_activity_to_trail(
|
||||||
request: Request, user: PublicUser, course_id: str, org_slug: str, activity_id: str
|
request: Request,
|
||||||
) -> Trail:
|
user: PublicUser,
|
||||||
trails = request.app.db["trails"]
|
course_id: int,
|
||||||
orgs = request.app.db["organizations"]
|
activity_id: int,
|
||||||
courseid = "course_" + course_id
|
db_session: Session,
|
||||||
activityid = "activity_" + activity_id
|
) -> TrailRead:
|
||||||
|
|
||||||
# get org_id from orgslug
|
# check if run already exists
|
||||||
org = await orgs.find_one({"slug": org_slug})
|
statement = select(TrailRun).where(TrailRun.course_id == course_id)
|
||||||
org_id = org["org_id"]
|
trailrun = db_session.exec(statement).first()
|
||||||
|
|
||||||
# find a trail with the user_id and course_id in the courses array
|
if trailrun:
|
||||||
trail = await trails.find_one(
|
|
||||||
{"user_id": user.user_id, "courses.course_id": courseid, "org_id": org_id}
|
|
||||||
)
|
|
||||||
|
|
||||||
if user.user_id == "anonymous":
|
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
status_code=status.HTTP_400_BAD_REQUEST, detail="TrailRun already exists"
|
||||||
detail="Anonymous users cannot add activity to trail",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if not trail:
|
statement = select(Course).where(Course.id == course_id)
|
||||||
return Trail(masked=False, courses=[])
|
course = db_session.exec(statement).first()
|
||||||
|
|
||||||
# if a trail has course_id in the courses array, then add the activity_id to the activities_marked_complete array
|
if not course:
|
||||||
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(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_404_NOT_FOUND, detail="Course not found"
|
||||||
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})
|
statement = select(Trail).where(
|
||||||
|
Trail.org_id == course.org_id, Trail.user_id == user.id
|
||||||
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 = db_session.exec(statement).first()
|
||||||
|
|
||||||
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"]})
|
|
||||||
|
|
||||||
if not trail:
|
if not trail:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND, detail="Trail not found"
|
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 not trailrun:
|
||||||
if element["course_id"] == course_id:
|
trailrun = TrailRun(
|
||||||
trail["courses"].remove(element)
|
trail_id=trail.id is not None,
|
||||||
break
|
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)
|
statement = select(TrailStep).where(
|
||||||
return Trail(**trail)
|
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