From 7d62ae4125b45a17cf0b7908f231c2d8b7c00a19 Mon Sep 17 00:00:00 2001 From: Mathieu Strypsteen Date: Sun, 3 Mar 2024 09:40:41 +0100 Subject: [PATCH 01/10] Add initial user routes --- backend/app.py | 4 +-- backend/db/implementation/SqlAdminDAO.py | 5 +++ backend/db/implementation/SqlStudentDAO.py | 5 +++ backend/db/implementation/SqlTeacherDAO.py | 5 +++ backend/db/interface/AdminDAO.py | 4 +++ backend/db/interface/StudentDAO.py | 4 +++ backend/db/interface/TeacherDAO.py | 4 +++ backend/domain/models/APIUser.py | 27 +++++++++++++++ backend/pyproject.toml | 6 ++-- backend/routes/login.py | 13 ++++++++ backend/routes/teachers.py | 38 ---------------------- backend/routes/users.py | 33 +++++++++++++++++++ 12 files changed, 106 insertions(+), 42 deletions(-) create mode 100644 backend/domain/models/APIUser.py create mode 100644 backend/routes/login.py delete mode 100644 backend/routes/teachers.py create mode 100644 backend/routes/users.py diff --git a/backend/app.py b/backend/app.py index 0eb2e828..88103ec8 100644 --- a/backend/app.py +++ b/backend/app.py @@ -1,12 +1,12 @@ import uvicorn from fastapi import FastAPI -from routes.teachers import teachers_router +from routes.users import users_router app = FastAPI() # Koppel routes uit andere modules. -app.include_router(teachers_router) +app.include_router(users_router, prefix="/api") if __name__ == "__main__": uvicorn.run("app:app") diff --git a/backend/db/implementation/SqlAdminDAO.py b/backend/db/implementation/SqlAdminDAO.py index 7f1349b7..e9a213f7 100644 --- a/backend/db/implementation/SqlAdminDAO.py +++ b/backend/db/implementation/SqlAdminDAO.py @@ -17,3 +17,8 @@ def create_admin(self, name: str, email: str) -> AdminDataclass: session.add(new_admin) session.commit() return new_admin.to_domain_model() + + def is_user_admin(self, user_id: int) -> bool: + with Session(engine) as session: + admin = session.get(Admin, user_id) + return admin is not None diff --git a/backend/db/implementation/SqlStudentDAO.py b/backend/db/implementation/SqlStudentDAO.py index e8556495..2dfd6fe0 100644 --- a/backend/db/implementation/SqlStudentDAO.py +++ b/backend/db/implementation/SqlStudentDAO.py @@ -17,3 +17,8 @@ def create_student(self, name: str, email: str) -> StudentDataclass: session.add(new_student) session.commit() return new_student.to_domain_model() + + def is_user_student(self, user_id: int) -> bool: + with Session(engine) as session: + student = session.get(Student, user_id) + return student is not None diff --git a/backend/db/implementation/SqlTeacherDAO.py b/backend/db/implementation/SqlTeacherDAO.py index 6092e87d..08fd49d4 100644 --- a/backend/db/implementation/SqlTeacherDAO.py +++ b/backend/db/implementation/SqlTeacherDAO.py @@ -17,3 +17,8 @@ def create_teacher(self, name: str, email: str) -> TeacherDataclass: session.add(new_teacher) session.commit() return new_teacher.to_domain_model() + + def is_user_teacher(self, user_id: int) -> bool: + with Session(engine) as session: + teacher = session.get(Teacher, user_id) + return teacher is not None diff --git a/backend/db/interface/AdminDAO.py b/backend/db/interface/AdminDAO.py index b5dac9c6..7b78c652 100644 --- a/backend/db/interface/AdminDAO.py +++ b/backend/db/interface/AdminDAO.py @@ -16,3 +16,7 @@ def create_admin(self, name: str, email: str) -> AdminDataclass: :return: De nieuwe admin """ raise NotImplementedError + + @abstractmethod + def is_user_admin(self, user_id: int) -> bool: + raise NotImplementedError diff --git a/backend/db/interface/StudentDAO.py b/backend/db/interface/StudentDAO.py index 5bc9deec..e7f9e8da 100644 --- a/backend/db/interface/StudentDAO.py +++ b/backend/db/interface/StudentDAO.py @@ -16,3 +16,7 @@ def create_student(self, name: str, email: str) -> StudentDataclass: :returns: De nieuw aangemaakte student """ raise NotImplementedError + + @abstractmethod + def is_user_student(self, user_id: int) -> bool: + raise NotImplementedError diff --git a/backend/db/interface/TeacherDAO.py b/backend/db/interface/TeacherDAO.py index 925c2906..99bea93c 100644 --- a/backend/db/interface/TeacherDAO.py +++ b/backend/db/interface/TeacherDAO.py @@ -16,3 +16,7 @@ def create_teacher(self, name: str, email: str) -> TeacherDataclass: :returns: De nieuw aangemaakte teacher. """ raise NotImplementedError + + @abstractmethod + def is_user_teacher(self, user_id: int) -> bool: + raise NotImplementedError diff --git a/backend/domain/models/APIUser.py b/backend/domain/models/APIUser.py new file mode 100644 index 00000000..716ea3d1 --- /dev/null +++ b/backend/domain/models/APIUser.py @@ -0,0 +1,27 @@ +from pydantic import BaseModel, EmailStr + +from db.implementation.SqlAdminDAO import SqlAdminDAO +from db.implementation.SqlStudentDAO import SqlStudentDAO +from db.implementation.SqlTeacherDAO import SqlTeacherDAO +from domain.models.UserDataclass import UserDataclass + + +class APIUser(BaseModel): + id: int + name: str + email: EmailStr + roles: list[str] + + +def convert_user(user: UserDataclass) -> APIUser: + result = APIUser(id=user.id, name=user.name, email=user.email, roles=[]) + teacher_dao = SqlTeacherDAO() + admin_dao = SqlAdminDAO() + student_dao = SqlStudentDAO() + if teacher_dao.is_user_teacher(user.id): + result.roles.append("teacher") + if admin_dao.is_user_admin(user.id): + result.roles.append("admin") + if student_dao.is_user_student(user.id): + result.roles.append("student") + return result diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 762500eb..1539153d 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -88,7 +88,9 @@ ignore = [ "TCH001", # Move application import `...` into a type-checking block (dit is enkel voor performance) "RUF009", # Do not perform function call `...` in dataclass defaults but needed for sql alchemy "PLR0913", # Too many arguments in function - "FBT001" # Boolean-typed positional argument in function dfinition + "FBT001", # Boolean-typed positional argument in function dfinition + "FBT002", + "B008" ] # Allow fix for all enabled rules (when `--fix`) is provided. @@ -125,4 +127,4 @@ docstring-code-format = false # # This only has an effect when the `docstring-code-format` setting is # enabled. -docstring-code-line-length = "dynamic" \ No newline at end of file +docstring-code-line-length = "dynamic" diff --git a/backend/routes/login.py b/backend/routes/login.py new file mode 100644 index 00000000..5661d40b --- /dev/null +++ b/backend/routes/login.py @@ -0,0 +1,13 @@ +from fastapi import Depends + +from db.implementation.SqlAdminDAO import SqlAdminDAO +from db.implementation.SqlUserDAO import SqlUserDAO +from domain.models.UserDataclass import UserDataclass + + +def get_authenticated_user() -> UserDataclass: + return SqlUserDAO().get(1) # Actually authenticate user + + +def is_user_admin(user: UserDataclass = Depends(get_authenticated_user)) -> bool: + return SqlAdminDAO().is_user_admin(user.id) diff --git a/backend/routes/teachers.py b/backend/routes/teachers.py deleted file mode 100644 index c69579b1..00000000 --- a/backend/routes/teachers.py +++ /dev/null @@ -1,38 +0,0 @@ -from fastapi import APIRouter - -from db.implementation.SqlTeacherDAO import SqlTeacherDAO -from db.interface.TeacherDAO import TeacherDAO -from domain.models.TeacherDataclass import TeacherDataclass - -teachers_router = APIRouter() - - -@teachers_router.get("/teachers") -def get_teachers() -> list[TeacherDataclass]: - dao: TeacherDAO = SqlTeacherDAO() - return dao.get_all() - - -@teachers_router.get("/teachers/{teacher_id}") -def get_teacher(teacher_id: int) -> TeacherDataclass: - dao: TeacherDAO = SqlTeacherDAO() - return dao.get(teacher_id) - - -""" -@teachers_router.post("/teachers") -def create_teacher(teacher_data: TeacherDataClassRequest) -> TeacherDataclass: - # can be commented because of the validation that happens through pydantic and FastAPI - # woordjes if not teacher_data: - # woordjes return Response(json.dumps({"error": "Foute JSON of Content-Type"}), status=HTTPStatus.BAD_REQUEST) - - # woordjes validation_result: ValidationResult = TeacherValidator.validate(teacher_data) - # - # woordjes if not validation_result: - # woordjes return Response(json.dumps({"error": validation_result.errors}), status=HTTPStatus.BAD_REQUEST) - - dao: TeacherDAO = SqlTeacherDAO() - # is niet meer nodig omdat teacher_data een instance is van TeacherDataclass - # woordjes lesgever = TeacherDataclass(**teacher_data) # Vul alle velden van het dataobject in met de json - return dao.create_teacher(teacher_data.name, teacher_data.email) -""" diff --git a/backend/routes/users.py b/backend/routes/users.py new file mode 100644 index 00000000..06bc3479 --- /dev/null +++ b/backend/routes/users.py @@ -0,0 +1,33 @@ +from fastapi import APIRouter, Depends, HTTPException + +from db.errors.database_errors import ItemNotFoundError +from db.implementation.SqlUserDAO import SqlUserDAO +from domain.models.APIUser import APIUser, convert_user +from domain.models.UserDataclass import UserDataclass +from routes.login import get_authenticated_user, is_user_admin + +users_router = APIRouter() + + +@users_router.get("/user") +def get_current_user(user: UserDataclass = Depends(get_authenticated_user)) -> APIUser: + return convert_user(user) + + +@users_router.get("/users") +def get_users(admin: bool = Depends(is_user_admin)) -> list[APIUser]: + if not admin: + raise HTTPException(status_code=403) + users = SqlUserDAO().get_all() + return [convert_user(user) for user in users] + + +@users_router.get("/users/{uid}") +def get_user(uid: int, admin: bool = Depends(is_user_admin)) -> APIUser: + if not admin: + raise HTTPException(status_code=403) + try: + user = SqlUserDAO().get(uid) + except ItemNotFoundError as err: + raise HTTPException(status_code=404) from err + return convert_user(user) From d23eee228fc08a5b79c6b0a8785036bb7eb59a96 Mon Sep 17 00:00:00 2001 From: Mathieu Strypsteen Date: Sun, 3 Mar 2024 11:04:03 +0100 Subject: [PATCH 02/10] Resolve circular imports --- backend/db/interface/AdminDAO.py | 10 ++++++---- backend/db/interface/GroupDAO.py | 18 ++++++++++-------- backend/db/interface/ProjectDAO.py | 12 +++++++----- backend/db/interface/StudentDAO.py | 10 ++++++---- backend/db/interface/SubjectDAO.py | 14 ++++++++------ backend/db/interface/SubmissionDAO.py | 17 ++++++++++------- backend/db/interface/TeacherDAO.py | 10 ++++++---- backend/db/interface/UserDAO.py | 5 +---- 8 files changed, 54 insertions(+), 42 deletions(-) diff --git a/backend/db/interface/AdminDAO.py b/backend/db/interface/AdminDAO.py index 7b78c652..ff8900ff 100644 --- a/backend/db/interface/AdminDAO.py +++ b/backend/db/interface/AdminDAO.py @@ -1,13 +1,15 @@ from abc import abstractmethod +from typing import TYPE_CHECKING from db.interface.AbstractDAO import AbstractDAO -from db.models.models import Admin -from domain.models.AdminDataclass import AdminDataclass +if TYPE_CHECKING: + from domain.models.AdminDataclass import AdminDataclass -class AdminDAO(AbstractDAO[Admin, AdminDataclass]): + +class AdminDAO(AbstractDAO): @abstractmethod - def create_admin(self, name: str, email: str) -> AdminDataclass: + def create_admin(self, name: str, email: str) -> "AdminDataclass": """ Maakt een nieuwe admin aan. diff --git a/backend/db/interface/GroupDAO.py b/backend/db/interface/GroupDAO.py index b1b9e9d3..0129b803 100644 --- a/backend/db/interface/GroupDAO.py +++ b/backend/db/interface/GroupDAO.py @@ -1,14 +1,16 @@ from abc import abstractmethod +from typing import TYPE_CHECKING from db.interface.AbstractDAO import AbstractDAO -from db.models.models import Group -from domain.models.GroupDataclass import GroupDataclass -from domain.models.StudentDataclass import StudentDataclass +if TYPE_CHECKING: + from domain.models.GroupDataclass import GroupDataclass + from domain.models.StudentDataclass import StudentDataclass -class GroupDAO(AbstractDAO[Group, GroupDataclass]): + +class GroupDAO(AbstractDAO): @abstractmethod - def create_group(self, project_id: int) -> GroupDataclass: + def create_group(self, project_id: int) -> "GroupDataclass": """ Creëert een nieuw GroupDataClass in de database en associeert het met een ProjectDataClass. @@ -19,7 +21,7 @@ def create_group(self, project_id: int) -> GroupDataclass: raise NotImplementedError @abstractmethod - def get_groups_of_project(self, project_id: int) -> list[GroupDataclass]: + def get_groups_of_project(self, project_id: int) -> list["GroupDataclass"]: """ Haalt alle groepen op die bij een bepaald project horen. @@ -29,7 +31,7 @@ def get_groups_of_project(self, project_id: int) -> list[GroupDataclass]: raise NotImplementedError @abstractmethod - def get_groups_of_student(self, student_id: int) -> list[GroupDataclass]: + def get_groups_of_student(self, student_id: int) -> list["GroupDataclass"]: """ Haalt alle groepen op die bij een bepaalde student horen. @@ -50,7 +52,7 @@ def add_student_to_group(self, student_id: int, group_id: int) -> None: raise NotImplementedError @abstractmethod - def get_students_of_group(self, group_id: int) -> list[StudentDataclass]: + def get_students_of_group(self, group_id: int) -> list["StudentDataclass"]: """ Gaat alle studenten geven die in een bepaalde groep zitten diff --git a/backend/db/interface/ProjectDAO.py b/backend/db/interface/ProjectDAO.py index ed2d1e6a..a0348c4d 100644 --- a/backend/db/interface/ProjectDAO.py +++ b/backend/db/interface/ProjectDAO.py @@ -1,12 +1,14 @@ from abc import abstractmethod from datetime import datetime +from typing import TYPE_CHECKING from db.interface.AbstractDAO import AbstractDAO -from db.models.models import Project -from domain.models.ProjectDataclass import ProjectDataclass +if TYPE_CHECKING: + from domain.models.ProjectDataclass import ProjectDataclass -class ProjectDAO(AbstractDAO[Project, ProjectDataclass]): + +class ProjectDAO(AbstractDAO): @abstractmethod def create_project( self, @@ -18,7 +20,7 @@ def create_project( requirements: str, visible: bool, max_students: int, - ) -> ProjectDataclass: + ) -> "ProjectDataclass": """ Creëert een nieuw ProjectDataClass in de database en associeert het met een SubjectDataClass. @@ -36,7 +38,7 @@ def create_project( raise NotImplementedError @abstractmethod - def get_projects_of_subject(self, subject_id: int) -> list[ProjectDataclass]: + def get_projects_of_subject(self, subject_id: int) -> list["ProjectDataclass"]: """ Haalt alle projecten op die bij een bepaald subject horen. diff --git a/backend/db/interface/StudentDAO.py b/backend/db/interface/StudentDAO.py index e7f9e8da..28555f35 100644 --- a/backend/db/interface/StudentDAO.py +++ b/backend/db/interface/StudentDAO.py @@ -1,13 +1,15 @@ from abc import abstractmethod +from typing import TYPE_CHECKING from db.interface.AbstractDAO import AbstractDAO -from db.models.models import Student -from domain.models.StudentDataclass import StudentDataclass +if TYPE_CHECKING: + from domain.models.StudentDataclass import StudentDataclass -class StudentDAO(AbstractDAO[Student, StudentDataclass]): + +class StudentDAO(AbstractDAO): @abstractmethod - def create_student(self, name: str, email: str) -> StudentDataclass: + def create_student(self, name: str, email: str) -> "StudentDataclass": """ Maakt een nieuwe student aan. diff --git a/backend/db/interface/SubjectDAO.py b/backend/db/interface/SubjectDAO.py index b759ab12..2e352c39 100644 --- a/backend/db/interface/SubjectDAO.py +++ b/backend/db/interface/SubjectDAO.py @@ -1,13 +1,15 @@ from abc import abstractmethod +from typing import TYPE_CHECKING from db.interface.AbstractDAO import AbstractDAO -from db.models.models import Subject -from domain.models.SubjectDataclass import SubjectDataclass +if TYPE_CHECKING: + from domain.models.SubjectDataclass import SubjectDataclass -class SubjectDAO(AbstractDAO[Subject, SubjectDataclass]): + +class SubjectDAO(AbstractDAO): @abstractmethod - def create_subject(self, name: str) -> SubjectDataclass: + def create_subject(self, name: str) -> "SubjectDataclass": """ Creëert een nieuw SubjectDataclass in de database. @@ -17,7 +19,7 @@ def create_subject(self, name: str) -> SubjectDataclass: raise NotImplementedError @abstractmethod - def get_subjects_of_teacher(self, teacher_id: int) -> list[SubjectDataclass]: + def get_subjects_of_teacher(self, teacher_id: int) -> list["SubjectDataclass"]: """ Haalt de subjects op die door een bepaalde teacher worden gegeven. @@ -27,7 +29,7 @@ def get_subjects_of_teacher(self, teacher_id: int) -> list[SubjectDataclass]: raise NotImplementedError @abstractmethod - def get_subjects_of_student(self, student_id: int) -> list[SubjectDataclass]: + def get_subjects_of_student(self, student_id: int) -> list["SubjectDataclass"]: """ Haalt de subjects op die door een bepaalde student worden gevolgd. diff --git a/backend/db/interface/SubmissionDAO.py b/backend/db/interface/SubmissionDAO.py index 30356b8f..da61d4bc 100644 --- a/backend/db/interface/SubmissionDAO.py +++ b/backend/db/interface/SubmissionDAO.py @@ -1,15 +1,18 @@ from abc import abstractmethod from datetime import datetime +from typing import TYPE_CHECKING from db.interface.AbstractDAO import AbstractDAO -from db.models.models import Submission -from domain.models.SubmissionDataclass import SubmissionDataclass, SubmissionState +if TYPE_CHECKING: + from domain.models.SubmissionDataclass import SubmissionDataclass, SubmissionState -class SubmissionDAO(AbstractDAO[Submission, SubmissionDataclass]): + +class SubmissionDAO(AbstractDAO): @abstractmethod - def create_submission(self, student_id: int, group_id: int, message: str, - state: SubmissionState, date_time: datetime) -> SubmissionDataclass: + def create_submission( + self, student_id: int, group_id: int, message: str, state: "SubmissionState", date_time: datetime + ) -> "SubmissionDataclass": """ Creëert een nieuw SubmissionDataClass in de database en associeert het met een StudentDataclass en een GroupDataClass. @@ -23,7 +26,7 @@ def create_submission(self, student_id: int, group_id: int, message: str, raise NotImplementedError @abstractmethod - def get_submissions_of_student(self, student_id: int) -> list[SubmissionDataclass]: + def get_submissions_of_student(self, student_id: int) -> list["SubmissionDataclass"]: """ Haalt alle projecten op die bij een bepaalde student horen. @@ -33,7 +36,7 @@ def get_submissions_of_student(self, student_id: int) -> list[SubmissionDataclas raise NotImplementedError @abstractmethod - def get_submissions_of_group(self, group_id: int) -> list[SubmissionDataclass]: + def get_submissions_of_group(self, group_id: int) -> list["SubmissionDataclass"]: """ Haalt alle projecten op die bij een bepaalde groep horen. diff --git a/backend/db/interface/TeacherDAO.py b/backend/db/interface/TeacherDAO.py index 99bea93c..54ea996c 100644 --- a/backend/db/interface/TeacherDAO.py +++ b/backend/db/interface/TeacherDAO.py @@ -1,13 +1,15 @@ from abc import abstractmethod +from typing import TYPE_CHECKING from db.interface.AbstractDAO import AbstractDAO -from db.models.models import Teacher -from domain.models.TeacherDataclass import TeacherDataclass +if TYPE_CHECKING: + from domain.models.TeacherDataclass import TeacherDataclass -class TeacherDAO(AbstractDAO[Teacher, TeacherDataclass]): + +class TeacherDAO(AbstractDAO): @abstractmethod - def create_teacher(self, name: str, email: str) -> TeacherDataclass: + def create_teacher(self, name: str, email: str) -> "TeacherDataclass": """ Maakt een nieuwe teacher aan. diff --git a/backend/db/interface/UserDAO.py b/backend/db/interface/UserDAO.py index 84acb923..04299bff 100644 --- a/backend/db/interface/UserDAO.py +++ b/backend/db/interface/UserDAO.py @@ -1,8 +1,5 @@ - from db.interface.AbstractDAO import AbstractDAO -from db.models.models import User -from domain.models.UserDataclass import UserDataclass -class UserDAO(AbstractDAO[User, UserDataclass]): +class UserDAO(AbstractDAO): pass From e317fa40fbe52ecf6d97ff7473c2f8b3705775b8 Mon Sep 17 00:00:00 2001 From: Mathieu Strypsteen Date: Sun, 3 Mar 2024 11:10:37 +0100 Subject: [PATCH 03/10] Add DAOProvider --- backend/db/implementation/SqlDAOProvider.py | 43 ++++++++++++++++++++ backend/db/interface/DAOProvider.py | 44 +++++++++++++++++++++ backend/db/interface/SubmissionDAO.py | 2 +- backend/domain/models/APIUser.py | 15 +++---- backend/routes/db.py | 6 +++ backend/routes/login.py | 13 +++--- backend/routes/users.py | 28 +++++++------ 7 files changed, 120 insertions(+), 31 deletions(-) create mode 100644 backend/db/implementation/SqlDAOProvider.py create mode 100644 backend/db/interface/DAOProvider.py create mode 100644 backend/routes/db.py diff --git a/backend/db/implementation/SqlDAOProvider.py b/backend/db/implementation/SqlDAOProvider.py new file mode 100644 index 00000000..e8b2cb9f --- /dev/null +++ b/backend/db/implementation/SqlDAOProvider.py @@ -0,0 +1,43 @@ +from db.implementation.SqlAdminDAO import SqlAdminDAO +from db.implementation.SqlGroupDAO import SqlGroupDAO +from db.implementation.SqlProjectDAO import SqlProjectDAO +from db.implementation.SqlStudentDAO import SqlStudentDAO +from db.implementation.SqlSubjectDAO import SqlSubjectDAO +from db.implementation.SqlSubmissionDAO import SqlSubmissionDAO +from db.implementation.SqlTeacherDAO import SqlTeacherDAO +from db.implementation.SqlUserDAO import SqlUserDAO +from db.interface.AdminDAO import AdminDAO +from db.interface.DAOProvider import DAOProvider +from db.interface.GroupDAO import GroupDAO +from db.interface.ProjectDAO import ProjectDAO +from db.interface.StudentDAO import StudentDAO +from db.interface.SubjectDAO import SubjectDAO +from db.interface.SubmissionDAO import SubmissionDAO +from db.interface.TeacherDAO import TeacherDAO +from db.interface.UserDAO import UserDAO + + +class SqlDAOProvider(DAOProvider): + def get_admin_dao(self) -> AdminDAO: + return SqlAdminDAO() + + def get_group_dao(self) -> GroupDAO: + return SqlGroupDAO() + + def get_project_dao(self) -> ProjectDAO: + return SqlProjectDAO() + + def get_student_dao(self) -> StudentDAO: + return SqlStudentDAO() + + def get_subject_dao(self) -> SubjectDAO: + return SqlSubjectDAO() + + def get_submission_dao(self) -> SubmissionDAO: + return SqlSubmissionDAO() + + def get_teacher_dao(self) -> TeacherDAO: + return SqlTeacherDAO() + + def get_user_dao(self) -> UserDAO: + return SqlUserDAO() diff --git a/backend/db/interface/DAOProvider.py b/backend/db/interface/DAOProvider.py new file mode 100644 index 00000000..6ebe43c5 --- /dev/null +++ b/backend/db/interface/DAOProvider.py @@ -0,0 +1,44 @@ +from abc import ABC, abstractmethod + +from db.interface.AdminDAO import AdminDAO +from db.interface.GroupDAO import GroupDAO +from db.interface.ProjectDAO import ProjectDAO +from db.interface.StudentDAO import StudentDAO +from db.interface.SubjectDAO import SubjectDAO +from db.interface.SubmissionDAO import SubmissionDAO +from db.interface.TeacherDAO import TeacherDAO +from db.interface.UserDAO import UserDAO + + +class DAOProvider(ABC): + @abstractmethod + def get_admin_dao(self) -> AdminDAO: + raise NotImplementedError + + @abstractmethod + def get_group_dao(self) -> GroupDAO: + raise NotImplementedError + + @abstractmethod + def get_project_dao(self) -> ProjectDAO: + raise NotImplementedError + + @abstractmethod + def get_student_dao(self) -> StudentDAO: + raise NotImplementedError + + @abstractmethod + def get_subject_dao(self) -> SubjectDAO: + raise NotImplementedError + + @abstractmethod + def get_submission_dao(self) -> SubmissionDAO: + raise NotImplementedError + + @abstractmethod + def get_teacher_dao(self) -> TeacherDAO: + raise NotImplementedError + + @abstractmethod + def get_user_dao(self) -> UserDAO: + raise NotImplementedError diff --git a/backend/db/interface/SubmissionDAO.py b/backend/db/interface/SubmissionDAO.py index da61d4bc..58f2c946 100644 --- a/backend/db/interface/SubmissionDAO.py +++ b/backend/db/interface/SubmissionDAO.py @@ -11,7 +11,7 @@ class SubmissionDAO(AbstractDAO): @abstractmethod def create_submission( - self, student_id: int, group_id: int, message: str, state: "SubmissionState", date_time: datetime + self, student_id: int, group_id: int, message: str, state: "SubmissionState", date_time: datetime, ) -> "SubmissionDataclass": """ Creëert een nieuw SubmissionDataClass in de database en associeert het met een StudentDataclass en een diff --git a/backend/domain/models/APIUser.py b/backend/domain/models/APIUser.py index 716ea3d1..85f34c0f 100644 --- a/backend/domain/models/APIUser.py +++ b/backend/domain/models/APIUser.py @@ -1,8 +1,6 @@ from pydantic import BaseModel, EmailStr -from db.implementation.SqlAdminDAO import SqlAdminDAO -from db.implementation.SqlStudentDAO import SqlStudentDAO -from db.implementation.SqlTeacherDAO import SqlTeacherDAO +from db.interface.DAOProvider import DAOProvider from domain.models.UserDataclass import UserDataclass @@ -13,15 +11,12 @@ class APIUser(BaseModel): roles: list[str] -def convert_user(user: UserDataclass) -> APIUser: +def convert_user(user: UserDataclass, dao_provider: DAOProvider) -> APIUser: result = APIUser(id=user.id, name=user.name, email=user.email, roles=[]) - teacher_dao = SqlTeacherDAO() - admin_dao = SqlAdminDAO() - student_dao = SqlStudentDAO() - if teacher_dao.is_user_teacher(user.id): + if dao_provider.get_teacher_dao().is_user_teacher(user.id): result.roles.append("teacher") - if admin_dao.is_user_admin(user.id): + if dao_provider.get_admin_dao().is_user_admin(user.id): result.roles.append("admin") - if student_dao.is_user_student(user.id): + if dao_provider.get_student_dao().is_user_student(user.id): result.roles.append("student") return result diff --git a/backend/routes/db.py b/backend/routes/db.py new file mode 100644 index 00000000..584aedad --- /dev/null +++ b/backend/routes/db.py @@ -0,0 +1,6 @@ +from db.implementation.SqlDAOProvider import SqlDAOProvider +from db.interface.DAOProvider import DAOProvider + + +def get_dao_provider() -> DAOProvider: + return SqlDAOProvider() diff --git a/backend/routes/login.py b/backend/routes/login.py index 5661d40b..d2e32ced 100644 --- a/backend/routes/login.py +++ b/backend/routes/login.py @@ -1,13 +1,12 @@ -from fastapi import Depends - -from db.implementation.SqlAdminDAO import SqlAdminDAO -from db.implementation.SqlUserDAO import SqlUserDAO from domain.models.UserDataclass import UserDataclass +from routes.db import get_dao_provider def get_authenticated_user() -> UserDataclass: - return SqlUserDAO().get(1) # Actually authenticate user + return get_dao_provider().get_user_dao().get(1) # Actually authenticate user -def is_user_admin(user: UserDataclass = Depends(get_authenticated_user)) -> bool: - return SqlAdminDAO().is_user_admin(user.id) +def is_user_admin() -> bool: + user = get_authenticated_user() + admin_dao = get_dao_provider().get_admin_dao() + return admin_dao.is_user_admin(user.id) diff --git a/backend/routes/users.py b/backend/routes/users.py index 06bc3479..2a247b51 100644 --- a/backend/routes/users.py +++ b/backend/routes/users.py @@ -1,33 +1,35 @@ -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, HTTPException from db.errors.database_errors import ItemNotFoundError -from db.implementation.SqlUserDAO import SqlUserDAO from domain.models.APIUser import APIUser, convert_user -from domain.models.UserDataclass import UserDataclass +from routes.db import get_dao_provider from routes.login import get_authenticated_user, is_user_admin users_router = APIRouter() @users_router.get("/user") -def get_current_user(user: UserDataclass = Depends(get_authenticated_user)) -> APIUser: - return convert_user(user) +def get_current_user() -> APIUser: + user = get_authenticated_user() + return convert_user(user, get_dao_provider()) @users_router.get("/users") -def get_users(admin: bool = Depends(is_user_admin)) -> list[APIUser]: - if not admin: +def get_users() -> list[APIUser]: + if not is_user_admin(): raise HTTPException(status_code=403) - users = SqlUserDAO().get_all() - return [convert_user(user) for user in users] + user_dao = get_dao_provider().get_user_dao() + users = user_dao.get_all() + return [convert_user(user, get_dao_provider()) for user in users] @users_router.get("/users/{uid}") -def get_user(uid: int, admin: bool = Depends(is_user_admin)) -> APIUser: - if not admin: +def get_user(uid: int) -> APIUser: + if not is_user_admin(): raise HTTPException(status_code=403) + user_dao = get_dao_provider().get_user_dao() try: - user = SqlUserDAO().get(uid) + user = user_dao.get(uid) except ItemNotFoundError as err: raise HTTPException(status_code=404) from err - return convert_user(user) + return convert_user(user, get_dao_provider()) From 54b6e4c9216e3bd1fb1a75c1eddd3ca18bc71b86 Mon Sep 17 00:00:00 2001 From: Mathieu Strypsteen Date: Sun, 3 Mar 2024 15:12:20 +0100 Subject: [PATCH 04/10] Add subject routes --- backend/app.py | 2 ++ backend/db/interface/AdminDAO.py | 3 +- backend/db/interface/GroupDAO.py | 3 +- backend/db/interface/ProjectDAO.py | 3 +- backend/db/interface/StudentDAO.py | 3 +- backend/db/interface/SubjectDAO.py | 3 +- backend/db/interface/SubmissionDAO.py | 10 ++++-- backend/db/interface/TeacherDAO.py | 3 +- backend/db/interface/UserDAO.py | 8 ++++- backend/domain/models/SubjectDataclass.py | 13 +++++++ backend/routes/subjects.py | 43 +++++++++++++++++++++++ 11 files changed, 85 insertions(+), 9 deletions(-) create mode 100644 backend/routes/subjects.py diff --git a/backend/app.py b/backend/app.py index 88103ec8..8b33acd3 100644 --- a/backend/app.py +++ b/backend/app.py @@ -1,11 +1,13 @@ import uvicorn from fastapi import FastAPI +from routes.subjects import subjects_router from routes.users import users_router app = FastAPI() # Koppel routes uit andere modules. +app.include_router(subjects_router, prefix="/api") app.include_router(users_router, prefix="/api") if __name__ == "__main__": diff --git a/backend/db/interface/AdminDAO.py b/backend/db/interface/AdminDAO.py index ff8900ff..c5a649d5 100644 --- a/backend/db/interface/AdminDAO.py +++ b/backend/db/interface/AdminDAO.py @@ -4,10 +4,11 @@ from db.interface.AbstractDAO import AbstractDAO if TYPE_CHECKING: + from db.models.models import Admin # noqa: F401 from domain.models.AdminDataclass import AdminDataclass -class AdminDAO(AbstractDAO): +class AdminDAO(AbstractDAO["Admin", "AdminDataclass"]): @abstractmethod def create_admin(self, name: str, email: str) -> "AdminDataclass": """ diff --git a/backend/db/interface/GroupDAO.py b/backend/db/interface/GroupDAO.py index 0129b803..1a962073 100644 --- a/backend/db/interface/GroupDAO.py +++ b/backend/db/interface/GroupDAO.py @@ -4,11 +4,12 @@ from db.interface.AbstractDAO import AbstractDAO if TYPE_CHECKING: + from db.models.models import Group # noqa: F401 from domain.models.GroupDataclass import GroupDataclass from domain.models.StudentDataclass import StudentDataclass -class GroupDAO(AbstractDAO): +class GroupDAO(AbstractDAO["Group", "GroupDataclass"]): @abstractmethod def create_group(self, project_id: int) -> "GroupDataclass": """ diff --git a/backend/db/interface/ProjectDAO.py b/backend/db/interface/ProjectDAO.py index a0348c4d..c5f46b1e 100644 --- a/backend/db/interface/ProjectDAO.py +++ b/backend/db/interface/ProjectDAO.py @@ -5,10 +5,11 @@ from db.interface.AbstractDAO import AbstractDAO if TYPE_CHECKING: + from db.models.models import Project # noqa: F401 from domain.models.ProjectDataclass import ProjectDataclass -class ProjectDAO(AbstractDAO): +class ProjectDAO(AbstractDAO["Project", "ProjectDataclass"]): @abstractmethod def create_project( self, diff --git a/backend/db/interface/StudentDAO.py b/backend/db/interface/StudentDAO.py index 28555f35..a0075422 100644 --- a/backend/db/interface/StudentDAO.py +++ b/backend/db/interface/StudentDAO.py @@ -4,10 +4,11 @@ from db.interface.AbstractDAO import AbstractDAO if TYPE_CHECKING: + from db.models.models import Student # noqa: F401 from domain.models.StudentDataclass import StudentDataclass -class StudentDAO(AbstractDAO): +class StudentDAO(AbstractDAO["Student", "StudentDataclass"]): @abstractmethod def create_student(self, name: str, email: str) -> "StudentDataclass": """ diff --git a/backend/db/interface/SubjectDAO.py b/backend/db/interface/SubjectDAO.py index 2e352c39..fd04d162 100644 --- a/backend/db/interface/SubjectDAO.py +++ b/backend/db/interface/SubjectDAO.py @@ -4,10 +4,11 @@ from db.interface.AbstractDAO import AbstractDAO if TYPE_CHECKING: + from db.models.models import Subject # noqa: F401 from domain.models.SubjectDataclass import SubjectDataclass -class SubjectDAO(AbstractDAO): +class SubjectDAO(AbstractDAO["Subject", "SubjectDataclass"]): @abstractmethod def create_subject(self, name: str) -> "SubjectDataclass": """ diff --git a/backend/db/interface/SubmissionDAO.py b/backend/db/interface/SubmissionDAO.py index 58f2c946..bd9bc873 100644 --- a/backend/db/interface/SubmissionDAO.py +++ b/backend/db/interface/SubmissionDAO.py @@ -5,13 +5,19 @@ from db.interface.AbstractDAO import AbstractDAO if TYPE_CHECKING: + from db.models.models import Submission # noqa: F401 from domain.models.SubmissionDataclass import SubmissionDataclass, SubmissionState -class SubmissionDAO(AbstractDAO): +class SubmissionDAO(AbstractDAO["Submission", "SubmissionDataclass"]): @abstractmethod def create_submission( - self, student_id: int, group_id: int, message: str, state: "SubmissionState", date_time: datetime, + self, + student_id: int, + group_id: int, + message: str, + state: "SubmissionState", + date_time: datetime, ) -> "SubmissionDataclass": """ Creëert een nieuw SubmissionDataClass in de database en associeert het met een StudentDataclass en een diff --git a/backend/db/interface/TeacherDAO.py b/backend/db/interface/TeacherDAO.py index 54ea996c..fb33d6a7 100644 --- a/backend/db/interface/TeacherDAO.py +++ b/backend/db/interface/TeacherDAO.py @@ -4,10 +4,11 @@ from db.interface.AbstractDAO import AbstractDAO if TYPE_CHECKING: + from db.models.models import Teacher # noqa: F401 from domain.models.TeacherDataclass import TeacherDataclass -class TeacherDAO(AbstractDAO): +class TeacherDAO(AbstractDAO["Teacher", "TeacherDataclass"]): @abstractmethod def create_teacher(self, name: str, email: str) -> "TeacherDataclass": """ diff --git a/backend/db/interface/UserDAO.py b/backend/db/interface/UserDAO.py index 04299bff..2de84906 100644 --- a/backend/db/interface/UserDAO.py +++ b/backend/db/interface/UserDAO.py @@ -1,5 +1,11 @@ +from typing import TYPE_CHECKING + from db.interface.AbstractDAO import AbstractDAO +if TYPE_CHECKING: + from db.models.models import User # noqa: F401 + from domain.models.UserDataclass import UserDataclass # noqa: F401 + -class UserDAO(AbstractDAO): +class UserDAO(AbstractDAO["User", "UserDataclass"]): pass diff --git a/backend/domain/models/SubjectDataclass.py b/backend/domain/models/SubjectDataclass.py index eb92c063..e5cbb080 100644 --- a/backend/domain/models/SubjectDataclass.py +++ b/backend/domain/models/SubjectDataclass.py @@ -1,6 +1,19 @@ from pydantic import BaseModel +from db.interface.DAOProvider import DAOProvider +from domain.models.UserDataclass import UserDataclass + class SubjectDataclass(BaseModel): id: int name: str + + def is_user_authorized(self, user: UserDataclass, dao_provider: DAOProvider) -> bool: + teacher_dao = dao_provider.get_teacher_dao() + student_dao = dao_provider.get_student_dao() + subject_dao = dao_provider.get_subject_dao() + if teacher_dao.is_user_teacher(user.id) and self in subject_dao.get_subjects_of_teacher(user.id): + return True + if student_dao.is_user_student(user.id) and self in subject_dao.get_subjects_of_student(user.id): + return True + return False diff --git a/backend/routes/subjects.py b/backend/routes/subjects.py new file mode 100644 index 00000000..ad752803 --- /dev/null +++ b/backend/routes/subjects.py @@ -0,0 +1,43 @@ +from fastapi import APIRouter, HTTPException + +from db.errors.database_errors import ItemNotFoundError +from domain.models.ProjectDataclass import ProjectDataclass +from domain.models.SubjectDataclass import SubjectDataclass +from routes.db import get_dao_provider +from routes.login import get_authenticated_user + +subjects_router = APIRouter() + + +@subjects_router.get("/subjects") +def get_subjects(teacher: bool = False) -> list[SubjectDataclass]: + user = get_authenticated_user() + subject_dao = get_dao_provider().get_subject_dao() + try: + if teacher: + return subject_dao.get_subjects_of_teacher(user.id) + return subject_dao.get_subjects_of_student(user.id) + except ItemNotFoundError as err: + raise HTTPException(status_code=404) from err + + +@subjects_router.get("/subjects/{subject_id}") +def get_subject(subject_id: int) -> SubjectDataclass: + subject_dao = get_dao_provider().get_subject_dao() + try: + return subject_dao.get(subject_id) + except ItemNotFoundError as err: + raise HTTPException(status_code=404) from err + + +@subjects_router.get("/subjects/{subject_id}/projects") +def get_subject_projects(subject_id: int) -> list[ProjectDataclass]: + subject_dao = get_dao_provider().get_subject_dao() + project_dao = get_dao_provider().get_project_dao() + try: + subject = subject_dao.get(subject_id) + if not subject.is_user_authorized(get_authenticated_user(), get_dao_provider()): + raise HTTPException(status_code=403) + return project_dao.get_projects_of_subject(subject_id) + except ItemNotFoundError as err: + raise HTTPException(status_code=404) from err From 63ec82d569aa135d4636a2f5b3f2d981c550fbd1 Mon Sep 17 00:00:00 2001 From: Mathieu Strypsteen Date: Sun, 3 Mar 2024 16:10:32 +0100 Subject: [PATCH 05/10] Add projects routes --- backend/app.py | 2 ++ backend/routes/projects.py | 41 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 backend/routes/projects.py diff --git a/backend/app.py b/backend/app.py index 8b33acd3..d488b4c7 100644 --- a/backend/app.py +++ b/backend/app.py @@ -1,6 +1,7 @@ import uvicorn from fastapi import FastAPI +from routes.projects import projects_router from routes.subjects import subjects_router from routes.users import users_router @@ -9,6 +10,7 @@ # Koppel routes uit andere modules. app.include_router(subjects_router, prefix="/api") app.include_router(users_router, prefix="/api") +app.include_router(projects_router, prefix="/api") if __name__ == "__main__": uvicorn.run("app:app") diff --git a/backend/routes/projects.py b/backend/routes/projects.py new file mode 100644 index 00000000..70174613 --- /dev/null +++ b/backend/routes/projects.py @@ -0,0 +1,41 @@ +from fastapi import APIRouter, HTTPException + +from db.errors.database_errors import ItemNotFoundError +from domain.models.ProjectDataclass import ProjectDataclass +from routes.db import get_dao_provider +from routes.login import get_authenticated_user + +projects_router = APIRouter() + + +@projects_router.get("/projects") +def get_subjects(teacher: bool = False) -> list[ProjectDataclass]: + user = get_authenticated_user() + project_dao = get_dao_provider().get_project_dao() + subject_dao = get_dao_provider().get_subject_dao() + try: + if teacher: + subjects = subject_dao.get_subjects_of_teacher(user.id) + else: + subjects = subject_dao.get_subjects_of_student(user.id) + projects = [] + for i in subjects: + projects += project_dao.get_projects_of_subject(i.id) + except ItemNotFoundError as err: + raise HTTPException(status_code=404) from err + return projects + + +@projects_router.get("/projects/{subject_id}") +def get_project(project_id: int) -> ProjectDataclass: + project_dao = get_dao_provider().get_project_dao() + subject_dao = get_dao_provider().get_subject_dao() + user = get_authenticated_user() + try: + project = project_dao.get(project_id) + except ItemNotFoundError as err: + raise HTTPException(status_code=404) from err + subject = subject_dao.get(project.subject_id) + if not subject.is_user_authorized(user, get_dao_provider()): + raise HTTPException(status_code=403) + return project From f6391b47660855c59e7a378d2db9e9a758382740 Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Mon, 4 Mar 2024 17:48:49 +0100 Subject: [PATCH 06/10] verplaatsen logica naar eigen map --- backend/domain/logic/SubjectLogic.py | 17 +++++++++++++++++ backend/domain/logic/UserLogic.py | 18 ++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 backend/domain/logic/SubjectLogic.py create mode 100644 backend/domain/logic/UserLogic.py diff --git a/backend/domain/logic/SubjectLogic.py b/backend/domain/logic/SubjectLogic.py new file mode 100644 index 00000000..7475ee63 --- /dev/null +++ b/backend/domain/logic/SubjectLogic.py @@ -0,0 +1,17 @@ +from db.interface.DAOProvider import DAOProvider +from domain.models.SubjectDataclass import SubjectDataclass +from domain.models.UserDataclass import UserDataclass + + +def is_user_authorized_for_subject(subject: SubjectDataclass, user: UserDataclass, dao_provider: DAOProvider) -> bool: + teacher_dao = dao_provider.get_teacher_dao() + student_dao = dao_provider.get_student_dao() + subject_dao = dao_provider.get_subject_dao() + + if teacher_dao.is_user_teacher(user.id) and subject in subject_dao.get_subjects_of_teacher(user.id): + return True + + if student_dao.is_user_student(user.id) and subject in subject_dao.get_subjects_of_student(user.id): + return True + + return False diff --git a/backend/domain/logic/UserLogic.py b/backend/domain/logic/UserLogic.py new file mode 100644 index 00000000..428465f6 --- /dev/null +++ b/backend/domain/logic/UserLogic.py @@ -0,0 +1,18 @@ +from db.interface.DAOProvider import DAOProvider +from domain.models.APIUser import APIUser +from domain.models.UserDataclass import UserDataclass + + +def convert_user(user: UserDataclass, dao_provider: DAOProvider) -> APIUser: + api_user = APIUser(id=user.id, name=user.name, email=user.email, roles=[]) + + if dao_provider.get_teacher_dao().is_user_teacher(user.id): + api_user.roles.append("teacher") + + if dao_provider.get_admin_dao().is_user_admin(user.id): + api_user.roles.append("admin") + + if dao_provider.get_student_dao().is_user_student(user.id): + api_user.roles.append("student") + + return api_user From 3f635e7972ded6d6b39c70485c1a6efba9108db0 Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Mon, 4 Mar 2024 17:49:40 +0100 Subject: [PATCH 07/10] verwijderen logica uit data klassen --- backend/domain/models/APIUser.py | 14 -------------- backend/domain/models/SubjectDataclass.py | 13 ------------- 2 files changed, 27 deletions(-) diff --git a/backend/domain/models/APIUser.py b/backend/domain/models/APIUser.py index 85f34c0f..f1397bfd 100644 --- a/backend/domain/models/APIUser.py +++ b/backend/domain/models/APIUser.py @@ -1,22 +1,8 @@ from pydantic import BaseModel, EmailStr -from db.interface.DAOProvider import DAOProvider -from domain.models.UserDataclass import UserDataclass - class APIUser(BaseModel): id: int name: str email: EmailStr roles: list[str] - - -def convert_user(user: UserDataclass, dao_provider: DAOProvider) -> APIUser: - result = APIUser(id=user.id, name=user.name, email=user.email, roles=[]) - if dao_provider.get_teacher_dao().is_user_teacher(user.id): - result.roles.append("teacher") - if dao_provider.get_admin_dao().is_user_admin(user.id): - result.roles.append("admin") - if dao_provider.get_student_dao().is_user_student(user.id): - result.roles.append("student") - return result diff --git a/backend/domain/models/SubjectDataclass.py b/backend/domain/models/SubjectDataclass.py index e5cbb080..eb92c063 100644 --- a/backend/domain/models/SubjectDataclass.py +++ b/backend/domain/models/SubjectDataclass.py @@ -1,19 +1,6 @@ from pydantic import BaseModel -from db.interface.DAOProvider import DAOProvider -from domain.models.UserDataclass import UserDataclass - class SubjectDataclass(BaseModel): id: int name: str - - def is_user_authorized(self, user: UserDataclass, dao_provider: DAOProvider) -> bool: - teacher_dao = dao_provider.get_teacher_dao() - student_dao = dao_provider.get_student_dao() - subject_dao = dao_provider.get_subject_dao() - if teacher_dao.is_user_teacher(user.id) and self in subject_dao.get_subjects_of_teacher(user.id): - return True - if student_dao.is_user_student(user.id) and self in subject_dao.get_subjects_of_student(user.id): - return True - return False From 422c17be386a8132dbde949b7f439238a5fcd93d Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Mon, 4 Mar 2024 17:50:21 +0100 Subject: [PATCH 08/10] aanpassen routes om nieuwe logic files te gebruiken --- backend/routes/projects.py | 3 ++- backend/routes/subjects.py | 3 ++- backend/routes/users.py | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/backend/routes/projects.py b/backend/routes/projects.py index 70174613..3d0b25dc 100644 --- a/backend/routes/projects.py +++ b/backend/routes/projects.py @@ -1,6 +1,7 @@ from fastapi import APIRouter, HTTPException from db.errors.database_errors import ItemNotFoundError +from domain.logic.SubjectLogic import is_user_authorized_for_subject from domain.models.ProjectDataclass import ProjectDataclass from routes.db import get_dao_provider from routes.login import get_authenticated_user @@ -36,6 +37,6 @@ def get_project(project_id: int) -> ProjectDataclass: except ItemNotFoundError as err: raise HTTPException(status_code=404) from err subject = subject_dao.get(project.subject_id) - if not subject.is_user_authorized(user, get_dao_provider()): + if not is_user_authorized_for_subject(subject, user, get_dao_provider()): raise HTTPException(status_code=403) return project diff --git a/backend/routes/subjects.py b/backend/routes/subjects.py index ad752803..a48bf7c8 100644 --- a/backend/routes/subjects.py +++ b/backend/routes/subjects.py @@ -1,6 +1,7 @@ from fastapi import APIRouter, HTTPException from db.errors.database_errors import ItemNotFoundError +from domain.logic.SubjectLogic import is_user_authorized_for_subject from domain.models.ProjectDataclass import ProjectDataclass from domain.models.SubjectDataclass import SubjectDataclass from routes.db import get_dao_provider @@ -36,7 +37,7 @@ def get_subject_projects(subject_id: int) -> list[ProjectDataclass]: project_dao = get_dao_provider().get_project_dao() try: subject = subject_dao.get(subject_id) - if not subject.is_user_authorized(get_authenticated_user(), get_dao_provider()): + if not is_user_authorized_for_subject(subject, get_authenticated_user(), get_dao_provider()): raise HTTPException(status_code=403) return project_dao.get_projects_of_subject(subject_id) except ItemNotFoundError as err: diff --git a/backend/routes/users.py b/backend/routes/users.py index 2a247b51..2c243a76 100644 --- a/backend/routes/users.py +++ b/backend/routes/users.py @@ -1,7 +1,8 @@ from fastapi import APIRouter, HTTPException from db.errors.database_errors import ItemNotFoundError -from domain.models.APIUser import APIUser, convert_user +from domain.logic.UserLogic import convert_user +from domain.models.APIUser import APIUser from routes.db import get_dao_provider from routes.login import get_authenticated_user, is_user_admin From 4319bce4bc44b4cab8fb304b5d66b4877b8bb5a5 Mon Sep 17 00:00:00 2001 From: Mathieu Strypsteen Date: Tue, 5 Mar 2024 13:19:48 +0100 Subject: [PATCH 09/10] Clean up DAOs --- backend/db/interface/AdminDAO.py | 11 ++++------- backend/db/interface/GroupDAO.py | 19 ++++++++----------- backend/db/interface/ProjectDAO.py | 13 +++++-------- backend/db/interface/StudentDAO.py | 11 ++++------- backend/db/interface/SubjectDAO.py | 15 ++++++--------- backend/db/interface/SubmissionDAO.py | 17 +++++++---------- backend/db/interface/TeacherDAO.py | 11 ++++------- backend/db/interface/UserDAO.py | 10 +++------- backend/routes/projects.py | 2 +- 9 files changed, 42 insertions(+), 67 deletions(-) diff --git a/backend/db/interface/AdminDAO.py b/backend/db/interface/AdminDAO.py index c5a649d5..7b78c652 100644 --- a/backend/db/interface/AdminDAO.py +++ b/backend/db/interface/AdminDAO.py @@ -1,16 +1,13 @@ from abc import abstractmethod -from typing import TYPE_CHECKING from db.interface.AbstractDAO import AbstractDAO +from db.models.models import Admin +from domain.models.AdminDataclass import AdminDataclass -if TYPE_CHECKING: - from db.models.models import Admin # noqa: F401 - from domain.models.AdminDataclass import AdminDataclass - -class AdminDAO(AbstractDAO["Admin", "AdminDataclass"]): +class AdminDAO(AbstractDAO[Admin, AdminDataclass]): @abstractmethod - def create_admin(self, name: str, email: str) -> "AdminDataclass": + def create_admin(self, name: str, email: str) -> AdminDataclass: """ Maakt een nieuwe admin aan. diff --git a/backend/db/interface/GroupDAO.py b/backend/db/interface/GroupDAO.py index 1a962073..b1b9e9d3 100644 --- a/backend/db/interface/GroupDAO.py +++ b/backend/db/interface/GroupDAO.py @@ -1,17 +1,14 @@ from abc import abstractmethod -from typing import TYPE_CHECKING from db.interface.AbstractDAO import AbstractDAO +from db.models.models import Group +from domain.models.GroupDataclass import GroupDataclass +from domain.models.StudentDataclass import StudentDataclass -if TYPE_CHECKING: - from db.models.models import Group # noqa: F401 - from domain.models.GroupDataclass import GroupDataclass - from domain.models.StudentDataclass import StudentDataclass - -class GroupDAO(AbstractDAO["Group", "GroupDataclass"]): +class GroupDAO(AbstractDAO[Group, GroupDataclass]): @abstractmethod - def create_group(self, project_id: int) -> "GroupDataclass": + def create_group(self, project_id: int) -> GroupDataclass: """ Creëert een nieuw GroupDataClass in de database en associeert het met een ProjectDataClass. @@ -22,7 +19,7 @@ def create_group(self, project_id: int) -> "GroupDataclass": raise NotImplementedError @abstractmethod - def get_groups_of_project(self, project_id: int) -> list["GroupDataclass"]: + def get_groups_of_project(self, project_id: int) -> list[GroupDataclass]: """ Haalt alle groepen op die bij een bepaald project horen. @@ -32,7 +29,7 @@ def get_groups_of_project(self, project_id: int) -> list["GroupDataclass"]: raise NotImplementedError @abstractmethod - def get_groups_of_student(self, student_id: int) -> list["GroupDataclass"]: + def get_groups_of_student(self, student_id: int) -> list[GroupDataclass]: """ Haalt alle groepen op die bij een bepaalde student horen. @@ -53,7 +50,7 @@ def add_student_to_group(self, student_id: int, group_id: int) -> None: raise NotImplementedError @abstractmethod - def get_students_of_group(self, group_id: int) -> list["StudentDataclass"]: + def get_students_of_group(self, group_id: int) -> list[StudentDataclass]: """ Gaat alle studenten geven die in een bepaalde groep zitten diff --git a/backend/db/interface/ProjectDAO.py b/backend/db/interface/ProjectDAO.py index c5f46b1e..ed2d1e6a 100644 --- a/backend/db/interface/ProjectDAO.py +++ b/backend/db/interface/ProjectDAO.py @@ -1,15 +1,12 @@ from abc import abstractmethod from datetime import datetime -from typing import TYPE_CHECKING from db.interface.AbstractDAO import AbstractDAO +from db.models.models import Project +from domain.models.ProjectDataclass import ProjectDataclass -if TYPE_CHECKING: - from db.models.models import Project # noqa: F401 - from domain.models.ProjectDataclass import ProjectDataclass - -class ProjectDAO(AbstractDAO["Project", "ProjectDataclass"]): +class ProjectDAO(AbstractDAO[Project, ProjectDataclass]): @abstractmethod def create_project( self, @@ -21,7 +18,7 @@ def create_project( requirements: str, visible: bool, max_students: int, - ) -> "ProjectDataclass": + ) -> ProjectDataclass: """ Creëert een nieuw ProjectDataClass in de database en associeert het met een SubjectDataClass. @@ -39,7 +36,7 @@ def create_project( raise NotImplementedError @abstractmethod - def get_projects_of_subject(self, subject_id: int) -> list["ProjectDataclass"]: + def get_projects_of_subject(self, subject_id: int) -> list[ProjectDataclass]: """ Haalt alle projecten op die bij een bepaald subject horen. diff --git a/backend/db/interface/StudentDAO.py b/backend/db/interface/StudentDAO.py index a0075422..e7f9e8da 100644 --- a/backend/db/interface/StudentDAO.py +++ b/backend/db/interface/StudentDAO.py @@ -1,16 +1,13 @@ from abc import abstractmethod -from typing import TYPE_CHECKING from db.interface.AbstractDAO import AbstractDAO +from db.models.models import Student +from domain.models.StudentDataclass import StudentDataclass -if TYPE_CHECKING: - from db.models.models import Student # noqa: F401 - from domain.models.StudentDataclass import StudentDataclass - -class StudentDAO(AbstractDAO["Student", "StudentDataclass"]): +class StudentDAO(AbstractDAO[Student, StudentDataclass]): @abstractmethod - def create_student(self, name: str, email: str) -> "StudentDataclass": + def create_student(self, name: str, email: str) -> StudentDataclass: """ Maakt een nieuwe student aan. diff --git a/backend/db/interface/SubjectDAO.py b/backend/db/interface/SubjectDAO.py index fd04d162..b759ab12 100644 --- a/backend/db/interface/SubjectDAO.py +++ b/backend/db/interface/SubjectDAO.py @@ -1,16 +1,13 @@ from abc import abstractmethod -from typing import TYPE_CHECKING from db.interface.AbstractDAO import AbstractDAO +from db.models.models import Subject +from domain.models.SubjectDataclass import SubjectDataclass -if TYPE_CHECKING: - from db.models.models import Subject # noqa: F401 - from domain.models.SubjectDataclass import SubjectDataclass - -class SubjectDAO(AbstractDAO["Subject", "SubjectDataclass"]): +class SubjectDAO(AbstractDAO[Subject, SubjectDataclass]): @abstractmethod - def create_subject(self, name: str) -> "SubjectDataclass": + def create_subject(self, name: str) -> SubjectDataclass: """ Creëert een nieuw SubjectDataclass in de database. @@ -20,7 +17,7 @@ def create_subject(self, name: str) -> "SubjectDataclass": raise NotImplementedError @abstractmethod - def get_subjects_of_teacher(self, teacher_id: int) -> list["SubjectDataclass"]: + def get_subjects_of_teacher(self, teacher_id: int) -> list[SubjectDataclass]: """ Haalt de subjects op die door een bepaalde teacher worden gegeven. @@ -30,7 +27,7 @@ def get_subjects_of_teacher(self, teacher_id: int) -> list["SubjectDataclass"]: raise NotImplementedError @abstractmethod - def get_subjects_of_student(self, student_id: int) -> list["SubjectDataclass"]: + def get_subjects_of_student(self, student_id: int) -> list[SubjectDataclass]: """ Haalt de subjects op die door een bepaalde student worden gevolgd. diff --git a/backend/db/interface/SubmissionDAO.py b/backend/db/interface/SubmissionDAO.py index bd9bc873..c5fd33c2 100644 --- a/backend/db/interface/SubmissionDAO.py +++ b/backend/db/interface/SubmissionDAO.py @@ -1,24 +1,21 @@ from abc import abstractmethod from datetime import datetime -from typing import TYPE_CHECKING from db.interface.AbstractDAO import AbstractDAO +from db.models.models import Submission +from domain.models.SubmissionDataclass import SubmissionDataclass, SubmissionState -if TYPE_CHECKING: - from db.models.models import Submission # noqa: F401 - from domain.models.SubmissionDataclass import SubmissionDataclass, SubmissionState - -class SubmissionDAO(AbstractDAO["Submission", "SubmissionDataclass"]): +class SubmissionDAO(AbstractDAO[Submission, SubmissionDataclass]): @abstractmethod def create_submission( self, student_id: int, group_id: int, message: str, - state: "SubmissionState", + state: SubmissionState, date_time: datetime, - ) -> "SubmissionDataclass": + ) -> SubmissionDataclass: """ Creëert een nieuw SubmissionDataClass in de database en associeert het met een StudentDataclass en een GroupDataClass. @@ -32,7 +29,7 @@ def create_submission( raise NotImplementedError @abstractmethod - def get_submissions_of_student(self, student_id: int) -> list["SubmissionDataclass"]: + def get_submissions_of_student(self, student_id: int) -> list[SubmissionDataclass]: """ Haalt alle projecten op die bij een bepaalde student horen. @@ -42,7 +39,7 @@ def get_submissions_of_student(self, student_id: int) -> list["SubmissionDatacla raise NotImplementedError @abstractmethod - def get_submissions_of_group(self, group_id: int) -> list["SubmissionDataclass"]: + def get_submissions_of_group(self, group_id: int) -> list[SubmissionDataclass]: """ Haalt alle projecten op die bij een bepaalde groep horen. diff --git a/backend/db/interface/TeacherDAO.py b/backend/db/interface/TeacherDAO.py index fb33d6a7..99bea93c 100644 --- a/backend/db/interface/TeacherDAO.py +++ b/backend/db/interface/TeacherDAO.py @@ -1,16 +1,13 @@ from abc import abstractmethod -from typing import TYPE_CHECKING from db.interface.AbstractDAO import AbstractDAO +from db.models.models import Teacher +from domain.models.TeacherDataclass import TeacherDataclass -if TYPE_CHECKING: - from db.models.models import Teacher # noqa: F401 - from domain.models.TeacherDataclass import TeacherDataclass - -class TeacherDAO(AbstractDAO["Teacher", "TeacherDataclass"]): +class TeacherDAO(AbstractDAO[Teacher, TeacherDataclass]): @abstractmethod - def create_teacher(self, name: str, email: str) -> "TeacherDataclass": + def create_teacher(self, name: str, email: str) -> TeacherDataclass: """ Maakt een nieuwe teacher aan. diff --git a/backend/db/interface/UserDAO.py b/backend/db/interface/UserDAO.py index 2de84906..6769baba 100644 --- a/backend/db/interface/UserDAO.py +++ b/backend/db/interface/UserDAO.py @@ -1,11 +1,7 @@ -from typing import TYPE_CHECKING - from db.interface.AbstractDAO import AbstractDAO - -if TYPE_CHECKING: - from db.models.models import User # noqa: F401 - from domain.models.UserDataclass import UserDataclass # noqa: F401 +from db.models.models import User +from domain.models.UserDataclass import UserDataclass -class UserDAO(AbstractDAO["User", "UserDataclass"]): +class UserDAO(AbstractDAO[User, UserDataclass]): pass diff --git a/backend/routes/projects.py b/backend/routes/projects.py index 3d0b25dc..88000fe3 100644 --- a/backend/routes/projects.py +++ b/backend/routes/projects.py @@ -27,7 +27,7 @@ def get_subjects(teacher: bool = False) -> list[ProjectDataclass]: return projects -@projects_router.get("/projects/{subject_id}") +@projects_router.get("/projects/{project_id}") def get_project(project_id: int) -> ProjectDataclass: project_dao = get_dao_provider().get_project_dao() subject_dao = get_dao_provider().get_subject_dao() From 56772a1726324080a135aeff66322a287c101b64 Mon Sep 17 00:00:00 2001 From: Mathieu Strypsteen Date: Tue, 5 Mar 2024 13:44:37 +0100 Subject: [PATCH 10/10] Use fastapi status --- backend/routes/projects.py | 8 ++++---- backend/routes/subjects.py | 10 +++++----- backend/routes/users.py | 8 ++++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/backend/routes/projects.py b/backend/routes/projects.py index 88000fe3..e73b6574 100644 --- a/backend/routes/projects.py +++ b/backend/routes/projects.py @@ -1,4 +1,4 @@ -from fastapi import APIRouter, HTTPException +from fastapi import APIRouter, HTTPException, status from db.errors.database_errors import ItemNotFoundError from domain.logic.SubjectLogic import is_user_authorized_for_subject @@ -23,7 +23,7 @@ def get_subjects(teacher: bool = False) -> list[ProjectDataclass]: for i in subjects: projects += project_dao.get_projects_of_subject(i.id) except ItemNotFoundError as err: - raise HTTPException(status_code=404) from err + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) from err return projects @@ -35,8 +35,8 @@ def get_project(project_id: int) -> ProjectDataclass: try: project = project_dao.get(project_id) except ItemNotFoundError as err: - raise HTTPException(status_code=404) from err + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) from err subject = subject_dao.get(project.subject_id) if not is_user_authorized_for_subject(subject, user, get_dao_provider()): - raise HTTPException(status_code=403) + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) return project diff --git a/backend/routes/subjects.py b/backend/routes/subjects.py index a48bf7c8..db602a81 100644 --- a/backend/routes/subjects.py +++ b/backend/routes/subjects.py @@ -1,4 +1,4 @@ -from fastapi import APIRouter, HTTPException +from fastapi import APIRouter, HTTPException, status from db.errors.database_errors import ItemNotFoundError from domain.logic.SubjectLogic import is_user_authorized_for_subject @@ -19,7 +19,7 @@ def get_subjects(teacher: bool = False) -> list[SubjectDataclass]: return subject_dao.get_subjects_of_teacher(user.id) return subject_dao.get_subjects_of_student(user.id) except ItemNotFoundError as err: - raise HTTPException(status_code=404) from err + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) from err @subjects_router.get("/subjects/{subject_id}") @@ -28,7 +28,7 @@ def get_subject(subject_id: int) -> SubjectDataclass: try: return subject_dao.get(subject_id) except ItemNotFoundError as err: - raise HTTPException(status_code=404) from err + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) from err @subjects_router.get("/subjects/{subject_id}/projects") @@ -38,7 +38,7 @@ def get_subject_projects(subject_id: int) -> list[ProjectDataclass]: try: subject = subject_dao.get(subject_id) if not is_user_authorized_for_subject(subject, get_authenticated_user(), get_dao_provider()): - raise HTTPException(status_code=403) + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) return project_dao.get_projects_of_subject(subject_id) except ItemNotFoundError as err: - raise HTTPException(status_code=404) from err + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) from err diff --git a/backend/routes/users.py b/backend/routes/users.py index 2c243a76..0c08846e 100644 --- a/backend/routes/users.py +++ b/backend/routes/users.py @@ -1,4 +1,4 @@ -from fastapi import APIRouter, HTTPException +from fastapi import APIRouter, HTTPException, status from db.errors.database_errors import ItemNotFoundError from domain.logic.UserLogic import convert_user @@ -18,7 +18,7 @@ def get_current_user() -> APIUser: @users_router.get("/users") def get_users() -> list[APIUser]: if not is_user_admin(): - raise HTTPException(status_code=403) + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) user_dao = get_dao_provider().get_user_dao() users = user_dao.get_all() return [convert_user(user, get_dao_provider()) for user in users] @@ -27,10 +27,10 @@ def get_users() -> list[APIUser]: @users_router.get("/users/{uid}") def get_user(uid: int) -> APIUser: if not is_user_admin(): - raise HTTPException(status_code=403) + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) user_dao = get_dao_provider().get_user_dao() try: user = user_dao.get(uid) except ItemNotFoundError as err: - raise HTTPException(status_code=404) from err + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND) from err return convert_user(user, get_dao_provider())