From 1a4c5a3c452cb8c617f5911acf51fa4c4774c602 Mon Sep 17 00:00:00 2001 From: Mathieu Strypsteen Date: Sun, 3 Mar 2024 09:40:41 +0100 Subject: [PATCH] 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)