diff --git a/backend/app.py b/backend/app.py index caaff4f2..bbe337c3 100644 --- a/backend/app.py +++ b/backend/app.py @@ -4,8 +4,9 @@ from starlette.requests import Request from starlette.responses import JSONResponse -from db.errors.database_errors import ActionAlreadyPerformedError, ItemNotFoundError -from routes.errors.authentication import InvalidRoleCredentialsError, StudentNotEnrolledError +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 from routes.student import student_router from routes.subject import subject_router @@ -20,7 +21,25 @@ 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") +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) @@ -39,10 +58,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)}, ) @@ -55,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/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/group.py b/backend/domain/logic/group.py index 76c2582c..eaab0039 100644 --- a/backend/domain/logic/group.py +++ b/backend/domain/logic/group.py @@ -1,7 +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 @@ -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 NoSuchRelationError(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/domain/logic/project.py b/backend/domain/logic/project.py index 835d7d6b..2e3b6967 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, Teacher 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,20 @@ 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] + + +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/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/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/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..b3f25f9b 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,20 @@ 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: + # 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) + 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/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/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/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 abe7619c..9de95992 100644 --- a/backend/routes/dependencies/role_dependencies.py +++ b/backend/routes/dependencies/role_dependencies.py @@ -3,18 +3,19 @@ from db.sessions import get_session from domain.logic.admin import get_admin, is_user_admin +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 +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 -from domain.models.SubjectDataclass import SubjectDataclass from domain.models.TeacherDataclass import TeacherDataclass from routes.errors.authentication import ( InvalidAdminCredentialsError, InvalidStudentCredentialsError, InvalidTeacherCredentialsError, - StudentNotEnrolledError, + NoAccessToSubjectError, ) @@ -43,26 +44,65 @@ 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] +def ensure_user_authorized_for_subject( + subject_id: int, + session: Session = Depends(get_session), + uid: int = Depends(get_authenticated_user), +) -> None: + if not is_user_authorized_for_subject(subject_id, session, uid): + raise NoAccessToSubjectError - 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_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 -def get_authenticated_student_for_subject( - subject_id: int, - session: Session = Depends(get_session), - student: StudentDataclass = Depends(get_authenticated_student), +def ensure_student_authorized_for_subject( + 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 + +def ensure_teacher_authorized_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 + + +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 + + +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/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/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)}, - ) 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) diff --git a/backend/routes/project.py b/backend/routes/project.py index 414b81b0..79e58d2f 100644 --- a/backend/routes/project.py +++ b/backend/routes/project.py @@ -2,15 +2,38 @@ from sqlalchemy.orm import Session from db.sessions import get_session +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 is_user_authorized_for_subject +from routes.dependencies.role_dependencies import ( + ensure_teacher_authorized_for_project, + ensure_user_authorized_for_project, +) project_router = APIRouter() -@project_router.get("/projects/{project_id}") -def get_subject_project(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), +) -> ProjectDataclass: project: ProjectDataclass = get_project(session, project_id) - is_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), +) -> 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/student.py b/backend/routes/student.py index 2f3845e1..1c764baf 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.subject import get_subjects_of_student +from domain.logic.project import get_projects_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 from routes.dependencies.role_dependencies import get_authenticated_student @@ -12,7 +14,24 @@ @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) + + +@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) diff --git a/backend/routes/subject.py b/backend/routes/subject.py index 6a4396cc..41857dc1 100644 --- a/backend/routes/subject.py +++ b/backend/routes/subject.py @@ -2,13 +2,14 @@ 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_teacher_authorized_for_subject, + ensure_user_authorized_for_subject, get_authenticated_user, - is_user_authorized_for_subject, ) subject_router = APIRouter() @@ -19,6 +20,25 @@ 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) + + +@subject_router.post("/subjects/{subject_id}/projects", dependencies=[Depends(ensure_teacher_authorized_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 6ccdbe4c..067077ec 100644 --- a/backend/routes/teacher.py +++ b/backend/routes/teacher.py @@ -1,10 +1,9 @@ -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.models.SubjectDataclass import SubjectDataclass +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 @@ -13,19 +12,18 @@ @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)]) +@teacher_router.post("/teacher/subjects") def create_subject_post( - subject: SubjectDataclass, + 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 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)