feat: add Assignments, Tasks, Submissions CRUD

This commit is contained in:
swve 2024-07-10 23:35:32 +02:00
parent cd2397f4f7
commit 47782b57bc
32 changed files with 1719 additions and 218 deletions

61
apps/api/poetry.lock generated
View file

@ -215,17 +215,17 @@ typecheck = ["mypy"]
[[package]] [[package]]
name = "boto3" name = "boto3"
version = "1.34.137" version = "1.34.143"
description = "The AWS SDK for Python" description = "The AWS SDK for Python"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "boto3-1.34.137-py3-none-any.whl", hash = "sha256:7cb697d67fd138ceebc6f789919ae370c092a50c6b0ccc4ef483027935502eab"}, {file = "boto3-1.34.143-py3-none-any.whl", hash = "sha256:0d16832f23e6bd3ae94e35ea8e625529850bfad9baccd426de96ad8f445d8e03"},
{file = "boto3-1.34.137.tar.gz", hash = "sha256:0b21b84db4619b3711a6f643d465a5a25e81231ee43615c55a20ff6b89c6cc3c"}, {file = "boto3-1.34.143.tar.gz", hash = "sha256:b590ce80c65149194def43ebf0ea1cf0533945502507837389a8d22e3ecbcf05"},
] ]
[package.dependencies] [package.dependencies]
botocore = ">=1.34.137,<1.35.0" botocore = ">=1.34.143,<1.35.0"
jmespath = ">=0.7.1,<2.0.0" jmespath = ">=0.7.1,<2.0.0"
s3transfer = ">=0.10.0,<0.11.0" s3transfer = ">=0.10.0,<0.11.0"
@ -234,13 +234,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
[[package]] [[package]]
name = "botocore" name = "botocore"
version = "1.34.137" version = "1.34.143"
description = "Low-level, data-driven core of boto 3." description = "Low-level, data-driven core of boto 3."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "botocore-1.34.137-py3-none-any.whl", hash = "sha256:a980fa4adec4bfa23fff70a3512622e9412c69c791898a52cafc2458b0be6040"}, {file = "botocore-1.34.143-py3-none-any.whl", hash = "sha256:094aea179e8aaa1bc957ad49cc27d93b189dd3a1f3075d8b0ca7c445a2a88430"},
{file = "botocore-1.34.137.tar.gz", hash = "sha256:e29c8e9bfda0b20a1997792968e85868bfce42fefad9730f633a81adcff3f2ef"}, {file = "botocore-1.34.143.tar.gz", hash = "sha256:059f032ec05733a836e04e869c5a15534420102f93116f3bc9a5b759b0651caf"},
] ]
[package.dependencies] [package.dependencies]
@ -844,13 +844,13 @@ tqdm = ["tqdm"]
[[package]] [[package]]
name = "google-auth" name = "google-auth"
version = "2.31.0" version = "2.32.0"
description = "Google Authentication Library" description = "Google Authentication Library"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "google-auth-2.31.0.tar.gz", hash = "sha256:87805c36970047247c8afe614d4e3af8eceafc1ebba0c679fe75ddd1d575e871"}, {file = "google_auth-2.32.0-py2.py3-none-any.whl", hash = "sha256:53326ea2ebec768070a94bee4e1b9194c9646ea0c2bd72422785bd0f9abfad7b"},
{file = "google_auth-2.31.0-py2.py3-none-any.whl", hash = "sha256:042c4702efa9f7d3c48d3a69341c209381b125faa6dbf3ebe56bc7e40ae05c23"}, {file = "google_auth-2.32.0.tar.gz", hash = "sha256:49315be72c55a6a37d62819e3573f6b416aca00721f7e3e31a008d928bf64022"},
] ]
[package.dependencies] [package.dependencies]
@ -1877,13 +1877,13 @@ sympy = "*"
[[package]] [[package]]
name = "openai" name = "openai"
version = "1.35.8" version = "1.35.13"
description = "The official Python library for the openai API" description = "The official Python library for the openai API"
optional = false optional = false
python-versions = ">=3.7.1" python-versions = ">=3.7.1"
files = [ files = [
{file = "openai-1.35.8-py3-none-any.whl", hash = "sha256:69d5b0f47f0c806d5da83fb0f84c147661395226d7f79acc78aa1d9b8c635887"}, {file = "openai-1.35.13-py3-none-any.whl", hash = "sha256:36ec3e93e0d1f243f69be85c89b9221a471c3e450dfd9df16c9829e3cdf63e60"},
{file = "openai-1.35.8.tar.gz", hash = "sha256:3f6101888bb516647edade74c503f2b937b8bab73408e799d58f2aba68bbe51c"}, {file = "openai-1.35.13.tar.gz", hash = "sha256:c684f3945608baf7d2dcc0ef3ee6f3e27e4c66f21076df0b47be45d57e6ae6e4"},
] ]
[package.dependencies] [package.dependencies]
@ -2732,13 +2732,13 @@ rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
[[package]] [[package]]
name = "resend" name = "resend"
version = "2.1.0" version = "2.2.0"
description = "Resend Python SDK" description = "Resend Python SDK"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "resend-2.1.0-py2.py3-none-any.whl", hash = "sha256:7f2a221983fab74a09f669c0c14a75daf547ffa4b4930141626d9cca55bca767"}, {file = "resend-2.2.0-py2.py3-none-any.whl", hash = "sha256:be420762885de25c816497f09207da1cd54d253c44d9f81f441367893a42d099"},
{file = "resend-2.1.0.tar.gz", hash = "sha256:92dc8e035c2ce8cf8210c1c322e86b0a4f509e0c82a80932d3323cd2f3a43d2d"}, {file = "resend-2.2.0.tar.gz", hash = "sha256:f44976e4a37bb66445280bd8ef201eaac536b07bbe7c4da8f1717f4fcc63da7e"},
] ]
[package.dependencies] [package.dependencies]
@ -2796,13 +2796,13 @@ crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"]
[[package]] [[package]]
name = "sentry-sdk" name = "sentry-sdk"
version = "2.7.1" version = "2.9.0"
description = "Python client for Sentry (https://sentry.io)" description = "Python client for Sentry (https://sentry.io)"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
{file = "sentry_sdk-2.7.1-py2.py3-none-any.whl", hash = "sha256:ef1b3d54eb715825657cd4bb3cb42bb4dc85087bac14c56b0fd8c21abd968c9a"}, {file = "sentry_sdk-2.9.0-py2.py3-none-any.whl", hash = "sha256:0bea5fa8b564cc0d09f2e6f55893e8f70286048b0ffb3a341d5b695d1af0e6ee"},
{file = "sentry_sdk-2.7.1.tar.gz", hash = "sha256:25006c7e68b75aaa5e6b9c6a420ece22e8d7daec4b7a906ffd3a8607b67c037b"}, {file = "sentry_sdk-2.9.0.tar.gz", hash = "sha256:4c85bad74df9767976afb3eeddc33e0e153300e887d637775a753a35ef99bee6"},
] ]
[package.dependencies] [package.dependencies]
@ -2847,13 +2847,13 @@ tornado = ["tornado (>=6)"]
[[package]] [[package]]
name = "setuptools" name = "setuptools"
version = "70.2.0" version = "70.3.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages" description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "setuptools-70.2.0-py3-none-any.whl", hash = "sha256:b8b8060bb426838fbe942479c90296ce976249451118ef566a5a0b7d8b78fb05"}, {file = "setuptools-70.3.0-py3-none-any.whl", hash = "sha256:fe384da74336c398e0d956d1cae0669bc02eed936cdb1d49b57de1990dc11ffc"},
{file = "setuptools-70.2.0.tar.gz", hash = "sha256:bd63e505105011b25c3c11f753f7e3b8465ea739efddaccef8f0efac2137bac1"}, {file = "setuptools-70.3.0.tar.gz", hash = "sha256:f171bab1dfbc86b132997f26a119f6056a57950d058587841a0082e8830f9dc5"},
] ]
[package.extras] [package.extras]
@ -3014,27 +3014,30 @@ full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7
[[package]] [[package]]
name = "sympy" name = "sympy"
version = "1.12.1" version = "1.13.0"
description = "Computer algebra system (CAS) in Python" description = "Computer algebra system (CAS) in Python"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "sympy-1.12.1-py3-none-any.whl", hash = "sha256:9b2cbc7f1a640289430e13d2a56f02f867a1da0190f2f99d8968c2f74da0e515"}, {file = "sympy-1.13.0-py3-none-any.whl", hash = "sha256:6b0b32a4673fb91bd3cac3b55406c8e01d53ae22780be467301cc452f6680c92"},
{file = "sympy-1.12.1.tar.gz", hash = "sha256:2877b03f998cd8c08f07cd0de5b767119cd3ef40d09f41c30d722f6686b0fb88"}, {file = "sympy-1.13.0.tar.gz", hash = "sha256:3b6af8f4d008b9a1a6a4268b335b984b23835f26d1d60b0526ebc71d48a25f57"},
] ]
[package.dependencies] [package.dependencies]
mpmath = ">=1.1.0,<1.4.0" mpmath = ">=1.1.0,<1.4"
[package.extras]
dev = ["hypothesis (>=6.70.0)", "pytest (>=7.1.0)"]
[[package]] [[package]]
name = "tenacity" name = "tenacity"
version = "8.4.2" version = "8.5.0"
description = "Retry code until it succeeds" description = "Retry code until it succeeds"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "tenacity-8.4.2-py3-none-any.whl", hash = "sha256:9e6f7cf7da729125c7437222f8a522279751cdfbe6b67bfe64f75d3a348661b2"}, {file = "tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687"},
{file = "tenacity-8.4.2.tar.gz", hash = "sha256:cd80a53a79336edba8489e767f729e4f391c896956b57140b5d7511a64bbd3ef"}, {file = "tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78"},
] ]
[package.extras] [package.extras]

