From 5d1a567bb4098f9f745c9ea8243546a3ea7fd589 Mon Sep 17 00:00:00 2001 From: Mathieu Strypsteen Date: Thu, 7 Mar 2024 17:17:46 +0100 Subject: [PATCH 01/13] Bug fixes in backend --- backend/app.py | 8 ++--- .../routes/dependencies/role_dependencies.py | 35 +++++++++---------- backend/routes/errors/authentication.py | 4 +-- backend/routes/project.py | 4 +-- backend/routes/subject.py | 4 +-- 5 files changed, 27 insertions(+), 28 deletions(-) diff --git a/backend/app.py b/backend/app.py index caaff4f2..52bf6714 100644 --- a/backend/app.py +++ b/backend/app.py @@ -5,7 +5,7 @@ from starlette.responses import JSONResponse from db.errors.database_errors import ActionAlreadyPerformedError, ItemNotFoundError -from routes.errors.authentication import InvalidRoleCredentialsError, StudentNotEnrolledError +from routes.errors.authentication import InvalidRoleCredentialsError, NoAccessToSubjectError from routes.project import project_router from routes.student import student_router from routes.subject import subject_router @@ -39,10 +39,10 @@ def item_not_found_error_handler(request: Request, exc: ItemNotFoundError) -> JS ) -@app.exception_handler(StudentNotEnrolledError) -def student_already_enrolled_error_handler(request: Request, exc: StudentNotEnrolledError) -> JSONResponse: +@app.exception_handler(NoAccessToSubjectError) +def no_access_to_subject_error_handler(request: Request, exc: NoAccessToSubjectError) -> JSONResponse: return JSONResponse( - status_code=status.HTTP_400_BAD_REQUEST, + status_code=status.HTTP_403_FORBIDDEN, content={"detail": str(exc)}, ) diff --git a/backend/routes/dependencies/role_dependencies.py b/backend/routes/dependencies/role_dependencies.py index abe7619c..e686b66b 100644 --- a/backend/routes/dependencies/role_dependencies.py +++ b/backend/routes/dependencies/role_dependencies.py @@ -8,13 +8,12 @@ from domain.logic.teacher import get_teacher, is_user_teacher from domain.models.AdminDataclass import AdminDataclass from domain.models.StudentDataclass import StudentDataclass -from domain.models.SubjectDataclass import SubjectDataclass from domain.models.TeacherDataclass import TeacherDataclass from routes.errors.authentication import ( InvalidAdminCredentialsError, InvalidStudentCredentialsError, InvalidTeacherCredentialsError, - StudentNotEnrolledError, + NoAccessToSubjectError, ) @@ -43,26 +42,26 @@ def get_authenticated_student(session: Session = Depends(get_session)) -> Studen return get_student(session, user_id) -def is_user_authorized_for_subject(subject_id: int, session: Session = Depends(get_session)) -> bool: - user_id = get_authenticated_user() - if is_user_teacher(session, user_id): - subjects_of_teacher: list[SubjectDataclass] = get_subjects_of_teacher(session, subject_id) - return subject_id in [subject.id for subject in subjects_of_teacher] - - if is_user_student(session, user_id): - subjects_of_student: list[SubjectDataclass] = get_subjects_of_student(session, subject_id) - return subject_id in [subject.id for subject in subjects_of_student] - - return False +def ensure_user_authorized_for_subject( + subject_id: int, + session: Session = Depends(get_session), + uid: int = Depends(get_authenticated_user), +) -> None: + subjects = [] + if is_user_teacher(session, uid): + subjects += get_subjects_of_teacher(session, uid) + if is_user_student(session, uid): + subjects += get_subjects_of_student(session, uid) + if subject_id not in [subject.id for subject in subjects]: + raise NoAccessToSubjectError def get_authenticated_student_for_subject( - subject_id: int, - session: Session = Depends(get_session), - student: StudentDataclass = Depends(get_authenticated_student), + subject_id: int, + session: Session = Depends(get_session), + student: StudentDataclass = Depends(get_authenticated_student), ) -> StudentDataclass: subjects_of_student = get_subjects_of_student(session, student.id) if subject_id not in [subject.id for subject in subjects_of_student]: - raise StudentNotEnrolledError + raise NoAccessToSubjectError return student - diff --git a/backend/routes/errors/authentication.py b/backend/routes/errors/authentication.py index f1437edf..4c51dd14 100644 --- a/backend/routes/errors/authentication.py +++ b/backend/routes/errors/authentication.py @@ -14,5 +14,5 @@ class InvalidStudentCredentialsError(InvalidRoleCredentialsError): ERROR_MESSAGE = "User does not have the required student role" -class StudentNotEnrolledError(Exception): - ERROR_MESSAGE = "Student is not enrolled in the subject" +class NoAccessToSubjectError(Exception): + ERROR_MESSAGE = "User doesn't have access to subject" diff --git a/backend/routes/project.py b/backend/routes/project.py index 414b81b0..f91ea9f8 100644 --- a/backend/routes/project.py +++ b/backend/routes/project.py @@ -4,7 +4,7 @@ from db.sessions import get_session from domain.logic.project import get_project from domain.models.ProjectDataclass import ProjectDataclass -from routes.dependencies.role_dependencies import is_user_authorized_for_subject +from routes.dependencies.role_dependencies import ensure_user_authorized_for_subject project_router = APIRouter() @@ -12,5 +12,5 @@ @project_router.get("/projects/{project_id}") def get_subject_project(project_id: int, session: Session = Depends(get_session)) -> ProjectDataclass: project: ProjectDataclass = get_project(session, project_id) - is_user_authorized_for_subject(project.subject_id) + ensure_user_authorized_for_subject(project.subject_id) return project diff --git a/backend/routes/subject.py b/backend/routes/subject.py index 6a4396cc..9b79930e 100644 --- a/backend/routes/subject.py +++ b/backend/routes/subject.py @@ -7,8 +7,8 @@ from domain.models.ProjectDataclass import ProjectDataclass from domain.models.SubjectDataclass import SubjectDataclass from routes.dependencies.role_dependencies import ( + ensure_user_authorized_for_subject, get_authenticated_user, - is_user_authorized_for_subject, ) subject_router = APIRouter() @@ -19,6 +19,6 @@ def subject_get(subject_id: int, session: Session = Depends(get_session)) -> Sub return get_subject(session, subject_id) -@subject_router.get("/subjects/{subject_id}/projects", dependencies=[Depends(is_user_authorized_for_subject)]) +@subject_router.get("/subjects/{subject_id}/projects", dependencies=[Depends(ensure_user_authorized_for_subject)]) def get_subject_projects(subject_id: int, session: Session = Depends(get_session)) -> list[ProjectDataclass]: return get_projects_of_subject(session, subject_id) From d036cc9e616403ab83e86d1723ae48db7ebe4f06 Mon Sep 17 00:00:00 2001 From: Mathieu Strypsteen Date: Thu, 7 Mar 2024 17:31:26 +0100 Subject: [PATCH 02/13] Remove unused file --- backend/routes/exception_handlers.py | 38 ---------------------------- 1 file changed, 38 deletions(-) delete mode 100644 backend/routes/exception_handlers.py diff --git a/backend/routes/exception_handlers.py b/backend/routes/exception_handlers.py deleted file mode 100644 index a0076c22..00000000 --- a/backend/routes/exception_handlers.py +++ /dev/null @@ -1,38 +0,0 @@ -from fastapi import Request, status -from fastapi.responses import JSONResponse - -from app import app -from db.errors.database_errors import ActionAlreadyPerformedError, ItemNotFoundError -from routes.errors.authentication import InvalidRoleCredentialsError, StudentNotEnrolledError - - -@app.exception_handler(InvalidRoleCredentialsError) -def invalid_admin_credentials_error_handler(request: Request, exc: InvalidRoleCredentialsError) -> JSONResponse: - return JSONResponse( - status_code=status.HTTP_403_FORBIDDEN, - content={"message": exc.ERROR_MESSAGE}, - ) - - -@app.exception_handler(ItemNotFoundError) -def item_not_found_error_handler(request: Request, exc: ItemNotFoundError) -> JSONResponse: - return JSONResponse( - status_code=status.HTTP_404_NOT_FOUND, - content={"detail": str(exc)}, - ) - - -@app.exception_handler(StudentNotEnrolledError) -def student_already_enrolled_error_handler(request: Request, exc: StudentNotEnrolledError) -> JSONResponse: - return JSONResponse( - status_code=status.HTTP_400_BAD_REQUEST, - content={"detail": str(exc)}, - ) - - -@app.exception_handler(ActionAlreadyPerformedError) -def action_already_performed_error_handler(request: Request, exc: ActionAlreadyPerformedError) -> JSONResponse: - return JSONResponse( - status_code=status.HTTP_400_BAD_REQUEST, - content={"detail": str(exc)}, - ) From c00947db6f7c042082107efc169f95cbf84ea04d Mon Sep 17 00:00:00 2001 From: Mathieu Strypsteen Date: Sun, 10 Mar 2024 15:10:48 +0100 Subject: [PATCH 03/13] Add modify_user_roles and refactor --- backend/db/models/models.py | 15 +++++++++------ backend/domain/logic/admin.py | 8 +++++--- backend/domain/logic/student.py | 7 +++++-- backend/domain/logic/teacher.py | 7 +++++-- backend/domain/logic/user.py | 17 ++++++++++++++++- backend/domain/models/SubjectDataclass.py | 4 ++++ backend/routes/project.py | 2 +- backend/routes/teacher.py | 11 ++++------- backend/routes/user.py | 15 +++++++++++---- 9 files changed, 60 insertions(+), 26 deletions(-) diff --git a/backend/db/models/models.py b/backend/db/models/models.py index 8ce51e0d..ba540dd1 100644 --- a/backend/db/models/models.py +++ b/backend/db/models/models.py @@ -39,12 +39,13 @@ def to_domain_model(self) -> UserDataclass: @dataclass() -class Admin(User): +class Admin(Base, AbstractModel): __tablename__ = "admins" id: Mapped[int] = mapped_column(ForeignKey(User.id), primary_key=True) + user: Mapped[User] = relationship() def to_domain_model(self) -> AdminDataclass: - return AdminDataclass(id=self.id, name=self.name, email=self.email) + return AdminDataclass(id=self.id, name=self.user.name, email=self.user.email) teachers_subjects = Table( @@ -68,25 +69,27 @@ def to_domain_model(self) -> AdminDataclass: @dataclass() -class Teacher(User): +class Teacher(Base, AbstractModel): __tablename__ = "teachers" id: Mapped[int] = mapped_column(ForeignKey(User.id), primary_key=True) + user: Mapped[User] = relationship() subjects: Mapped[list["Subject"]] = relationship(secondary=teachers_subjects, back_populates="teachers") def to_domain_model(self) -> TeacherDataclass: - return TeacherDataclass(id=self.id, name=self.name, email=self.email) + return TeacherDataclass(id=self.id, name=self.user.name, email=self.user.email) @dataclass() -class Student(User): +class Student(Base, AbstractModel): __tablename__ = "students" id: Mapped[int] = mapped_column(ForeignKey(User.id), primary_key=True) + user: Mapped[User] = relationship() subjects: Mapped[list["Subject"]] = relationship(secondary=students_subjects, back_populates="students") groups: Mapped[list["Group"]] = relationship(secondary=students_groups, back_populates="students") submissions: Mapped[list["Submission"]] = relationship(back_populates="student") def to_domain_model(self) -> StudentDataclass: - return StudentDataclass(id=self.id, name=self.name, email=self.email) + return StudentDataclass(id=self.id, name=self.user.name, email=self.user.email) @dataclass() diff --git a/backend/domain/logic/admin.py b/backend/domain/logic/admin.py index aaa83f61..8f5eb019 100644 --- a/backend/domain/logic/admin.py +++ b/backend/domain/logic/admin.py @@ -1,12 +1,15 @@ from sqlalchemy.orm import Session -from db.models.models import Admin +from db.models.models import Admin, User from domain.logic.basic_operations import get, get_all from domain.models.AdminDataclass import AdminDataclass def create_admin(session: Session, name: str, email: str) -> AdminDataclass: - new_admin: Admin = Admin(name=name, email=email) + new_user: User = User(name=name, email=email) + session.add(new_user) + session.commit() + new_admin: Admin = Admin(id=new_user.id) session.add(new_admin) session.commit() return new_admin.to_domain_model() @@ -20,7 +23,6 @@ def get_all_admins(session: Session) -> list[AdminDataclass]: return [admin.to_domain_model() for admin in get_all(session, Admin)] - def is_user_admin(session: Session, user_id: int) -> bool: admin = session.get(Admin, user_id) return admin is not None diff --git a/backend/domain/logic/student.py b/backend/domain/logic/student.py index 59c4049e..4d161e10 100644 --- a/backend/domain/logic/student.py +++ b/backend/domain/logic/student.py @@ -1,12 +1,15 @@ from sqlalchemy.orm import Session -from db.models.models import Student +from db.models.models import Student, User from domain.logic.basic_operations import get, get_all from domain.models.StudentDataclass import StudentDataclass def create_student(session: Session, name: str, email: str) -> StudentDataclass: - new_student: Student = Student(name=name, email=email) + new_user: User = User(name=name, email=email) + session.add(new_user) + session.commit() + new_student: Student = Student(id=new_user.id) session.add(new_student) session.commit() return new_student.to_domain_model() diff --git a/backend/domain/logic/teacher.py b/backend/domain/logic/teacher.py index 1bb93b08..4f665646 100644 --- a/backend/domain/logic/teacher.py +++ b/backend/domain/logic/teacher.py @@ -1,12 +1,15 @@ from sqlalchemy.orm import Session -from db.models.models import Teacher +from db.models.models import Teacher, User from domain.logic.basic_operations import get, get_all from domain.models.TeacherDataclass import TeacherDataclass def create_teacher(session: Session, name: str, email: str) -> TeacherDataclass: - new_teacher: Teacher = Teacher(name=name, email=email) + new_user: User = User(name=name, email=email) + session.add(new_user) + session.commit() + new_teacher: Teacher = Teacher(id=new_user.id) session.add(new_teacher) session.commit() return new_teacher.to_domain_model() diff --git a/backend/domain/logic/user.py b/backend/domain/logic/user.py index 4e902e57..92c7ff5c 100644 --- a/backend/domain/logic/user.py +++ b/backend/domain/logic/user.py @@ -1,6 +1,6 @@ from sqlalchemy.orm import Session -from db.models.models import User +from db.models.models import Admin, Student, Teacher, User from domain.logic.admin import is_user_admin from domain.logic.basic_operations import get, get_all from domain.logic.role_enum import Role @@ -31,3 +31,18 @@ def get_user(session: Session, user_id: int) -> UserDataclass: def get_all_users(session: Session) -> list[UserDataclass]: return [user.to_domain_model() for user in get_all(session, User)] + + +def modify_user_roles(session: Session, uid: int, roles: list[Role]) -> None: + if Role.STUDENT in roles and session.get(Student, uid) is None: + student = Student(id=uid) + session.add(student) + if Role.TEACHER in roles and session.get(Teacher, uid) is None: + teacher = Teacher(id=uid) + session.add(teacher) + if Role.ADMIN in roles and session.get(Admin, uid) is None: + admin = Admin(id=uid) + session.add(admin) + if Role.ADMIN not in roles and session.get(Admin, uid) is not None: + session.delete(get(session, Admin, uid)) + session.commit() diff --git a/backend/domain/models/SubjectDataclass.py b/backend/domain/models/SubjectDataclass.py index eb92c063..cea578dc 100644 --- a/backend/domain/models/SubjectDataclass.py +++ b/backend/domain/models/SubjectDataclass.py @@ -4,3 +4,7 @@ class SubjectDataclass(BaseModel): id: int name: str + + +class SubjectInput(BaseModel): + name: str diff --git a/backend/routes/project.py b/backend/routes/project.py index f91ea9f8..2e3a4045 100644 --- a/backend/routes/project.py +++ b/backend/routes/project.py @@ -10,7 +10,7 @@ @project_router.get("/projects/{project_id}") -def get_subject_project(project_id: int, session: Session = Depends(get_session)) -> ProjectDataclass: +def project_get(project_id: int, session: Session = Depends(get_session)) -> ProjectDataclass: project: ProjectDataclass = get_project(session, project_id) ensure_user_authorized_for_subject(project.subject_id) return project diff --git a/backend/routes/teacher.py b/backend/routes/teacher.py index 6ccdbe4c..0dcf4397 100644 --- a/backend/routes/teacher.py +++ b/backend/routes/teacher.py @@ -4,7 +4,7 @@ from db.sessions import get_session from domain.logic.subject import create_subject, get_subjects_of_teacher -from domain.models.SubjectDataclass import SubjectDataclass +from domain.models.SubjectDataclass import SubjectDataclass, SubjectInput from domain.models.TeacherDataclass import TeacherDataclass from routes.dependencies.role_dependencies import get_authenticated_teacher @@ -13,19 +13,16 @@ @teacher_router.get("/teacher/subjects") def subjects_of_teacher_get( - session: Session = Depends(get_session), - teacher: TeacherDataclass = Depends(get_authenticated_teacher), + session: Session = Depends(get_session), + teacher: TeacherDataclass = Depends(get_authenticated_teacher), ) -> list[SubjectDataclass]: return get_subjects_of_teacher(session, teacher.id) @teacher_router.post("/teacher/subjects", dependencies=[Depends(get_authenticated_teacher)]) def create_subject_post( - subject: SubjectDataclass, + subject: SubjectInput, session: Session = Depends(get_session), ) -> Response: create_subject(session, name=subject.name) return Response(status_code=status.HTTP_201_CREATED) - - - diff --git a/backend/routes/user.py b/backend/routes/user.py index c14c52d4..ceb208e7 100644 --- a/backend/routes/user.py +++ b/backend/routes/user.py @@ -1,10 +1,11 @@ -from fastapi import APIRouter, Depends +from fastapi import APIRouter, Depends, Response, status from sqlalchemy.orm import Session from db.models.models import User from db.sessions import get_session from domain.logic.basic_operations import get, get_all -from domain.logic.user import convert_user +from domain.logic.role_enum import Role +from domain.logic.user import convert_user, modify_user_roles from domain.models.APIUser import APIUser from domain.models.UserDataclass import UserDataclass from routes.dependencies.role_dependencies import get_authenticated_admin, get_authenticated_user @@ -14,8 +15,8 @@ @users_router.get("/user") def get_current_user( - session: Session = Depends(get_session), - user_id: int = Depends(get_authenticated_user), + session: Session = Depends(get_session), + user_id: int = Depends(get_authenticated_user), ) -> APIUser: user: UserDataclass = get(session, User, user_id).to_domain_model() return convert_user(session, user) @@ -31,3 +32,9 @@ def get_users(session: Session = Depends(get_session)) -> list[APIUser]: def get_user(uid: int, session: Session = Depends(get_session)) -> APIUser: user: UserDataclass = get(session, User, uid).to_domain_model() return convert_user(session, user) + + +@users_router.patch("/users/{uid}", dependencies=[Depends(get_authenticated_admin)]) +def modify_user(uid: int, roles: list[Role], session: Session = Depends(get_session)) -> Response: + modify_user_roles(session, uid, roles) + return Response(status_code=status.HTTP_204_NO_CONTENT) From dfc16c40538490889eef2d3af9a15227dbf4f669 Mon Sep 17 00:00:00 2001 From: Mathieu Strypsteen Date: Sun, 10 Mar 2024 16:27:01 +0100 Subject: [PATCH 04/13] Add more routes --- backend/domain/logic/project.py | 28 ++++++++++++------- backend/domain/models/ProjectDataclass.py | 10 +++++++ .../routes/dependencies/role_dependencies.py | 11 ++++++++ backend/routes/student.py | 14 ++++++++-- backend/routes/subject.py | 24 ++++++++++++++-- backend/routes/teacher.py | 15 +++++----- 6 files changed, 81 insertions(+), 21 deletions(-) diff --git a/backend/domain/logic/project.py b/backend/domain/logic/project.py index 835d7d6b..36e1f5fa 100644 --- a/backend/domain/logic/project.py +++ b/backend/domain/logic/project.py @@ -2,21 +2,21 @@ from sqlalchemy.orm import Session -from db.models.models import Project, Subject +from db.models.models import Project, Student, Subject from domain.logic.basic_operations import get, get_all from domain.models.ProjectDataclass import ProjectDataclass def create_project( - session: Session, - subject_id: int, - name: str, - deadline: datetime, - archived: bool, - description: str, - requirements: str, - visible: bool, - max_students: int, + session: Session, + subject_id: int, + name: str, + deadline: datetime, + archived: bool, + description: str, + requirements: str, + visible: bool, + max_students: int, ) -> ProjectDataclass: subject: Subject = get(session, Subject, subject_id) @@ -49,3 +49,11 @@ def get_projects_of_subject(session: Session, subject_id: int) -> list[ProjectDa projects: list[Project] = subject.projects return [project.to_domain_model() for project in projects] + +def get_projects_of_student(session: Session, user_id: int) -> list[ProjectDataclass]: + student = get(session, Student, ident=user_id) + subjects = student.subjects + projects = [] + for i in subjects: + projects += i.projects + return [project.to_domain_model() for project in projects] diff --git a/backend/domain/models/ProjectDataclass.py b/backend/domain/models/ProjectDataclass.py index bca35785..b189d4d9 100644 --- a/backend/domain/models/ProjectDataclass.py +++ b/backend/domain/models/ProjectDataclass.py @@ -13,3 +13,13 @@ class ProjectDataclass(BaseModel): visible: bool max_students: PositiveInt subject_id: int + + +class ProjectInput(BaseModel): + name: str + deadline: datetime + archived: bool + description: str + requirements: str + visible: bool + max_students: PositiveInt diff --git a/backend/routes/dependencies/role_dependencies.py b/backend/routes/dependencies/role_dependencies.py index e686b66b..84ac0666 100644 --- a/backend/routes/dependencies/role_dependencies.py +++ b/backend/routes/dependencies/role_dependencies.py @@ -65,3 +65,14 @@ def get_authenticated_student_for_subject( if subject_id not in [subject.id for subject in subjects_of_student]: raise NoAccessToSubjectError return student + + +def get_authenticated_teacher_for_subject( + subject_id: int, + session: Session = Depends(get_session), + teacher: TeacherDataclass = Depends(get_authenticated_teacher), +) -> TeacherDataclass: + subjects_of_teacher = get_subjects_of_teacher(session, teacher.id) + if subject_id not in [subject.id for subject in subjects_of_teacher]: + raise NoAccessToSubjectError + return teacher diff --git a/backend/routes/student.py b/backend/routes/student.py index 2f3845e1..59fb3912 100644 --- a/backend/routes/student.py +++ b/backend/routes/student.py @@ -2,7 +2,9 @@ from sqlalchemy.orm import Session from db.sessions import get_session +from domain.logic.project import get_projects_of_student from domain.logic.subject import get_subjects_of_student +from domain.models.ProjectDataclass import ProjectDataclass from domain.models.StudentDataclass import StudentDataclass from domain.models.SubjectDataclass import SubjectDataclass from routes.dependencies.role_dependencies import get_authenticated_student @@ -12,7 +14,15 @@ @student_router.get("/student/subjects") def subjects_of_student_get( - session: Session = Depends(get_session), - student: StudentDataclass = Depends(get_authenticated_student), + session: Session = Depends(get_session), + student: StudentDataclass = Depends(get_authenticated_student), ) -> list[SubjectDataclass]: return get_subjects_of_student(session, student.id) + + +@student_router.get("/student/projects") +def projects_of_student_get( + session: Session = Depends(get_session), + student: StudentDataclass = Depends(get_authenticated_student), +) -> list[ProjectDataclass]: + return get_projects_of_student(session, student.id) diff --git a/backend/routes/subject.py b/backend/routes/subject.py index 9b79930e..525e893c 100644 --- a/backend/routes/subject.py +++ b/backend/routes/subject.py @@ -2,12 +2,13 @@ from sqlalchemy.orm import Session from db.sessions import get_session -from domain.logic.project import get_projects_of_subject +from domain.logic.project import create_project, get_projects_of_subject from domain.logic.subject import get_subject -from domain.models.ProjectDataclass import ProjectDataclass +from domain.models.ProjectDataclass import ProjectDataclass, ProjectInput from domain.models.SubjectDataclass import SubjectDataclass from routes.dependencies.role_dependencies import ( ensure_user_authorized_for_subject, + get_authenticated_teacher_for_subject, get_authenticated_user, ) @@ -22,3 +23,22 @@ def subject_get(subject_id: int, session: Session = Depends(get_session)) -> Sub @subject_router.get("/subjects/{subject_id}/projects", dependencies=[Depends(ensure_user_authorized_for_subject)]) def get_subject_projects(subject_id: int, session: Session = Depends(get_session)) -> list[ProjectDataclass]: return get_projects_of_subject(session, subject_id) + + +@subject_router.post("/subjects/{subject_id}/projects", dependencies=[Depends(get_authenticated_teacher_for_subject)]) +def new_project( + subject_id: int, + project: ProjectInput, + session: Session = Depends(get_session), +) -> ProjectDataclass: + return create_project( + session, + subject_id=subject_id, + name=project.name, + deadline=project.deadline, + archived=project.archived, + description=project.description, + requirements=project.requirements, + visible=project.visible, + max_students=project.max_students, + ) diff --git a/backend/routes/teacher.py b/backend/routes/teacher.py index 0dcf4397..067077ec 100644 --- a/backend/routes/teacher.py +++ b/backend/routes/teacher.py @@ -1,9 +1,8 @@ -from fastapi import APIRouter, Depends, status +from fastapi import APIRouter, Depends from sqlalchemy.orm import Session -from starlette.responses import Response from db.sessions import get_session -from domain.logic.subject import create_subject, get_subjects_of_teacher +from domain.logic.subject import add_teacher_to_subject, create_subject, get_subjects_of_teacher from domain.models.SubjectDataclass import SubjectDataclass, SubjectInput from domain.models.TeacherDataclass import TeacherDataclass from routes.dependencies.role_dependencies import get_authenticated_teacher @@ -19,10 +18,12 @@ def subjects_of_teacher_get( return get_subjects_of_teacher(session, teacher.id) -@teacher_router.post("/teacher/subjects", dependencies=[Depends(get_authenticated_teacher)]) +@teacher_router.post("/teacher/subjects") def create_subject_post( subject: SubjectInput, + teacher: TeacherDataclass = Depends(get_authenticated_teacher), session: Session = Depends(get_session), -) -> Response: - create_subject(session, name=subject.name) - return Response(status_code=status.HTTP_201_CREATED) +) -> SubjectDataclass: + new_subject = create_subject(session, name=subject.name) + add_teacher_to_subject(session, teacher_id=teacher.id, subject_id=new_subject.id) + return new_subject From 56b013a4839b32be1aacfc349d364e7e5df51d2a Mon Sep 17 00:00:00 2001 From: Mathieu Strypsteen Date: Sun, 10 Mar 2024 22:39:18 +0100 Subject: [PATCH 05/13] Add /project/{id}/groups and refactor --- backend/domain/logic/subject.py | 13 ++++++++++ .../routes/dependencies/role_dependencies.py | 20 +++++++++------ backend/routes/project.py | 25 ++++++++++++++++--- 3 files changed, 47 insertions(+), 11 deletions(-) diff --git a/backend/domain/logic/subject.py b/backend/domain/logic/subject.py index 7fff59fb..91aaf9c4 100644 --- a/backend/domain/logic/subject.py +++ b/backend/domain/logic/subject.py @@ -3,6 +3,8 @@ from db.errors.database_errors import ActionAlreadyPerformedError from db.models.models import Student, Subject, Teacher from domain.logic.basic_operations import get, get_all +from domain.logic.student import is_user_student +from domain.logic.teacher import is_user_teacher from domain.models.SubjectDataclass import SubjectDataclass @@ -55,3 +57,14 @@ def get_subjects_of_student(session: Session, student_id: int) -> list[SubjectDa student: Student = get(session, Student, ident=student_id) subjects: list[Subject] = student.subjects return [vak.to_domain_model() for vak in subjects] + + +def is_user_authorized_for_subject(subject_id: int, session: Session, uid: int) -> bool: + subjects = [] + if is_user_teacher(session, uid): + subjects += get_subjects_of_teacher(session, uid) + if is_user_student(session, uid): + subjects += get_subjects_of_student(session, uid) + if subject_id in [subject.id for subject in subjects]: + return True + return False diff --git a/backend/routes/dependencies/role_dependencies.py b/backend/routes/dependencies/role_dependencies.py index 84ac0666..785af22b 100644 --- a/backend/routes/dependencies/role_dependencies.py +++ b/backend/routes/dependencies/role_dependencies.py @@ -3,8 +3,9 @@ from db.sessions import get_session from domain.logic.admin import get_admin, is_user_admin +from domain.logic.project import get_project from domain.logic.student import get_student, is_user_student -from domain.logic.subject import get_subjects_of_student, get_subjects_of_teacher +from domain.logic.subject import get_subjects_of_student, get_subjects_of_teacher, is_user_authorized_for_subject from domain.logic.teacher import get_teacher, is_user_teacher from domain.models.AdminDataclass import AdminDataclass from domain.models.StudentDataclass import StudentDataclass @@ -47,12 +48,17 @@ def ensure_user_authorized_for_subject( session: Session = Depends(get_session), uid: int = Depends(get_authenticated_user), ) -> None: - subjects = [] - if is_user_teacher(session, uid): - subjects += get_subjects_of_teacher(session, uid) - if is_user_student(session, uid): - subjects += get_subjects_of_student(session, uid) - if subject_id not in [subject.id for subject in subjects]: + if not is_user_authorized_for_subject(subject_id, session, uid): + raise NoAccessToSubjectError + + +def ensure_user_authorized_for_project( + project_id: int, + session: Session = Depends(get_session), + uid: int = Depends(get_authenticated_user), +) -> None: + project = get_project(session, project_id) + if not is_user_authorized_for_subject(project.subject_id, session, uid): raise NoAccessToSubjectError diff --git a/backend/routes/project.py b/backend/routes/project.py index 2e3a4045..feae4d6f 100644 --- a/backend/routes/project.py +++ b/backend/routes/project.py @@ -2,15 +2,32 @@ from sqlalchemy.orm import Session from db.sessions import get_session +from domain.logic.group import get_groups_of_project from domain.logic.project import get_project +from domain.models.GroupDataclass import GroupDataclass from domain.models.ProjectDataclass import ProjectDataclass -from routes.dependencies.role_dependencies import ensure_user_authorized_for_subject +from routes.dependencies.role_dependencies import ( + ensure_user_authorized_for_project, + get_authenticated_user, +) project_router = APIRouter() -@project_router.get("/projects/{project_id}") -def project_get(project_id: int, session: Session = Depends(get_session)) -> ProjectDataclass: +@project_router.get("/projects/{project_id}", dependencies=[Depends(ensure_user_authorized_for_project)]) +def project_get( + project_id: int, + session: Session = Depends(get_session), + uid: int = Depends(get_authenticated_user), +) -> ProjectDataclass: project: ProjectDataclass = get_project(session, project_id) - ensure_user_authorized_for_subject(project.subject_id) return project + + +@project_router.get("/projects/{project_id}/groups", dependencies=[Depends(ensure_user_authorized_for_project)]) +def project_get_groups( + project_id: int, + session: Session = Depends(get_session), + uid: int = Depends(get_authenticated_user), +) -> list[GroupDataclass]: + return get_groups_of_project(session, project_id) From cfcaf4af233b59c9aa4f3a8d17ccdc1a03c548fc Mon Sep 17 00:00:00 2001 From: Mathieu Strypsteen Date: Mon, 11 Mar 2024 14:45:32 +0100 Subject: [PATCH 06/13] Add route for joining subject --- backend/routes/project.py | 7 +------ backend/routes/student.py | 11 ++++++++++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/backend/routes/project.py b/backend/routes/project.py index feae4d6f..e4e84d65 100644 --- a/backend/routes/project.py +++ b/backend/routes/project.py @@ -6,10 +6,7 @@ from domain.logic.project import get_project from domain.models.GroupDataclass import GroupDataclass from domain.models.ProjectDataclass import ProjectDataclass -from routes.dependencies.role_dependencies import ( - ensure_user_authorized_for_project, - get_authenticated_user, -) +from routes.dependencies.role_dependencies import ensure_user_authorized_for_project project_router = APIRouter() @@ -18,7 +15,6 @@ def project_get( project_id: int, session: Session = Depends(get_session), - uid: int = Depends(get_authenticated_user), ) -> ProjectDataclass: project: ProjectDataclass = get_project(session, project_id) return project @@ -28,6 +24,5 @@ def project_get( def project_get_groups( project_id: int, session: Session = Depends(get_session), - uid: int = Depends(get_authenticated_user), ) -> list[GroupDataclass]: return get_groups_of_project(session, project_id) diff --git a/backend/routes/student.py b/backend/routes/student.py index 59fb3912..deecf869 100644 --- a/backend/routes/student.py +++ b/backend/routes/student.py @@ -3,7 +3,7 @@ from db.sessions import get_session from domain.logic.project import get_projects_of_student -from domain.logic.subject import get_subjects_of_student +from domain.logic.subject import add_student_to_subject, get_subjects_of_student from domain.models.ProjectDataclass import ProjectDataclass from domain.models.StudentDataclass import StudentDataclass from domain.models.SubjectDataclass import SubjectDataclass @@ -26,3 +26,12 @@ def projects_of_student_get( student: StudentDataclass = Depends(get_authenticated_student), ) -> list[ProjectDataclass]: return get_projects_of_student(session, student.id) + + +@student_router.post("/student/subjects{subject_id}/join") +def student_subject_join( + subject_id: int, + session: Session = Depends(get_session), + student: StudentDataclass = Depends(get_authenticated_student), +) -> None: + add_student_to_subject(session, student.id, subject_id) From 9a765f5f89d979374da91664d2570e7f28db45f8 Mon Sep 17 00:00:00 2001 From: Mathieu Strypsteen Date: Tue, 12 Mar 2024 10:43:24 +0100 Subject: [PATCH 07/13] Add route for creating group --- backend/domain/logic/project.py | 11 ++++++++++- .../routes/dependencies/role_dependencies.py | 17 ++++++++++++++--- backend/routes/project.py | 15 +++++++++++++-- backend/routes/subject.py | 4 ++-- 4 files changed, 39 insertions(+), 8 deletions(-) diff --git a/backend/domain/logic/project.py b/backend/domain/logic/project.py index 36e1f5fa..2e3b6967 100644 --- a/backend/domain/logic/project.py +++ b/backend/domain/logic/project.py @@ -2,7 +2,7 @@ from sqlalchemy.orm import Session -from db.models.models import Project, Student, Subject +from db.models.models import Project, Student, Subject, Teacher from domain.logic.basic_operations import get, get_all from domain.models.ProjectDataclass import ProjectDataclass @@ -57,3 +57,12 @@ def get_projects_of_student(session: Session, user_id: int) -> list[ProjectDatac for i in subjects: projects += i.projects return [project.to_domain_model() for project in projects] + + +def get_projects_of_teacher(session: Session, user_id: int) -> list[ProjectDataclass]: + teacher = get(session, Teacher, ident=user_id) + subjects = teacher.subjects + projects = [] + for i in subjects: + projects += i.projects + return [project.to_domain_model() for project in projects] diff --git a/backend/routes/dependencies/role_dependencies.py b/backend/routes/dependencies/role_dependencies.py index 785af22b..78ddfbd5 100644 --- a/backend/routes/dependencies/role_dependencies.py +++ b/backend/routes/dependencies/role_dependencies.py @@ -3,7 +3,7 @@ from db.sessions import get_session from domain.logic.admin import get_admin, is_user_admin -from domain.logic.project import get_project +from domain.logic.project import get_project, get_projects_of_teacher from domain.logic.student import get_student, is_user_student from domain.logic.subject import get_subjects_of_student, get_subjects_of_teacher, is_user_authorized_for_subject from domain.logic.teacher import get_teacher, is_user_teacher @@ -62,7 +62,7 @@ def ensure_user_authorized_for_project( raise NoAccessToSubjectError -def get_authenticated_student_for_subject( +def ensure_student_authorized_for_subject( subject_id: int, session: Session = Depends(get_session), student: StudentDataclass = Depends(get_authenticated_student), @@ -73,7 +73,7 @@ def get_authenticated_student_for_subject( return student -def get_authenticated_teacher_for_subject( +def ensure_teacher_authorized_for_subject( subject_id: int, session: Session = Depends(get_session), teacher: TeacherDataclass = Depends(get_authenticated_teacher), @@ -82,3 +82,14 @@ def get_authenticated_teacher_for_subject( if subject_id not in [subject.id for subject in subjects_of_teacher]: raise NoAccessToSubjectError return teacher + + +def ensure_teacher_authorized_for_project( + project_id: int, + session: Session = Depends(get_session), + teacher: TeacherDataclass = Depends(get_authenticated_teacher), +) -> TeacherDataclass: + projects_of_teacher = get_projects_of_teacher(session, teacher.id) + if project_id not in [project.id for project in projects_of_teacher]: + raise NoAccessToSubjectError + return teacher diff --git a/backend/routes/project.py b/backend/routes/project.py index e4e84d65..79e58d2f 100644 --- a/backend/routes/project.py +++ b/backend/routes/project.py @@ -2,11 +2,14 @@ from sqlalchemy.orm import Session from db.sessions import get_session -from domain.logic.group import get_groups_of_project +from domain.logic.group import create_group, get_groups_of_project from domain.logic.project import get_project from domain.models.GroupDataclass import GroupDataclass from domain.models.ProjectDataclass import ProjectDataclass -from routes.dependencies.role_dependencies import ensure_user_authorized_for_project +from routes.dependencies.role_dependencies import ( + ensure_teacher_authorized_for_project, + ensure_user_authorized_for_project, +) project_router = APIRouter() @@ -26,3 +29,11 @@ def project_get_groups( session: Session = Depends(get_session), ) -> list[GroupDataclass]: return get_groups_of_project(session, project_id) + + +@project_router.post("/projects/{project_id}/groups", dependencies=[Depends(ensure_teacher_authorized_for_project)]) +def project_create_group( + project_id: int, + session: Session = Depends(get_session), +) -> GroupDataclass: + return create_group(session, project_id) diff --git a/backend/routes/subject.py b/backend/routes/subject.py index 525e893c..41857dc1 100644 --- a/backend/routes/subject.py +++ b/backend/routes/subject.py @@ -7,8 +7,8 @@ from domain.models.ProjectDataclass import ProjectDataclass, ProjectInput from domain.models.SubjectDataclass import SubjectDataclass from routes.dependencies.role_dependencies import ( + ensure_teacher_authorized_for_subject, ensure_user_authorized_for_subject, - get_authenticated_teacher_for_subject, get_authenticated_user, ) @@ -25,7 +25,7 @@ def get_subject_projects(subject_id: int, session: Session = Depends(get_session return get_projects_of_subject(session, subject_id) -@subject_router.post("/subjects/{subject_id}/projects", dependencies=[Depends(get_authenticated_teacher_for_subject)]) +@subject_router.post("/subjects/{subject_id}/projects", dependencies=[Depends(ensure_teacher_authorized_for_subject)]) def new_project( subject_id: int, project: ProjectInput, From 44a2e3fede2b0fb4bae2f8b0b43e785861890256 Mon Sep 17 00:00:00 2001 From: Mathieu Strypsteen Date: Tue, 12 Mar 2024 15:30:04 +0100 Subject: [PATCH 08/13] Add routes for joining/leaving a group --- backend/app.py | 2 ++ backend/domain/logic/group.py | 13 ++++++++- backend/fill_database_mock.py | 4 ++- .../routes/dependencies/role_dependencies.py | 15 ++++++++++- backend/routes/group.py | 27 +++++++++++++++++++ 5 files changed, 58 insertions(+), 3 deletions(-) diff --git a/backend/app.py b/backend/app.py index 52bf6714..1ec9e990 100644 --- a/backend/app.py +++ b/backend/app.py @@ -6,6 +6,7 @@ from db.errors.database_errors import ActionAlreadyPerformedError, ItemNotFoundError from routes.errors.authentication import InvalidRoleCredentialsError, NoAccessToSubjectError +from routes.group import group_router from routes.project import project_router from routes.student import student_router from routes.subject import subject_router @@ -20,6 +21,7 @@ app.include_router(users_router, prefix="/api") app.include_router(project_router, prefix="/api") app.include_router(subject_router, prefix="/api") +app.include_router(group_router, prefix="/api") # Koppel de exception handlers diff --git a/backend/domain/logic/group.py b/backend/domain/logic/group.py index 76c2582c..788e95d8 100644 --- a/backend/domain/logic/group.py +++ b/backend/domain/logic/group.py @@ -1,4 +1,3 @@ - from sqlalchemy.orm import Session from db.errors.database_errors import ActionAlreadyPerformedError @@ -51,6 +50,18 @@ def add_student_to_group(session: Session, student_id: int, group_id: int) -> No session.commit() +def remove_student_from_group(session: Session, student_id: int, group_id: int) -> None: + student: Student = get(session, Student, ident=student_id) + group: Group = get(session, Group, ident=group_id) + + if student not in group.students: + msg = f"Student with id {student_id} is not in group with id {group_id}" + raise ActionAlreadyPerformedError(msg) + + group.students.remove(student) + session.commit() + + def get_students_of_group(session: Session, group_id: int) -> list[StudentDataclass]: group: Group = get(session, Group, ident=group_id) students: list[Student] = group.students diff --git a/backend/fill_database_mock.py b/backend/fill_database_mock.py index 880048fa..98d1b126 100644 --- a/backend/fill_database_mock.py +++ b/backend/fill_database_mock.py @@ -8,7 +8,7 @@ from domain.logic.group import add_student_to_group, create_group from domain.logic.project import create_project from domain.logic.student import create_student -from domain.logic.subject import add_teacher_to_subject, create_subject +from domain.logic.subject import add_student_to_subject, add_teacher_to_subject, create_subject from domain.logic.teacher import create_teacher if __name__ == "__main__": @@ -83,6 +83,8 @@ add_teacher_to_subject(session, teacher2.id, algoritmen.id) add_teacher_to_subject(session, teacher3.id, webtech.id) + add_student_to_subject(session, student1.id, objeprog.id) + # Add students to groups add_student_to_group(session, student1.id, groep1_objprog.id) add_student_to_group(session, student2.id, groep1_objprog.id) diff --git a/backend/routes/dependencies/role_dependencies.py b/backend/routes/dependencies/role_dependencies.py index 78ddfbd5..9de95992 100644 --- a/backend/routes/dependencies/role_dependencies.py +++ b/backend/routes/dependencies/role_dependencies.py @@ -3,7 +3,8 @@ from db.sessions import get_session from domain.logic.admin import get_admin, is_user_admin -from domain.logic.project import get_project, get_projects_of_teacher +from domain.logic.group import get_group +from domain.logic.project import get_project, get_projects_of_student, get_projects_of_teacher from domain.logic.student import get_student, is_user_student from domain.logic.subject import get_subjects_of_student, get_subjects_of_teacher, is_user_authorized_for_subject from domain.logic.teacher import get_teacher, is_user_teacher @@ -93,3 +94,15 @@ def ensure_teacher_authorized_for_project( if project_id not in [project.id for project in projects_of_teacher]: raise NoAccessToSubjectError return teacher + + +def ensure_student_authorized_for_group( + group_id: int, + session: Session = Depends(get_session), + student: StudentDataclass = Depends(get_authenticated_student), +) -> StudentDataclass: + group = get_group(session, group_id) + projects_of_student = get_projects_of_student(session, student.id) + if group.project_id not in [project.id for project in projects_of_student]: + raise NoAccessToSubjectError + return student diff --git a/backend/routes/group.py b/backend/routes/group.py index e69de29b..22623654 100644 --- a/backend/routes/group.py +++ b/backend/routes/group.py @@ -0,0 +1,27 @@ +from fastapi import APIRouter, Depends +from sqlalchemy.orm import Session + +from db.sessions import get_session +from domain.logic.group import add_student_to_group, remove_student_from_group +from domain.models.StudentDataclass import StudentDataclass +from routes.dependencies.role_dependencies import ensure_student_authorized_for_group + +group_router = APIRouter() + + +@group_router.post("/groups/{group_id}/join") +def group_join( + group_id: int, + student: StudentDataclass = Depends(ensure_student_authorized_for_group), + session: Session = Depends(get_session), +) -> None: + add_student_to_group(session, student.id, group_id) + + +@group_router.post("/groups/{group_id}/leave") +def group_leave( + group_id: int, + student: StudentDataclass = Depends(ensure_student_authorized_for_group), + session: Session = Depends(get_session), +) -> None: + remove_student_from_group(session, student.id, group_id) From fddfbdd16d6e1d737f1fa3f3e514c984df357dff Mon Sep 17 00:00:00 2001 From: Mathieu Strypsteen Date: Tue, 12 Mar 2024 16:17:42 +0100 Subject: [PATCH 09/13] Fix join subject route --- backend/routes/student.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/routes/student.py b/backend/routes/student.py index deecf869..1c764baf 100644 --- a/backend/routes/student.py +++ b/backend/routes/student.py @@ -28,7 +28,7 @@ def projects_of_student_get( return get_projects_of_student(session, student.id) -@student_router.post("/student/subjects{subject_id}/join") +@student_router.post("/student/subjects/{subject_id}/join") def student_subject_join( subject_id: int, session: Session = Depends(get_session), From 993c5afae7c5db96f6d907a85e6a6440363570c1 Mon Sep 17 00:00:00 2001 From: Mathieu Strypsteen Date: Tue, 12 Mar 2024 16:48:35 +0100 Subject: [PATCH 10/13] Add comment --- backend/domain/logic/user.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/domain/logic/user.py b/backend/domain/logic/user.py index 92c7ff5c..b3f25f9b 100644 --- a/backend/domain/logic/user.py +++ b/backend/domain/logic/user.py @@ -34,6 +34,8 @@ def get_all_users(session: Session) -> list[UserDataclass]: def modify_user_roles(session: Session, uid: int, roles: list[Role]) -> None: + # Er is geen ondersteuning om een student/teacher role af te nemen, + # want dit zou problemen geven met relaties in de databank if Role.STUDENT in roles and session.get(Student, uid) is None: student = Student(id=uid) session.add(student) From bbcbbca8d8f1f870dacffcf538511857ce4b64e6 Mon Sep 17 00:00:00 2001 From: Matthias Seghers Date: Tue, 12 Mar 2024 16:51:19 +0100 Subject: [PATCH 11/13] added debug options to make dev life easier --- backend/app.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/backend/app.py b/backend/app.py index 1ec9e990..eeee0710 100644 --- a/backend/app.py +++ b/backend/app.py @@ -23,6 +23,23 @@ app.include_router(subject_router, prefix="/api") app.include_router(group_router, prefix="/api") +DEBUG = False # Should always be false in repo + +if DEBUG: + from fastapi.middleware.cors import CORSMiddleware + origins = [ + "https://localhost", + "https://localhost:8080", + "http://localhost:5173" + ] + + app.add_middleware( + CORSMiddleware, + allow_origins=origins, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], + ) # Koppel de exception handlers @app.exception_handler(InvalidRoleCredentialsError) From a309f3e89165a7348501d84b3b2e7629e9a56913 Mon Sep 17 00:00:00 2001 From: Matthias Seghers Date: Tue, 12 Mar 2024 16:53:58 +0100 Subject: [PATCH 12/13] i'm stoopid forgor ruff --- backend/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/app.py b/backend/app.py index eeee0710..030c32e4 100644 --- a/backend/app.py +++ b/backend/app.py @@ -30,7 +30,7 @@ origins = [ "https://localhost", "https://localhost:8080", - "http://localhost:5173" + "http://localhost:5173", ] app.add_middleware( From 7b3a36df8ccbf2ef4236d30db056cada51203d37 Mon Sep 17 00:00:00 2001 From: Mathieu Strypsteen Date: Tue, 12 Mar 2024 17:25:51 +0100 Subject: [PATCH 13/13] Rename error --- backend/app.py | 10 +++++++++- backend/db/errors/database_errors.py | 6 +++++- backend/domain/logic/group.py | 4 ++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/backend/app.py b/backend/app.py index 030c32e4..bbe337c3 100644 --- a/backend/app.py +++ b/backend/app.py @@ -4,7 +4,7 @@ from starlette.requests import Request from starlette.responses import JSONResponse -from db.errors.database_errors import ActionAlreadyPerformedError, ItemNotFoundError +from db.errors.database_errors import ActionAlreadyPerformedError, ItemNotFoundError, NoSuchRelationError from routes.errors.authentication import InvalidRoleCredentialsError, NoAccessToSubjectError from routes.group import group_router from routes.project import project_router @@ -74,5 +74,13 @@ def action_already_performed_error_handler(request: Request, exc: ActionAlreadyP ) +@app.exception_handler(NoSuchRelationError) +def no_such_relation_error_handler(request: Request, exc: NoSuchRelationError) -> JSONResponse: + return JSONResponse( + status_code=status.HTTP_400_BAD_REQUEST, + content={"detail": str(exc)}, + ) + + if __name__ == "__main__": uvicorn.run("app:app") diff --git a/backend/db/errors/database_errors.py b/backend/db/errors/database_errors.py index e2805cc3..05a400e7 100644 --- a/backend/db/errors/database_errors.py +++ b/backend/db/errors/database_errors.py @@ -1,5 +1,4 @@ class ItemNotFoundError(Exception): - def __init__(self, message: str) -> None: super().__init__(message) @@ -7,3 +6,8 @@ def __init__(self, message: str) -> None: class ActionAlreadyPerformedError(Exception): def __init__(self, message: str) -> None: super().__init__(message) + + +class NoSuchRelationError(Exception): + def __init__(self, message: str) -> None: + super().__init__(message) diff --git a/backend/domain/logic/group.py b/backend/domain/logic/group.py index 788e95d8..eaab0039 100644 --- a/backend/domain/logic/group.py +++ b/backend/domain/logic/group.py @@ -1,6 +1,6 @@ from sqlalchemy.orm import Session -from db.errors.database_errors import ActionAlreadyPerformedError +from db.errors.database_errors import ActionAlreadyPerformedError, NoSuchRelationError from db.models.models import Group, Project, Student from domain.logic.basic_operations import get, get_all from domain.models.GroupDataclass import GroupDataclass @@ -56,7 +56,7 @@ def remove_student_from_group(session: Session, student_id: int, group_id: int) if student not in group.students: msg = f"Student with id {student_id} is not in group with id {group_id}" - raise ActionAlreadyPerformedError(msg) + raise NoSuchRelationError(msg) group.students.remove(student) session.commit()