View file

@ -1,133 +0,0 @@
from typing import Optional
from openai import BaseModel
from sqlalchemy import JSON, Column, ForeignKey
from sqlmodel import Field, SQLModel
from enum import Enum
class AssignmentTypeEnum(str, Enum):
FILE_SUBMISSION = "FILE_SUBMISSION"
QUIZ = "QUIZ"
OTHER = "OTHER"
class Assignment(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
assignment_uuid: str
title: str
description: str
due_date: str
org_id: int = Field(
sa_column=Column("org_id", ForeignKey("organization.id", ondelete="CASCADE"))
)
course_id: int = Field(
sa_column=Column("course_id", ForeignKey("course.id", ondelete="CASCADE"))
)
chapter_id: int = Field(
sa_column=Column("chapter_id", ForeignKey("chapter.id", ondelete="CASCADE"))
)
activity_id: int = Field(
sa_column=Column("activity_id", ForeignKey("activity.id", ondelete="CASCADE"))
)
creation_date: str
update_date: str
class AssignmentTask(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
assignment_task_uuid: str = ""
title: str = ""
description: str = ""
hint: str = ""
assignment_type: AssignmentTypeEnum
contents: dict = Field(default={}, sa_column=Column(JSON))
max_grade_value: int = (
0 # Value is always between 0-100 and is used as the maximum grade for the task
)
# Foreign keys
assignment_id: int = Field(
sa_column=Column(
"assignment_id", ForeignKey("assignment.id", ondelete="CASCADE")
)
)
org_id: int = Field(
sa_column=Column("org_id", ForeignKey("organization.id", ondelete="CASCADE"))
)
course_id: int = Field(
sa_column=Column("course_id", ForeignKey("course.id", ondelete="CASCADE"))
)
chapter_id: int = Field(
sa_column=Column("chapter_id", ForeignKey("chapter.id", ondelete="CASCADE"))
)
activity_id: int = Field(
sa_column=Column("activity_id", ForeignKey("activity.id", ondelete="CASCADE"))
)
creation_date: str
update_date: str
class AssignmentTaskSubmission(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
assignment_task_submission_uuid: str = ""
task_submission: dict = Field(default={}, sa_column=Column(JSON))
grade: int = (
0 # Value is always between 0-100 depending on the questions, this is used to calculate the final grade on the AssignmentUser model
)
task_submission_grade_feedback: str = "" # Feedback given by the teacher
# Foreign keys
user_id: int = Field(
sa_column=Column("user_id", ForeignKey("user.id", ondelete="CASCADE"))
)
activity_id: int = Field(
sa_column=Column("activity_id", ForeignKey("activity.id", ondelete="CASCADE"))
)
course_id: int = Field(
sa_column=Column("course_id", ForeignKey("course.id", ondelete="CASCADE"))
)
chapter_id: int = Field(
sa_column=Column("chapter_id", ForeignKey("chapter.id", ondelete="CASCADE"))
)
assignment_task_id: int = Field(
sa_column=Column(
"assignment_task_id", ForeignKey("assignment_task.id", ondelete="CASCADE")
)
)
creation_date: str = ""
update_date: str = ""
# Note on grading :
# To calculate the final grade :
class AssignmentUserSubmissionStatus(str, Enum):
PENDING = "PENDING"
SUBMITTED = "SUBMITTED"
GRADED = "GRADED"
LATE = "LATE"
NOT_SUBMITTED = "NOT_SUBMITTED"
class AssignmentUserSubmission(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
assignment_user_uuid: str = ""
submission_status: AssignmentUserSubmissionStatus = (
AssignmentUserSubmissionStatus.PENDING
)
grading: str = ""
user_id: int = Field(
sa_column=Column("user_id", ForeignKey("user.id", ondelete="CASCADE"))
)
assignment_id: int = Field(
sa_column=Column(
"assignment_id", ForeignKey("assignment.id", ondelete="CASCADE")
)
)
creation_date: str = ""
update_date: str = ""

View file

@ -0,0 +1,317 @@
from typing import Optional, Dict
from sqlalchemy import JSON, Column, ForeignKey
from sqlmodel import Field, SQLModel
from enum import Enum
## Assignment ##
class GradingTypeEnum(str, Enum):
ALPHABET = "ALPHABET"
NUMERIC = "NUMERIC"
PERCENTAGE = "PERCENTAGE"
class AssignmentBase(SQLModel):
"""Represents the common fields for an assignment."""
title: str
description: str
due_date: str
published: Optional[bool] = False
grading_type: GradingTypeEnum
org_id: int
course_id: int
chapter_id: int
activity_id: int
class AssignmentCreate(AssignmentBase):
"""Model for creating a new assignment."""
pass # Inherits all fields from AssignmentBase
class AssignmentRead(AssignmentBase):
"""Model for reading an assignment."""
id: int
assignment_uuid: str
creation_date: Optional[str]
update_date: Optional[str]
class AssignmentUpdate(SQLModel):
"""Model for updating an assignment."""
title: Optional[str]
description: Optional[str]
due_date: Optional[str]
published: Optional[bool]
grading_type: Optional[GradingTypeEnum]
org_id: Optional[int]
course_id: Optional[int]
chapter_id: Optional[int]
activity_id: Optional[int]
update_date: Optional[str]
class Assignment(AssignmentBase, table=True):
"""Represents an assignment with relevant details and foreign keys."""
id: Optional[int] = Field(default=None, primary_key=True)
creation_date: Optional[str]
update_date: Optional[str]
assignment_uuid: str
org_id: int = Field(
sa_column=Column("org_id", ForeignKey("organization.id", ondelete="CASCADE"))
)
course_id: int = Field(
sa_column=Column("course_id", ForeignKey("course.id", ondelete="CASCADE"))
)
chapter_id: int = Field(
sa_column=Column("chapter_id", ForeignKey("chapter.id", ondelete="CASCADE"))
)
activity_id: int = Field(
sa_column=Column("activity_id", ForeignKey("activity.id", ondelete="CASCADE"))
)
## Assignment ##
## AssignmentTask ##
class AssignmentTaskTypeEnum(str, Enum):
FILE_SUBMISSION = "FILE_SUBMISSION"
QUIZ = "QUIZ"
FORM = "FORM" # soon to be implemented
OTHER = "OTHER"
class AssignmentTaskBase(SQLModel):
"""Represents the common fields for an assignment task."""
title: str
description: str
hint: str
assignment_type: AssignmentTaskTypeEnum
contents: Dict = Field(default={}, sa_column=Column(JSON))
max_grade_value: int = 0 # Value is always between 0-100
assignment_id: int
org_id: int
course_id: int
chapter_id: int
activity_id: int
class AssignmentTaskCreate(AssignmentTaskBase ):
"""Model for creating a new assignment task."""
pass # Inherits all fields from AssignmentTaskBase
class AssignmentTaskRead(AssignmentTaskBase):
"""Model for reading an assignment task."""
id: int
assignment_task_uuid: str
class AssignmentTaskUpdate(SQLModel):
"""Model for updating an assignment task."""
title: Optional[str]
description: Optional[str]
hint: Optional[str]
assignment_type: Optional[AssignmentTaskTypeEnum]
contents: Optional[Dict] = Field(default={}, sa_column=Column(JSON))
max_grade_value: Optional[int]
assignment_id: Optional[int]
org_id: Optional[int]
course_id: Optional[int]
chapter_id: Optional[int]
activity_id: Optional[int]
class AssignmentTask(AssignmentTaskBase, table=True):
"""Represents a task within an assignment with various attributes and foreign keys."""
id: Optional[int] = Field(default=None, primary_key=True)
assignment_task_uuid: str
creation_date: str
update_date: str
assignment_id: int = Field(
sa_column=Column(
"assignment_id", ForeignKey("assignment.id", ondelete="CASCADE")
)
)
org_id: int = Field(
sa_column=Column("org_id", ForeignKey("organization.id", ondelete="CASCADE"))
)
course_id: int = Field(
sa_column=Column("course_id", ForeignKey("course.id", ondelete="CASCADE"))
)
chapter_id: int = Field(
sa_column=Column("chapter_id", ForeignKey("chapter.id", ondelete="CASCADE"))
)
activity_id: int = Field(
sa_column=Column("activity_id", ForeignKey("activity.id", ondelete="CASCADE"))
)
## AssignmentTask ##
## AssignmentTaskSubmission ##
class AssignmentTaskSubmissionBase(SQLModel):
"""Represents the common fields for an assignment task submission."""
task_submission: Dict = Field(default={}, sa_column=Column(JSON))
grade: int = 0 # Value is always between 0-100
task_submission_grade_feedback: str
assignment_type: AssignmentTaskTypeEnum
user_id: int
activity_id: int
course_id: int
chapter_id: int
assignment_task_id: int
class AssignmentTaskSubmissionCreate(AssignmentTaskSubmissionBase):
"""Model for creating a new assignment task submission."""
pass # Inherits all fields from AssignmentTaskSubmissionBase
class AssignmentTaskSubmissionRead(AssignmentTaskSubmissionBase):
"""Model for reading an assignment task submission."""
id: int
creation_date: str
update_date: str
class AssignmentTaskSubmissionUpdate(SQLModel):
"""Model for updating an assignment task submission."""
assignment_task_submission_uuid: Optional[str]
task_submission: Optional[Dict] = Field(default={}, sa_column=Column(JSON))
grade: Optional[int]
task_submission_grade_feedback: Optional[str]
assignment_type: Optional[AssignmentTaskTypeEnum]
user_id: Optional[int]
activity_id: Optional[int]
course_id: Optional[int]
chapter_id: Optional[int]
assignment_task_id: Optional[int]
class AssignmentTaskSubmission(AssignmentTaskSubmissionBase, table=True):
"""Represents a submission for a specific assignment task with grade and feedback."""
id: Optional[int] = Field(default=None, primary_key=True)
assignment_task_submission_uuid: str
task_submission: Dict = Field(default={}, sa_column=Column(JSON))
grade: int = 0 # Value is always between 0-100
task_submission_grade_feedback: str
assignment_type: AssignmentTaskTypeEnum
user_id: int = Field(
sa_column=Column("user_id", ForeignKey("user.id", ondelete="CASCADE"))
)
activity_id: int = Field(
sa_column=Column("activity_id", ForeignKey("activity.id", ondelete="CASCADE"))
)
course_id: int = Field(
sa_column=Column("course_id", ForeignKey("course.id", ondelete="CASCADE"))
)
chapter_id: int = Field(
sa_column=Column("chapter_id", ForeignKey("chapter.id", ondelete="CASCADE"))
)
assignment_task_id: int = Field(
sa_column=Column(
"assignment_task_id", ForeignKey("assignmenttask.id", ondelete="CASCADE")
)
)
creation_date: str
update_date: str
## AssignmentTaskSubmission ##
## AssignmentUserSubmission ##
class AssignmentUserSubmissionStatus(str, Enum):
PENDING = "PENDING"
SUBMITTED = "SUBMITTED"
GRADED = "GRADED"
LATE = "LATE"
NOT_SUBMITTED = "NOT_SUBMITTED"
class AssignmentUserSubmissionBase(SQLModel):
"""Represents the submission status of an assignment for a user."""
submission_status: AssignmentUserSubmissionStatus = (
AssignmentUserSubmissionStatus.PENDING
)
grade: str
user_id: int = Field(
sa_column=Column("user_id", ForeignKey("user.id", ondelete="CASCADE"))
)
assignment_id: int = Field(
sa_column=Column(
"assignment_id", ForeignKey("assignment.id", ondelete="CASCADE")
)
)
class AssignmentUserSubmissionCreate(AssignmentUserSubmissionBase):
"""Model for creating a new assignment user submission."""
pass # Inherits all fields from AssignmentUserSubmissionBase
class AssignmentUserSubmissionRead(AssignmentUserSubmissionBase):
"""Model for reading an assignment user submission."""
id: int
creation_date: str
update_date: str
class AssignmentUserSubmissionUpdate(SQLModel):
"""Model for updating an assignment user submission."""
submission_status: Optional[AssignmentUserSubmissionStatus]
grade: Optional[str]
user_id: Optional[int]
assignment_id: Optional[int]
class AssignmentUserSubmission(AssignmentUserSubmissionBase, table=True):
"""Represents the submission status of an assignment for a user."""
id: Optional[int] = Field(default=None, primary_key=True)
creation_date: str
update_date: str
submission_status: AssignmentUserSubmissionStatus = (
AssignmentUserSubmissionStatus.PENDING
)
grade: str
user_id: int = Field(
sa_column=Column("user_id", ForeignKey("user.id", ondelete="CASCADE"))
)
assignment_id: int = Field(
sa_column=Column(
"assignment_id", ForeignKey("assignment.id", ondelete="CASCADE")
)
)

View file

@ -35,7 +35,7 @@ class BlockCreate(BlockBase):
class BlockRead(BlockBase): class BlockRead(BlockBase):
id: int id: int = Field(default=None, primary_key=True)
org_id: int = Field(default=None, foreign_key="organization.id") org_id: int = Field(default=None, foreign_key="organization.id")
course_id: int = Field(default=None, foreign_key="course.id") course_id: int = Field(default=None, foreign_key="course.id")
chapter_id: int = Field(default=None, foreign_key="chapter.id") chapter_id: int = Field(default=None, foreign_key="chapter.id")

View file

@ -2,7 +2,7 @@ from typing import Any, List, Optional
from pydantic import BaseModel from pydantic import BaseModel
from sqlalchemy import Column, ForeignKey from sqlalchemy import Column, ForeignKey
from sqlmodel import Field, SQLModel from sqlmodel import Field, SQLModel
from src.db.activities import ActivityRead from src.db.courses.activities import ActivityRead
class ChapterBase(SQLModel): class ChapterBase(SQLModel):
@ -33,10 +33,10 @@ class ChapterCreate(ChapterBase):
class ChapterUpdate(ChapterBase): class ChapterUpdate(ChapterBase):
name: Optional[str] name: Optional[str]
description: Optional[str] description: Optional[str] = ""
thumbnail_image: Optional[str] thumbnail_image: Optional[str] = ""
course_id: Optional[int] course_id: Optional[int]
org_id: Optional[int] org_id: Optional[int] # type: ignore
class ChapterRead(ChapterBase): class ChapterRead(ChapterBase):

View file

@ -3,7 +3,7 @@ from sqlalchemy import Column, ForeignKey, Integer
from sqlmodel import Field, SQLModel from sqlmodel import Field, SQLModel
from src.db.users import UserRead from src.db.users import UserRead
from src.db.trails import TrailRead from src.db.trails import TrailRead
from src.db.chapters import ChapterRead from src.db.courses.chapters import ChapterRead
class CourseBase(SQLModel): class CourseBase(SQLModel):

View file

@ -6,8 +6,12 @@ from src.db.trail_runs import TrailRunRead
class TrailBase(SQLModel): class TrailBase(SQLModel):
org_id: int = Field(default=None, foreign_key="organization.id") org_id: int = Field(
user_id: int = Field(default=None, foreign_key="user.id") sa_column=Column(Integer, ForeignKey("organization.id", ondelete="CASCADE"))
)
user_id: int = Field(
sa_column=Column(Integer, ForeignKey("user.id", ondelete="CASCADE"))
)
class Trail(TrailBase, table=True): class Trail(TrailBase, table=True):
@ -20,6 +24,7 @@ class Trail(TrailBase, table=True):
class TrailCreate(TrailBase): class TrailCreate(TrailBase):
pass pass
# TODO: This is a hacky way to get around the list[TrailRun] issue, find a better way to do this # TODO: This is a hacky way to get around the list[TrailRun] issue, find a better way to do this
class TrailRead(BaseModel): class TrailRead(BaseModel):
id: Optional[int] = Field(default=None, primary_key=True) id: Optional[int] = Field(default=None, primary_key=True)

View file

@ -1,8 +1,9 @@
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends
from src.routers import usergroups from src.routers import usergroups
from src.routers import blocks, dev, trail, users, auth, orgs, roles from src.routers import dev, trail, users, auth, orgs, roles
from src.routers.ai import ai from src.routers.ai import ai
from src.routers.courses import chapters, collections, courses, activities from src.routers.courses import chapters, collections, courses, assignments
from src.routers.courses.activities import activities, blocks
from src.routers.install import install from src.routers.install import install
from src.services.dev.dev import isDevModeEnabledOrRaise from src.services.dev.dev import isDevModeEnabledOrRaise
from src.services.install.install import isInstallModeEnabled from src.services.install.install import isInstallModeEnabled
@ -19,6 +20,7 @@ v1_router.include_router(orgs.router, prefix="/orgs", tags=["orgs"])
v1_router.include_router(roles.router, prefix="/roles", tags=["roles"]) v1_router.include_router(roles.router, prefix="/roles", tags=["roles"])
v1_router.include_router(blocks.router, prefix="/blocks", tags=["blocks"]) v1_router.include_router(blocks.router, prefix="/blocks", tags=["blocks"])
v1_router.include_router(courses.router, prefix="/courses", tags=["courses"]) v1_router.include_router(courses.router, prefix="/courses", tags=["courses"])
v1_router.include_router(assignments.router, prefix="/assignments", tags=["assignments"])
v1_router.include_router(chapters.router, prefix="/chapters", tags=["chapters"]) v1_router.include_router(chapters.router, prefix="/chapters", tags=["chapters"])
v1_router.include_router(activities.router, prefix="/activities", tags=["activities"]) v1_router.include_router(activities.router, prefix="/activities", tags=["activities"])
v1_router.include_router(collections.router, prefix="/collections", tags=["collections"]) v1_router.include_router(collections.router, prefix="/collections", tags=["collections"])

View file

@ -1,6 +1,6 @@
from typing import List from typing import List
from fastapi import APIRouter, Depends, UploadFile, Form, Request from fastapi import APIRouter, Depends, UploadFile, Form, Request
from src.db.activities import ActivityCreate, ActivityRead, ActivityUpdate from src.db.courses.activities import ActivityCreate, ActivityRead, ActivityUpdate
from src.db.users import PublicUser from src.db.users import PublicUser
from src.core.events.database import get_db_session from src.core.events.database import get_db_session
from src.services.courses.activities.activities import ( from src.services.courses.activities.activities import (

View file

@ -1,5 +1,5 @@
from fastapi import APIRouter, Depends, UploadFile, Form, Request from fastapi import APIRouter, Depends, UploadFile, Form, Request
from src.db.blocks import BlockRead from src.db.courses.blocks import BlockRead
from src.core.events.database import get_db_session from src.core.events.database import get_db_session
from src.security.auth import get_current_user from src.security.auth import get_current_user
from src.services.blocks.block_types.imageBlock.imageBlock import ( from src.services.blocks.block_types.imageBlock.imageBlock import (

View file

@ -0,0 +1,310 @@
from fastapi import APIRouter, Depends, Request
from src.db.courses.assignments import (
AssignmentCreate,
AssignmentRead,
AssignmentTaskCreate,
AssignmentTaskSubmissionCreate,
AssignmentTaskUpdate,
AssignmentUpdate,
AssignmentUserSubmissionCreate,
)
from src.db.users import PublicUser
from src.core.events.database import get_db_session
from src.security.auth import get_current_user
from src.services.courses.activities.assignments import (
create_assignment,
create_assignment_submission,
create_assignment_task,
create_assignment_task_submission,
delete_assignment,
delete_assignment_submission,
delete_assignment_task,
delete_assignment_task_submission,
read_assignment,
read_assignment_submissions,
read_assignment_task_submissions,
read_assignment_tasks,
read_user_assignment_submissions,
read_user_assignment_task_submissions,
update_assignment,
update_assignment_submission,
update_assignment_task,
)
router = APIRouter()
## ASSIGNMENTS ##
@router.post("/")
async def api_create_assignments(
request: Request,
assignment_object: AssignmentCreate,
current_user: PublicUser = Depends(get_current_user),
db_session=Depends(get_db_session),
) -> AssignmentRead:
"""
Create new activity
"""
return await create_assignment(request, assignment_object, current_user, db_session)
@router.get("/{assignment_uuid}")
async def api_read_assignment(
request: Request,
assignment_uuid: str,
current_user: PublicUser = Depends(get_current_user),
db_session=Depends(get_db_session),
) -> AssignmentRead:
"""
Read an assignment
"""
return await read_assignment(request, assignment_uuid, current_user, db_session)
@router.put("/{assignment_uuid}")
async def api_update_assignment(
request: Request,
assignment_uuid: str,
assignment_object: AssignmentUpdate,
current_user: PublicUser = Depends(get_current_user),
db_session=Depends(get_db_session),
) -> AssignmentRead:
"""
Update an assignment
"""
return await update_assignment(
request, assignment_uuid, assignment_object, current_user, db_session
)
@router.delete("/{assignment_uuid}")
async def api_delete_assignment(
request: Request,
assignment_uuid: str,
current_user: PublicUser = Depends(get_current_user),
db_session=Depends(get_db_session),
):
"""
Delete an assignment
"""
return await delete_assignment(request, assignment_uuid, current_user, db_session)
## ASSIGNMENTS Tasks ##
@router.post("/{assignment_uuid}/tasks")
async def api_create_assignment_tasks(
request: Request,
assignment_uuid: str,
assignment_task_object: AssignmentTaskCreate,
current_user: PublicUser = Depends(get_current_user),
db_session=Depends(get_db_session),
):
"""
Create new tasks for an assignment
"""
return await create_assignment_task(
request, assignment_uuid, assignment_task_object, current_user, db_session
)
@router.get("/{assignment_uuid}/tasks")
async def api_read_assignment_tasks(
request: Request,
assignment_uuid: str,
current_user: PublicUser = Depends(get_current_user),
db_session=Depends(get_db_session),
):
"""
Read tasks for an assignment
"""
return await read_assignment_tasks(
request, assignment_uuid, current_user, db_session
)
@router.put("/{assignment_uuid}/tasks/{task_uuid}")
async def api_update_assignment_tasks(
request: Request,
assignment_task_uuid: str,
assignment_task_object: AssignmentTaskUpdate,
current_user: PublicUser = Depends(get_current_user),
db_session=Depends(get_db_session),
):
"""
Update tasks for an assignment
"""
return await update_assignment_task(
request, assignment_task_uuid, assignment_task_object, current_user, db_session
)
@router.delete("/{assignment_uuid}/tasks/{task_uuid}")
async def api_delete_assignment_tasks(
request: Request,
assignment_task_uuid: str,
current_user: PublicUser = Depends(get_current_user),
db_session=Depends(get_db_session),
):
"""
Delete tasks for an assignment
"""
return await delete_assignment_task(
request, assignment_task_uuid, current_user, db_session
)
## ASSIGNMENTS Tasks Submissions ##
@router.post("/{assignment_uuid}/tasks/{assignment_task_uuid}/submissions")
async def api_create_assignment_task_submissions(
request: Request,
assignment_task_submission_object: AssignmentTaskSubmissionCreate,
assignment_task_uuid: str,
current_user: PublicUser = Depends(get_current_user),
db_session=Depends(get_db_session),
):
"""
Create new task submissions for an assignment
"""
return await create_assignment_task_submission(
request,
assignment_task_uuid,
assignment_task_submission_object,
current_user,
db_session,
)
@router.get("/{assignment_uuid}/tasks/{assignment_task_uuid}/submissions/{user_id}")
async def api_read_user_assignment_task_submissions(
request: Request,
assignment_task_uuid: str,
user_id: int,
current_user: PublicUser = Depends(get_current_user),
db_session=Depends(get_db_session),
):
"""
Read task submissions for an assignment from a user
"""
return await read_user_assignment_task_submissions(
request, assignment_task_uuid, user_id, current_user, db_session
)
@router.get("/{assignment_uuid}/tasks/{assignment_task_uuid}/submissions")
async def api_read_assignment_task_submissions(
request: Request,
assignment_task_uuid: str,
current_user: PublicUser = Depends(get_current_user),
db_session=Depends(get_db_session),
):
"""
Read task submissions for an assignment from a user
"""
return await read_assignment_task_submissions(
request, assignment_task_uuid, current_user, db_session
)
@router.delete(
"/{assignment_uuid}/tasks/{assignment_task_uuid}/submissions/{assignment_task_submission_uuid}"
)
async def api_delete_assignment_task_submissions(
request: Request,
assignment_task_submission_uuid: str,
current_user: PublicUser = Depends(get_current_user),
db_session=Depends(get_db_session),
):
"""
Delete task submissions for an assignment from a user
"""
return await delete_assignment_task_submission(
request, assignment_task_submission_uuid, current_user, db_session
)
## ASSIGNMENTS Submissions ##
@router.post("/{assignment_uuid}/submissions")
async def api_create_assignment_submissions(
request: Request,
assignment_uuid: str,
assignment_submission: AssignmentUserSubmissionCreate,
current_user: PublicUser = Depends(get_current_user),
db_session=Depends(get_db_session),
):
"""
Create new submissions for an assignment
"""
return await create_assignment_submission(
request, assignment_uuid, assignment_submission, current_user, db_session
)
@router.get("/{assignment_uuid}/submissions")
async def api_read_assignment_submissions(
request: Request,
assignment_uuid: str,
current_user: PublicUser = Depends(get_current_user),
db_session=Depends(get_db_session),
):
"""
Read submissions for an assignment
"""
return await read_assignment_submissions(
request, assignment_uuid, current_user, db_session
)
@router.get("/{assignment_uuid}/submissions/{user_id}")
async def api_read_user_assignment_submissions(
request: Request,
assignment_uuid: str,
user_id: int,
current_user: PublicUser = Depends(get_current_user),
db_session=Depends(get_db_session),
):
"""
Read submissions for an assignment from a user
"""
return await read_user_assignment_submissions(
request, assignment_uuid, user_id, current_user, db_session
)
@router.put("/{assignment_uuid}/submissions/{user_id}")
async def api_update_user_assignment_submissions(
request: Request,
assignment_uuid: str,
user_id: str,
assignment_submission: AssignmentUserSubmissionCreate,
current_user: PublicUser = Depends(get_current_user),
db_session=Depends(get_db_session),
):
"""
Update submissions for an assignment from a user
"""
return await update_assignment_submission(
request, user_id, assignment_submission, current_user, db_session
)
@router.delete("/{assignment_uuid}/submissions/{user_id}")
async def api_delete_user_assignment_submissions(
request: Request,
assignment_id: str,
user_id: str,
current_user: PublicUser = Depends(get_current_user),
db_session=Depends(get_db_session),
):
"""
Delete submissions for an assignment from a user
"""
return await delete_assignment_submission(
request, assignment_id, user_id, current_user, db_session
)

View file

@ -1,7 +1,7 @@
from typing import List from typing import List
from fastapi import APIRouter, Depends, Request from fastapi import APIRouter, Depends, Request
from src.core.events.database import get_db_session from src.core.events.database import get_db_session
from src.db.chapters import ( from src.db.courses.chapters import (
ChapterCreate, ChapterCreate,
ChapterRead, ChapterRead,
ChapterUpdate, ChapterUpdate,

View file

@ -2,13 +2,13 @@ from typing import List
from fastapi import APIRouter, Depends, UploadFile, Form, Request from fastapi import APIRouter, Depends, UploadFile, Form, Request
from sqlmodel import Session from sqlmodel import Session
from src.core.events.database import get_db_session from src.core.events.database import get_db_session
from src.db.course_updates import ( from src.db.courses.course_updates import (
CourseUpdateCreate, CourseUpdateCreate,
CourseUpdateRead, CourseUpdateRead,
CourseUpdateUpdate, CourseUpdateUpdate,
) )
from src.db.users import PublicUser from src.db.users import PublicUser
from src.db.courses import ( from src.db.courses.courses import (
CourseCreate, CourseCreate,
CourseRead, CourseRead,
CourseUpdate, CourseUpdate,

View file

@ -3,7 +3,7 @@ from fastapi import HTTPException, status, Request
from sqlalchemy import null from sqlalchemy import null
from sqlmodel import Session, select from sqlmodel import Session, select
from src.db.collections import Collection from src.db.collections import Collection
from src.db.courses import Course from src.db.courses.courses import Course
from src.db.resource_authors import ResourceAuthor, ResourceAuthorshipEnum from src.db.resource_authors import ResourceAuthor, ResourceAuthorshipEnum
from src.db.roles import Role from src.db.roles import Role
from src.db.user_organizations import UserOrganization from src.db.user_organizations import UserOrganization

View file

@ -3,10 +3,10 @@ from sqlmodel import Session, select
from src.db.organization_config import OrganizationConfig from src.db.organization_config import OrganizationConfig
from src.db.organizations import Organization from src.db.organizations import Organization
from src.services.ai.utils import check_limits_and_config, count_ai_ask from src.services.ai.utils import check_limits_and_config, count_ai_ask
from src.db.courses import Course, CourseRead from src.db.courses.courses import Course, CourseRead
from src.core.events.database import get_db_session from src.core.events.database import get_db_session
from src.db.users import PublicUser from src.db.users import PublicUser
from src.db.activities import Activity, ActivityRead from src.db.courses.activities import Activity, ActivityRead
from src.security.auth import get_current_user from src.security.auth import get_current_user
from src.services.ai.base import ask_ai, get_chat_session_history from src.services.ai.base import ask_ai, get_chat_session_history

View file

@ -3,9 +3,9 @@ from uuid import uuid4
from src.db.organizations import Organization from src.db.organizations import Organization
from fastapi import HTTPException, status, UploadFile, Request from fastapi import HTTPException, status, UploadFile, Request
from sqlmodel import Session, select from sqlmodel import Session, select
from src.db.activities import Activity from src.db.courses.activities import Activity
from src.db.blocks import Block, BlockRead, BlockTypeEnum from src.db.courses.blocks import Block, BlockRead, BlockTypeEnum
from src.db.courses import Course from src.db.courses.courses import Course
from src.services.blocks.utils.upload_files import upload_file_and_return_file_object from src.services.blocks.utils.upload_files import upload_file_and_return_file_object
from src.services.users.users import PublicUser from src.services.users.users import PublicUser

View file

@ -3,9 +3,9 @@ from uuid import uuid4
from src.db.organizations import Organization from src.db.organizations import Organization
from fastapi import HTTPException, status, UploadFile, Request from fastapi import HTTPException, status, UploadFile, Request
from sqlmodel import Session, select from sqlmodel import Session, select
from src.db.activities import Activity from src.db.courses.activities import Activity
from src.db.blocks import Block, BlockRead, BlockTypeEnum from src.db.courses.blocks import Block, BlockRead, BlockTypeEnum
from src.db.courses import Course from src.db.courses.courses import Course
from src.services.blocks.utils.upload_files import upload_file_and_return_file_object from src.services.blocks.utils.upload_files import upload_file_and_return_file_object
from src.services.users.users import PublicUser from src.services.users.users import PublicUser

View file

@ -3,9 +3,9 @@ from uuid import uuid4
from src.db.organizations import Organization from src.db.organizations import Organization
from fastapi import HTTPException, status, UploadFile, Request from fastapi import HTTPException, status, UploadFile, Request
from sqlmodel import Session, select from sqlmodel import Session, select
from src.db.activities import Activity from src.db.courses.activities import Activity
from src.db.blocks import Block, BlockRead, BlockTypeEnum from src.db.courses.blocks import Block, BlockRead, BlockTypeEnum
from src.db.courses import Course from src.db.courses.courses import Course
from src.services.blocks.utils.upload_files import upload_file_and_return_file_object from src.services.blocks.utils.upload_files import upload_file_and_return_file_object
from src.services.users.users import PublicUser from src.services.users.users import PublicUser

View file

@ -1,14 +1,14 @@
from typing import Literal from typing import Literal
from sqlmodel import Session, select from sqlmodel import Session, select
from src.db.courses import Course from src.db.courses.courses import Course
from src.db.chapters import Chapter from src.db.courses.chapters import Chapter
from src.security.rbac.rbac import ( from src.security.rbac.rbac import (
authorization_verify_based_on_roles_and_authorship_and_usergroups, authorization_verify_based_on_roles_and_authorship_and_usergroups,
authorization_verify_if_element_is_public, authorization_verify_if_element_is_public,
authorization_verify_if_user_is_anon, authorization_verify_if_user_is_anon,
) )
from src.db.activities import ActivityCreate, Activity, ActivityRead, ActivityUpdate from src.db.courses.activities import ActivityCreate, Activity, ActivityRead, ActivityUpdate
from src.db.chapter_activities import ChapterActivity from src.db.courses.chapter_activities import ChapterActivity
from src.db.users import AnonymousUser, PublicUser from src.db.users import AnonymousUser, PublicUser
from fastapi import HTTPException, Request from fastapi import HTTPException, Request
from uuid import uuid4 from uuid import uuid4
@ -58,7 +58,7 @@ async def create_activity(
statement = ( statement = (
select(ChapterActivity) select(ChapterActivity)
.where(ChapterActivity.chapter_id == activity_object.chapter_id) .where(ChapterActivity.chapter_id == activity_object.chapter_id)
.order_by(ChapterActivity.order) .order_by(ChapterActivity.order) # type: ignore
) )
chapter_activities = db_session.exec(statement).all() chapter_activities = db_session.exec(statement).all()

View file

@ -0,0 +1,997 @@
####################################################
# CRUD
####################################################
from datetime import datetime
from typing import Literal
from uuid import uuid4
from fastapi import HTTPException, Request
from sqlmodel import Session, select
from src.db.courses.assignments import (
Assignment,
AssignmentCreate,
AssignmentRead,
AssignmentTask,
AssignmentTaskCreate,
AssignmentTaskRead,
AssignmentTaskSubmission,
AssignmentTaskSubmissionCreate,
AssignmentTaskSubmissionRead,
AssignmentTaskUpdate,
AssignmentUpdate,
AssignmentUserSubmission,
AssignmentUserSubmissionCreate,
AssignmentUserSubmissionRead,
)
from src.db.courses.courses import Course
from src.db.users import AnonymousUser, PublicUser
from src.security.rbac.rbac import (
authorization_verify_based_on_roles_and_authorship_and_usergroups,
authorization_verify_if_element_is_public,
authorization_verify_if_user_is_anon,
)
## > Assignments CRUD
async def create_assignment(
request: Request,
assignment_object: AssignmentCreate,
current_user: PublicUser | AnonymousUser,
db_session: Session,
):
# Check if org exists
statement = select(Course).where(Course.id == assignment_object.course_id)
course = db_session.exec(statement).first()
if not course:
raise HTTPException(
status_code=404,
detail="Course not found",
)
# RBAC check
await rbac_check(request, course.course_uuid, current_user, "create", db_session)
# Create Assignment
assignment = Assignment(**assignment_object.model_dump())
assignment.assignment_uuid = str(f"assignment_{uuid4()}")
assignment.creation_date = str(datetime.now())
assignment.update_date = str(datetime.now())
assignment.org_id = course.org_id
# Insert Assignment in DB
db_session.add(assignment)
db_session.commit()
db_session.refresh(assignment)
# return assignment read
return AssignmentRead.model_validate(assignment)
async def read_assignment(
request: Request,
assignment_uuid: str,
current_user: PublicUser | AnonymousUser,
db_session: Session,
):
# Check if assignment exists
statement = select(Assignment).where(Assignment.assignment_uuid == assignment_uuid)
assignment = db_session.exec(statement).first()
if not assignment:
raise HTTPException(
status_code=404,
detail="Assignment not found",
)
# Check if course exists
statement = select(Course).where(Course.id == assignment.course_id)
course = db_session.exec(statement).first()
if not course:
raise HTTPException(
status_code=404,
detail="Course not found",
)
# RBAC check
await rbac_check(request, course.course_uuid, current_user, "read", db_session)
# return assignment read
return AssignmentRead.model_validate(assignment)
async def update_assignment(
request: Request,
assignment_uuid: str,
assignment_object: AssignmentUpdate,
current_user: PublicUser | AnonymousUser,
db_session: Session,
):
# Check if assignment exists
statement = select(Assignment).where(Assignment.assignment_uuid == assignment_uuid)
assignment = db_session.exec(statement).first()
if not assignment:
raise HTTPException(
status_code=404,
detail="Assignment not found",
)
# Check if course exists
statement = select(Course).where(Course.id == assignment.course_id)
course = db_session.exec(statement).first()
if not course:
raise HTTPException(
status_code=404,
detail="Course not found",
)
# RBAC check
await rbac_check(request, course.course_uuid, current_user, "update", db_session)
# Update only the fields that were passed in
for var, value in vars(assignment_object).items():
if value is not None:
setattr(assignment, var, value)
assignment.update_date = str(datetime.now())
# Insert Assignment in DB
db_session.add(assignment)
db_session.commit()
db_session.refresh(assignment)
# return assignment read
return AssignmentRead.model_validate(assignment)
async def delete_assignment(
request: Request,
assignment_uuid: str,
current_user: PublicUser | AnonymousUser,
db_session: Session,
):
# Check if assignment exists
statement = select(Assignment).where(Assignment.assignment_uuid == assignment_uuid)
assignment = db_session.exec(statement).first()
if not assignment:
raise HTTPException(
status_code=404,
detail="Assignment not found",
)
# Check if course exists
statement = select(Course).where(Course.id == assignment.course_id)
course = db_session.exec(statement).first()
if not course:
raise HTTPException(
status_code=404,
detail="Course not found",
)
# RBAC check
await rbac_check(request, course.course_uuid, current_user, "delete", db_session)
# Delete Assignment
db_session.delete(assignment)
db_session.commit()
return {"message": "Assignment deleted"}
## > Assignments Tasks CRUD
async def create_assignment_task(
request: Request,
assignment_uuid: str,
assignment_task_object: AssignmentTaskCreate,
current_user: PublicUser | AnonymousUser,
db_session: Session,
):
# Check if assignment exists
statement = select(Assignment).where(Assignment.assignment_uuid == assignment_uuid)
assignment = db_session.exec(statement).first()
if not assignment:
raise HTTPException(
status_code=404,
detail="Assignment not found",
)
# Check if course exists
statement = select(Course).where(Course.id == assignment.course_id)
course = db_session.exec(statement).first()
if not course:
raise HTTPException(
status_code=404,
detail="Course not found",
)
# RBAC check
await rbac_check(request, course.course_uuid, current_user, "create", db_session)
# Create Assignment Task
assignment_task = AssignmentTask(**assignment_task_object.model_dump())
assignment_task.assignment_task_uuid = str(f"assignmenttask_{uuid4()}")
assignment_task.creation_date = str(datetime.now())
assignment_task.update_date = str(datetime.now())
assignment_task.org_id = course.org_id
# Insert Assignment Task in DB
db_session.add(assignment_task)
db_session.commit()
db_session.refresh(assignment_task)
# return assignment task read
return AssignmentTaskRead.model_validate(assignment_task)
async def read_assignment_tasks(
request: Request,
assignment_uuid: str,
current_user: PublicUser | AnonymousUser,
db_session: Session,
):
# Find assignment
statement = select(Assignment).where(Assignment.assignment_uuid == assignment_uuid)
assignment = db_session.exec(statement).first()
if not assignment:
raise HTTPException(
status_code=404,
detail="Assignment not found",
)
# Check if course exists
statement = select(Course).where(Course.id == assignment.course_id)
course = db_session.exec(statement).first()
if not course:
raise HTTPException(
status_code=404,
detail="Course not found",
)
# Find assignments tasks for an assignment
statement = select(AssignmentTask).where(
assignment.assignment_uuid == assignment_uuid
)
# RBAC check
await rbac_check(request, course.course_uuid, current_user, "read", db_session)
# return assignment tasks read
return [
AssignmentTaskRead.model_validate(assignment_task)
for assignment_task in db_session.exec(statement).all()
]
async def update_assignment_task(
request: Request,
assignment_task_uuid: str,
assignment_task_object: AssignmentTaskUpdate,
current_user: PublicUser | AnonymousUser,
db_session: Session,
):
# Check if assignment task exists
statement = select(AssignmentTask).where(
AssignmentTask.assignment_task_uuid == assignment_task_uuid
)
assignment_task = db_session.exec(statement).first()
if not assignment_task:
raise HTTPException(
status_code=404,
detail="Assignment Task not found",
)
# Check if assignment exists
statement = select(Assignment).where(Assignment.id == assignment_task.assignment_id)
assignment = db_session.exec(statement).first()
if not assignment:
raise HTTPException(
status_code=404,
detail="Assignment not found",
)
# Check if course exists
statement = select(Course).where(Course.id == assignment.course_id)
course = db_session.exec(statement).first()
if not course:
raise HTTPException(
status_code=404,
detail="Course not found",
)
# RBAC check
await rbac_check(request, course.course_uuid, current_user, "update", db_session)
# Update only the fields that were passed in
for var, value in vars(assignment_task_object).items():
if value is not None:
setattr(assignment_task, var, value)
assignment_task.update_date = str(datetime.now())
# Insert Assignment Task in DB
db_session.add(assignment_task)
db_session.commit()
db_session.refresh(assignment_task)
# return assignment task read
return AssignmentTaskRead.model_validate(assignment_task)
async def delete_assignment_task(
request: Request,
assignment_task_uuid: str,
current_user: PublicUser | AnonymousUser,
db_session: Session,
):
# Check if assignment task exists
statement = select(AssignmentTask).where(
AssignmentTask.assignment_task_uuid == assignment_task_uuid
)
assignment_task = db_session.exec(statement).first()
if not assignment_task:
raise HTTPException(
status_code=404,
detail="Assignment Task not found",
)
# Check if assignment exists
statement = select(Assignment).where(Assignment.id == assignment_task.assignment_id)
assignment = db_session.exec(statement).first()
if not assignment:
raise HTTPException(
status_code=404,
detail="Assignment not found",
)
# Check if course exists
statement = select(Course).where(Course.id == assignment.course_id)
course = db_session.exec(statement).first()
if not course:
raise HTTPException(
status_code=404,
detail="Course not found",
)
# RBAC check
await rbac_check(request, course.course_uuid, current_user, "delete", db_session)
# Delete Assignment Task
db_session.delete(assignment_task)
db_session.commit()
return {"message": "Assignment Task deleted"}
## > Assignments Tasks Submissions CRUD
async def create_assignment_task_submission(
request: Request,
assignment_task_uuid: str,
assignment_task_submission_object: AssignmentTaskSubmissionCreate,
current_user: PublicUser | AnonymousUser,
db_session: Session,
):
# Check if assignment task exists
statement = select(AssignmentTask).where(
AssignmentTask.assignment_task_uuid == assignment_task_uuid
)
assignment_task = db_session.exec(statement).first()
if not assignment_task:
raise HTTPException(
status_code=404,
detail="Assignment Task not found",
)
# Check if assignment exists
statement = select(Assignment).where(Assignment.id == assignment_task.assignment_id)
assignment = db_session.exec(statement).first()
if not assignment:
raise HTTPException(
status_code=404,
detail="Assignment not found",
)
# Check if course exists
statement = select(Course).where(Course.id == assignment.course_id)
course = db_session.exec(statement).first()
if not course:
raise HTTPException(
status_code=404,
detail="Course not found",
)
# RBAC check
await rbac_check(request, course.course_uuid, current_user, "create", db_session)
# Create Assignment Task Submission
assignment_task_submission = AssignmentTaskSubmission(
**assignment_task_submission_object.model_dump()
)
assignment_task_submission.assignment_task_submission_uuid = str(
f"assignmenttasksubmission_{uuid4()}"
)
assignment_task_submission.creation_date = str(datetime.now())
assignment_task_submission.update_date = str(datetime.now())
assignment_task_submission.org_id = course.org_id
# Insert Assignment Task Submission in DB
db_session.add(assignment_task_submission)
db_session.commit()
db_session.refresh(assignment_task_submission)
# return assignment task submission read
return AssignmentTaskSubmissionRead.model_validate(assignment_task_submission)
async def read_user_assignment_task_submissions(
request: Request,
assignment_task_submission_uuid: str,
user_id: int,
current_user: PublicUser | AnonymousUser,
db_session: Session,
):
# Check if assignment task submission exists
statement = select(AssignmentTaskSubmission).where(
AssignmentTaskSubmission.assignment_task_submission_uuid
== assignment_task_submission_uuid,
AssignmentTaskSubmission.user_id == user_id,
)
assignment_task_submission = db_session.exec(statement).first()
if not assignment_task_submission:
raise HTTPException(
status_code=404,
detail="Assignment Task Submission not found",
)
# Check if assignment task exists
statement = select(AssignmentTask).where(
AssignmentTask.id == assignment_task_submission.assignment_task_id
)
assignment_task = db_session.exec(statement).first()
if not assignment_task:
raise HTTPException(
status_code=404,
detail="Assignment Task not found",
)
# Check if assignment exists
statement = select(Assignment).where(Assignment.id == assignment_task.assignment_id)
assignment = db_session.exec(statement).first()
if not assignment:
raise HTTPException(
status_code=404,
detail="Assignment not found",
)
# Check if course exists
statement = select(Course).where(Course.id == assignment.course_id)
course = db_session.exec(statement).first()
if not course:
raise HTTPException(
status_code=404,
detail="Course not found",
)
# RBAC check
await rbac_check(request, course.course_uuid, current_user, "read", db_session)
# return assignment task submission read
return AssignmentTaskSubmissionRead.model_validate(assignment_task_submission)
async def read_assignment_task_submissions(
request: Request,
assignment_task_submission_uuid: str,
current_user: PublicUser | AnonymousUser,
db_session: Session,
):
# Check if assignment task submission exists
statement = select(AssignmentTaskSubmission).where(
AssignmentTaskSubmission.assignment_task_submission_uuid
== assignment_task_submission_uuid,
)
assignment_task_submission = db_session.exec(statement).first()
if not assignment_task_submission:
raise HTTPException(
status_code=404,
detail="Assignment Task Submission not found",
)
# Check if assignment task exists
statement = select(AssignmentTask).where(
AssignmentTask.id == assignment_task_submission.assignment_task_id
)
assignment_task = db_session.exec(statement).first()
if not assignment_task:
raise HTTPException(
status_code=404,
detail="Assignment Task not found",
)
# Check if assignment exists
statement = select(Assignment).where(Assignment.id == assignment_task.assignment_id)
assignment = db_session.exec(statement).first()
if not assignment:
raise HTTPException(
status_code=404,
detail="Assignment not found",
)
# Check if course exists
statement = select(Course).where(Course.id == assignment.course_id)
course = db_session.exec(statement).first()
if not course:
raise HTTPException(
status_code=404,
detail="Course not found",
)
# RBAC check
await rbac_check(request, course.course_uuid, current_user, "read", db_session)
# return assignment task submission read
return AssignmentTaskSubmissionRead.model_validate(assignment_task_submission)
async def update_assignment_task_submission(
request: Request,
assignment_task_submission_uuid: str,
assignment_task_submission_object: AssignmentTaskSubmissionCreate,
current_user: PublicUser | AnonymousUser,
db_session: Session,
):
# Check if assignment task submission exists
statement = select(AssignmentTaskSubmission).where(
AssignmentTaskSubmission.assignment_task_submission_uuid
== assignment_task_submission_uuid
)
assignment_task_submission = db_session.exec(statement).first()
if not assignment_task_submission:
raise HTTPException(
status_code=404,
detail="Assignment Task Submission not found",
)
# Check if assignment task exists
statement = select(AssignmentTask).where(
AssignmentTask.id == assignment_task_submission.assignment_task_id
)
assignment_task = db_session.exec(statement).first()
if not assignment_task:
raise HTTPException(
status_code=404,
detail="Assignment Task not found",
)
# Check if assignment exists
statement = select(Assignment).where(Assignment.id == assignment_task.assignment_id)
assignment = db_session.exec(statement).first()
if not assignment:
raise HTTPException(
status_code=404,
detail="Assignment not found",
)
# Check if course exists
statement = select(Course).where(Course.id == assignment.course_id)
course = db_session.exec(statement).first()
if not course:
raise HTTPException(
status_code=404,
detail="Course not found",
)
# RBAC check
await rbac_check(request, course.course_uuid, current_user, "update", db_session)
# Update only the fields that were passed in
for var, value in vars(assignment_task_submission_object).items():
if value is not None:
setattr(assignment_task_submission, var, value)
assignment_task_submission.update_date = str(datetime.now())
# Insert Assignment Task Submission in DB
db_session.add(assignment_task_submission)
db_session.commit()
db_session.refresh(assignment_task_submission)
# return assignment task submission read
return AssignmentTaskSubmissionRead.model_validate(assignment_task_submission)
async def delete_assignment_task_submission(
request: Request,
assignment_task_submission_uuid: str,
current_user: PublicUser | AnonymousUser,
db_session: Session,
):
# Check if assignment task submission exists
statement = select(AssignmentTaskSubmission).where(
AssignmentTaskSubmission.assignment_task_submission_uuid
== assignment_task_submission_uuid
)
assignment_task_submission = db_session.exec(statement).first()
if not assignment_task_submission:
raise HTTPException(
status_code=404,
detail="Assignment Task Submission not found",
)
# Check if assignment task exists
statement = select(AssignmentTask).where(
AssignmentTask.id == assignment_task_submission.assignment_task_id
)
assignment_task = db_session.exec(statement).first()
if not assignment_task:
raise HTTPException(
status_code=404,
detail="Assignment Task not found",
)
# Check if assignment exists
statement = select(Assignment).where(Assignment.id == assignment_task.assignment_id)
assignment = db_session.exec(statement).first()
if not assignment:
raise HTTPException(
status_code=404,
detail="Assignment not found",
)
# Check if course exists
statement = select(Course).where(Course.id == assignment.course_id)
course = db_session.exec(statement).first()
if not course:
raise HTTPException(
status_code=404,
detail="Course not found",
)
# RBAC check
await rbac_check(request, course.course_uuid, current_user, "delete", db_session)
# Delete Assignment Task Submission
db_session.delete(assignment_task_submission)
db_session.commit()
return {"message": "Assignment Task Submission deleted"}
## > Assignments Submissions CRUD
async def create_assignment_submission(
request: Request,
assignment_uuid: str,
assignment_user_submission_object: AssignmentUserSubmissionCreate,
current_user: PublicUser | AnonymousUser,
db_session: Session,
):
# Check if assignment exists
statement = select(Assignment).where(Assignment.assignment_uuid == assignment_uuid)
assignment = db_session.exec(statement).first()
if not assignment:
raise HTTPException(
status_code=404,
detail="Assignment not found",
)
# Check if the submission has already been made
statement = select(AssignmentUserSubmission).where(
AssignmentUserSubmission.assignment_id == assignment.id,
AssignmentUserSubmission.user_id == assignment_user_submission_object.user_id,
)
assignment_user_submission = db_session.exec(statement).first()
if assignment_user_submission:
raise HTTPException(
status_code=400,
detail="Assignment User Submission already exists",
)
# Check if course exists
statement = select(Course).where(Course.id == assignment.course_id)
course = db_session.exec(statement).first()
if not course:
raise HTTPException(
status_code=404,
detail="Course not found",
)
# RBAC check
await rbac_check(request, course.course_uuid, current_user, "create", db_session)
# Create Assignment User Submission
assignment_user_submission = AssignmentUserSubmission(
**assignment_user_submission_object.model_dump()
)
assignment_user_submission.assignment_user_submission_uuid = str(
f"assignmentusersubmission_{uuid4()}"
)
assignment_user_submission.creation_date = str(datetime.now())
assignment_user_submission.update_date = str(datetime.now())
assignment_user_submission.org_id = course.org_id
# Insert Assignment User Submission in DB
db_session.add(assignment_user_submission)
db_session.commit()
# return assignment user submission read
return AssignmentUserSubmissionRead.model_validate(assignment_user_submission)
async def read_assignment_submissions(
request: Request,
assignment_uuid: str,
current_user: PublicUser | AnonymousUser,
db_session: Session,
):
# Find assignment
statement = select(Assignment).where(Assignment.assignment_uuid == assignment_uuid)
assignment = db_session.exec(statement).first()
if not assignment:
raise HTTPException(
status_code=404,
detail="Assignment not found",
)
# Check if course exists
statement = select(Course).where(Course.id == assignment.course_id)
course = db_session.exec(statement).first()
if not course:
raise HTTPException(
status_code=404,
detail="Course not found",
)
# Find assignments tasks for an assignment
statement = select(AssignmentUserSubmission).where(
assignment.assignment_uuid == assignment_uuid
)
# RBAC check
await rbac_check(request, course.course_uuid, current_user, "read", db_session)
# return assignment tasks read
return [
AssignmentUserSubmissionRead.model_validate(assignment_user_submission)
for assignment_user_submission in db_session.exec(statement).all()
]
async def read_user_assignment_submissions(
request: Request,
assignment_uuid: str,
user_id: int,
current_user: PublicUser | AnonymousUser,
db_session: Session,
):
# Find assignment
statement = select(Assignment).where(Assignment.assignment_uuid == assignment_uuid)
assignment = db_session.exec(statement).first()
if not assignment:
raise HTTPException(
status_code=404,
detail="Assignment not found",
)
# Check if course exists
statement = select(Course).where(Course.id == assignment.course_id)
course = db_session.exec(statement).first()
if not course:
raise HTTPException(
status_code=404,
detail="Course not found",
)
# Find assignments tasks for an assignment
statement = select(AssignmentUserSubmission).where(
assignment.assignment_uuid == assignment_uuid,
AssignmentUserSubmission.user_id == user_id,
)
# RBAC check
await rbac_check(request, course.course_uuid, current_user, "read", db_session)
# return assignment tasks read
return [
AssignmentUserSubmissionRead.model_validate(assignment_user_submission)
for assignment_user_submission in db_session.exec(statement).all()
]
async def update_assignment_submission(
request: Request,
user_id: str,
assignment_user_submission_object: AssignmentUserSubmissionCreate,
current_user: PublicUser | AnonymousUser,
db_session: Session,
):
# Check if assignment user submission exists
statement = select(AssignmentUserSubmission).where(
AssignmentUserSubmission.user_id == user_id
)
assignment_user_submission = db_session.exec(statement).first()
if not assignment_user_submission:
raise HTTPException(
status_code=404,
detail="Assignment User Submission not found",
)
# Check if assignment exists
statement = select(Assignment).where(
Assignment.id == assignment_user_submission.assignment_id
)
assignment = db_session.exec(statement).first()
if not assignment:
raise HTTPException(
status_code=404,
detail="Assignment not found",
)
# Check if course exists
statement = select(Course).where(Course.id == assignment.course_id)
course = db_session.exec(statement).first()
if not course:
raise HTTPException(
status_code=404,
detail="Course not found",
)
# RBAC check
await rbac_check(request, course.course_uuid, current_user, "update", db_session)
# Update only the fields that were passed in
for var, value in vars(assignment_user_submission_object).items():
if value is not None:
setattr(assignment_user_submission, var, value)
assignment_user_submission.update_date = str(datetime.now())
# Insert Assignment User Submission in DB
db_session.add(assignment_user_submission)
db_session.commit()
db_session.refresh(assignment_user_submission)
# return assignment user submission read
return AssignmentUserSubmissionRead.model_validate(assignment_user_submission)
async def delete_assignment_submission(
request: Request,
user_id: str,
assignment_id: str,
current_user: PublicUser | AnonymousUser,
db_session: Session,
):
# Check if assignment user submission exists
statement = select(AssignmentUserSubmission).where(
AssignmentUserSubmission.user_id == user_id,
AssignmentUserSubmission.assignment_id == assignment_id,
)
assignment_user_submission = db_session.exec(statement).first()
if not assignment_user_submission:
raise HTTPException(
status_code=404,
detail="Assignment User Submission not found",
)
# Check if assignment exists
statement = select(Assignment).where(
Assignment.id == assignment_user_submission.assignment_id
)
assignment = db_session.exec(statement).first()
if not assignment:
raise HTTPException(
status_code=404,
detail="Assignment not found",
)
# Check if course exists
statement = select(Course).where(Course.id == assignment.course_id)
course = db_session.exec(statement).first()
if not course:
raise HTTPException(
status_code=404,
detail="Course not found",
)
# RBAC check
await rbac_check(request, course.course_uuid, current_user, "delete", db_session)
# Delete Assignment User Submission
db_session.delete(assignment_user_submission)
db_session.commit()
return {"message": "Assignment User Submission deleted"}
## 🔒 RBAC Utils ##
async def rbac_check(
request: Request,
course_uuid: str,
current_user: PublicUser | AnonymousUser,
action: Literal["create", "read", "update", "delete"],
db_session: Session,
):
if action == "read":
if current_user.id == 0: # Anonymous user
res = await authorization_verify_if_element_is_public(
request, course_uuid, action, db_session
)
return res
else:
res = (
await authorization_verify_based_on_roles_and_authorship_and_usergroups(
request, current_user.id, action, course_uuid, db_session
)
)
return res
else:
await authorization_verify_if_user_is_anon(current_user.id)
await authorization_verify_based_on_roles_and_authorship_and_usergroups(
request,
current_user.id,
action,
course_uuid,
db_session,
)
## 🔒 RBAC Utils ##

View file

@ -1,20 +1,20 @@
from typing import Literal from typing import Literal
from src.db.courses import Course from src.db.courses.courses import Course
from src.db.organizations import Organization from src.db.organizations import Organization
from sqlmodel import Session, select from sqlmodel import Session, select
from src.security.rbac.rbac import ( from src.security.rbac.rbac import (
authorization_verify_based_on_roles_and_authorship_and_usergroups, authorization_verify_based_on_roles_and_authorship_and_usergroups,
authorization_verify_if_user_is_anon, authorization_verify_if_user_is_anon,
) )
from src.db.chapters import Chapter from src.db.courses.chapters import Chapter
from src.db.activities import ( from src.db.courses.activities import (
Activity, Activity,
ActivityRead, ActivityRead,
ActivitySubTypeEnum, ActivitySubTypeEnum,
ActivityTypeEnum, ActivityTypeEnum,
) )
from src.db.chapter_activities import ChapterActivity from src.db.courses.chapter_activities import ChapterActivity
from src.db.course_chapters import CourseChapter from src.db.courses.course_chapters import CourseChapter
from src.db.users import AnonymousUser, PublicUser from src.db.users import AnonymousUser, PublicUser
from src.services.courses.activities.uploads.pdfs import upload_pdf from src.services.courses.activities.uploads.pdfs import upload_pdf
from fastapi import HTTPException, status, UploadFile, Request from fastapi import HTTPException, status, UploadFile, Request

View file

@ -1,5 +1,5 @@
from src.db.activities import ActivityRead from src.db.courses.activities import ActivityRead
from src.db.courses import CourseRead from src.db.courses.courses import CourseRead
def structure_activity_content_by_type(activity): def structure_activity_content_by_type(activity):

View file

@ -1,5 +1,5 @@
from typing import Literal from typing import Literal
from src.db.courses import Course from src.db.courses.courses import Course
from src.db.organizations import Organization from src.db.organizations import Organization
from pydantic import BaseModel from pydantic import BaseModel
@ -8,15 +8,15 @@ from src.security.rbac.rbac import (
authorization_verify_based_on_roles_and_authorship_and_usergroups, authorization_verify_based_on_roles_and_authorship_and_usergroups,
authorization_verify_if_user_is_anon, authorization_verify_if_user_is_anon,
) )
from src.db.chapters import Chapter from src.db.courses.chapters import Chapter
from src.db.activities import ( from src.db.courses.activities import (
Activity, Activity,
ActivityRead, ActivityRead,
ActivitySubTypeEnum, ActivitySubTypeEnum,
ActivityTypeEnum, ActivityTypeEnum,
) )
from src.db.chapter_activities import ChapterActivity from src.db.courses.chapter_activities import ChapterActivity
from src.db.course_chapters import CourseChapter from src.db.courses.course_chapters import CourseChapter
from src.db.users import AnonymousUser, PublicUser from src.db.users import AnonymousUser, PublicUser
from src.services.courses.activities.uploads.videos import upload_video from src.services.courses.activities.uploads.videos import upload_video
from fastapi import HTTPException, status, UploadFile, Request from fastapi import HTTPException, status, UploadFile, Request

View file

@ -8,10 +8,10 @@ from src.security.rbac.rbac import (
authorization_verify_if_element_is_public, authorization_verify_if_element_is_public,
authorization_verify_if_user_is_anon, authorization_verify_if_user_is_anon,
) )
from src.db.course_chapters import CourseChapter from src.db.courses.course_chapters import CourseChapter
from src.db.activities import Activity, ActivityRead from src.db.courses.activities import Activity, ActivityRead
from src.db.chapter_activities import ChapterActivity from src.db.courses.chapter_activities import ChapterActivity
from src.db.chapters import ( from src.db.courses.chapters import (
Chapter, Chapter,
ChapterCreate, ChapterCreate,
ChapterRead, ChapterRead,

View file

@ -15,7 +15,7 @@ from src.db.collections import (
CollectionUpdate, CollectionUpdate,
) )
from src.db.collections_courses import CollectionCourse from src.db.collections_courses import CollectionCourse
from src.db.courses import Course from src.db.courses.courses import Course
from src.services.users.users import PublicUser from src.services.users.users import PublicUser
from fastapi import HTTPException, status, Request from fastapi import HTTPException, status, Request

View file

@ -8,7 +8,7 @@ from src.db.organizations import Organization
from src.services.trail.trail import get_user_trail_with_orgid from src.services.trail.trail import get_user_trail_with_orgid
from src.db.resource_authors import ResourceAuthor, ResourceAuthorshipEnum from src.db.resource_authors import ResourceAuthor, ResourceAuthorshipEnum
from src.db.users import PublicUser, AnonymousUser, User, UserRead from src.db.users import PublicUser, AnonymousUser, User, UserRead
from src.db.courses import ( from src.db.courses.courses import (
Course, Course,
CourseCreate, CourseCreate,
CourseRead, CourseRead,

View file

@ -3,13 +3,13 @@ from typing import List
from uuid import uuid4 from uuid import uuid4
from fastapi import HTTPException, Request, status from fastapi import HTTPException, Request, status
from sqlmodel import Session, col, select from sqlmodel import Session, col, select
from src.db.course_updates import ( from src.db.courses.course_updates import (
CourseUpdate, CourseUpdate,
CourseUpdateCreate, CourseUpdateCreate,
CourseUpdateRead, CourseUpdateRead,
CourseUpdateUpdate, CourseUpdateUpdate,
) )
from src.db.courses import Course from src.db.courses.courses import Course
from src.db.organizations import Organization from src.db.organizations import Organization
from src.db.users import AnonymousUser, PublicUser from src.db.users import AnonymousUser, PublicUser
from src.services.courses.courses import rbac_check from src.services.courses.courses import rbac_check

View file

@ -1,10 +1,10 @@
from datetime import datetime from datetime import datetime
from uuid import uuid4 from uuid import uuid4
from src.db.chapter_activities import ChapterActivity from src.db.courses.chapter_activities import ChapterActivity
from fastapi import HTTPException, Request, status from fastapi import HTTPException, Request, status
from sqlmodel import Session, select from sqlmodel import Session, select
from src.db.activities import Activity from src.db.courses.activities import Activity
from src.db.courses import Course from src.db.courses.courses import Course
from src.db.trail_runs import TrailRun, TrailRunRead from src.db.trail_runs import TrailRun, TrailRunRead
from src.db.trail_steps import TrailStep from src.db.trail_steps import TrailStep
from src.db.trails import Trail, TrailCreate, TrailRead from src.db.trails import Trail, TrailCreate, TrailRead