From 2318d5631668814a66225c365e90276ad7a4eb38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stef=20Oss=C3=A9?= Date: Tue, 9 Apr 2024 17:22:25 +0200 Subject: [PATCH 01/90] Created files --- .../components/authentication/RequireAuth.tsx | 2 +- frontend/src/dataloaders/LoginLoader.ts | 2 +- frontend/src/dataloaders/SharedFunctions.ts | 2 +- frontend/src/main.tsx | 2 ++ frontend/src/pages/Test.tsx | 12 ++++++++++ frontend/src/pages/login/LoginScreen.tsx | 2 +- frontend/src/pages/root.tsx | 8 +++---- frontend/src/utils/ApiInterfaces.ts | 8 +++---- frontend/src/utils/{ => api}/ApiFetch.ts | 2 +- frontend/src/utils/api/Groups.ts | 23 +++++++++++++++++++ frontend/src/utils/api/Login.ts | 0 frontend/src/utils/api/Project.ts | 0 frontend/src/utils/api/Student.ts | 0 frontend/src/utils/api/Subject.ts | 0 frontend/src/utils/api/Submission.ts | 0 frontend/src/utils/api/Teacher.ts | 0 frontend/src/utils/api/User.ts | 0 17 files changed, 50 insertions(+), 13 deletions(-) create mode 100644 frontend/src/pages/Test.tsx rename frontend/src/utils/{ => api}/ApiFetch.ts (91%) create mode 100644 frontend/src/utils/api/Groups.ts create mode 100644 frontend/src/utils/api/Login.ts create mode 100644 frontend/src/utils/api/Project.ts create mode 100644 frontend/src/utils/api/Student.ts create mode 100644 frontend/src/utils/api/Subject.ts create mode 100644 frontend/src/utils/api/Submission.ts create mode 100644 frontend/src/utils/api/Teacher.ts create mode 100644 frontend/src/utils/api/User.ts diff --git a/frontend/src/components/authentication/RequireAuth.tsx b/frontend/src/components/authentication/RequireAuth.tsx index 7841130b..3491ea11 100644 --- a/frontend/src/components/authentication/RequireAuth.tsx +++ b/frontend/src/components/authentication/RequireAuth.tsx @@ -10,7 +10,7 @@ const RequireAuth = ({allowedRoles}: Props) => { const location = useLocation(); if (user) { return ( - (allowedRoles && user.user_roles.find(role => allowedRoles.includes(role))) + (allowedRoles && user.roles.find(role => allowedRoles.includes(role))) ? : ); diff --git a/frontend/src/dataloaders/LoginLoader.ts b/frontend/src/dataloaders/LoginLoader.ts index b504affe..ec7625f6 100644 --- a/frontend/src/dataloaders/LoginLoader.ts +++ b/frontend/src/dataloaders/LoginLoader.ts @@ -1,5 +1,5 @@ import {User} from "../utils/ApiInterfaces.ts"; -import apiFetch from "../utils/ApiFetch.ts"; +import apiFetch from "../utils/api/ApiFetch.ts"; export const LOGIN_ROUTER_ID = "login"; diff --git a/frontend/src/dataloaders/SharedFunctions.ts b/frontend/src/dataloaders/SharedFunctions.ts index 252fbb1a..cbc60806 100644 --- a/frontend/src/dataloaders/SharedFunctions.ts +++ b/frontend/src/dataloaders/SharedFunctions.ts @@ -1,5 +1,5 @@ import {CompleteProject, Group, Project, Subject, Submission} from "../utils/ApiInterfaces.ts"; -import apiFetch from "../utils/ApiFetch.ts"; +import apiFetch from "../utils/api/ApiFetch.ts"; export enum teacherStudentRole { STUDENT = "student", diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index ca0f1260..28d210e7 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -25,6 +25,7 @@ import CoursesViewTeacher from "./pages/teacher/CoursesViewTeacher.tsx"; import {CreateProject} from "./pages/teacher/CreateProject.tsx"; import CreateCourse from "./pages/teacher/CreateCourse.tsx"; import projectsTeacherLoader, {PROJECTS_TEACHER_ROUTER_ID} from "./dataloaders/projectsTeacherLoader.ts"; +import Test from "./pages/Test.tsx"; const router = createBrowserRouter( createRoutesFromElements( @@ -33,6 +34,7 @@ const router = createBrowserRouter( } loader={loginLoader} errorElement={}/> }/> + }/> {/* Protected routes */} }> diff --git a/frontend/src/pages/Test.tsx b/frontend/src/pages/Test.tsx new file mode 100644 index 00000000..626d973e --- /dev/null +++ b/frontend/src/pages/Test.tsx @@ -0,0 +1,12 @@ +import {projectGroup} from "../utils/api/Groups.ts"; + +export default function Test () { + function clickEvent () { + console.log(projectGroup(2)) + } + + return (
+

Welcome on this test page, click the button below to test te function

+ +
) +} \ No newline at end of file diff --git a/frontend/src/pages/login/LoginScreen.tsx b/frontend/src/pages/login/LoginScreen.tsx index e407a104..0f9beba1 100644 --- a/frontend/src/pages/login/LoginScreen.tsx +++ b/frontend/src/pages/login/LoginScreen.tsx @@ -35,7 +35,7 @@ const ticketLogin = async (ticket: string, setUser: React.Dispatch { - return (data && data.user_id && data.user_name && data.user_email && data.user_roles); + return (data && data.id && data.name && data.email && data.roles); } diff --git a/frontend/src/pages/root.tsx b/frontend/src/pages/root.tsx index a9c2d7b2..ea446625 100644 --- a/frontend/src/pages/root.tsx +++ b/frontend/src/pages/root.tsx @@ -2,17 +2,17 @@ import {JSX} from "react"; import useAuth from "../hooks/useAuth.ts"; import {Navigate} from "react-router-dom"; -export const DEBUG: boolean = false; // should always be false on the repo. +export const DEBUG: boolean = true; // should always be false on the repo. export default function Root(): JSX.Element { const {user} = useAuth() let to: string = "/error" - if (user?.user_roles.includes('TEACHER')) { + if (user?.roles.includes('TEACHER')) { to = "/teacher"; - } else if (user?.user_roles.includes('STUDENT')) { + } else if (user?.roles.includes('STUDENT')) { to = "/student"; - } else if (user?.user_roles.includes('ADMIN')) { + } else if (user?.roles.includes('ADMIN')) { to = "/admin"; } diff --git a/frontend/src/utils/ApiInterfaces.ts b/frontend/src/utils/ApiInterfaces.ts index ff17b611..ef530a5b 100644 --- a/frontend/src/utils/ApiInterfaces.ts +++ b/frontend/src/utils/ApiInterfaces.ts @@ -48,8 +48,8 @@ export interface Token { } export interface User { - user_id: number, - user_name: string, - user_email: string, - user_roles: string[] + id: number, + name: string, + email: string, + roles: string[] } \ No newline at end of file diff --git a/frontend/src/utils/ApiFetch.ts b/frontend/src/utils/api/ApiFetch.ts similarity index 91% rename from frontend/src/utils/ApiFetch.ts rename to frontend/src/utils/api/ApiFetch.ts index 19c8d250..e4934a1d 100644 --- a/frontend/src/utils/ApiFetch.ts +++ b/frontend/src/utils/api/ApiFetch.ts @@ -1,4 +1,4 @@ -import {DEBUG} from "../pages/root.tsx"; +import {DEBUG} from "../../pages/root.tsx"; const ApiFetch = async (url: string, options?: RequestInit) => { diff --git a/frontend/src/utils/api/Groups.ts b/frontend/src/utils/api/Groups.ts new file mode 100644 index 00000000..54bc3af5 --- /dev/null +++ b/frontend/src/utils/api/Groups.ts @@ -0,0 +1,23 @@ +import ApiFetch from "./ApiFetch.ts"; + +export function joinGroup(groupId: number) { + void ApiFetch(`/groups/${groupId}/join`, + {method: 'POST', headers: {'Content-Type': 'application/json'}}); +} + +export function leaveGroup(groupId: number) { + void ApiFetch(`/groups/${groupId}/leave`, + {method: 'POST', headers: {'Content-Type': 'application/json'}}); +} + +export async function listGroupMembers(groupId: number) { + let members = await ApiFetch(`/groups/${groupId}/members`, + {method: 'GET', headers: {'Content-Type': 'application/json'}}); + return members; +} + +export function projectGroup(projectId: number) { + let group = void ApiFetch(`/projects/${projectId}/group`, + {method: 'GET', headers: {'Content-Type': 'application/json'}}); + return group; +} \ No newline at end of file diff --git a/frontend/src/utils/api/Login.ts b/frontend/src/utils/api/Login.ts new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/utils/api/Project.ts b/frontend/src/utils/api/Project.ts new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/utils/api/Student.ts b/frontend/src/utils/api/Student.ts new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/utils/api/Subject.ts b/frontend/src/utils/api/Subject.ts new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/utils/api/Submission.ts b/frontend/src/utils/api/Submission.ts new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/utils/api/Teacher.ts b/frontend/src/utils/api/Teacher.ts new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/utils/api/User.ts b/frontend/src/utils/api/User.ts new file mode 100644 index 00000000..e69de29b From 81deb20f15122f7619437595a133be383bd6c4dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stef=20Oss=C3=A9?= Date: Tue, 9 Apr 2024 21:39:56 +0200 Subject: [PATCH 02/90] Functies user, projects, subjects, group, course --- .../components/authentication/ErrorLogin.tsx | 2 +- .../{LoginLoader.ts => LoginLoader.tsx} | 2 +- .../src/dataloaders/ProjectsStudentLoader.ts | 2 +- frontend/src/dataloaders/SharedFunctions.ts | 2 +- .../src/dataloaders/projectsTeacherLoader.ts | 2 +- frontend/src/main.tsx | 6 ++-- frontend/src/pages/Test.tsx | 12 ------- frontend/src/pages/login/LoginScreen.tsx | 27 +++++++------- frontend/src/utils/{api => }/ApiFetch.ts | 2 +- frontend/src/utils/InputInterfaces.ts | 9 +++++ frontend/src/utils/api/Groups.ts | 14 +------- frontend/src/utils/api/Login.ts | 16 +++++++++ frontend/src/utils/api/Project.ts | 36 +++++++++++++++++++ frontend/src/utils/api/Student.ts | 7 ++++ frontend/src/utils/api/Subject.ts | 25 +++++++++++++ frontend/src/utils/api/Teacher.ts | 13 +++++++ frontend/src/utils/api/User.ts | 8 +++++ 17 files changed, 139 insertions(+), 46 deletions(-) rename frontend/src/dataloaders/{LoginLoader.ts => LoginLoader.tsx} (93%) delete mode 100644 frontend/src/pages/Test.tsx rename frontend/src/utils/{api => }/ApiFetch.ts (91%) create mode 100644 frontend/src/utils/InputInterfaces.ts diff --git a/frontend/src/components/authentication/ErrorLogin.tsx b/frontend/src/components/authentication/ErrorLogin.tsx index 0ab4aa45..1e0ab292 100644 --- a/frontend/src/components/authentication/ErrorLogin.tsx +++ b/frontend/src/components/authentication/ErrorLogin.tsx @@ -1,5 +1,5 @@ import {JSX} from "react"; -import delphi_full from "../../../public/delphi_full.png"; +import delphi_full from "/delphi_full.png"; export default function ErrorLogin(): JSX.Element { diff --git a/frontend/src/dataloaders/LoginLoader.ts b/frontend/src/dataloaders/LoginLoader.tsx similarity index 93% rename from frontend/src/dataloaders/LoginLoader.ts rename to frontend/src/dataloaders/LoginLoader.tsx index 9edadd53..7c96b78e 100644 --- a/frontend/src/dataloaders/LoginLoader.ts +++ b/frontend/src/dataloaders/LoginLoader.tsx @@ -1,7 +1,7 @@ import {User} from "../utils/ApiInterfaces.ts"; import {Backend_user} from "../utils/BackendInterfaces.ts"; import {mapUser} from "../utils/ApiTypesMapper.ts"; -import apiFetch from "../utils/api/ApiFetch.ts"; +import apiFetch from "../utils/ApiFetch.ts"; export const LOGIN_ROUTER_ID = "login"; diff --git a/frontend/src/dataloaders/ProjectsStudentLoader.ts b/frontend/src/dataloaders/ProjectsStudentLoader.ts index 43b29101..9e782310 100644 --- a/frontend/src/dataloaders/ProjectsStudentLoader.ts +++ b/frontend/src/dataloaders/ProjectsStudentLoader.ts @@ -1,6 +1,6 @@ import {CompleteProject, Group, Submission} from "../utils/ApiInterfaces.ts"; import {getAllProjectsAndSubjects, teacherStudentRole} from "./SharedFunctions.ts"; -import apiFetch from "../utils/api/ApiFetch.ts"; +import apiFetch from "../utils/ApiFetch.ts"; import {Backend_group, Backend_submission} from "../utils/BackendInterfaces.ts"; import {mapGroup, mapSubmission} from "../utils/ApiTypesMapper.ts"; diff --git a/frontend/src/dataloaders/SharedFunctions.ts b/frontend/src/dataloaders/SharedFunctions.ts index 89c9e2d1..da516496 100644 --- a/frontend/src/dataloaders/SharedFunctions.ts +++ b/frontend/src/dataloaders/SharedFunctions.ts @@ -1,7 +1,7 @@ import {Project, properSubject, Subject} from "../utils/ApiInterfaces.ts"; import {mapProjectList, mapSubjectList} from "../utils/ApiTypesMapper.ts"; import {Backend_Project, Backend_Subject} from "../utils/BackendInterfaces.ts"; -import apiFetch from "../utils/api/ApiFetch.ts"; +import apiFetch from "../utils/ApiFetch.ts"; export enum teacherStudentRole { STUDENT = "student", diff --git a/frontend/src/dataloaders/projectsTeacherLoader.ts b/frontend/src/dataloaders/projectsTeacherLoader.ts index a709ecf1..87fedcea 100644 --- a/frontend/src/dataloaders/projectsTeacherLoader.ts +++ b/frontend/src/dataloaders/projectsTeacherLoader.ts @@ -1,6 +1,6 @@ import {CompleteProjectTeacher, Group, Submission} from "../utils/ApiInterfaces.ts"; import {getAllProjectsAndSubjects, teacherStudentRole} from "./SharedFunctions.ts"; -import apiFetch from "../utils/api/ApiFetch.ts"; +import apiFetch from "../utils/ApiFetch.ts"; import {Backend_group} from "../utils/BackendInterfaces.ts"; import {mapGroupList} from "../utils/ApiTypesMapper.ts"; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 982d6dfb..2014e4b9 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -12,7 +12,7 @@ import studentLoader, {STUDENT_ROUTER_ID} from "./dataloaders/StudentLoader.ts"; import Unauthorized from "./components/authentication/Unauthorized.tsx"; import teacherLoader, {TEACHER_ROUTER_ID} from "./dataloaders/TeacherLoader.ts"; // import coursesTeacherLoader, {SUBJECT_TEACHER_ROUTER_ID} from "./dataloaders/CoursesTeacherLoader.ts"; -import loginLoader, {LOGIN_ROUTER_ID} from "./dataloaders/LoginLoader.ts"; +import loginLoader, {LOGIN_ROUTER_ID} from "./dataloaders/LoginLoader.tsx"; import ErrorLogin from "./components/authentication/ErrorLogin.tsx"; import ProjectsViewStudent from "./pages/student/ProjectsViewStudent.tsx"; import CoursesViewStudent from "./pages/student/CoursesViewStudent.tsx"; @@ -46,7 +46,7 @@ const router = createBrowserRouter( }/> - }> + }> } loader={studentLoader}/> } loader={projectsStudentLoader}/> @@ -54,7 +54,7 @@ const router = createBrowserRouter( loader={coursesStudentLoader}/> - }> + }> } loader={teacherLoader}/> } loader={projectsTeacherLoader}/> diff --git a/frontend/src/pages/Test.tsx b/frontend/src/pages/Test.tsx deleted file mode 100644 index 626d973e..00000000 --- a/frontend/src/pages/Test.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import {projectGroup} from "../utils/api/Groups.ts"; - -export default function Test () { - function clickEvent () { - console.log(projectGroup(2)) - } - - return (
-

Welcome on this test page, click the button below to test te function

- -
) -} \ No newline at end of file diff --git a/frontend/src/pages/login/LoginScreen.tsx b/frontend/src/pages/login/LoginScreen.tsx index 7b9ec838..f02eea18 100644 --- a/frontend/src/pages/login/LoginScreen.tsx +++ b/frontend/src/pages/login/LoginScreen.tsx @@ -1,10 +1,10 @@ import React, {JSX, useEffect} from "react"; import {Navigate, useLocation, useRouteLoaderData} from 'react-router-dom'; import useAuth from "../../hooks/useAuth.ts"; -import loginLoader, {LOGIN_ROUTER_ID, loginLoaderObject} from "../../dataloaders/LoginLoader.ts"; +import loginLoader, {LOGIN_ROUTER_ID, loginLoaderObject} from "../../dataloaders/LoginLoader.tsx"; import LoginForm from "../../components/authentication/LoginForm.tsx"; -import {DEBUG} from "../root.tsx"; import {Token, User} from "../../utils/ApiInterfaces.ts"; +import {post_ticket} from "../../utils/api/Login.ts"; interface location_type { search?: { ticket?: string }, @@ -13,17 +13,12 @@ interface location_type { } const ticketLogin = async (ticket: string, setUser: React.Dispatch>) => { - let url = '/api/login?ticket=' + ticket - if (DEBUG) { - url = 'http://127.0.0.1:8000/api/login?ticket=' + ticket - } - const token = await (await fetch(url, {method: 'POST', headers: {'Content-Type': 'application/json'}})) - .json() as Token + const token: Token = await post_ticket(ticket) if (token.token) { localStorage.setItem('token', token.token) const result: loginLoaderObject = await loginLoader() - if (result.user) { + if (isUser(result.user)) { setUser(result.user) }else{ localStorage.removeItem('token') @@ -34,6 +29,10 @@ const ticketLogin = async (ticket: string, setUser: React.Dispatch { + return (data && data.user_id && data.user_name && data.user_email && data.user_roles); +} + export default function LoginScreen(): JSX.Element { const {user, setUser} = useAuth(); const location = useLocation() as location_type; @@ -52,9 +51,13 @@ export default function LoginScreen(): JSX.Element { useEffect(() => { // If the saved token is valid => the user will be logged in if (data && data.user) { - setUser(data.user) - } - else if (!user && ticket) { + if (isUser(data.user)) { + setUser(data.user) + }else{ + setUser(undefined) + localStorage.removeItem('token') + } + } else if (!user && ticket) { void ticketLogin(ticket, setUser); } }, [data, setUser, ticket, user]); diff --git a/frontend/src/utils/api/ApiFetch.ts b/frontend/src/utils/ApiFetch.ts similarity index 91% rename from frontend/src/utils/api/ApiFetch.ts rename to frontend/src/utils/ApiFetch.ts index e4934a1d..19c8d250 100644 --- a/frontend/src/utils/api/ApiFetch.ts +++ b/frontend/src/utils/ApiFetch.ts @@ -1,4 +1,4 @@ -import {DEBUG} from "../../pages/root.tsx"; +import {DEBUG} from "../pages/root.tsx"; const ApiFetch = async (url: string, options?: RequestInit) => { diff --git a/frontend/src/utils/InputInterfaces.ts b/frontend/src/utils/InputInterfaces.ts new file mode 100644 index 00000000..a2514a70 --- /dev/null +++ b/frontend/src/utils/InputInterfaces.ts @@ -0,0 +1,9 @@ +export interface ProjectInput{ + name: string, + deadline: Date, + archived: boolean, + description: string, + requirements: string, + visible: boolean, + max_students: number, +} \ No newline at end of file diff --git a/frontend/src/utils/api/Groups.ts b/frontend/src/utils/api/Groups.ts index 54bc3af5..2edec938 100644 --- a/frontend/src/utils/api/Groups.ts +++ b/frontend/src/utils/api/Groups.ts @@ -1,4 +1,4 @@ -import ApiFetch from "./ApiFetch.ts"; +import ApiFetch from "../ApiFetch.ts"; export function joinGroup(groupId: number) { void ApiFetch(`/groups/${groupId}/join`, @@ -8,16 +8,4 @@ export function joinGroup(groupId: number) { export function leaveGroup(groupId: number) { void ApiFetch(`/groups/${groupId}/leave`, {method: 'POST', headers: {'Content-Type': 'application/json'}}); -} - -export async function listGroupMembers(groupId: number) { - let members = await ApiFetch(`/groups/${groupId}/members`, - {method: 'GET', headers: {'Content-Type': 'application/json'}}); - return members; -} - -export function projectGroup(projectId: number) { - let group = void ApiFetch(`/projects/${projectId}/group`, - {method: 'GET', headers: {'Content-Type': 'application/json'}}); - return group; } \ No newline at end of file diff --git a/frontend/src/utils/api/Login.ts b/frontend/src/utils/api/Login.ts index e69de29b..af35a0f5 100644 --- a/frontend/src/utils/api/Login.ts +++ b/frontend/src/utils/api/Login.ts @@ -0,0 +1,16 @@ +import {DEBUG} from "../../pages/root.tsx"; +import {Token} from "../ApiInterfaces.ts"; + +export async function post_ticket(ticket: string){ + let url = '/api/login?ticket=' + ticket + if (DEBUG) { + url = 'http://127.0.0.1:8000/api/login?ticket=' + ticket + } + + return await (await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + },})) + .json() as Token +} \ No newline at end of file diff --git a/frontend/src/utils/api/Project.ts b/frontend/src/utils/api/Project.ts index e69de29b..04e62b87 100644 --- a/frontend/src/utils/api/Project.ts +++ b/frontend/src/utils/api/Project.ts @@ -0,0 +1,36 @@ +import apiFetch from "../ApiFetch.ts"; +import {ProjectInput} from "../InputInterfaces.ts"; +import {Backend_group, Backend_Project} from "../BackendInterfaces.ts"; +import {mapGroup} from "../ApiTypesMapper.ts"; + +export async function project_create_group(project_id: number){ + const groupData = await apiFetch(`/projects/${project_id}/groups`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }) as Backend_group + return mapGroup(groupData) +} + +export async function update_project(project_id: number, projectInput: ProjectInput){ + const projectData = await apiFetch(`/projects/${project_id}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(projectInput) + }) as Backend_Project + return { + project_id: projectData.id, + project_name: projectData.name, + project_deadline: projectData.deadline, + project_archived: projectData.archived, + project_description: projectData.description, + project_requirements: projectData.requirements, + project_visible: projectData.visible, + project_max_students: projectData.max_students, + subject_id: projectData.subject_id + } +} + diff --git a/frontend/src/utils/api/Student.ts b/frontend/src/utils/api/Student.ts index e69de29b..2269b84b 100644 --- a/frontend/src/utils/api/Student.ts +++ b/frontend/src/utils/api/Student.ts @@ -0,0 +1,7 @@ +import apiFetch from "../ApiFetch.ts"; + +export function join_subject(subjectId: number){ + void apiFetch(`/student/subjects/${subjectId}/join`, { + method: 'POST' + }) +} \ No newline at end of file diff --git a/frontend/src/utils/api/Subject.ts b/frontend/src/utils/api/Subject.ts index e69de29b..fa9850f3 100644 --- a/frontend/src/utils/api/Subject.ts +++ b/frontend/src/utils/api/Subject.ts @@ -0,0 +1,25 @@ +import {ProjectInput} from "../InputInterfaces.ts"; +import ApiFetch from "../ApiFetch.ts"; +import {Backend_Project} from "../BackendInterfaces.ts"; + +export async function subject_create_project(subjectId: number, projectInput: ProjectInput) { + const projectData: Backend_Project = (await ApiFetch(`/subjects/${subjectId}/projects`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(projectInput) + }) as Backend_Project) + + return { + project_id: projectData.id, + project_name: projectData.name, + project_deadline: projectData.deadline, + project_archived: projectData.archived, + project_description: projectData.description, + project_requirements: projectData.requirements, + project_visible: projectData.visible, + project_max_students: projectData.max_students, + subject_id: projectData.subject_id + } +} \ No newline at end of file diff --git a/frontend/src/utils/api/Teacher.ts b/frontend/src/utils/api/Teacher.ts index e69de29b..ab4a1a09 100644 --- a/frontend/src/utils/api/Teacher.ts +++ b/frontend/src/utils/api/Teacher.ts @@ -0,0 +1,13 @@ +import {Backend_Subject} from "../BackendInterfaces.ts"; +import apiFetch from "../ApiFetch.ts"; + +export async function createSubject(name: string) { + return (await apiFetch('/teacher/subjects', + { + headers: { + 'Content-Type': 'application/json' + }, + method: 'POST', + body: JSON.stringify({name: name}) + })) as Backend_Subject +} \ No newline at end of file diff --git a/frontend/src/utils/api/User.ts b/frontend/src/utils/api/User.ts index e69de29b..f731bc77 100644 --- a/frontend/src/utils/api/User.ts +++ b/frontend/src/utils/api/User.ts @@ -0,0 +1,8 @@ +import {Language} from "../../components/Settings.tsx"; +import apiFetch from "../ApiFetch.ts"; + +export function modify_language(language: Language){ + void apiFetch(`/user?language=${language}`, { + method: 'PATCH' + }) +} \ No newline at end of file From 3f6508cce0ce659d54fa951829b3fe8b08c56a57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stef=20Oss=C3=A9?= Date: Tue, 9 Apr 2024 21:42:08 +0200 Subject: [PATCH 03/90] debug false --- frontend/src/pages/root.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/root.tsx b/frontend/src/pages/root.tsx index 747c296a..a9c2d7b2 100644 --- a/frontend/src/pages/root.tsx +++ b/frontend/src/pages/root.tsx @@ -2,7 +2,7 @@ import {JSX} from "react"; import useAuth from "../hooks/useAuth.ts"; import {Navigate} from "react-router-dom"; -export const DEBUG: boolean = true; // should always be false on the repo. +export const DEBUG: boolean = false; // should always be false on the repo. export default function Root(): JSX.Element { const {user} = useAuth() From 135265f9bfeb70a917f6b82263ff35addf3fc375 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stef=20Oss=C3=A9?= Date: Tue, 9 Apr 2024 22:07:49 +0200 Subject: [PATCH 04/90] Revert "Functies user, projects, subjects, group, course" This reverts commit 81deb20f15122f7619437595a133be383bd6c4dd. --- .../components/authentication/ErrorLogin.tsx | 2 +- .../{LoginLoader.tsx => LoginLoader.ts} | 2 +- .../src/dataloaders/ProjectsStudentLoader.ts | 2 +- frontend/src/dataloaders/SharedFunctions.ts | 2 +- .../src/dataloaders/projectsTeacherLoader.ts | 2 +- frontend/src/main.tsx | 6 ++-- frontend/src/pages/Test.tsx | 12 +++++++ frontend/src/pages/login/LoginScreen.tsx | 27 +++++++------- frontend/src/utils/InputInterfaces.ts | 9 ----- frontend/src/utils/{ => api}/ApiFetch.ts | 2 +- frontend/src/utils/api/Groups.ts | 14 +++++++- frontend/src/utils/api/Login.ts | 16 --------- frontend/src/utils/api/Project.ts | 36 ------------------- frontend/src/utils/api/Student.ts | 7 ---- frontend/src/utils/api/Subject.ts | 25 ------------- frontend/src/utils/api/Teacher.ts | 13 ------- frontend/src/utils/api/User.ts | 8 ----- 17 files changed, 46 insertions(+), 139 deletions(-) rename frontend/src/dataloaders/{LoginLoader.tsx => LoginLoader.ts} (93%) create mode 100644 frontend/src/pages/Test.tsx delete mode 100644 frontend/src/utils/InputInterfaces.ts rename frontend/src/utils/{ => api}/ApiFetch.ts (91%) diff --git a/frontend/src/components/authentication/ErrorLogin.tsx b/frontend/src/components/authentication/ErrorLogin.tsx index 1e0ab292..0ab4aa45 100644 --- a/frontend/src/components/authentication/ErrorLogin.tsx +++ b/frontend/src/components/authentication/ErrorLogin.tsx @@ -1,5 +1,5 @@ import {JSX} from "react"; -import delphi_full from "/delphi_full.png"; +import delphi_full from "../../../public/delphi_full.png"; export default function ErrorLogin(): JSX.Element { diff --git a/frontend/src/dataloaders/LoginLoader.tsx b/frontend/src/dataloaders/LoginLoader.ts similarity index 93% rename from frontend/src/dataloaders/LoginLoader.tsx rename to frontend/src/dataloaders/LoginLoader.ts index 7c96b78e..9edadd53 100644 --- a/frontend/src/dataloaders/LoginLoader.tsx +++ b/frontend/src/dataloaders/LoginLoader.ts @@ -1,7 +1,7 @@ import {User} from "../utils/ApiInterfaces.ts"; import {Backend_user} from "../utils/BackendInterfaces.ts"; import {mapUser} from "../utils/ApiTypesMapper.ts"; -import apiFetch from "../utils/ApiFetch.ts"; +import apiFetch from "../utils/api/ApiFetch.ts"; export const LOGIN_ROUTER_ID = "login"; diff --git a/frontend/src/dataloaders/ProjectsStudentLoader.ts b/frontend/src/dataloaders/ProjectsStudentLoader.ts index 9e782310..43b29101 100644 --- a/frontend/src/dataloaders/ProjectsStudentLoader.ts +++ b/frontend/src/dataloaders/ProjectsStudentLoader.ts @@ -1,6 +1,6 @@ import {CompleteProject, Group, Submission} from "../utils/ApiInterfaces.ts"; import {getAllProjectsAndSubjects, teacherStudentRole} from "./SharedFunctions.ts"; -import apiFetch from "../utils/ApiFetch.ts"; +import apiFetch from "../utils/api/ApiFetch.ts"; import {Backend_group, Backend_submission} from "../utils/BackendInterfaces.ts"; import {mapGroup, mapSubmission} from "../utils/ApiTypesMapper.ts"; diff --git a/frontend/src/dataloaders/SharedFunctions.ts b/frontend/src/dataloaders/SharedFunctions.ts index da516496..89c9e2d1 100644 --- a/frontend/src/dataloaders/SharedFunctions.ts +++ b/frontend/src/dataloaders/SharedFunctions.ts @@ -1,7 +1,7 @@ import {Project, properSubject, Subject} from "../utils/ApiInterfaces.ts"; import {mapProjectList, mapSubjectList} from "../utils/ApiTypesMapper.ts"; import {Backend_Project, Backend_Subject} from "../utils/BackendInterfaces.ts"; -import apiFetch from "../utils/ApiFetch.ts"; +import apiFetch from "../utils/api/ApiFetch.ts"; export enum teacherStudentRole { STUDENT = "student", diff --git a/frontend/src/dataloaders/projectsTeacherLoader.ts b/frontend/src/dataloaders/projectsTeacherLoader.ts index 87fedcea..a709ecf1 100644 --- a/frontend/src/dataloaders/projectsTeacherLoader.ts +++ b/frontend/src/dataloaders/projectsTeacherLoader.ts @@ -1,6 +1,6 @@ import {CompleteProjectTeacher, Group, Submission} from "../utils/ApiInterfaces.ts"; import {getAllProjectsAndSubjects, teacherStudentRole} from "./SharedFunctions.ts"; -import apiFetch from "../utils/ApiFetch.ts"; +import apiFetch from "../utils/api/ApiFetch.ts"; import {Backend_group} from "../utils/BackendInterfaces.ts"; import {mapGroupList} from "../utils/ApiTypesMapper.ts"; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 2014e4b9..982d6dfb 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -12,7 +12,7 @@ import studentLoader, {STUDENT_ROUTER_ID} from "./dataloaders/StudentLoader.ts"; import Unauthorized from "./components/authentication/Unauthorized.tsx"; import teacherLoader, {TEACHER_ROUTER_ID} from "./dataloaders/TeacherLoader.ts"; // import coursesTeacherLoader, {SUBJECT_TEACHER_ROUTER_ID} from "./dataloaders/CoursesTeacherLoader.ts"; -import loginLoader, {LOGIN_ROUTER_ID} from "./dataloaders/LoginLoader.tsx"; +import loginLoader, {LOGIN_ROUTER_ID} from "./dataloaders/LoginLoader.ts"; import ErrorLogin from "./components/authentication/ErrorLogin.tsx"; import ProjectsViewStudent from "./pages/student/ProjectsViewStudent.tsx"; import CoursesViewStudent from "./pages/student/CoursesViewStudent.tsx"; @@ -46,7 +46,7 @@ const router = createBrowserRouter( }/> - }> + }> } loader={studentLoader}/> } loader={projectsStudentLoader}/> @@ -54,7 +54,7 @@ const router = createBrowserRouter( loader={coursesStudentLoader}/> - }> + }> } loader={teacherLoader}/> } loader={projectsTeacherLoader}/> diff --git a/frontend/src/pages/Test.tsx b/frontend/src/pages/Test.tsx new file mode 100644 index 00000000..626d973e --- /dev/null +++ b/frontend/src/pages/Test.tsx @@ -0,0 +1,12 @@ +import {projectGroup} from "../utils/api/Groups.ts"; + +export default function Test () { + function clickEvent () { + console.log(projectGroup(2)) + } + + return (
+

Welcome on this test page, click the button below to test te function

+ +
) +} \ No newline at end of file diff --git a/frontend/src/pages/login/LoginScreen.tsx b/frontend/src/pages/login/LoginScreen.tsx index f02eea18..7b9ec838 100644 --- a/frontend/src/pages/login/LoginScreen.tsx +++ b/frontend/src/pages/login/LoginScreen.tsx @@ -1,10 +1,10 @@ import React, {JSX, useEffect} from "react"; import {Navigate, useLocation, useRouteLoaderData} from 'react-router-dom'; import useAuth from "../../hooks/useAuth.ts"; -import loginLoader, {LOGIN_ROUTER_ID, loginLoaderObject} from "../../dataloaders/LoginLoader.tsx"; +import loginLoader, {LOGIN_ROUTER_ID, loginLoaderObject} from "../../dataloaders/LoginLoader.ts"; import LoginForm from "../../components/authentication/LoginForm.tsx"; +import {DEBUG} from "../root.tsx"; import {Token, User} from "../../utils/ApiInterfaces.ts"; -import {post_ticket} from "../../utils/api/Login.ts"; interface location_type { search?: { ticket?: string }, @@ -13,12 +13,17 @@ interface location_type { } const ticketLogin = async (ticket: string, setUser: React.Dispatch>) => { - const token: Token = await post_ticket(ticket) + let url = '/api/login?ticket=' + ticket + if (DEBUG) { + url = 'http://127.0.0.1:8000/api/login?ticket=' + ticket + } + const token = await (await fetch(url, {method: 'POST', headers: {'Content-Type': 'application/json'}})) + .json() as Token if (token.token) { localStorage.setItem('token', token.token) const result: loginLoaderObject = await loginLoader() - if (isUser(result.user)) { + if (result.user) { setUser(result.user) }else{ localStorage.removeItem('token') @@ -29,10 +34,6 @@ const ticketLogin = async (ticket: string, setUser: React.Dispatch { - return (data && data.user_id && data.user_name && data.user_email && data.user_roles); -} - export default function LoginScreen(): JSX.Element { const {user, setUser} = useAuth(); const location = useLocation() as location_type; @@ -51,13 +52,9 @@ export default function LoginScreen(): JSX.Element { useEffect(() => { // If the saved token is valid => the user will be logged in if (data && data.user) { - if (isUser(data.user)) { - setUser(data.user) - }else{ - setUser(undefined) - localStorage.removeItem('token') - } - } else if (!user && ticket) { + setUser(data.user) + } + else if (!user && ticket) { void ticketLogin(ticket, setUser); } }, [data, setUser, ticket, user]); diff --git a/frontend/src/utils/InputInterfaces.ts b/frontend/src/utils/InputInterfaces.ts deleted file mode 100644 index a2514a70..00000000 --- a/frontend/src/utils/InputInterfaces.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface ProjectInput{ - name: string, - deadline: Date, - archived: boolean, - description: string, - requirements: string, - visible: boolean, - max_students: number, -} \ No newline at end of file diff --git a/frontend/src/utils/ApiFetch.ts b/frontend/src/utils/api/ApiFetch.ts similarity index 91% rename from frontend/src/utils/ApiFetch.ts rename to frontend/src/utils/api/ApiFetch.ts index 19c8d250..e4934a1d 100644 --- a/frontend/src/utils/ApiFetch.ts +++ b/frontend/src/utils/api/ApiFetch.ts @@ -1,4 +1,4 @@ -import {DEBUG} from "../pages/root.tsx"; +import {DEBUG} from "../../pages/root.tsx"; const ApiFetch = async (url: string, options?: RequestInit) => { diff --git a/frontend/src/utils/api/Groups.ts b/frontend/src/utils/api/Groups.ts index 2edec938..54bc3af5 100644 --- a/frontend/src/utils/api/Groups.ts +++ b/frontend/src/utils/api/Groups.ts @@ -1,4 +1,4 @@ -import ApiFetch from "../ApiFetch.ts"; +import ApiFetch from "./ApiFetch.ts"; export function joinGroup(groupId: number) { void ApiFetch(`/groups/${groupId}/join`, @@ -8,4 +8,16 @@ export function joinGroup(groupId: number) { export function leaveGroup(groupId: number) { void ApiFetch(`/groups/${groupId}/leave`, {method: 'POST', headers: {'Content-Type': 'application/json'}}); +} + +export async function listGroupMembers(groupId: number) { + let members = await ApiFetch(`/groups/${groupId}/members`, + {method: 'GET', headers: {'Content-Type': 'application/json'}}); + return members; +} + +export function projectGroup(projectId: number) { + let group = void ApiFetch(`/projects/${projectId}/group`, + {method: 'GET', headers: {'Content-Type': 'application/json'}}); + return group; } \ No newline at end of file diff --git a/frontend/src/utils/api/Login.ts b/frontend/src/utils/api/Login.ts index af35a0f5..e69de29b 100644 --- a/frontend/src/utils/api/Login.ts +++ b/frontend/src/utils/api/Login.ts @@ -1,16 +0,0 @@ -import {DEBUG} from "../../pages/root.tsx"; -import {Token} from "../ApiInterfaces.ts"; - -export async function post_ticket(ticket: string){ - let url = '/api/login?ticket=' + ticket - if (DEBUG) { - url = 'http://127.0.0.1:8000/api/login?ticket=' + ticket - } - - return await (await fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - },})) - .json() as Token -} \ No newline at end of file diff --git a/frontend/src/utils/api/Project.ts b/frontend/src/utils/api/Project.ts index 04e62b87..e69de29b 100644 --- a/frontend/src/utils/api/Project.ts +++ b/frontend/src/utils/api/Project.ts @@ -1,36 +0,0 @@ -import apiFetch from "../ApiFetch.ts"; -import {ProjectInput} from "../InputInterfaces.ts"; -import {Backend_group, Backend_Project} from "../BackendInterfaces.ts"; -import {mapGroup} from "../ApiTypesMapper.ts"; - -export async function project_create_group(project_id: number){ - const groupData = await apiFetch(`/projects/${project_id}/groups`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - } - }) as Backend_group - return mapGroup(groupData) -} - -export async function update_project(project_id: number, projectInput: ProjectInput){ - const projectData = await apiFetch(`/projects/${project_id}`, { - method: 'PATCH', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(projectInput) - }) as Backend_Project - return { - project_id: projectData.id, - project_name: projectData.name, - project_deadline: projectData.deadline, - project_archived: projectData.archived, - project_description: projectData.description, - project_requirements: projectData.requirements, - project_visible: projectData.visible, - project_max_students: projectData.max_students, - subject_id: projectData.subject_id - } -} - diff --git a/frontend/src/utils/api/Student.ts b/frontend/src/utils/api/Student.ts index 2269b84b..e69de29b 100644 --- a/frontend/src/utils/api/Student.ts +++ b/frontend/src/utils/api/Student.ts @@ -1,7 +0,0 @@ -import apiFetch from "../ApiFetch.ts"; - -export function join_subject(subjectId: number){ - void apiFetch(`/student/subjects/${subjectId}/join`, { - method: 'POST' - }) -} \ No newline at end of file diff --git a/frontend/src/utils/api/Subject.ts b/frontend/src/utils/api/Subject.ts index fa9850f3..e69de29b 100644 --- a/frontend/src/utils/api/Subject.ts +++ b/frontend/src/utils/api/Subject.ts @@ -1,25 +0,0 @@ -import {ProjectInput} from "../InputInterfaces.ts"; -import ApiFetch from "../ApiFetch.ts"; -import {Backend_Project} from "../BackendInterfaces.ts"; - -export async function subject_create_project(subjectId: number, projectInput: ProjectInput) { - const projectData: Backend_Project = (await ApiFetch(`/subjects/${subjectId}/projects`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(projectInput) - }) as Backend_Project) - - return { - project_id: projectData.id, - project_name: projectData.name, - project_deadline: projectData.deadline, - project_archived: projectData.archived, - project_description: projectData.description, - project_requirements: projectData.requirements, - project_visible: projectData.visible, - project_max_students: projectData.max_students, - subject_id: projectData.subject_id - } -} \ No newline at end of file diff --git a/frontend/src/utils/api/Teacher.ts b/frontend/src/utils/api/Teacher.ts index ab4a1a09..e69de29b 100644 --- a/frontend/src/utils/api/Teacher.ts +++ b/frontend/src/utils/api/Teacher.ts @@ -1,13 +0,0 @@ -import {Backend_Subject} from "../BackendInterfaces.ts"; -import apiFetch from "../ApiFetch.ts"; - -export async function createSubject(name: string) { - return (await apiFetch('/teacher/subjects', - { - headers: { - 'Content-Type': 'application/json' - }, - method: 'POST', - body: JSON.stringify({name: name}) - })) as Backend_Subject -} \ No newline at end of file diff --git a/frontend/src/utils/api/User.ts b/frontend/src/utils/api/User.ts index f731bc77..e69de29b 100644 --- a/frontend/src/utils/api/User.ts +++ b/frontend/src/utils/api/User.ts @@ -1,8 +0,0 @@ -import {Language} from "../../components/Settings.tsx"; -import apiFetch from "../ApiFetch.ts"; - -export function modify_language(language: Language){ - void apiFetch(`/user?language=${language}`, { - method: 'PATCH' - }) -} \ No newline at end of file From 6669ee69b0f7c3493d0b5b117eb7b016237f1eb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stef=20Oss=C3=A9?= Date: Tue, 9 Apr 2024 21:39:56 +0200 Subject: [PATCH 05/90] Functies user, projects, subjects, group, course --- .../components/authentication/ErrorLogin.tsx | 2 +- .../{LoginLoader.ts => LoginLoader.tsx} | 2 +- .../src/dataloaders/ProjectsStudentLoader.ts | 2 +- frontend/src/dataloaders/SharedFunctions.ts | 2 +- .../src/dataloaders/projectsTeacherLoader.ts | 2 +- frontend/src/main.tsx | 6 ++-- frontend/src/pages/Test.tsx | 12 ------- frontend/src/pages/login/LoginScreen.tsx | 27 +++++++------- frontend/src/utils/{api => }/ApiFetch.ts | 2 +- frontend/src/utils/InputInterfaces.ts | 9 +++++ frontend/src/utils/api/Groups.ts | 14 +------- frontend/src/utils/api/Login.ts | 16 +++++++++ frontend/src/utils/api/Project.ts | 36 +++++++++++++++++++ frontend/src/utils/api/Student.ts | 7 ++++ frontend/src/utils/api/Subject.ts | 25 +++++++++++++ frontend/src/utils/api/Teacher.ts | 13 +++++++ frontend/src/utils/api/User.ts | 8 +++++ 17 files changed, 139 insertions(+), 46 deletions(-) rename frontend/src/dataloaders/{LoginLoader.ts => LoginLoader.tsx} (93%) delete mode 100644 frontend/src/pages/Test.tsx rename frontend/src/utils/{api => }/ApiFetch.ts (91%) create mode 100644 frontend/src/utils/InputInterfaces.ts diff --git a/frontend/src/components/authentication/ErrorLogin.tsx b/frontend/src/components/authentication/ErrorLogin.tsx index 0ab4aa45..1e0ab292 100644 --- a/frontend/src/components/authentication/ErrorLogin.tsx +++ b/frontend/src/components/authentication/ErrorLogin.tsx @@ -1,5 +1,5 @@ import {JSX} from "react"; -import delphi_full from "../../../public/delphi_full.png"; +import delphi_full from "/delphi_full.png"; export default function ErrorLogin(): JSX.Element { diff --git a/frontend/src/dataloaders/LoginLoader.ts b/frontend/src/dataloaders/LoginLoader.tsx similarity index 93% rename from frontend/src/dataloaders/LoginLoader.ts rename to frontend/src/dataloaders/LoginLoader.tsx index 9edadd53..7c96b78e 100644 --- a/frontend/src/dataloaders/LoginLoader.ts +++ b/frontend/src/dataloaders/LoginLoader.tsx @@ -1,7 +1,7 @@ import {User} from "../utils/ApiInterfaces.ts"; import {Backend_user} from "../utils/BackendInterfaces.ts"; import {mapUser} from "../utils/ApiTypesMapper.ts"; -import apiFetch from "../utils/api/ApiFetch.ts"; +import apiFetch from "../utils/ApiFetch.ts"; export const LOGIN_ROUTER_ID = "login"; diff --git a/frontend/src/dataloaders/ProjectsStudentLoader.ts b/frontend/src/dataloaders/ProjectsStudentLoader.ts index 43b29101..9e782310 100644 --- a/frontend/src/dataloaders/ProjectsStudentLoader.ts +++ b/frontend/src/dataloaders/ProjectsStudentLoader.ts @@ -1,6 +1,6 @@ import {CompleteProject, Group, Submission} from "../utils/ApiInterfaces.ts"; import {getAllProjectsAndSubjects, teacherStudentRole} from "./SharedFunctions.ts"; -import apiFetch from "../utils/api/ApiFetch.ts"; +import apiFetch from "../utils/ApiFetch.ts"; import {Backend_group, Backend_submission} from "../utils/BackendInterfaces.ts"; import {mapGroup, mapSubmission} from "../utils/ApiTypesMapper.ts"; diff --git a/frontend/src/dataloaders/SharedFunctions.ts b/frontend/src/dataloaders/SharedFunctions.ts index 89c9e2d1..da516496 100644 --- a/frontend/src/dataloaders/SharedFunctions.ts +++ b/frontend/src/dataloaders/SharedFunctions.ts @@ -1,7 +1,7 @@ import {Project, properSubject, Subject} from "../utils/ApiInterfaces.ts"; import {mapProjectList, mapSubjectList} from "../utils/ApiTypesMapper.ts"; import {Backend_Project, Backend_Subject} from "../utils/BackendInterfaces.ts"; -import apiFetch from "../utils/api/ApiFetch.ts"; +import apiFetch from "../utils/ApiFetch.ts"; export enum teacherStudentRole { STUDENT = "student", diff --git a/frontend/src/dataloaders/projectsTeacherLoader.ts b/frontend/src/dataloaders/projectsTeacherLoader.ts index a709ecf1..87fedcea 100644 --- a/frontend/src/dataloaders/projectsTeacherLoader.ts +++ b/frontend/src/dataloaders/projectsTeacherLoader.ts @@ -1,6 +1,6 @@ import {CompleteProjectTeacher, Group, Submission} from "../utils/ApiInterfaces.ts"; import {getAllProjectsAndSubjects, teacherStudentRole} from "./SharedFunctions.ts"; -import apiFetch from "../utils/api/ApiFetch.ts"; +import apiFetch from "../utils/ApiFetch.ts"; import {Backend_group} from "../utils/BackendInterfaces.ts"; import {mapGroupList} from "../utils/ApiTypesMapper.ts"; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 982d6dfb..2014e4b9 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -12,7 +12,7 @@ import studentLoader, {STUDENT_ROUTER_ID} from "./dataloaders/StudentLoader.ts"; import Unauthorized from "./components/authentication/Unauthorized.tsx"; import teacherLoader, {TEACHER_ROUTER_ID} from "./dataloaders/TeacherLoader.ts"; // import coursesTeacherLoader, {SUBJECT_TEACHER_ROUTER_ID} from "./dataloaders/CoursesTeacherLoader.ts"; -import loginLoader, {LOGIN_ROUTER_ID} from "./dataloaders/LoginLoader.ts"; +import loginLoader, {LOGIN_ROUTER_ID} from "./dataloaders/LoginLoader.tsx"; import ErrorLogin from "./components/authentication/ErrorLogin.tsx"; import ProjectsViewStudent from "./pages/student/ProjectsViewStudent.tsx"; import CoursesViewStudent from "./pages/student/CoursesViewStudent.tsx"; @@ -46,7 +46,7 @@ const router = createBrowserRouter( }/> - }> + }> } loader={studentLoader}/> } loader={projectsStudentLoader}/> @@ -54,7 +54,7 @@ const router = createBrowserRouter( loader={coursesStudentLoader}/> - }> + }> } loader={teacherLoader}/> } loader={projectsTeacherLoader}/> diff --git a/frontend/src/pages/Test.tsx b/frontend/src/pages/Test.tsx deleted file mode 100644 index 626d973e..00000000 --- a/frontend/src/pages/Test.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import {projectGroup} from "../utils/api/Groups.ts"; - -export default function Test () { - function clickEvent () { - console.log(projectGroup(2)) - } - - return (
-

Welcome on this test page, click the button below to test te function

- -
) -} \ No newline at end of file diff --git a/frontend/src/pages/login/LoginScreen.tsx b/frontend/src/pages/login/LoginScreen.tsx index 7b9ec838..f02eea18 100644 --- a/frontend/src/pages/login/LoginScreen.tsx +++ b/frontend/src/pages/login/LoginScreen.tsx @@ -1,10 +1,10 @@ import React, {JSX, useEffect} from "react"; import {Navigate, useLocation, useRouteLoaderData} from 'react-router-dom'; import useAuth from "../../hooks/useAuth.ts"; -import loginLoader, {LOGIN_ROUTER_ID, loginLoaderObject} from "../../dataloaders/LoginLoader.ts"; +import loginLoader, {LOGIN_ROUTER_ID, loginLoaderObject} from "../../dataloaders/LoginLoader.tsx"; import LoginForm from "../../components/authentication/LoginForm.tsx"; -import {DEBUG} from "../root.tsx"; import {Token, User} from "../../utils/ApiInterfaces.ts"; +import {post_ticket} from "../../utils/api/Login.ts"; interface location_type { search?: { ticket?: string }, @@ -13,17 +13,12 @@ interface location_type { } const ticketLogin = async (ticket: string, setUser: React.Dispatch>) => { - let url = '/api/login?ticket=' + ticket - if (DEBUG) { - url = 'http://127.0.0.1:8000/api/login?ticket=' + ticket - } - const token = await (await fetch(url, {method: 'POST', headers: {'Content-Type': 'application/json'}})) - .json() as Token + const token: Token = await post_ticket(ticket) if (token.token) { localStorage.setItem('token', token.token) const result: loginLoaderObject = await loginLoader() - if (result.user) { + if (isUser(result.user)) { setUser(result.user) }else{ localStorage.removeItem('token') @@ -34,6 +29,10 @@ const ticketLogin = async (ticket: string, setUser: React.Dispatch { + return (data && data.user_id && data.user_name && data.user_email && data.user_roles); +} + export default function LoginScreen(): JSX.Element { const {user, setUser} = useAuth(); const location = useLocation() as location_type; @@ -52,9 +51,13 @@ export default function LoginScreen(): JSX.Element { useEffect(() => { // If the saved token is valid => the user will be logged in if (data && data.user) { - setUser(data.user) - } - else if (!user && ticket) { + if (isUser(data.user)) { + setUser(data.user) + }else{ + setUser(undefined) + localStorage.removeItem('token') + } + } else if (!user && ticket) { void ticketLogin(ticket, setUser); } }, [data, setUser, ticket, user]); diff --git a/frontend/src/utils/api/ApiFetch.ts b/frontend/src/utils/ApiFetch.ts similarity index 91% rename from frontend/src/utils/api/ApiFetch.ts rename to frontend/src/utils/ApiFetch.ts index e4934a1d..19c8d250 100644 --- a/frontend/src/utils/api/ApiFetch.ts +++ b/frontend/src/utils/ApiFetch.ts @@ -1,4 +1,4 @@ -import {DEBUG} from "../../pages/root.tsx"; +import {DEBUG} from "../pages/root.tsx"; const ApiFetch = async (url: string, options?: RequestInit) => { diff --git a/frontend/src/utils/InputInterfaces.ts b/frontend/src/utils/InputInterfaces.ts new file mode 100644 index 00000000..a2514a70 --- /dev/null +++ b/frontend/src/utils/InputInterfaces.ts @@ -0,0 +1,9 @@ +export interface ProjectInput{ + name: string, + deadline: Date, + archived: boolean, + description: string, + requirements: string, + visible: boolean, + max_students: number, +} \ No newline at end of file diff --git a/frontend/src/utils/api/Groups.ts b/frontend/src/utils/api/Groups.ts index 54bc3af5..2edec938 100644 --- a/frontend/src/utils/api/Groups.ts +++ b/frontend/src/utils/api/Groups.ts @@ -1,4 +1,4 @@ -import ApiFetch from "./ApiFetch.ts"; +import ApiFetch from "../ApiFetch.ts"; export function joinGroup(groupId: number) { void ApiFetch(`/groups/${groupId}/join`, @@ -8,16 +8,4 @@ export function joinGroup(groupId: number) { export function leaveGroup(groupId: number) { void ApiFetch(`/groups/${groupId}/leave`, {method: 'POST', headers: {'Content-Type': 'application/json'}}); -} - -export async function listGroupMembers(groupId: number) { - let members = await ApiFetch(`/groups/${groupId}/members`, - {method: 'GET', headers: {'Content-Type': 'application/json'}}); - return members; -} - -export function projectGroup(projectId: number) { - let group = void ApiFetch(`/projects/${projectId}/group`, - {method: 'GET', headers: {'Content-Type': 'application/json'}}); - return group; } \ No newline at end of file diff --git a/frontend/src/utils/api/Login.ts b/frontend/src/utils/api/Login.ts index e69de29b..af35a0f5 100644 --- a/frontend/src/utils/api/Login.ts +++ b/frontend/src/utils/api/Login.ts @@ -0,0 +1,16 @@ +import {DEBUG} from "../../pages/root.tsx"; +import {Token} from "../ApiInterfaces.ts"; + +export async function post_ticket(ticket: string){ + let url = '/api/login?ticket=' + ticket + if (DEBUG) { + url = 'http://127.0.0.1:8000/api/login?ticket=' + ticket + } + + return await (await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + },})) + .json() as Token +} \ No newline at end of file diff --git a/frontend/src/utils/api/Project.ts b/frontend/src/utils/api/Project.ts index e69de29b..04e62b87 100644 --- a/frontend/src/utils/api/Project.ts +++ b/frontend/src/utils/api/Project.ts @@ -0,0 +1,36 @@ +import apiFetch from "../ApiFetch.ts"; +import {ProjectInput} from "../InputInterfaces.ts"; +import {Backend_group, Backend_Project} from "../BackendInterfaces.ts"; +import {mapGroup} from "../ApiTypesMapper.ts"; + +export async function project_create_group(project_id: number){ + const groupData = await apiFetch(`/projects/${project_id}/groups`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }) as Backend_group + return mapGroup(groupData) +} + +export async function update_project(project_id: number, projectInput: ProjectInput){ + const projectData = await apiFetch(`/projects/${project_id}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(projectInput) + }) as Backend_Project + return { + project_id: projectData.id, + project_name: projectData.name, + project_deadline: projectData.deadline, + project_archived: projectData.archived, + project_description: projectData.description, + project_requirements: projectData.requirements, + project_visible: projectData.visible, + project_max_students: projectData.max_students, + subject_id: projectData.subject_id + } +} + diff --git a/frontend/src/utils/api/Student.ts b/frontend/src/utils/api/Student.ts index e69de29b..2269b84b 100644 --- a/frontend/src/utils/api/Student.ts +++ b/frontend/src/utils/api/Student.ts @@ -0,0 +1,7 @@ +import apiFetch from "../ApiFetch.ts"; + +export function join_subject(subjectId: number){ + void apiFetch(`/student/subjects/${subjectId}/join`, { + method: 'POST' + }) +} \ No newline at end of file diff --git a/frontend/src/utils/api/Subject.ts b/frontend/src/utils/api/Subject.ts index e69de29b..fa9850f3 100644 --- a/frontend/src/utils/api/Subject.ts +++ b/frontend/src/utils/api/Subject.ts @@ -0,0 +1,25 @@ +import {ProjectInput} from "../InputInterfaces.ts"; +import ApiFetch from "../ApiFetch.ts"; +import {Backend_Project} from "../BackendInterfaces.ts"; + +export async function subject_create_project(subjectId: number, projectInput: ProjectInput) { + const projectData: Backend_Project = (await ApiFetch(`/subjects/${subjectId}/projects`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(projectInput) + }) as Backend_Project) + + return { + project_id: projectData.id, + project_name: projectData.name, + project_deadline: projectData.deadline, + project_archived: projectData.archived, + project_description: projectData.description, + project_requirements: projectData.requirements, + project_visible: projectData.visible, + project_max_students: projectData.max_students, + subject_id: projectData.subject_id + } +} \ No newline at end of file diff --git a/frontend/src/utils/api/Teacher.ts b/frontend/src/utils/api/Teacher.ts index e69de29b..ab4a1a09 100644 --- a/frontend/src/utils/api/Teacher.ts +++ b/frontend/src/utils/api/Teacher.ts @@ -0,0 +1,13 @@ +import {Backend_Subject} from "../BackendInterfaces.ts"; +import apiFetch from "../ApiFetch.ts"; + +export async function createSubject(name: string) { + return (await apiFetch('/teacher/subjects', + { + headers: { + 'Content-Type': 'application/json' + }, + method: 'POST', + body: JSON.stringify({name: name}) + })) as Backend_Subject +} \ No newline at end of file diff --git a/frontend/src/utils/api/User.ts b/frontend/src/utils/api/User.ts index e69de29b..f731bc77 100644 --- a/frontend/src/utils/api/User.ts +++ b/frontend/src/utils/api/User.ts @@ -0,0 +1,8 @@ +import {Language} from "../../components/Settings.tsx"; +import apiFetch from "../ApiFetch.ts"; + +export function modify_language(language: Language){ + void apiFetch(`/user?language=${language}`, { + method: 'PATCH' + }) +} \ No newline at end of file From 0e0f35b200bb079b2544b0ec5545d58c1149f506 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stef=20Oss=C3=A9?= Date: Tue, 9 Apr 2024 22:28:48 +0200 Subject: [PATCH 06/90] debug false --- frontend/src/pages/root.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/root.tsx b/frontend/src/pages/root.tsx index 747c296a..a9c2d7b2 100644 --- a/frontend/src/pages/root.tsx +++ b/frontend/src/pages/root.tsx @@ -2,7 +2,7 @@ import {JSX} from "react"; import useAuth from "../hooks/useAuth.ts"; import {Navigate} from "react-router-dom"; -export const DEBUG: boolean = true; // should always be false on the repo. +export const DEBUG: boolean = false; // should always be false on the repo. export default function Root(): JSX.Element { const {user} = useAuth() From 6341a6067b1a29c9b2987969bbd2f53569132c4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stef=20Oss=C3=A9?= Date: Tue, 9 Apr 2024 22:29:02 +0200 Subject: [PATCH 07/90] debug false --- backend/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/app.py b/backend/app.py index 78f1ed44..a1eb9cb9 100644 --- a/backend/app.py +++ b/backend/app.py @@ -49,7 +49,7 @@ # Add Middlewares app.add_middleware(DatabaseSessionMiddleware) -DEBUG = True # Should always be false in repo +DEBUG = False # Should always be false in repo if DEBUG: from fastapi.middleware.cors import CORSMiddleware From 91776e9785606456d0a371c5ef72f3d617e7b781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stef=20Oss=C3=A9?= Date: Sat, 13 Apr 2024 09:15:40 +0200 Subject: [PATCH 08/90] small fix --- .../{LoginLoader.tsx => LoginLoader.ts} | 0 frontend/src/main.tsx | 18 +++++++++--------- frontend/src/pages/login/LoginScreen.tsx | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) rename frontend/src/dataloaders/{LoginLoader.tsx => LoginLoader.ts} (100%) diff --git a/frontend/src/dataloaders/LoginLoader.tsx b/frontend/src/dataloaders/LoginLoader.ts similarity index 100% rename from frontend/src/dataloaders/LoginLoader.tsx rename to frontend/src/dataloaders/LoginLoader.ts diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 2014e4b9..5cdac8dd 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -12,7 +12,7 @@ import studentLoader, {STUDENT_ROUTER_ID} from "./dataloaders/StudentLoader.ts"; import Unauthorized from "./components/authentication/Unauthorized.tsx"; import teacherLoader, {TEACHER_ROUTER_ID} from "./dataloaders/TeacherLoader.ts"; // import coursesTeacherLoader, {SUBJECT_TEACHER_ROUTER_ID} from "./dataloaders/CoursesTeacherLoader.ts"; -import loginLoader, {LOGIN_ROUTER_ID} from "./dataloaders/LoginLoader.tsx"; +import loginLoader, {LOGIN_ROUTER_ID} from "./dataloaders/LoginLoader.ts"; import ErrorLogin from "./components/authentication/ErrorLogin.tsx"; import ProjectsViewStudent from "./pages/student/ProjectsViewStudent.tsx"; import CoursesViewStudent from "./pages/student/CoursesViewStudent.tsx"; @@ -46,20 +46,20 @@ const router = createBrowserRouter( }/> - }> - } loader={studentLoader}/> - } + }> + } loader={studentLoader}/> + } loader={projectsStudentLoader}/> - } + } loader={coursesStudentLoader}/> }> - } loader={teacherLoader}/> - } + } loader={teacherLoader}/> + } loader={projectsTeacherLoader}/> - }/> - }/> + } loader={coursesTeacherLoader}/> }/> diff --git a/frontend/src/pages/login/LoginScreen.tsx b/frontend/src/pages/login/LoginScreen.tsx index f02eea18..9e12869a 100644 --- a/frontend/src/pages/login/LoginScreen.tsx +++ b/frontend/src/pages/login/LoginScreen.tsx @@ -1,7 +1,7 @@ import React, {JSX, useEffect} from "react"; import {Navigate, useLocation, useRouteLoaderData} from 'react-router-dom'; import useAuth from "../../hooks/useAuth.ts"; -import loginLoader, {LOGIN_ROUTER_ID, loginLoaderObject} from "../../dataloaders/LoginLoader.tsx"; +import loginLoader, {LOGIN_ROUTER_ID, loginLoaderObject} from "../../dataloaders/LoginLoader.ts"; import LoginForm from "../../components/authentication/LoginForm.tsx"; import {Token, User} from "../../utils/ApiInterfaces.ts"; import {post_ticket} from "../../utils/api/Login.ts"; From 561bfea4ac94c3d3235753b6e4b5ee709b329056 Mon Sep 17 00:00:00 2001 From: Matthias Seghers Date: Thu, 4 Apr 2024 15:25:14 +0200 Subject: [PATCH 09/90] little changes --- frontend/src/dataloaders/SharedFunctions.ts | 6 ++---- frontend/src/utils/ApiInterfaces.ts | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/frontend/src/dataloaders/SharedFunctions.ts b/frontend/src/dataloaders/SharedFunctions.ts index 312f9913..8cabcdf1 100644 --- a/frontend/src/dataloaders/SharedFunctions.ts +++ b/frontend/src/dataloaders/SharedFunctions.ts @@ -7,10 +7,8 @@ export enum teacherStudentRole { } export async function projectsLoader(role: teacherStudentRole): Promise { - const getter = await getAllProjectsAndSubjects(role); - const subjects = getter.subjects; - const projects = getter.projects; - // TODO: add submission data + const {subjects, projects} = await getAllProjectsAndSubjects(role); + // TODO: add submission data there seems to no api available just yet. for (let i = 0; i < projects.length; i++) { const subject: Subject | undefined = subjects.find(subject => subject.id === projects[i].subject_id); if (subject !== undefined) { diff --git a/frontend/src/utils/ApiInterfaces.ts b/frontend/src/utils/ApiInterfaces.ts index 6efe8f93..c7a721be 100644 --- a/frontend/src/utils/ApiInterfaces.ts +++ b/frontend/src/utils/ApiInterfaces.ts @@ -13,7 +13,7 @@ export interface Project { visible: string, max_students: number, subject_id: number, - subject_name: string | undefined | null + subject_name?: string } export interface properSubject { From 0eafed3dca25d4ad7a203413d7a7547f33facc7f Mon Sep 17 00:00:00 2001 From: Matthias Seghers Date: Thu, 4 Apr 2024 20:09:32 +0200 Subject: [PATCH 10/90] changed interface structure to better match api --- frontend/src/dataloaders/SharedFunctions.ts | 28 +++++++--- frontend/src/utils/ApiInterfaces.ts | 58 ++++++++++++++------- 2 files changed, 59 insertions(+), 27 deletions(-) diff --git a/frontend/src/dataloaders/SharedFunctions.ts b/frontend/src/dataloaders/SharedFunctions.ts index 8cabcdf1..bd042e4e 100644 --- a/frontend/src/dataloaders/SharedFunctions.ts +++ b/frontend/src/dataloaders/SharedFunctions.ts @@ -1,4 +1,4 @@ -import {Project, Subject} from "../utils/ApiInterfaces.ts"; +import {CompleteProject, Group, Project, Subject, Submission} from "../utils/ApiInterfaces.ts"; import apiFetch from "../utils/ApiFetch.ts"; export enum teacherStudentRole { @@ -6,16 +6,23 @@ export enum teacherStudentRole { TEACHER = "teacher" } -export async function projectsLoader(role: teacherStudentRole): Promise { +export async function projectsLoader(role: teacherStudentRole): Promise { const {subjects, projects} = await getAllProjectsAndSubjects(role); // TODO: add submission data there seems to no api available just yet. - for (let i = 0; i < projects.length; i++) { - const subject: Subject | undefined = subjects.find(subject => subject.id === projects[i].subject_id); - if (subject !== undefined) { - projects[i].subject_name = subject.name; + const submissions: Submission[] = await Promise.all(projects.map(project => { + return getSubmissionforProject(project.project_id); + })); + return projects.map((project, index) => { + const subject = subjects.find(subject => subject.subject_id === project.subject_id); + if (subject === undefined) { + throw Error("there should always be a subject for a project"); } - } - return projects; + return { + ...project, + ...subject, + ...submissions[index] + } + }); } export interface projectsAndSubjects { @@ -28,3 +35,8 @@ export async function getAllProjectsAndSubjects(role: teacherStudentRole): Promi const subjects: Subject[] = (await apiFetch(`/${role}/subjects`)) as Subject[]; return {projects, subjects} } + +export async function getSubmissionforProject(project_id: number): Promise { + const group: Group = (await apiFetch(`/projects/${project_id}/group`)) as Group; + return (await apiFetch(`/groups/${group.group_id}/submission`)) as Submission; +} diff --git a/frontend/src/utils/ApiInterfaces.ts b/frontend/src/utils/ApiInterfaces.ts index c7a721be..ff17b611 100644 --- a/frontend/src/utils/ApiInterfaces.ts +++ b/frontend/src/utils/ApiInterfaces.ts @@ -1,24 +1,44 @@ export interface Subject { - id: number, - name: string + subject_id: number, + subject_name: string } export interface Project { - id: number, - name: string, - deadline: string | Date, - archived: boolean, - description: string, - requirements: string, - visible: string, - max_students: number, - subject_id: number, - subject_name?: string + project_id: number, + project_name: string, + project_deadline: string | Date, + project_archived: boolean, + project_description: string, + project_requirements: string, + project_visible: string, + project_max_students: number, + subject_id: number } -export interface properSubject { - id: number, - name: string, +export enum SUBMISSION_STATE { + Pending = 1, + Approved = 2, + Rejected = 3 +} + +export interface Submission { + submission_id: number, + submission_date: string | Date, + submission_group_id: number, + submission_student_id: number, + submission_state: SUBMISSION_STATE, + submission_message: string, + submission_filename: string +} + +export interface Group { + group_id: number, + project_id: number +} + +export interface CompleteProject extends Project, Submission, Subject {} + +export interface properSubject extends Subject { active_projects: number, first_deadline: Date | null | string } @@ -28,8 +48,8 @@ export interface Token { } export interface User { - id: number, - name: string, - email: string, - roles: string[] + user_id: number, + user_name: string, + user_email: string, + user_roles: string[] } \ No newline at end of file From d0efd58c098f61cdc6eeaf45de887f06cdca742b Mon Sep 17 00:00:00 2001 From: Matthias Seghers Date: Fri, 5 Apr 2024 12:01:37 +0200 Subject: [PATCH 11/90] /teacher and /teacher/projects now have good loaders. --- frontend/src/dataloaders/SharedFunctions.ts | 8 +++++--- frontend/src/dataloaders/StudentLoader.ts | 9 ++++----- frontend/src/dataloaders/TeacherLoader.ts | 9 +++++---- frontend/src/dataloaders/projectsTeacherLoader.ts | 13 +++++++++++++ frontend/src/main.tsx | 3 ++- 5 files changed, 29 insertions(+), 13 deletions(-) create mode 100644 frontend/src/dataloaders/projectsTeacherLoader.ts diff --git a/frontend/src/dataloaders/SharedFunctions.ts b/frontend/src/dataloaders/SharedFunctions.ts index bd042e4e..252fbb1a 100644 --- a/frontend/src/dataloaders/SharedFunctions.ts +++ b/frontend/src/dataloaders/SharedFunctions.ts @@ -8,9 +8,11 @@ export enum teacherStudentRole { export async function projectsLoader(role: teacherStudentRole): Promise { const {subjects, projects} = await getAllProjectsAndSubjects(role); - // TODO: add submission data there seems to no api available just yet. + if (! Array.isArray(projects) || ! Array.isArray(subjects)) { + throw Error("Problem loading projects or courses."); + } const submissions: Submission[] = await Promise.all(projects.map(project => { - return getSubmissionforProject(project.project_id); + return getSubmissionForProject(project.project_id); })); return projects.map((project, index) => { const subject = subjects.find(subject => subject.subject_id === project.subject_id); @@ -36,7 +38,7 @@ export async function getAllProjectsAndSubjects(role: teacherStudentRole): Promi return {projects, subjects} } -export async function getSubmissionforProject(project_id: number): Promise { +export async function getSubmissionForProject(project_id: number): Promise { const group: Group = (await apiFetch(`/projects/${project_id}/group`)) as Group; return (await apiFetch(`/groups/${group.group_id}/submission`)) as Submission; } diff --git a/frontend/src/dataloaders/StudentLoader.ts b/frontend/src/dataloaders/StudentLoader.ts index 39dd96d0..e8dc3eba 100644 --- a/frontend/src/dataloaders/StudentLoader.ts +++ b/frontend/src/dataloaders/StudentLoader.ts @@ -1,15 +1,14 @@ -import {Project} from "../utils/ApiInterfaces.ts"; +import {CompleteProject} from "../utils/ApiInterfaces.ts"; import {projectsLoader, teacherStudentRole} from "./SharedFunctions.ts"; export interface studentLoaderObject { - projects: Project[] + projects: CompleteProject[] } export const STUDENT_ROUTER_ID = "student"; export default async function studentLoader(): Promise { - const projects: Project[] = await projectsLoader(teacherStudentRole.STUDENT); - // TODO: add submission data - return {"projects": projects}; + const projects: CompleteProject[] = await projectsLoader(teacherStudentRole.STUDENT); + return {projects}; } \ No newline at end of file diff --git a/frontend/src/dataloaders/TeacherLoader.ts b/frontend/src/dataloaders/TeacherLoader.ts index da3e74ca..e2ea98e8 100644 --- a/frontend/src/dataloaders/TeacherLoader.ts +++ b/frontend/src/dataloaders/TeacherLoader.ts @@ -1,13 +1,14 @@ -import {Project} from "../utils/ApiInterfaces.ts"; +import {CompleteProject} from "../utils/ApiInterfaces.ts"; import {projectsLoader, teacherStudentRole} from "./SharedFunctions.ts"; export interface teacherLoaderObject { - projects: Project[] + projects: CompleteProject[] } export const TEACHER_ROUTER_ID = "teacher"; export default async function teacherLoader(): Promise { - const projects: Project[] = await projectsLoader(teacherStudentRole.TEACHER); - return {"projects": projects} + const projects: CompleteProject[] = (await projectsLoader(teacherStudentRole.TEACHER)) + .filter(project => !project.project_archived && project.project_visible); + return {projects}; } \ No newline at end of file diff --git a/frontend/src/dataloaders/projectsTeacherLoader.ts b/frontend/src/dataloaders/projectsTeacherLoader.ts new file mode 100644 index 00000000..a330a59e --- /dev/null +++ b/frontend/src/dataloaders/projectsTeacherLoader.ts @@ -0,0 +1,13 @@ +import {CompleteProject} from "../utils/ApiInterfaces.ts"; +import {projectsLoader, teacherStudentRole} from "./SharedFunctions.ts"; + +export interface projectsTeacherLoaderObject { + projects: CompleteProject[] +} + +export const PROJECTS_TEACHER_ROUTER_ID = "projects_teacher"; + +export default async function projectsTeacherLoader(): Promise { + const projects: CompleteProject[] = await projectsLoader(teacherStudentRole.TEACHER); + return {projects}; +} \ No newline at end of file diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index f258a6f8..ca0f1260 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -24,6 +24,7 @@ import ProjectsViewTeacher from "./pages/teacher/ProjectsViewTeacher.tsx"; import CoursesViewTeacher from "./pages/teacher/CoursesViewTeacher.tsx"; import {CreateProject} from "./pages/teacher/CreateProject.tsx"; import CreateCourse from "./pages/teacher/CreateCourse.tsx"; +import projectsTeacherLoader, {PROJECTS_TEACHER_ROUTER_ID} from "./dataloaders/projectsTeacherLoader.ts"; const router = createBrowserRouter( createRoutesFromElements( @@ -50,7 +51,7 @@ const router = createBrowserRouter( }> } loader={teacherLoader}/> - }/> + } loader={projectsTeacherLoader}/> }/> } /*loader={subjectsTeacherLoader} id={SUBJECT_TEACHER_ROUTER_ID}*//> }/> From de87852bfb215a837f702ab4b1a991b7620fac78 Mon Sep 17 00:00:00 2001 From: Matthias Seghers Date: Fri, 5 Apr 2024 12:30:08 +0200 Subject: [PATCH 12/90] updated pre-commit to also check the linting of the frontend --- backend/.pre-commit-config.yaml | 7 +++- .../src/dataloaders/SubjectsTeacherLoader.ts | 35 +++++++++---------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/backend/.pre-commit-config.yaml b/backend/.pre-commit-config.yaml index fbd27e2b..9f74ecc4 100644 --- a/backend/.pre-commit-config.yaml +++ b/backend/.pre-commit-config.yaml @@ -12,4 +12,9 @@ repos: entry: pyright language: system types: [python] - always_run: true \ No newline at end of file + always_run: true + - id: eslint-and-tsc + name: eslint and tsc linting checks + entry: pwd + language: system + always_run: true diff --git a/frontend/src/dataloaders/SubjectsTeacherLoader.ts b/frontend/src/dataloaders/SubjectsTeacherLoader.ts index e5c066a1..a6d08357 100644 --- a/frontend/src/dataloaders/SubjectsTeacherLoader.ts +++ b/frontend/src/dataloaders/SubjectsTeacherLoader.ts @@ -1,27 +1,26 @@ -import {Project, properSubject, Subject} from "../utils/ApiInterfaces.ts"; -import {getAllProjectsAndSubjects, projectsAndSubjects, teacherStudentRole} from "./SharedFunctions.ts"; +import {properSubject} from "../utils/ApiInterfaces.ts"; export interface subjectsTeacherLoaderObject { subjects: properSubject[] } -export const SUBJECT_TEACHER_ROUTER_ID = "subjectTeacher"; +// export const SUBJECT_TEACHER_ROUTER_ID = "subjectTeacher"; export default async function subjectsTeacherLoader(): Promise { - const temp: projectsAndSubjects = await getAllProjectsAndSubjects(teacherStudentRole.TEACHER); - const subjects: Subject[] = temp.subjects; - const projects: Project[] = temp.projects; + // const temp: projectsAndSubjects = await getAllProjectsAndSubjects(teacherStudentRole.TEACHER); + // const subjects: Subject[] = temp.subjects; + // const projects: Project[] = temp.projects; - const p_subjects: properSubject[] = subjects.map(subject => { - const active_projects = projects.filter(project => - project.archived && project.subject_id === subject.id - ); - return { - id: subject.id, - name: subject.name, - active_projects: active_projects.length, - first_deadline: null // TODO: add deadlines when needed api endpoints are added. - }; - }); - return {"subjects": p_subjects} + // const p_subjects: properSubject[] = subjects.map(subject => { + // const active_projects = projects.filter(project => + // project.archived && project.subject_id === subject.id + // ); + // return { + // id: subject.id, + // name: subject.name, + // active_projects: active_projects.length, + // first_deadline: null // TODO: add deadlines when needed api endpoints are added. + // }; + // }); + return {"subjects": []} } \ No newline at end of file From 68b72e861f24710282a5a2d996cf191fdd083f67 Mon Sep 17 00:00:00 2001 From: Matthias Seghers Date: Fri, 5 Apr 2024 12:45:39 +0200 Subject: [PATCH 13/90] updated pre-commit to also check the linting of the frontend --- backend/.pre-commit-config.yaml | 2 +- frontend-lint-check.sh | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100755 frontend-lint-check.sh diff --git a/backend/.pre-commit-config.yaml b/backend/.pre-commit-config.yaml index 9f74ecc4..38e73435 100644 --- a/backend/.pre-commit-config.yaml +++ b/backend/.pre-commit-config.yaml @@ -15,6 +15,6 @@ repos: always_run: true - id: eslint-and-tsc name: eslint and tsc linting checks - entry: pwd + entry: ./frontend-lint-check.sh language: system always_run: true diff --git a/frontend-lint-check.sh b/frontend-lint-check.sh new file mode 100755 index 00000000..95b9732f --- /dev/null +++ b/frontend-lint-check.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +cd frontend +npm run lint From 3ab12fa89b0a7958e3616fa2089f92263d1908e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stef=20Oss=C3=A9?= Date: Sat, 6 Apr 2024 16:39:26 +0200 Subject: [PATCH 14/90] fixing linting --- frontend/src/components/Header.tsx | 2 +- frontend/src/components/authentication/RequireAuth.tsx | 2 +- frontend/src/pages/login/LoginScreen.tsx | 2 +- frontend/src/pages/root.tsx | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index f36e4380..eb041bf0 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -14,7 +14,7 @@ export function Header(props: { page_title: string, home: string }): JSX.Element

{props.page_title}

diff --git a/frontend/src/components/authentication/RequireAuth.tsx b/frontend/src/components/authentication/RequireAuth.tsx index 3491ea11..7841130b 100644 --- a/frontend/src/components/authentication/RequireAuth.tsx +++ b/frontend/src/components/authentication/RequireAuth.tsx @@ -10,7 +10,7 @@ const RequireAuth = ({allowedRoles}: Props) => { const location = useLocation(); if (user) { return ( - (allowedRoles && user.roles.find(role => allowedRoles.includes(role))) + (allowedRoles && user.user_roles.find(role => allowedRoles.includes(role))) ? : ); diff --git a/frontend/src/pages/login/LoginScreen.tsx b/frontend/src/pages/login/LoginScreen.tsx index 0f9beba1..e407a104 100644 --- a/frontend/src/pages/login/LoginScreen.tsx +++ b/frontend/src/pages/login/LoginScreen.tsx @@ -35,7 +35,7 @@ const ticketLogin = async (ticket: string, setUser: React.Dispatch { - return (data && data.id && data.name && data.email && data.roles); + return (data && data.user_id && data.user_name && data.user_email && data.user_roles); } diff --git a/frontend/src/pages/root.tsx b/frontend/src/pages/root.tsx index b87b4bb4..a9c2d7b2 100644 --- a/frontend/src/pages/root.tsx +++ b/frontend/src/pages/root.tsx @@ -8,11 +8,11 @@ export default function Root(): JSX.Element { const {user} = useAuth() let to: string = "/error" - if (user?.roles.includes('TEACHER')) { + if (user?.user_roles.includes('TEACHER')) { to = "/teacher"; - } else if (user?.roles.includes('STUDENT')) { + } else if (user?.user_roles.includes('STUDENT')) { to = "/student"; - } else if (user?.roles.includes('ADMIN')) { + } else if (user?.user_roles.includes('ADMIN')) { to = "/admin"; } From c7ecf00954370ebe86ade5ed8d85cdaa7af34860 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stef=20Oss=C3=A9?= Date: Tue, 9 Apr 2024 17:22:25 +0200 Subject: [PATCH 15/90] Created files --- .../components/authentication/RequireAuth.tsx | 2 +- frontend/src/dataloaders/LoginLoader.ts | 2 +- frontend/src/dataloaders/SharedFunctions.ts | 2 +- frontend/src/main.tsx | 2 ++ frontend/src/pages/Test.tsx | 12 ++++++++++ frontend/src/pages/login/LoginScreen.tsx | 2 +- frontend/src/pages/root.tsx | 8 +++---- frontend/src/utils/ApiInterfaces.ts | 8 +++---- frontend/src/utils/{ => api}/ApiFetch.ts | 2 +- frontend/src/utils/api/Groups.ts | 23 +++++++++++++++++++ frontend/src/utils/api/Login.ts | 0 frontend/src/utils/api/Project.ts | 0 frontend/src/utils/api/Student.ts | 0 frontend/src/utils/api/Subject.ts | 0 frontend/src/utils/api/Submission.ts | 0 frontend/src/utils/api/Teacher.ts | 0 frontend/src/utils/api/User.ts | 0 17 files changed, 50 insertions(+), 13 deletions(-) create mode 100644 frontend/src/pages/Test.tsx rename frontend/src/utils/{ => api}/ApiFetch.ts (91%) create mode 100644 frontend/src/utils/api/Groups.ts create mode 100644 frontend/src/utils/api/Login.ts create mode 100644 frontend/src/utils/api/Project.ts create mode 100644 frontend/src/utils/api/Student.ts create mode 100644 frontend/src/utils/api/Subject.ts create mode 100644 frontend/src/utils/api/Submission.ts create mode 100644 frontend/src/utils/api/Teacher.ts create mode 100644 frontend/src/utils/api/User.ts diff --git a/frontend/src/components/authentication/RequireAuth.tsx b/frontend/src/components/authentication/RequireAuth.tsx index 7841130b..3491ea11 100644 --- a/frontend/src/components/authentication/RequireAuth.tsx +++ b/frontend/src/components/authentication/RequireAuth.tsx @@ -10,7 +10,7 @@ const RequireAuth = ({allowedRoles}: Props) => { const location = useLocation(); if (user) { return ( - (allowedRoles && user.user_roles.find(role => allowedRoles.includes(role))) + (allowedRoles && user.roles.find(role => allowedRoles.includes(role))) ? : ); diff --git a/frontend/src/dataloaders/LoginLoader.ts b/frontend/src/dataloaders/LoginLoader.ts index b504affe..ec7625f6 100644 --- a/frontend/src/dataloaders/LoginLoader.ts +++ b/frontend/src/dataloaders/LoginLoader.ts @@ -1,5 +1,5 @@ import {User} from "../utils/ApiInterfaces.ts"; -import apiFetch from "../utils/ApiFetch.ts"; +import apiFetch from "../utils/api/ApiFetch.ts"; export const LOGIN_ROUTER_ID = "login"; diff --git a/frontend/src/dataloaders/SharedFunctions.ts b/frontend/src/dataloaders/SharedFunctions.ts index 252fbb1a..cbc60806 100644 --- a/frontend/src/dataloaders/SharedFunctions.ts +++ b/frontend/src/dataloaders/SharedFunctions.ts @@ -1,5 +1,5 @@ import {CompleteProject, Group, Project, Subject, Submission} from "../utils/ApiInterfaces.ts"; -import apiFetch from "../utils/ApiFetch.ts"; +import apiFetch from "../utils/api/ApiFetch.ts"; export enum teacherStudentRole { STUDENT = "student", diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index ca0f1260..28d210e7 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -25,6 +25,7 @@ import CoursesViewTeacher from "./pages/teacher/CoursesViewTeacher.tsx"; import {CreateProject} from "./pages/teacher/CreateProject.tsx"; import CreateCourse from "./pages/teacher/CreateCourse.tsx"; import projectsTeacherLoader, {PROJECTS_TEACHER_ROUTER_ID} from "./dataloaders/projectsTeacherLoader.ts"; +import Test from "./pages/Test.tsx"; const router = createBrowserRouter( createRoutesFromElements( @@ -33,6 +34,7 @@ const router = createBrowserRouter( } loader={loginLoader} errorElement={}/> }/> + }/> {/* Protected routes */} }> diff --git a/frontend/src/pages/Test.tsx b/frontend/src/pages/Test.tsx new file mode 100644 index 00000000..626d973e --- /dev/null +++ b/frontend/src/pages/Test.tsx @@ -0,0 +1,12 @@ +import {projectGroup} from "../utils/api/Groups.ts"; + +export default function Test () { + function clickEvent () { + console.log(projectGroup(2)) + } + + return (
+

Welcome on this test page, click the button below to test te function

+ +
) +} \ No newline at end of file diff --git a/frontend/src/pages/login/LoginScreen.tsx b/frontend/src/pages/login/LoginScreen.tsx index e407a104..0f9beba1 100644 --- a/frontend/src/pages/login/LoginScreen.tsx +++ b/frontend/src/pages/login/LoginScreen.tsx @@ -35,7 +35,7 @@ const ticketLogin = async (ticket: string, setUser: React.Dispatch { - return (data && data.user_id && data.user_name && data.user_email && data.user_roles); + return (data && data.id && data.name && data.email && data.roles); } diff --git a/frontend/src/pages/root.tsx b/frontend/src/pages/root.tsx index a9c2d7b2..ea446625 100644 --- a/frontend/src/pages/root.tsx +++ b/frontend/src/pages/root.tsx @@ -2,17 +2,17 @@ import {JSX} from "react"; import useAuth from "../hooks/useAuth.ts"; import {Navigate} from "react-router-dom"; -export const DEBUG: boolean = false; // should always be false on the repo. +export const DEBUG: boolean = true; // should always be false on the repo. export default function Root(): JSX.Element { const {user} = useAuth() let to: string = "/error" - if (user?.user_roles.includes('TEACHER')) { + if (user?.roles.includes('TEACHER')) { to = "/teacher"; - } else if (user?.user_roles.includes('STUDENT')) { + } else if (user?.roles.includes('STUDENT')) { to = "/student"; - } else if (user?.user_roles.includes('ADMIN')) { + } else if (user?.roles.includes('ADMIN')) { to = "/admin"; } diff --git a/frontend/src/utils/ApiInterfaces.ts b/frontend/src/utils/ApiInterfaces.ts index ff17b611..ef530a5b 100644 --- a/frontend/src/utils/ApiInterfaces.ts +++ b/frontend/src/utils/ApiInterfaces.ts @@ -48,8 +48,8 @@ export interface Token { } export interface User { - user_id: number, - user_name: string, - user_email: string, - user_roles: string[] + id: number, + name: string, + email: string, + roles: string[] } \ No newline at end of file diff --git a/frontend/src/utils/ApiFetch.ts b/frontend/src/utils/api/ApiFetch.ts similarity index 91% rename from frontend/src/utils/ApiFetch.ts rename to frontend/src/utils/api/ApiFetch.ts index 19c8d250..e4934a1d 100644 --- a/frontend/src/utils/ApiFetch.ts +++ b/frontend/src/utils/api/ApiFetch.ts @@ -1,4 +1,4 @@ -import {DEBUG} from "../pages/root.tsx"; +import {DEBUG} from "../../pages/root.tsx"; const ApiFetch = async (url: string, options?: RequestInit) => { diff --git a/frontend/src/utils/api/Groups.ts b/frontend/src/utils/api/Groups.ts new file mode 100644 index 00000000..54bc3af5 --- /dev/null +++ b/frontend/src/utils/api/Groups.ts @@ -0,0 +1,23 @@ +import ApiFetch from "./ApiFetch.ts"; + +export function joinGroup(groupId: number) { + void ApiFetch(`/groups/${groupId}/join`, + {method: 'POST', headers: {'Content-Type': 'application/json'}}); +} + +export function leaveGroup(groupId: number) { + void ApiFetch(`/groups/${groupId}/leave`, + {method: 'POST', headers: {'Content-Type': 'application/json'}}); +} + +export async function listGroupMembers(groupId: number) { + let members = await ApiFetch(`/groups/${groupId}/members`, + {method: 'GET', headers: {'Content-Type': 'application/json'}}); + return members; +} + +export function projectGroup(projectId: number) { + let group = void ApiFetch(`/projects/${projectId}/group`, + {method: 'GET', headers: {'Content-Type': 'application/json'}}); + return group; +} \ No newline at end of file diff --git a/frontend/src/utils/api/Login.ts b/frontend/src/utils/api/Login.ts new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/utils/api/Project.ts b/frontend/src/utils/api/Project.ts new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/utils/api/Student.ts b/frontend/src/utils/api/Student.ts new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/utils/api/Subject.ts b/frontend/src/utils/api/Subject.ts new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/utils/api/Submission.ts b/frontend/src/utils/api/Submission.ts new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/utils/api/Teacher.ts b/frontend/src/utils/api/Teacher.ts new file mode 100644 index 00000000..e69de29b diff --git a/frontend/src/utils/api/User.ts b/frontend/src/utils/api/User.ts new file mode 100644 index 00000000..e69de29b From 32c53f0526923f9d4694dc622e5162c5ba406de3 Mon Sep 17 00:00:00 2001 From: ALBERICLOOS Date: Sat, 6 Apr 2024 13:20:40 +0200 Subject: [PATCH 16/90] add project student loader --- frontend/src/dataloaders/ProjectsStudentLoader.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 frontend/src/dataloaders/ProjectsStudentLoader.ts diff --git a/frontend/src/dataloaders/ProjectsStudentLoader.ts b/frontend/src/dataloaders/ProjectsStudentLoader.ts new file mode 100644 index 00000000..75cb0c67 --- /dev/null +++ b/frontend/src/dataloaders/ProjectsStudentLoader.ts @@ -0,0 +1,13 @@ +import {CompleteProject} from "../utils/ApiInterfaces.ts"; +import {projectsLoader, teacherStudentRole} from "./SharedFunctions.ts"; + +export const PROJECTS_STUDENT_ROUTER_ID = "projects_student"; + +export interface projectsStudentLoaderObject { + projects: CompleteProject[] +} + +export default async function projectsStudentLoader(): Promise { + const projects: CompleteProject[] = await projectsLoader(teacherStudentRole.STUDENT); + return {projects}; +} \ No newline at end of file From ce8881ceb00f3344616703ff938b2bc18abaae81 Mon Sep 17 00:00:00 2001 From: ALBERICLOOS Date: Sat, 6 Apr 2024 13:22:42 +0200 Subject: [PATCH 17/90] add student loader to main --- frontend/src/main.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 28d210e7..c900e1f8 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -25,7 +25,7 @@ import CoursesViewTeacher from "./pages/teacher/CoursesViewTeacher.tsx"; import {CreateProject} from "./pages/teacher/CreateProject.tsx"; import CreateCourse from "./pages/teacher/CreateCourse.tsx"; import projectsTeacherLoader, {PROJECTS_TEACHER_ROUTER_ID} from "./dataloaders/projectsTeacherLoader.ts"; -import Test from "./pages/Test.tsx"; +import projectsStudentLoader, {PROJECTS_STUDENT_ROUTER_ID} from "./dataloaders/ProjectsStudentLoader.ts"; const router = createBrowserRouter( createRoutesFromElements( @@ -34,7 +34,6 @@ const router = createBrowserRouter( } loader={loginLoader} errorElement={}/> }/> - }/> {/* Protected routes */} }> @@ -47,15 +46,18 @@ const router = createBrowserRouter( }> } loader={studentLoader}/> - }/> + } + loader={projectsStudentLoader}/> }/> }> } loader={teacherLoader}/> - } loader={projectsTeacherLoader}/> + } + loader={projectsTeacherLoader}/> }/> - } /*loader={subjectsTeacherLoader} id={SUBJECT_TEACHER_ROUTER_ID}*//> + } /*loader={subjectsTeacherLoader} id={SUBJECT_TEACHER_ROUTER_ID}*//> }/> From ff1c76eb38c0bd779b138e66bbb9374bd1715e57 Mon Sep 17 00:00:00 2001 From: ALBERICLOOS Date: Sat, 6 Apr 2024 13:39:31 +0200 Subject: [PATCH 18/90] add loader to projectview file --- frontend/src/pages/student/ProjectsViewStudent.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/student/ProjectsViewStudent.tsx b/frontend/src/pages/student/ProjectsViewStudent.tsx index 17bd8b6d..dab6fb42 100644 --- a/frontend/src/pages/student/ProjectsViewStudent.tsx +++ b/frontend/src/pages/student/ProjectsViewStudent.tsx @@ -3,8 +3,15 @@ import {Header} from "../../components/Header.tsx"; import {Sidebar} from "../../components/Sidebar.tsx"; import {SearchBar} from "../../components/SearchBar.tsx"; import {Table, TableRowProjects} from "../../components/Table.tsx"; +import {studentLoaderObject} from "../../dataloaders/StudentLoader.ts"; +import {useRouteLoaderData} from "react-router-dom"; +import {PROJECTS_STUDENT_ROUTER_ID} from "../../dataloaders/ProjectsStudentLoader.ts"; export default function ProjectsViewStudent(): JSX.Element { + + const data: studentLoaderObject = useRouteLoaderData(PROJECTS_STUDENT_ROUTER_ID) as studentLoaderObject + console.log(data) + const tableProjectsActive: TableRowProjects[] = [ { name: "Markov Decision Diagram", @@ -46,7 +53,8 @@ export default function ProjectsViewStudent(): JSX.Element {
-
+
From 0c0dbba51ab53271887b76336af42a69ca9d528f Mon Sep 17 00:00:00 2001 From: ALBERICLOOS Date: Sat, 6 Apr 2024 13:53:14 +0200 Subject: [PATCH 19/90] add student courses dataloader --- frontend/src/dataloaders/CoursesStudentLoader.ts | 13 +++++++++++++ frontend/src/main.tsx | 4 +++- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 frontend/src/dataloaders/CoursesStudentLoader.ts diff --git a/frontend/src/dataloaders/CoursesStudentLoader.ts b/frontend/src/dataloaders/CoursesStudentLoader.ts new file mode 100644 index 00000000..2cdbd518 --- /dev/null +++ b/frontend/src/dataloaders/CoursesStudentLoader.ts @@ -0,0 +1,13 @@ +import {properSubject} from "../utils/ApiInterfaces.ts"; +import {coursesLoader, teacherStudentRole} from "./SharedFunctions.ts"; + +export const COURSES_STUDENT_ROUTER_ID = "courses_student"; + +export interface coursesStudentLoaderObject { + courses: properSubject[] +} + +export default async function coursesStudentLoader(): Promise { + const courses: properSubject[] = await coursesLoader(teacherStudentRole.STUDENT); + return {courses}; +} \ No newline at end of file diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index c900e1f8..21346521 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -26,6 +26,7 @@ import {CreateProject} from "./pages/teacher/CreateProject.tsx"; import CreateCourse from "./pages/teacher/CreateCourse.tsx"; import projectsTeacherLoader, {PROJECTS_TEACHER_ROUTER_ID} from "./dataloaders/projectsTeacherLoader.ts"; import projectsStudentLoader, {PROJECTS_STUDENT_ROUTER_ID} from "./dataloaders/ProjectsStudentLoader.ts"; +import coursesStudentLoader, {COURSES_STUDENT_ROUTER_ID} from './dataloaders/CoursesStudentLoader.ts'; const router = createBrowserRouter( createRoutesFromElements( @@ -48,7 +49,8 @@ const router = createBrowserRouter( } loader={studentLoader}/> } loader={projectsStudentLoader}/> - }/> + } + loader={coursesStudentLoader}/> }> From b208af2c06f8a223295f664be22a52cd96a06db1 Mon Sep 17 00:00:00 2001 From: ALBERICLOOS Date: Sat, 6 Apr 2024 14:03:15 +0200 Subject: [PATCH 20/90] change coursedataloader --- frontend/src/dataloaders/SharedFunctions.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/frontend/src/dataloaders/SharedFunctions.ts b/frontend/src/dataloaders/SharedFunctions.ts index cbc60806..6807baa3 100644 --- a/frontend/src/dataloaders/SharedFunctions.ts +++ b/frontend/src/dataloaders/SharedFunctions.ts @@ -1,4 +1,4 @@ -import {CompleteProject, Group, Project, Subject, Submission} from "../utils/ApiInterfaces.ts"; +import {CompleteProject, Group, Project, properSubject, Subject, Submission} from "../utils/ApiInterfaces.ts"; import apiFetch from "../utils/api/ApiFetch.ts"; export enum teacherStudentRole { @@ -6,6 +6,25 @@ export enum teacherStudentRole { TEACHER = "teacher" } +export async function coursesLoader(role: teacherStudentRole): Promise { + const {subjects, projects} = await getAllProjectsAndSubjects(role); + if (!Array.isArray(projects) || !Array.isArray(subjects)) { + throw Error("Problem loading projects or courses."); + } + return subjects.map((subject) => { + const first_deadline = null; // TODO: add deadlines when needed api endpoints are added. + const project = projects.find(project => project.subject_id === subject.subject_id); + return { + active_projects: projects.filter(project => project.subject_id === subject.subject_id).length, + first_deadline: first_deadline, + ...project, + ...subject + } + }); + + +} + export async function projectsLoader(role: teacherStudentRole): Promise { const {subjects, projects} = await getAllProjectsAndSubjects(role); if (! Array.isArray(projects) || ! Array.isArray(subjects)) { From dcd63278c3215e4103690471358166cfefc2514a Mon Sep 17 00:00:00 2001 From: ALBERICLOOS Date: Sat, 6 Apr 2024 15:17:42 +0200 Subject: [PATCH 21/90] course dataloader teacher --- .../src/dataloaders/CoursesTeacherLoader.ts | 13 ++++++++++ frontend/src/dataloaders/SharedFunctions.ts | 5 ++-- .../src/dataloaders/SubjectsTeacherLoader.ts | 26 ------------------- frontend/src/main.tsx | 7 ++--- 4 files changed, 19 insertions(+), 32 deletions(-) create mode 100644 frontend/src/dataloaders/CoursesTeacherLoader.ts delete mode 100644 frontend/src/dataloaders/SubjectsTeacherLoader.ts diff --git a/frontend/src/dataloaders/CoursesTeacherLoader.ts b/frontend/src/dataloaders/CoursesTeacherLoader.ts new file mode 100644 index 00000000..4ca3bb8b --- /dev/null +++ b/frontend/src/dataloaders/CoursesTeacherLoader.ts @@ -0,0 +1,13 @@ +import {properSubject} from "../utils/ApiInterfaces.ts"; +import {coursesLoader, teacherStudentRole} from "./SharedFunctions.ts"; + +export interface subjectsTeacherLoaderObject { + courses: properSubject[] +} + +export const COURSES_TEACHER_ROUTER_ID = "courses_teacher"; + +export default async function coursesTeacherLoader(): Promise { + const courses: properSubject[] = await coursesLoader(teacherStudentRole.TEACHER); + return {courses}; +} \ No newline at end of file diff --git a/frontend/src/dataloaders/SharedFunctions.ts b/frontend/src/dataloaders/SharedFunctions.ts index 6807baa3..5b21e226 100644 --- a/frontend/src/dataloaders/SharedFunctions.ts +++ b/frontend/src/dataloaders/SharedFunctions.ts @@ -13,12 +13,11 @@ export async function coursesLoader(role: teacherStudentRole): Promise { const first_deadline = null; // TODO: add deadlines when needed api endpoints are added. - const project = projects.find(project => project.subject_id === subject.subject_id); return { active_projects: projects.filter(project => project.subject_id === subject.subject_id).length, first_deadline: first_deadline, - ...project, - ...subject + subject_id: subject.subject_id, + subject_name: subject.subject_name } }); diff --git a/frontend/src/dataloaders/SubjectsTeacherLoader.ts b/frontend/src/dataloaders/SubjectsTeacherLoader.ts deleted file mode 100644 index a6d08357..00000000 --- a/frontend/src/dataloaders/SubjectsTeacherLoader.ts +++ /dev/null @@ -1,26 +0,0 @@ -import {properSubject} from "../utils/ApiInterfaces.ts"; - -export interface subjectsTeacherLoaderObject { - subjects: properSubject[] -} - -// export const SUBJECT_TEACHER_ROUTER_ID = "subjectTeacher"; - -export default async function subjectsTeacherLoader(): Promise { - // const temp: projectsAndSubjects = await getAllProjectsAndSubjects(teacherStudentRole.TEACHER); - // const subjects: Subject[] = temp.subjects; - // const projects: Project[] = temp.projects; - - // const p_subjects: properSubject[] = subjects.map(subject => { - // const active_projects = projects.filter(project => - // project.archived && project.subject_id === subject.id - // ); - // return { - // id: subject.id, - // name: subject.name, - // active_projects: active_projects.length, - // first_deadline: null // TODO: add deadlines when needed api endpoints are added. - // }; - // }); - return {"subjects": []} -} \ No newline at end of file diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 21346521..982d6dfb 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -11,7 +11,7 @@ import HomeTeacher from "./pages/teacher/HomeTeacher.tsx"; import studentLoader, {STUDENT_ROUTER_ID} from "./dataloaders/StudentLoader.ts"; import Unauthorized from "./components/authentication/Unauthorized.tsx"; import teacherLoader, {TEACHER_ROUTER_ID} from "./dataloaders/TeacherLoader.ts"; -// import subjectsTeacherLoader, {SUBJECT_TEACHER_ROUTER_ID} from "./dataloaders/SubjectsTeacherLoader.ts"; +// import coursesTeacherLoader, {SUBJECT_TEACHER_ROUTER_ID} from "./dataloaders/CoursesTeacherLoader.ts"; import loginLoader, {LOGIN_ROUTER_ID} from "./dataloaders/LoginLoader.ts"; import ErrorLogin from "./components/authentication/ErrorLogin.tsx"; import ProjectsViewStudent from "./pages/student/ProjectsViewStudent.tsx"; @@ -27,6 +27,7 @@ import CreateCourse from "./pages/teacher/CreateCourse.tsx"; import projectsTeacherLoader, {PROJECTS_TEACHER_ROUTER_ID} from "./dataloaders/projectsTeacherLoader.ts"; import projectsStudentLoader, {PROJECTS_STUDENT_ROUTER_ID} from "./dataloaders/ProjectsStudentLoader.ts"; import coursesStudentLoader, {COURSES_STUDENT_ROUTER_ID} from './dataloaders/CoursesStudentLoader.ts'; +import coursesTeacherLoader, {COURSES_TEACHER_ROUTER_ID} from "./dataloaders/CoursesTeacherLoader.ts"; const router = createBrowserRouter( createRoutesFromElements( @@ -58,8 +59,8 @@ const router = createBrowserRouter( } loader={projectsTeacherLoader}/> }/> - } /*loader={subjectsTeacherLoader} id={SUBJECT_TEACHER_ROUTER_ID}*//> + } loader={coursesTeacherLoader}/> }/> From 99ce8c57ee89667f87b44c81050f6df320800c26 Mon Sep 17 00:00:00 2001 From: ALBERICLOOS Date: Sat, 6 Apr 2024 15:26:51 +0200 Subject: [PATCH 22/90] add data to view components --- frontend/src/pages/student/CoursesViewStudent.tsx | 5 +++++ frontend/src/pages/teacher/CoursesViewTeacher.tsx | 9 ++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/frontend/src/pages/student/CoursesViewStudent.tsx b/frontend/src/pages/student/CoursesViewStudent.tsx index a1148a22..847d2003 100644 --- a/frontend/src/pages/student/CoursesViewStudent.tsx +++ b/frontend/src/pages/student/CoursesViewStudent.tsx @@ -4,9 +4,14 @@ import {Sidebar} from "../../components/Sidebar.tsx"; import '../../assets/styles/students_components.css' import {SearchBar} from "../../components/SearchBar.tsx"; import {Table, TableRowCourses} from "../../components/Table.tsx"; +import {useRouteLoaderData} from "react-router-dom"; +import {COURSES_STUDENT_ROUTER_ID, coursesStudentLoaderObject} from "../../dataloaders/CoursesStudentLoader.ts"; export default function CoursesViewStudent(): JSX.Element { + const data: coursesStudentLoaderObject = useRouteLoaderData(COURSES_STUDENT_ROUTER_ID) as coursesStudentLoaderObject + console.log(data) + const tableCoursesActive: TableRowCourses[] = [ { name: "Automaten, berekenbaarheid & complexiteit", diff --git a/frontend/src/pages/teacher/CoursesViewTeacher.tsx b/frontend/src/pages/teacher/CoursesViewTeacher.tsx index 6695eed8..6a30bba6 100644 --- a/frontend/src/pages/teacher/CoursesViewTeacher.tsx +++ b/frontend/src/pages/teacher/CoursesViewTeacher.tsx @@ -4,14 +4,13 @@ import {Header} from "../../components/Header.tsx"; import {Sidebar} from "../../components/Sidebar.tsx"; import {SearchBar} from "../../components/SearchBar.tsx"; import {RegularATag} from "../../components/RegularATag.tsx"; - -// import {useRouteLoaderData} from "react-router-dom"; -// import {SUBJECT_TEACHER_ROUTER_ID, subjectsTeacherLoaderObject} from "../../dataloaders/SubjectsTeacherLoader.ts"; +import {COURSES_TEACHER_ROUTER_ID, coursesTeacherLoaderObject} from "../../dataloaders/CoursesTeacherLoader.ts"; +import {useRouteLoaderData} from "react-router-dom"; export default function CoursesViewTeacher(): JSX.Element { - // const data= useRouteLoaderData(SUBJECT_TEACHER_ROUTER_ID) as subjectsTeacherLoaderObject; - // console.log(data); + const data = useRouteLoaderData(COURSES_TEACHER_ROUTER_ID) as coursesTeacherLoaderObject; + console.log(data); const tableCoursesActive: TableRowCourses[] = [ { From 934f6f60eb040c4f291b9ee6a166a0d6dc46d1e9 Mon Sep 17 00:00:00 2001 From: ALBERICLOOS Date: Sat, 6 Apr 2024 15:27:06 +0200 Subject: [PATCH 23/90] change return value --- frontend/src/dataloaders/CoursesTeacherLoader.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/dataloaders/CoursesTeacherLoader.ts b/frontend/src/dataloaders/CoursesTeacherLoader.ts index 4ca3bb8b..ce501537 100644 --- a/frontend/src/dataloaders/CoursesTeacherLoader.ts +++ b/frontend/src/dataloaders/CoursesTeacherLoader.ts @@ -1,13 +1,13 @@ import {properSubject} from "../utils/ApiInterfaces.ts"; import {coursesLoader, teacherStudentRole} from "./SharedFunctions.ts"; -export interface subjectsTeacherLoaderObject { +export interface coursesTeacherLoaderObject { courses: properSubject[] } export const COURSES_TEACHER_ROUTER_ID = "courses_teacher"; -export default async function coursesTeacherLoader(): Promise { +export default async function coursesTeacherLoader(): Promise { const courses: properSubject[] = await coursesLoader(teacherStudentRole.TEACHER); return {courses}; } \ No newline at end of file From c4a5cb78b99cf98e69a1b194dda94a5e74bcf269 Mon Sep 17 00:00:00 2001 From: ALBERICLOOS Date: Sat, 6 Apr 2024 15:30:55 +0200 Subject: [PATCH 24/90] no p tags in p tags --- frontend/src/components/Header.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index eb041bf0..204ed32c 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -11,7 +11,7 @@ export function Header(props: { page_title: string, home: string }): JSX.Element {"image"}/ -

{props.page_title}

+

{props.page_title}

- ) -} \ No newline at end of file diff --git a/frontend/src/pages/login/LoginScreen.tsx b/frontend/src/pages/login/LoginScreen.tsx index b30f9c4e..99323787 100644 --- a/frontend/src/pages/login/LoginScreen.tsx +++ b/frontend/src/pages/login/LoginScreen.tsx @@ -1,10 +1,10 @@ import React, {JSX, useEffect} from "react"; import {Navigate, useLocation, useRouteLoaderData} from 'react-router-dom'; import useAuth from "../../hooks/useAuth.ts"; -import loginLoader, {LOGIN_ROUTER_ID, loginLoaderObject} from "../../dataloaders/LoginLoader.ts"; +import loginLoader, {LOGIN_ROUTER_ID, loginLoaderObject} from "../../dataloaders/LoginLoader.tsx"; import LoginForm from "../../components/authentication/LoginForm.tsx"; -import {DEBUG} from "../root.tsx"; import {Token, User} from "../../utils/ApiInterfaces.ts"; +import {post_ticket} from "../../utils/api/Login.ts"; interface location_type { search?: { ticket?: string }, @@ -13,17 +13,12 @@ interface location_type { } const ticketLogin = async (ticket: string, setUser: React.Dispatch>) => { - let url = '/api/login?ticket=' + ticket - if (DEBUG) { - url = 'http://127.0.0.1:8000/api/login?ticket=' + ticket - } - const token = await (await fetch(url, {method: 'POST', headers: {'Content-Type': 'application/json'}})) - .json() as Token + const token: Token = await post_ticket(ticket) if (token.token) { localStorage.setItem('token', token.token) const result: loginLoaderObject = await loginLoader() - if (result.user) { + if (isUser(result.user)) { setUser(result.user) }else{ localStorage.removeItem('token') @@ -34,6 +29,7 @@ const ticketLogin = async (ticket: string, setUser: React.Dispatch { return (data && data.id && data.name && data.email && data.roles); @@ -42,6 +38,12 @@ const isUser = (data?: User) => { ======= >>>>>>> a25cbf2 (mapping backend_user <> user) +======= +const isUser = (data?: User) => { + return (data && data.user_id && data.user_name && data.user_email && data.user_roles); +} + +>>>>>>> 81deb20 (Functies user, projects, subjects, group, course) export default function LoginScreen(): JSX.Element { const {user, setUser} = useAuth(); const location = useLocation() as location_type; @@ -60,9 +62,13 @@ export default function LoginScreen(): JSX.Element { useEffect(() => { // If the saved token is valid => the user will be logged in if (data && data.user) { - setUser(data.user) - } - else if (!user && ticket) { + if (isUser(data.user)) { + setUser(data.user) + }else{ + setUser(undefined) + localStorage.removeItem('token') + } + } else if (!user && ticket) { void ticketLogin(ticket, setUser); } }, [data, setUser, ticket, user]); diff --git a/frontend/src/utils/api/ApiFetch.ts b/frontend/src/utils/ApiFetch.ts similarity index 91% rename from frontend/src/utils/api/ApiFetch.ts rename to frontend/src/utils/ApiFetch.ts index e4934a1d..19c8d250 100644 --- a/frontend/src/utils/api/ApiFetch.ts +++ b/frontend/src/utils/ApiFetch.ts @@ -1,4 +1,4 @@ -import {DEBUG} from "../../pages/root.tsx"; +import {DEBUG} from "../pages/root.tsx"; const ApiFetch = async (url: string, options?: RequestInit) => { diff --git a/frontend/src/utils/InputInterfaces.ts b/frontend/src/utils/InputInterfaces.ts new file mode 100644 index 00000000..a2514a70 --- /dev/null +++ b/frontend/src/utils/InputInterfaces.ts @@ -0,0 +1,9 @@ +export interface ProjectInput{ + name: string, + deadline: Date, + archived: boolean, + description: string, + requirements: string, + visible: boolean, + max_students: number, +} \ No newline at end of file diff --git a/frontend/src/utils/api/Groups.ts b/frontend/src/utils/api/Groups.ts index 54bc3af5..2edec938 100644 --- a/frontend/src/utils/api/Groups.ts +++ b/frontend/src/utils/api/Groups.ts @@ -1,4 +1,4 @@ -import ApiFetch from "./ApiFetch.ts"; +import ApiFetch from "../ApiFetch.ts"; export function joinGroup(groupId: number) { void ApiFetch(`/groups/${groupId}/join`, @@ -8,16 +8,4 @@ export function joinGroup(groupId: number) { export function leaveGroup(groupId: number) { void ApiFetch(`/groups/${groupId}/leave`, {method: 'POST', headers: {'Content-Type': 'application/json'}}); -} - -export async function listGroupMembers(groupId: number) { - let members = await ApiFetch(`/groups/${groupId}/members`, - {method: 'GET', headers: {'Content-Type': 'application/json'}}); - return members; -} - -export function projectGroup(projectId: number) { - let group = void ApiFetch(`/projects/${projectId}/group`, - {method: 'GET', headers: {'Content-Type': 'application/json'}}); - return group; } \ No newline at end of file diff --git a/frontend/src/utils/api/Login.ts b/frontend/src/utils/api/Login.ts index e69de29b..af35a0f5 100644 --- a/frontend/src/utils/api/Login.ts +++ b/frontend/src/utils/api/Login.ts @@ -0,0 +1,16 @@ +import {DEBUG} from "../../pages/root.tsx"; +import {Token} from "../ApiInterfaces.ts"; + +export async function post_ticket(ticket: string){ + let url = '/api/login?ticket=' + ticket + if (DEBUG) { + url = 'http://127.0.0.1:8000/api/login?ticket=' + ticket + } + + return await (await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + },})) + .json() as Token +} \ No newline at end of file diff --git a/frontend/src/utils/api/Project.ts b/frontend/src/utils/api/Project.ts index e69de29b..04e62b87 100644 --- a/frontend/src/utils/api/Project.ts +++ b/frontend/src/utils/api/Project.ts @@ -0,0 +1,36 @@ +import apiFetch from "../ApiFetch.ts"; +import {ProjectInput} from "../InputInterfaces.ts"; +import {Backend_group, Backend_Project} from "../BackendInterfaces.ts"; +import {mapGroup} from "../ApiTypesMapper.ts"; + +export async function project_create_group(project_id: number){ + const groupData = await apiFetch(`/projects/${project_id}/groups`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }) as Backend_group + return mapGroup(groupData) +} + +export async function update_project(project_id: number, projectInput: ProjectInput){ + const projectData = await apiFetch(`/projects/${project_id}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(projectInput) + }) as Backend_Project + return { + project_id: projectData.id, + project_name: projectData.name, + project_deadline: projectData.deadline, + project_archived: projectData.archived, + project_description: projectData.description, + project_requirements: projectData.requirements, + project_visible: projectData.visible, + project_max_students: projectData.max_students, + subject_id: projectData.subject_id + } +} + diff --git a/frontend/src/utils/api/Student.ts b/frontend/src/utils/api/Student.ts index e69de29b..2269b84b 100644 --- a/frontend/src/utils/api/Student.ts +++ b/frontend/src/utils/api/Student.ts @@ -0,0 +1,7 @@ +import apiFetch from "../ApiFetch.ts"; + +export function join_subject(subjectId: number){ + void apiFetch(`/student/subjects/${subjectId}/join`, { + method: 'POST' + }) +} \ No newline at end of file diff --git a/frontend/src/utils/api/Subject.ts b/frontend/src/utils/api/Subject.ts index e69de29b..fa9850f3 100644 --- a/frontend/src/utils/api/Subject.ts +++ b/frontend/src/utils/api/Subject.ts @@ -0,0 +1,25 @@ +import {ProjectInput} from "../InputInterfaces.ts"; +import ApiFetch from "../ApiFetch.ts"; +import {Backend_Project} from "../BackendInterfaces.ts"; + +export async function subject_create_project(subjectId: number, projectInput: ProjectInput) { + const projectData: Backend_Project = (await ApiFetch(`/subjects/${subjectId}/projects`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(projectInput) + }) as Backend_Project) + + return { + project_id: projectData.id, + project_name: projectData.name, + project_deadline: projectData.deadline, + project_archived: projectData.archived, + project_description: projectData.description, + project_requirements: projectData.requirements, + project_visible: projectData.visible, + project_max_students: projectData.max_students, + subject_id: projectData.subject_id + } +} \ No newline at end of file diff --git a/frontend/src/utils/api/Teacher.ts b/frontend/src/utils/api/Teacher.ts index e69de29b..ab4a1a09 100644 --- a/frontend/src/utils/api/Teacher.ts +++ b/frontend/src/utils/api/Teacher.ts @@ -0,0 +1,13 @@ +import {Backend_Subject} from "../BackendInterfaces.ts"; +import apiFetch from "../ApiFetch.ts"; + +export async function createSubject(name: string) { + return (await apiFetch('/teacher/subjects', + { + headers: { + 'Content-Type': 'application/json' + }, + method: 'POST', + body: JSON.stringify({name: name}) + })) as Backend_Subject +} \ No newline at end of file diff --git a/frontend/src/utils/api/User.ts b/frontend/src/utils/api/User.ts index e69de29b..f731bc77 100644 --- a/frontend/src/utils/api/User.ts +++ b/frontend/src/utils/api/User.ts @@ -0,0 +1,8 @@ +import {Language} from "../../components/Settings.tsx"; +import apiFetch from "../ApiFetch.ts"; + +export function modify_language(language: Language){ + void apiFetch(`/user?language=${language}`, { + method: 'PATCH' + }) +} \ No newline at end of file From 122815fcdad5e7a5b2d42f26848ae029998c28e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stef=20Oss=C3=A9?= Date: Tue, 9 Apr 2024 21:42:08 +0200 Subject: [PATCH 37/90] debug false --- frontend/src/pages/root.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/root.tsx b/frontend/src/pages/root.tsx index ea446625..b87b4bb4 100644 --- a/frontend/src/pages/root.tsx +++ b/frontend/src/pages/root.tsx @@ -2,7 +2,7 @@ import {JSX} from "react"; import useAuth from "../hooks/useAuth.ts"; import {Navigate} from "react-router-dom"; -export const DEBUG: boolean = true; // should always be false on the repo. +export const DEBUG: boolean = false; // should always be false on the repo. export default function Root(): JSX.Element { const {user} = useAuth() From e2593b065215f7c49e432b12a39b2b1d97b7955c Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Sat, 6 Apr 2024 18:06:47 +0200 Subject: [PATCH 38/90] update dependencies --- backend/poetry.lock | 64 +++++++++++++++++++++--------------------- backend/pyproject.toml | 6 ++-- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/backend/poetry.lock b/backend/poetry.lock index 2e9fd54b..43a2273c 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -137,18 +137,18 @@ idna = ">=2.0.0" [[package]] name = "fastapi" -version = "0.110.0" +version = "0.110.1" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" files = [ - {file = "fastapi-0.110.0-py3-none-any.whl", hash = "sha256:87a1f6fb632a218222c5984be540055346a8f5d8a68e8f6fb647b1dc9934de4b"}, - {file = "fastapi-0.110.0.tar.gz", hash = "sha256:266775f0dcc95af9d3ef39bad55cff525329a931d5fd51930aadd4f428bf7ff3"}, + {file = "fastapi-0.110.1-py3-none-any.whl", hash = "sha256:5df913203c482f820d31f48e635e022f8cbfe7350e4830ef05a3163925b1addc"}, + {file = "fastapi-0.110.1.tar.gz", hash = "sha256:6feac43ec359dfe4f45b2c18ec8c94edb8dc2dfc461d417d9e626590c071baad"}, ] [package.dependencies] pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" -starlette = ">=0.36.3,<0.37.0" +starlette = ">=0.37.2,<0.38.0" typing-extensions = ">=4.8.0" [package.extras] @@ -579,13 +579,13 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pyright" -version = "1.1.356" +version = "1.1.357" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" files = [ - {file = "pyright-1.1.356-py3-none-any.whl", hash = "sha256:a101b0f375f93d7082f9046cfaa7ba15b7cf8e1939ace45e984c351f6e8feb99"}, - {file = "pyright-1.1.356.tar.gz", hash = "sha256:f05b8b29d06b96ed4a0885dad5a31d9dff691ca12b2f658249f583d5f2754021"}, + {file = "pyright-1.1.357-py3-none-any.whl", hash = "sha256:1cf29ee38e4928131895cd8e90eef37b5b77e2ed72a14e6e8e2405266f5f0aca"}, + {file = "pyright-1.1.357.tar.gz", hash = "sha256:7c66261116c78c5fa9629134fe85c54cc5302ab73e376be4b0a99d89c80a9403"}, ] [package.dependencies] @@ -671,28 +671,28 @@ files = [ [[package]] name = "ruff" -version = "0.3.4" +version = "0.3.5" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.3.4-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:60c870a7d46efcbc8385d27ec07fe534ac32f3b251e4fc44b3cbfd9e09609ef4"}, - {file = "ruff-0.3.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6fc14fa742e1d8f24910e1fff0bd5e26d395b0e0e04cc1b15c7c5e5fe5b4af91"}, - {file = "ruff-0.3.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3ee7880f653cc03749a3bfea720cf2a192e4f884925b0cf7eecce82f0ce5854"}, - {file = "ruff-0.3.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cf133dd744f2470b347f602452a88e70dadfbe0fcfb5fd46e093d55da65f82f7"}, - {file = "ruff-0.3.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f3860057590e810c7ffea75669bdc6927bfd91e29b4baa9258fd48b540a4365"}, - {file = "ruff-0.3.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:986f2377f7cf12efac1f515fc1a5b753c000ed1e0a6de96747cdf2da20a1b369"}, - {file = "ruff-0.3.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fd98e85869603e65f554fdc5cddf0712e352fe6e61d29d5a6fe087ec82b76c"}, - {file = "ruff-0.3.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64abeed785dad51801b423fa51840b1764b35d6c461ea8caef9cf9e5e5ab34d9"}, - {file = "ruff-0.3.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df52972138318bc7546d92348a1ee58449bc3f9eaf0db278906eb511889c4b50"}, - {file = "ruff-0.3.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:98e98300056445ba2cc27d0b325fd044dc17fcc38e4e4d2c7711585bd0a958ed"}, - {file = "ruff-0.3.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:519cf6a0ebed244dce1dc8aecd3dc99add7a2ee15bb68cf19588bb5bf58e0488"}, - {file = "ruff-0.3.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bb0acfb921030d00070539c038cd24bb1df73a2981e9f55942514af8b17be94e"}, - {file = "ruff-0.3.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cf187a7e7098233d0d0c71175375c5162f880126c4c716fa28a8ac418dcf3378"}, - {file = "ruff-0.3.4-py3-none-win32.whl", hash = "sha256:af27ac187c0a331e8ef91d84bf1c3c6a5dea97e912a7560ac0cef25c526a4102"}, - {file = "ruff-0.3.4-py3-none-win_amd64.whl", hash = "sha256:de0d5069b165e5a32b3c6ffbb81c350b1e3d3483347196ffdf86dc0ef9e37dd6"}, - {file = "ruff-0.3.4-py3-none-win_arm64.whl", hash = "sha256:6810563cc08ad0096b57c717bd78aeac888a1bfd38654d9113cb3dc4d3f74232"}, - {file = "ruff-0.3.4.tar.gz", hash = "sha256:f0f4484c6541a99862b693e13a151435a279b271cff20e37101116a21e2a1ad1"}, + {file = "ruff-0.3.5-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:aef5bd3b89e657007e1be6b16553c8813b221ff6d92c7526b7e0227450981eac"}, + {file = "ruff-0.3.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:89b1e92b3bd9fca249153a97d23f29bed3992cff414b222fcd361d763fc53f12"}, + {file = "ruff-0.3.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e55771559c89272c3ebab23326dc23e7f813e492052391fe7950c1a5a139d89"}, + {file = "ruff-0.3.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dabc62195bf54b8a7876add6e789caae0268f34582333cda340497c886111c39"}, + {file = "ruff-0.3.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a05f3793ba25f194f395578579c546ca5d83e0195f992edc32e5907d142bfa3"}, + {file = "ruff-0.3.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dfd3504e881082959b4160ab02f7a205f0fadc0a9619cc481982b6837b2fd4c0"}, + {file = "ruff-0.3.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87258e0d4b04046cf1d6cc1c56fadbf7a880cc3de1f7294938e923234cf9e498"}, + {file = "ruff-0.3.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:712e71283fc7d9f95047ed5f793bc019b0b0a29849b14664a60fd66c23b96da1"}, + {file = "ruff-0.3.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a532a90b4a18d3f722c124c513ffb5e5eaff0cc4f6d3aa4bda38e691b8600c9f"}, + {file = "ruff-0.3.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:122de171a147c76ada00f76df533b54676f6e321e61bd8656ae54be326c10296"}, + {file = "ruff-0.3.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d80a6b18a6c3b6ed25b71b05eba183f37d9bc8b16ace9e3d700997f00b74660b"}, + {file = "ruff-0.3.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a7b6e63194c68bca8e71f81de30cfa6f58ff70393cf45aab4c20f158227d5936"}, + {file = "ruff-0.3.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a759d33a20c72f2dfa54dae6e85e1225b8e302e8ac655773aff22e542a300985"}, + {file = "ruff-0.3.5-py3-none-win32.whl", hash = "sha256:9d8605aa990045517c911726d21293ef4baa64f87265896e491a05461cae078d"}, + {file = "ruff-0.3.5-py3-none-win_amd64.whl", hash = "sha256:dc56bb16a63c1303bd47563c60482a1512721053d93231cf7e9e1c6954395a0e"}, + {file = "ruff-0.3.5-py3-none-win_arm64.whl", hash = "sha256:faeeae9905446b975dcf6d4499dc93439b131f1443ee264055c5716dd947af55"}, + {file = "ruff-0.3.5.tar.gz", hash = "sha256:a067daaeb1dc2baf9b82a32dae67d154d95212080c80435eb052d95da647763d"}, ] [[package]] @@ -839,13 +839,13 @@ url = ["furl (>=0.4.1)"] [[package]] name = "starlette" -version = "0.36.3" +version = "0.37.2" description = "The little ASGI library that shines." optional = false python-versions = ">=3.8" files = [ - {file = "starlette-0.36.3-py3-none-any.whl", hash = "sha256:13d429aa93a61dc40bf503e8c801db1f1bca3dc706b10ef2434a36123568f044"}, - {file = "starlette-0.36.3.tar.gz", hash = "sha256:90a671733cfb35771d8cc605e0b679d23b992f8dcfad48cc60b38cb29aeb7080"}, + {file = "starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee"}, + {file = "starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"}, ] [package.dependencies] @@ -856,13 +856,13 @@ full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7 [[package]] name = "typing-extensions" -version = "4.10.0" +version = "4.11.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, - {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, + {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, + {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, ] [[package]] @@ -906,4 +906,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "3052d8bb8b57b811c463ced8c56323a9749783fead22fd4eef8648ca38252575" +content-hash = "b3da44b22aad280c124d99bb01b41e4b88db5c8ef94d972d8f71899ebe0c25fe" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 9d886e0f..fd712117 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -17,13 +17,13 @@ authors = [ python = "^3.12" uvicorn = "^0.29.0" sqlalchemy = "^2.0.29" -ruff = "^0.3.4" +ruff = "^0.3.5" python-multipart = "^0.0.9" -pyright = "^1.1.356" +pyright = "^1.1.357" pyjwt = "^2.8.0" psycopg2-binary = "^2.9.9" pre-commit = "^3.7.0" -fastapi = "^0.110.0" +fastapi = "^0.110.1" email-validator = "^2.1.1" httpx = "^0.27.0" defusedxml = "^0.7.1" From 0270092d3efffb78667dd005b2483ec2e2b8e017 Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Sat, 6 Apr 2024 18:07:44 +0200 Subject: [PATCH 39/90] clean unused imports --- backend/db/create_database_tables.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/db/create_database_tables.py b/backend/db/create_database_tables.py index 98d326b2..afb15769 100644 --- a/backend/db/create_database_tables.py +++ b/backend/db/create_database_tables.py @@ -1,8 +1,7 @@ -from sqlalchemy import Engine, MetaData, Table, inspect +from sqlalchemy import Engine from sqlalchemy.orm import Session, sessionmaker from sqlalchemy_utils import create_database, database_exists -import db.models.models from db.extensions import Base, engine From 725d4d01cc9cb32d5325bd6ab8bcd710f2a2b142 Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Sat, 6 Apr 2024 21:48:49 +0200 Subject: [PATCH 40/90] Use SQLModel instead of SQLAlchemy #123 --- backend/db/create_database_tables.py | 19 -- backend/db/models/models.py | 345 ++++++++++------------ backend/domain/models/AdminDataclass.py | 5 - backend/domain/models/GroupDataclass.py | 6 - backend/domain/models/StudentDataclass.py | 5 - backend/domain/models/TeacherDataclass.py | 5 - backend/domain/models/UserDataclass.py | 13 - 7 files changed, 164 insertions(+), 234 deletions(-) delete mode 100644 backend/db/create_database_tables.py delete mode 100644 backend/domain/models/AdminDataclass.py delete mode 100644 backend/domain/models/GroupDataclass.py delete mode 100644 backend/domain/models/StudentDataclass.py delete mode 100644 backend/domain/models/TeacherDataclass.py delete mode 100644 backend/domain/models/UserDataclass.py diff --git a/backend/db/create_database_tables.py b/backend/db/create_database_tables.py deleted file mode 100644 index afb15769..00000000 --- a/backend/db/create_database_tables.py +++ /dev/null @@ -1,19 +0,0 @@ -from sqlalchemy import Engine -from sqlalchemy.orm import Session, sessionmaker -from sqlalchemy_utils import create_database, database_exists - -from db.extensions import Base, engine - - -def initialize_tables(session_instance: Session, engine_instance: Engine) -> None: - if not database_exists(engine_instance.url): - create_database(engine_instance.url) - Base.metadata.drop_all(engine_instance) - Base.metadata.create_all(engine_instance) - session_instance.commit() - - -if __name__ == "__main__": - session = sessionmaker(autocommit=False, bind=engine)() - initialize_tables(session, engine) - session.close() diff --git a/backend/db/models/models.py b/backend/db/models/models.py index 09eb5e97..612007ea 100644 --- a/backend/db/models/models.py +++ b/backend/db/models/models.py @@ -1,186 +1,169 @@ -from abc import abstractmethod -from dataclasses import dataclass # automatically add special methods as __init__() and __repr__() from datetime import datetime -from typing import Generic, TypeVar - -from pydantic import BaseModel -from sqlalchemy import Column, ForeignKey, Table -from sqlalchemy.orm import Mapped, mapped_column, relationship - -from db.extensions import Base -from domain.models.AdminDataclass import AdminDataclass -from domain.models.GroupDataclass import GroupDataclass -from domain.models.ProjectDataclass import ProjectDataclass -from domain.models.StudentDataclass import StudentDataclass -from domain.models.SubjectDataclass import SubjectDataclass -from domain.models.SubmissionDataclass import SubmissionDataclass, SubmissionState -from domain.models.TeacherDataclass import TeacherDataclass -from domain.models.UserDataclass import UserDataclass - -# Create a generic type variable bound to subclasses of BaseModel. -D = TypeVar("D", bound=BaseModel) - - -@dataclass() -class AbstractModel(Generic[D]): - """ - This class is meant to be inherited by the python classes for the database tables. - It makes sure that every child implements the to_domain_model function - and receives Pydantic data validation. - """ - - @abstractmethod - def to_domain_model(self) -> D: - """ - Change to an actual easy-to-use dataclass defined in [domain/models/*]. - This prevents working with instances of SQLAlchemy's Base class. - """ - - -# See the EER diagram for a more visual representation. - - -@dataclass() -class User(Base, AbstractModel): - __tablename__ = "users" - name: Mapped[str] - email: Mapped[str] - language: Mapped[str] = mapped_column(default="EN") - id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) - - def to_domain_model(self) -> UserDataclass: - return UserDataclass(id=self.id, name=self.name, language=self.language, email=self.email) - - -@dataclass() -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.user.name, language=self.user.language, email=self.user.email) - - -teachers_subjects = Table( - "teachers_subjects", - Base.metadata, - Column("teacher_id", ForeignKey("teachers.id"), primary_key=True), - Column("subject_id", ForeignKey("subjects.id"), primary_key=True), -) -students_subjects = Table( - "students_subjects", - Base.metadata, - Column("student_id", ForeignKey("students.id"), primary_key=True), - Column("subject_id", ForeignKey("subjects.id"), primary_key=True), -) -students_groups = Table( - "students_groups", - Base.metadata, - Column("student_id", ForeignKey("students.id"), primary_key=True), - Column("group_id", ForeignKey("groups.id"), primary_key=True), -) - - -@dataclass() -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.user.name, language=self.user.language, email=self.user.email) - - -@dataclass() -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.user.name, language=self.user.language, email=self.user.email) - - -@dataclass() -class Subject(Base, AbstractModel): - __tablename__ = "subjects" - name: Mapped[str] - id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) - teachers: Mapped[list[Teacher]] = relationship(secondary=teachers_subjects, back_populates="subjects") - students: Mapped[list[Student]] = relationship(secondary=students_subjects, back_populates="subjects") - projects: Mapped[list["Project"]] = relationship(back_populates="subject") - - def to_domain_model(self) -> SubjectDataclass: - return SubjectDataclass(id=self.id, name=self.name) - - -@dataclass() -class Project(Base, AbstractModel): - __tablename__ = "projects" - name: Mapped[str] - deadline: Mapped[datetime] - archived: Mapped[bool] - description: Mapped[str] - requirements: Mapped[str] - visible: Mapped[bool] - max_students: Mapped[int] - id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) - subject_id: Mapped[int] = mapped_column(ForeignKey(Subject.id)) - subject: Mapped[Subject] = relationship(back_populates="projects") - groups: Mapped[list["Group"]] = relationship(back_populates="project") - - def to_domain_model(self) -> ProjectDataclass: - return ProjectDataclass( - id=self.id, - name=self.name, - deadline=self.deadline, - archived=self.archived, - description=self.description, - requirements=self.requirements, - visible=self.visible, - max_students=self.max_students, - subject_id=self.subject_id, + +from sqlmodel import Field, Relationship, Session, SQLModel + +from db.extensions import engine +from domain.models.SubmissionDataclass import SubmissionState + + +class User(SQLModel, table=True): + name: str + email: str + language: str = "EN" + id: int = Field(default=-1, primary_key=True) + + admin: "Admin" = Relationship(back_populates="user") + teacher: "Teacher" = Relationship(back_populates="user") + student: "Student" = Relationship(back_populates="user") + + +class Admin(SQLModel, table=True): + id: int = Field(default=-1, foreign_key="user.id", primary_key=True) + user: User = Relationship(back_populates="admin") + + +class TeacherSubject(SQLModel, table=True): + teacher_id: int = Field(foreign_key="teacher.id", primary_key=True) + subject_id: int = Field(foreign_key="subject.id", primary_key=True) + + +class StudentSubject(SQLModel, table=True): + student_id: int = Field(foreign_key="student.id", primary_key=True) + subject_id: int = Field(foreign_key="subject.id", primary_key=True) + + +class StudentGroup(SQLModel, table=True): + student_id: int = Field(foreign_key="student.id", primary_key=True) + group_id: int = Field(foreign_key="group.id", primary_key=True) + + +class Teacher(SQLModel, table=True): + id: int = Field(default=-1, foreign_key="user.id", primary_key=True) + user: User = Relationship(back_populates="teacher") + subjects: list["Subject"] = Relationship(link_model=TeacherSubject, back_populates="teachers") + + +class Student(SQLModel, table=True): + id: int = Field(default=-1, foreign_key="user.id", primary_key=True) + user: User = Relationship(back_populates="student") + subjects: list["Subject"] = Relationship(link_model=StudentSubject, back_populates="students") + groups: list["Group"] = Relationship(link_model=StudentGroup, back_populates="students") + submissions: list["Submission"] = Relationship(back_populates="student") + + +class Subject(SQLModel, table=True): + name: str + id: int = Field(default=-1, primary_key=True) + teachers: list[Teacher] = Relationship(link_model=TeacherSubject, back_populates="subjects") + students: list[Student] = Relationship(link_model=StudentSubject, back_populates="subjects") + projects: list["Project"] = Relationship(back_populates="subject") + + +class Project(SQLModel, table=True): + name: str + deadline: datetime + archived: bool + description: str + requirements: str + visible: bool + max_students: int + id: int = Field(default=-1, primary_key=True) + subject_id: int = Field(default=-1, foreign_key="subject.id") + subject: Subject = Relationship(back_populates="projects") + groups: list["Group"] = Relationship(back_populates="project") + + +class Group(SQLModel, table=True): + id: int = Field(default=-1, primary_key=True) + project_id: int = Field(default=-1, foreign_key="project.id") + project: Project = Relationship(back_populates="groups") + students: list[Student] = Relationship(link_model=StudentGroup, back_populates="groups") + submissions: list["Submission"] = Relationship(back_populates="group") + + +class Submission(SQLModel, table=True): + date_time: datetime + state: SubmissionState + message: str + filename: str + id: int = Field(default=-1, primary_key=True) + group_id: int = Field(default=-1, foreign_key="group.id") + group: Group = Relationship(back_populates="submissions") + student_id: int = Field(default=-1, foreign_key="student.id") + student: Student = Relationship(back_populates="submissions") + + +if __name__ == "__main__": + # Initialize database engine; replace connection string as needed + + # Create tables + SQLModel.metadata.drop_all(engine) + SQLModel.metadata.create_all(engine) + + # Insert mock data + with Session(engine) as session: + + user1 = User(name="John Doe", email="john@example.com") + user2 = User(name="Jane Doe", email="jane@example.com") + + teacher1 = Teacher(user=user1) + student1 = Student(user=user2) + + subject1 = Subject(name="Math") + subject2 = Subject(name="English") + + project1 = Project( + name="Math Project", + deadline=datetime.now(), + archived=False, + description="Math project description", + requirements="Math project requirements", + visible=True, + max_students=2, + subject=subject1, + ) + project2 = Project( + name="English Project", + deadline=datetime.now(), + archived=False, + description="English project description", + requirements="English project requirements", + visible=True, + max_students=2, + subject=subject2, ) + group1 = Group(project=project1) + group2 = Group(project=project2) -@dataclass() -class Group(Base, AbstractModel): - __tablename__ = "groups" - id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) - project_id: Mapped[int] = mapped_column(ForeignKey(Project.id)) - project: Mapped[Project] = relationship(back_populates="groups") - students: Mapped[list[Student]] = relationship(secondary=students_groups, back_populates="groups") - submissions: Mapped[list["Submission"]] = relationship(back_populates="group") - - def to_domain_model(self) -> GroupDataclass: - return GroupDataclass(id=self.id, project_id=self.project_id) - - -@dataclass() -class Submission(Base, AbstractModel): - __tablename__ = "submissions" - date_time: Mapped[datetime] - state: Mapped[SubmissionState] - message: Mapped[str] - filename: Mapped[str] - id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) - group_id: Mapped[int] = mapped_column(ForeignKey(Group.id)) - group: Mapped[Group] = relationship(back_populates="submissions") - student_id: Mapped[int] = mapped_column(ForeignKey(Student.id)) - student: Mapped[Student] = relationship(back_populates="submissions") - - def to_domain_model(self) -> SubmissionDataclass: - return SubmissionDataclass( - id=self.id, - date_time=self.date_time, - group_id=self.group_id, - student_id=self.student_id, - state=self.state, - message=self.message, - filename=self.filename, + submission1 = Submission( + date_time=datetime.now(), + state=SubmissionState.Pending, + message="Submission message", + filename="submission.txt", + group=group1, + student=student1, ) + submission2 = Submission( + date_time=datetime.now(), + state=SubmissionState.Pending, + message="Submission message", + filename="submission.txt", + group=group2, + student=student1, + ) + + session.add(user1) + session.add(user2) + session.add(teacher1) + session.add(student1) + session.add(subject1) + session.add(subject2) + session.add(project1) + session.add(project2) + session.add(group1) + session.add(group2) + session.add(submission1) + session.add(submission2) + + session.commit() diff --git a/backend/domain/models/AdminDataclass.py b/backend/domain/models/AdminDataclass.py deleted file mode 100644 index 646cb390..00000000 --- a/backend/domain/models/AdminDataclass.py +++ /dev/null @@ -1,5 +0,0 @@ -from domain.models.UserDataclass import UserDataclass - - -class AdminDataclass(UserDataclass): - pass diff --git a/backend/domain/models/GroupDataclass.py b/backend/domain/models/GroupDataclass.py deleted file mode 100644 index e14914e5..00000000 --- a/backend/domain/models/GroupDataclass.py +++ /dev/null @@ -1,6 +0,0 @@ -from pydantic import BaseModel - - -class GroupDataclass(BaseModel): - id: int - project_id: int diff --git a/backend/domain/models/StudentDataclass.py b/backend/domain/models/StudentDataclass.py deleted file mode 100644 index fe30af4e..00000000 --- a/backend/domain/models/StudentDataclass.py +++ /dev/null @@ -1,5 +0,0 @@ -from domain.models.UserDataclass import UserDataclass - - -class StudentDataclass(UserDataclass): - pass diff --git a/backend/domain/models/TeacherDataclass.py b/backend/domain/models/TeacherDataclass.py deleted file mode 100644 index ddf3e95d..00000000 --- a/backend/domain/models/TeacherDataclass.py +++ /dev/null @@ -1,5 +0,0 @@ -from domain.models.UserDataclass import UserDataclass - - -class TeacherDataclass(UserDataclass): - pass diff --git a/backend/domain/models/UserDataclass.py b/backend/domain/models/UserDataclass.py deleted file mode 100644 index 5f1e8114..00000000 --- a/backend/domain/models/UserDataclass.py +++ /dev/null @@ -1,13 +0,0 @@ -from pydantic import BaseModel, EmailStr - - -class UserDataclass(BaseModel): - """ - This user does not have any roles yet. - When the roles become specified, use the almost equivalent APIUser. - """ - - id: int - name: str - language: str - email: EmailStr From cb7739f8f30c529312bca6cce9cda4925a41f44f Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Sat, 6 Apr 2024 21:50:34 +0200 Subject: [PATCH 41/90] comment refactor + sqlmodel import #123 --- backend/db/extensions.py | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/backend/db/extensions.py b/backend/db/extensions.py index c2714e9b..1f4bd9bb 100644 --- a/backend/db/extensions.py +++ b/backend/db/extensions.py @@ -1,23 +1,18 @@ import os -from sqlalchemy import create_engine -from sqlalchemy.orm import DeclarativeBase +from sqlmodel import create_engine """ Retrieve the variables needed for a connection to the database. We use environment variables for the code to be more adaptable across different environments. The second variable in os.getenv specifies the default value. """ -# where the database is hosted -db_host = os.getenv("DB_HOST", "localhost") -# port number on which the database server is listening -db_port = os.getenv("DB_PORT", "5432") -# username for the database -db_user = os.getenv("DB_USERNAME", "postgres") -# password for the user -db_password = os.getenv("DB_PASSWORD", "postgres") -# name of the database -db_database = os.getenv("DB_DATABASE", "delphi") + +db_host = os.getenv("DB_HOST", "localhost") # where the database is hosted +db_port = os.getenv("DB_PORT", "5432") # port number on which the database server is listening +db_user = os.getenv("DB_USERNAME", "postgres") # username for the database +db_password = os.getenv("DB_PASSWORD", "postgres") # password for the user +db_database = os.getenv("DB_DATABASE", "delphi") # name of the database # dialect+driver://username:password@host:port/database DB_URI = f"postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_database}" @@ -25,10 +20,3 @@ # The engine manages database-operations. # There is only one instance of the engine, specified here. engine = create_engine(DB_URI) - - -class Base(DeclarativeBase): - """ - This class is meant to be inherited from to define the database tables, see [db/models/models.py]. - For usage, please check https://docs.sqlalchemy.org/en/20/orm/declarative_styles.html#using-a-declarative-base-class. - """ From b2f67b0fd75d0f5df6df33efcd907d7766f2e58a Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Sat, 6 Apr 2024 21:52:31 +0200 Subject: [PATCH 42/90] inlined create_database_tables.py + sqlmodel import #123 --- backend/db/fill_database_mock.py | 492 ++++++++++++++++--------------- 1 file changed, 249 insertions(+), 243 deletions(-) diff --git a/backend/db/fill_database_mock.py b/backend/db/fill_database_mock.py index e6f190ed..bbd0f94d 100644 --- a/backend/db/fill_database_mock.py +++ b/backend/db/fill_database_mock.py @@ -1,9 +1,9 @@ from datetime import datetime from psycopg2 import tz -from sqlalchemy.orm import sessionmaker +from sqlalchemy_utils import create_database, database_exists +from sqlmodel import Session, SQLModel -from db.create_database_tables import initialize_tables from db.extensions import engine from domain.logic.admin import create_admin from domain.logic.group import add_student_to_group, create_group @@ -17,244 +17,250 @@ from domain.models.SubmissionDataclass import SubmissionState if __name__ == "__main__": - SessionLocal = sessionmaker(autocommit=False, bind=engine) - session = SessionLocal() - initialize_tables(session, engine) # drops existing tables and makes new ones - - # Create subjects - objeprog = create_subject(session, name="Objectgericht Programmeren") - algoritmen = create_subject(session, name="Algoritmen en Datastructuren") - webtech = create_subject(session, name="Webtechnologie") - - # Create projects for subjects - objprog_project = create_project( - session=session, - subject_id=objeprog.id, - name="Flash Cards", - archived=False, - visible=True, - description="Maak iets in JavaFX", - requirements="Een zip bestand met Java-code", - max_students=3, - deadline=datetime(2024, 12, 31, 23, 59, 59, tzinfo=tz.LOCAL), - ) - - objprog_project_2 = create_project( - session=session, - subject_id=objeprog.id, - name="Schaakklok", - archived=True, - visible=True, - description="Een wedstrijdklok is een apparaat waarbij in één behuizing twee uurwerken zijn aangebracht zodanig" - "dat er slechts één tegelijk kan lopen. Een wedstrijdklok wordt gebruikt bij een bordspel voor" - "twee spelers om de bedenktijd te meten. Een speler moet een aantal zetten binnen een bepaalde" - "tijd doen, of alle zetten binnen de aangegeven tijd, of eerst een aantal zetten binnen een" - "bepaalde tijd en de resterende zetten binnen een bepaalde tijd. Een speler die zijn tijd" - 'overschrijdt, verliest de partij. Hij ging "door zijn vlag".\n\n\n' - "Een wedstrijdklok kan worden gebruikt bij dammen, go, schaken en andere bordspellen." - "Men kan dus ook van schaakklok, damklok, goklok of iets anders spreken, maar het gaat om" - "hetzelfde apparaat en wedstrijdklok is de gebruikelijke benaming.", - requirements="Een bestand genaamd klok.java", - max_students=999, - deadline=datetime(2024, 2, 29, 00, tzinfo=tz.LOCAL), - ) - - algo_project = create_project( - session=session, - subject_id=algoritmen.id, - name="Sorteer Algoritmen Implementatie", - archived=False, - visible=True, - description="Implementeer verschillende sorteeralgoritmen", - requirements="Code in Python", - max_students=1, - deadline=datetime(2024, 11, 15, 23, 59, 59, tzinfo=tz.LOCAL), - ) - - web_project = create_project( - session=session, - subject_id=webtech.id, - name="Webshop", - archived=False, - visible=True, - description="Bouw een eenvoudige webshop", - requirements="Gebruik van HTML, CSS, en JavaScript", - max_students=4, - deadline=datetime(2024, 10, 30, 23, 59, 59, tzinfo=tz.LOCAL), - ) - - # Create groups for projects - groep1_objprog = create_group(session, objprog_project.id) - groep2_objprog = create_group(session, objprog_project.id) - groep3_objprog = create_group(session, objprog_project.id) - groep4_objprog = create_group(session, objprog_project.id) # empty group - groep1_algo = create_group(session, algo_project.id) - groep2_algo = create_group(session, algo_project.id) - groep3_algo = create_group(session, algo_project.id) - groep4_algo = create_group(session, algo_project.id) - groep5_algo = create_group(session, algo_project.id) - groep6_algo = create_group(session, algo_project.id) - groep7_algo = create_group(session, algo_project.id) - groep8_algo = create_group(session, algo_project.id) - groep1_web = create_group(session, web_project.id) - groep2_web = create_group(session, web_project.id) # empty group - - # Create students - student1 = create_student(session, "Lukas", "Lukas.BarraganTorres@UGent.be") - student2 = create_student(session, "Alberic", "Alberic.Loos@UGent.be") - student3 = create_student(session, "Matthias", "matseghe.Seghers@UGent.be") - student4 = create_student(session, "Ruben", "Ruben.Vandamme@UGent.be") - student5 = create_student(session, "Emma", "emmavdwa.Vandewalle@UGent.be") - student6 = create_student(session, "Robbe", "Robbe.VandeKeere@UGent.be") - student7 = create_student(session, "Stef", "Stef.Osse@UGent.be") - student8 = create_student(session, "Mathieu", "Mathieu.Strypsteen@UGent.be") - - # Create teachers - teacher1 = create_teacher(session, "Kris Coolsaet", "kris.coolsaet@ugent.be") - teacher2 = create_teacher(session, "Sophie Devolder", "sophie.devolder@ugent.be") - teacher3 = create_teacher(session, "Pieter-Jan De Smet", "pj.desmet@ugent.be") - - # Create admin - admin = create_admin(session, "Admin", "admin@gmail.com") - - # Add teachers to subjects - add_teacher_to_subject(session, teacher1.id, objeprog.id) - add_teacher_to_subject(session, teacher2.id, algoritmen.id) - add_teacher_to_subject(session, teacher3.id, webtech.id) - add_teacher_to_subject(session, teacher3.id, objeprog.id) - - # Add students to subjects - - # noinspection DuplicatedCode - add_student_to_subject(session, student1.id, objeprog.id) - add_student_to_subject(session, student2.id, objeprog.id) - add_student_to_subject(session, student3.id, objeprog.id) - add_student_to_subject(session, student4.id, objeprog.id) - add_student_to_subject(session, student5.id, objeprog.id) - add_student_to_subject(session, student6.id, objeprog.id) - add_student_to_subject(session, student7.id, objeprog.id) - add_student_to_subject(session, student8.id, objeprog.id) - - add_student_to_subject(session, student1.id, algoritmen.id) - add_student_to_subject(session, student2.id, algoritmen.id) - add_student_to_subject(session, student3.id, algoritmen.id) - # noinspection DuplicatedCode - add_student_to_subject(session, student4.id, algoritmen.id) - add_student_to_subject(session, student5.id, algoritmen.id) - add_student_to_subject(session, student6.id, algoritmen.id) - add_student_to_subject(session, student7.id, algoritmen.id) - add_student_to_subject(session, student8.id, algoritmen.id) - - add_student_to_subject(session, student1.id, webtech.id) - add_student_to_subject(session, student2.id, webtech.id) - add_student_to_subject(session, student3.id, webtech.id) - add_student_to_subject(session, student5.id, webtech.id) # 4 en 8 zullen assistent zijn bij dit vak - add_student_to_subject(session, student6.id, webtech.id) - add_student_to_subject(session, student7.id, webtech.id) - - # Add students to groups - - # noinspection DuplicatedCode - add_student_to_group(session, student1.id, groep1_objprog.id) - add_student_to_group(session, student2.id, groep1_objprog.id) - add_student_to_group(session, student3.id, groep1_objprog.id) - add_student_to_group(session, student4.id, groep2_objprog.id) - add_student_to_group(session, student5.id, groep2_objprog.id) - add_student_to_group(session, student6.id, groep3_objprog.id) - add_student_to_group(session, student7.id, groep3_objprog.id) - add_student_to_group(session, student8.id, groep3_objprog.id) - - add_student_to_group(session, student4.id, groep1_algo.id) - # noinspection DuplicatedCode - add_student_to_group(session, student5.id, groep2_algo.id) - add_student_to_group(session, student6.id, groep3_algo.id) - add_student_to_group(session, student1.id, groep4_algo.id) - add_student_to_group(session, student2.id, groep5_algo.id) - add_student_to_group(session, student3.id, groep6_algo.id) - add_student_to_group(session, student8.id, groep7_algo.id) - add_student_to_group(session, student7.id, groep8_algo.id) - - add_student_to_group(session, student1.id, groep1_web.id) - add_student_to_group(session, student3.id, groep1_web.id) - add_student_to_group(session, student5.id, groep1_web.id) - - # Create submissions (one per group) - create_submission( - session=session, - student_id=student1.id, - group_id=groep1_objprog.id, - message="Eerste versie", - state=SubmissionState.Rejected, - date_time=datetime(2024, 3, 22, 22, 55, 3, tzinfo=tz.LOCAL), - filename="flashcards.zip", - ) - - create_submission( - session=session, - student_id=student5.id, - group_id=groep2_objprog.id, - message="", - state=SubmissionState.Pending, - date_time=datetime(2024, 3, 22, 22, 57, 34, tzinfo=tz.LOCAL), - filename="flashcards.zip", - ) - - create_submission( - session=session, - student_id=student8.id, - group_id=groep3_objprog.id, - message="blablabla", - state=SubmissionState.Approved, - date_time=datetime(2024, 3, 22, 22, 59, 17, tzinfo=tz.LOCAL), - filename="flashcards.zip", - ) - - create_submission( - session=session, - student_id=student4.id, - group_id=groep1_algo.id, - message="Optimalisatie + enkele bug fixes", - state=SubmissionState.Approved, - date_time=datetime(2024, 3, 21, 7, 59, 13, tzinfo=tz.LOCAL), - filename="sorteer_algoritmen.py", - ) - - create_submission( - session=session, - student_id=student2.id, - group_id=groep5_algo.id, - message="Klaar is kees!", - state=SubmissionState.Approved, - date_time=datetime(2024, 2, 1, 12, 20, 45, tzinfo=tz.LOCAL), - filename="sorteer_algoritmen.py", - ) - - create_submission( - session=session, - student_id=student3.id, - group_id=groep6_algo.id, - message="Nog wat werk", - state=SubmissionState.Rejected, - date_time=datetime(2024, 3, 22, 23, 57, 34, tzinfo=tz.LOCAL), - filename="sorteer_algoritmen.py", - ) - - create_submission( - session=session, - student_id=student7.id, - group_id=groep8_algo.id, - message="", - state=SubmissionState.Rejected, - date_time=datetime(2024, 3, 22, 22, 19, 0, tzinfo=tz.LOCAL), - filename="sorteer_algoritmen.py", - ) - - # make assistants - modify_user_roles(session, student4.id, [Role.TEACHER]) - add_teacher_to_subject(session, student4.id, webtech.id) - - modify_user_roles(session, student8.id, [Role.TEACHER]) - add_teacher_to_subject(session, student8.id, webtech.id) - - session.commit() - session.close() + with Session(engine) as session: + if not database_exists(engine.url): + create_database(engine.url) + + SQLModel.metadata.drop_all(engine) + SQLModel.metadata.create_all(engine) + + session.commit() + + # Create subjects + objeprog = create_subject(session, name="Objectgericht Programmeren") + algoritmen = create_subject(session, name="Algoritmen en Datastructuren") + webtech = create_subject(session, name="Webtechnologie") + + # Create projects for subjects + objprog_project = create_project( + session=session, + subject_id=objeprog.id, + name="Flash Cards", + archived=False, + visible=True, + description="Maak iets in JavaFX", + requirements="Een zip bestand met Java-code", + max_students=3, + deadline=datetime(2024, 12, 31, 23, 59, 59, tzinfo=tz.LOCAL), + ) + + objprog_project_2 = create_project( + session=session, + subject_id=objeprog.id, + name="Schaakklok", + archived=True, + visible=True, + description="Een wedstrijdklok is een apparaat waarbij in één behuizing twee uurwerken zijn aangebracht " + "zodanig" + "dat er slechts één tegelijk kan lopen. Een wedstrijdklok wordt gebruikt bij een bordspel voor" + "twee spelers om de bedenktijd te meten. Een speler moet een aantal zetten binnen een bepaalde" + "tijd doen, of alle zetten binnen de aangegeven tijd, of eerst een aantal zetten binnen een" + "bepaalde tijd en de resterende zetten binnen een bepaalde tijd. Een speler die zijn tijd" + 'overschrijdt, verliest de partij. Hij ging "door zijn vlag".\n\n\n' + "Een wedstrijdklok kan worden gebruikt bij dammen, go, schaken en andere bordspellen." + "Men kan dus ook van schaakklok, damklok, goklok of iets anders spreken, maar het gaat om" + "hetzelfde apparaat en wedstrijdklok is de gebruikelijke benaming.", + requirements="Een bestand genaamd klok.java", + max_students=999, + deadline=datetime(2024, 2, 29, 00, tzinfo=tz.LOCAL), + ) + + algo_project = create_project( + session=session, + subject_id=algoritmen.id, + name="Sorteer Algoritmen Implementatie", + archived=False, + visible=True, + description="Implementeer verschillende sorteeralgoritmen", + requirements="Code in Python", + max_students=1, + deadline=datetime(2024, 11, 15, 23, 59, 59, tzinfo=tz.LOCAL), + ) + + web_project = create_project( + session=session, + subject_id=webtech.id, + name="Webshop", + archived=False, + visible=True, + description="Bouw een eenvoudige webshop", + requirements="Gebruik van HTML, CSS, en JavaScript", + max_students=4, + deadline=datetime(2024, 10, 30, 23, 59, 59, tzinfo=tz.LOCAL), + ) + + # Create groups for projects + groep1_objprog = create_group(session, objprog_project.id) + groep2_objprog = create_group(session, objprog_project.id) + groep3_objprog = create_group(session, objprog_project.id) + groep4_objprog = create_group(session, objprog_project.id) # empty group + groep1_algo = create_group(session, algo_project.id) + groep2_algo = create_group(session, algo_project.id) + groep3_algo = create_group(session, algo_project.id) + groep4_algo = create_group(session, algo_project.id) + groep5_algo = create_group(session, algo_project.id) + groep6_algo = create_group(session, algo_project.id) + groep7_algo = create_group(session, algo_project.id) + groep8_algo = create_group(session, algo_project.id) + groep1_web = create_group(session, web_project.id) + groep2_web = create_group(session, web_project.id) # empty group + + # Create students + student1 = create_student(session, "Lukas", "Lukas.BarraganTorres@UGent.be") + student2 = create_student(session, "Alberic", "Alberic.Loos@UGent.be") + student3 = create_student(session, "Matthias", "matseghe.Seghers@UGent.be") + student4 = create_student(session, "Ruben", "Ruben.Vandamme@UGent.be") + student5 = create_student(session, "Emma", "emmavdwa.Vandewalle@UGent.be") + student6 = create_student(session, "Robbe", "Robbe.VandeKeere@UGent.be") + student7 = create_student(session, "Stef", "Stef.Osse@UGent.be") + student8 = create_student(session, "Mathieu", "Mathieu.Strypsteen@UGent.be") + + # Create teachers + teacher1 = create_teacher(session, "Kris Coolsaet", "kris.coolsaet@ugent.be") + teacher2 = create_teacher(session, "Sophie Devolder", "sophie.devolder@ugent.be") + teacher3 = create_teacher(session, "Pieter-Jan De Smet", "pj.desmet@ugent.be") + + # Create admin + admin = create_admin(session, "Admin", "admin@gmail.com") + + # Add teachers to subjects + add_teacher_to_subject(session, teacher1.id, objeprog.id) + add_teacher_to_subject(session, teacher2.id, algoritmen.id) + add_teacher_to_subject(session, teacher3.id, webtech.id) + add_teacher_to_subject(session, teacher3.id, objeprog.id) + + # Add students to subjects + + # noinspection DuplicatedCode + add_student_to_subject(session, student1.id, objeprog.id) + add_student_to_subject(session, student2.id, objeprog.id) + add_student_to_subject(session, student3.id, objeprog.id) + add_student_to_subject(session, student4.id, objeprog.id) + add_student_to_subject(session, student5.id, objeprog.id) + add_student_to_subject(session, student6.id, objeprog.id) + add_student_to_subject(session, student7.id, objeprog.id) + add_student_to_subject(session, student8.id, objeprog.id) + + add_student_to_subject(session, student1.id, algoritmen.id) + add_student_to_subject(session, student2.id, algoritmen.id) + add_student_to_subject(session, student3.id, algoritmen.id) + # noinspection DuplicatedCode + add_student_to_subject(session, student4.id, algoritmen.id) + add_student_to_subject(session, student5.id, algoritmen.id) + add_student_to_subject(session, student6.id, algoritmen.id) + add_student_to_subject(session, student7.id, algoritmen.id) + add_student_to_subject(session, student8.id, algoritmen.id) + + add_student_to_subject(session, student1.id, webtech.id) + add_student_to_subject(session, student2.id, webtech.id) + add_student_to_subject(session, student3.id, webtech.id) + add_student_to_subject(session, student5.id, webtech.id) # 4 en 8 zullen assistent zijn bij dit vak + add_student_to_subject(session, student6.id, webtech.id) + add_student_to_subject(session, student7.id, webtech.id) + + # Add students to groups + + # noinspection DuplicatedCode + add_student_to_group(session, student1.id, groep1_objprog.id) + add_student_to_group(session, student2.id, groep1_objprog.id) + add_student_to_group(session, student3.id, groep1_objprog.id) + add_student_to_group(session, student4.id, groep2_objprog.id) + add_student_to_group(session, student5.id, groep2_objprog.id) + add_student_to_group(session, student6.id, groep3_objprog.id) + add_student_to_group(session, student7.id, groep3_objprog.id) + add_student_to_group(session, student8.id, groep3_objprog.id) + + add_student_to_group(session, student4.id, groep1_algo.id) + # noinspection DuplicatedCode + add_student_to_group(session, student5.id, groep2_algo.id) + add_student_to_group(session, student6.id, groep3_algo.id) + add_student_to_group(session, student1.id, groep4_algo.id) + add_student_to_group(session, student2.id, groep5_algo.id) + add_student_to_group(session, student3.id, groep6_algo.id) + add_student_to_group(session, student8.id, groep7_algo.id) + add_student_to_group(session, student7.id, groep8_algo.id) + + add_student_to_group(session, student1.id, groep1_web.id) + add_student_to_group(session, student3.id, groep1_web.id) + add_student_to_group(session, student5.id, groep1_web.id) + + # Create submissions (one per group) + create_submission( + session=session, + student_id=student1.id, + group_id=groep1_objprog.id, + message="Eerste versie", + state=SubmissionState.Rejected, + date_time=datetime(2024, 3, 22, 22, 55, 3, tzinfo=tz.LOCAL), + filename="flashcards.zip", + ) + + create_submission( + session=session, + student_id=student5.id, + group_id=groep2_objprog.id, + message="", + state=SubmissionState.Pending, + date_time=datetime(2024, 3, 22, 22, 57, 34, tzinfo=tz.LOCAL), + filename="flashcards.zip", + ) + + create_submission( + session=session, + student_id=student8.id, + group_id=groep3_objprog.id, + message="blablabla", + state=SubmissionState.Approved, + date_time=datetime(2024, 3, 22, 22, 59, 17, tzinfo=tz.LOCAL), + filename="flashcards.zip", + ) + + create_submission( + session=session, + student_id=student4.id, + group_id=groep1_algo.id, + message="Optimalisatie + enkele bug fixes", + state=SubmissionState.Approved, + date_time=datetime(2024, 3, 21, 7, 59, 13, tzinfo=tz.LOCAL), + filename="sorteer_algoritmen.py", + ) + + create_submission( + session=session, + student_id=student2.id, + group_id=groep5_algo.id, + message="Klaar is kees!", + state=SubmissionState.Approved, + date_time=datetime(2024, 2, 1, 12, 20, 45, tzinfo=tz.LOCAL), + filename="sorteer_algoritmen.py", + ) + + create_submission( + session=session, + student_id=student3.id, + group_id=groep6_algo.id, + message="Nog wat werk", + state=SubmissionState.Rejected, + date_time=datetime(2024, 3, 22, 23, 57, 34, tzinfo=tz.LOCAL), + filename="sorteer_algoritmen.py", + ) + + create_submission( + session=session, + student_id=student7.id, + group_id=groep8_algo.id, + message="", + state=SubmissionState.Rejected, + date_time=datetime(2024, 3, 22, 22, 19, 0, tzinfo=tz.LOCAL), + filename="sorteer_algoritmen.py", + ) + + # make assistants + modify_user_roles(session, student4.id, [Role.TEACHER]) + add_teacher_to_subject(session, student4.id, webtech.id) + + modify_user_roles(session, student8.id, [Role.TEACHER]) + add_teacher_to_subject(session, student8.id, webtech.id) + + session.commit() + session.close() From ae0a3b5c7d1e1831ab6b4b9234d6531c131f2766 Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Sat, 6 Apr 2024 21:53:10 +0200 Subject: [PATCH 43/90] use of sqlmodel session object #123 --- backend/db/sessions.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/backend/db/sessions.py b/backend/db/sessions.py index fdfda192..2559ad33 100644 --- a/backend/db/sessions.py +++ b/backend/db/sessions.py @@ -1,20 +1,10 @@ from collections.abc import Generator -from sqlalchemy.orm import Session, sessionmaker +from sqlmodel import Session from db.extensions import engine -# Generator for db sessions. Check the [documentation.md] for the use of sessions. -SessionLocal: sessionmaker[Session] = sessionmaker(autocommit=False, autoflush=False, bind=engine) - def get_session() -> Generator[Session, None, None]: - """ - Returns a generator for session objects. - To be used as dependency injection. - """ - db = SessionLocal() - try: - yield db - finally: - db.close() + with Session(engine) as session: + yield session From a42297dc7abf3dbe1dbeb0d67dcfbdd40bcfbd11 Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Sat, 6 Apr 2024 21:54:50 +0200 Subject: [PATCH 44/90] Removed Dataclasses and to_domain_model() #123 --- backend/domain/logic/admin.py | 15 +++++----- backend/domain/logic/basic_operations.py | 6 ++-- backend/domain/logic/group.py | 35 ++++++++++------------ backend/domain/logic/project.py | 29 +++++++++---------- backend/domain/logic/student.py | 15 +++++----- backend/domain/logic/subject.py | 37 ++++++++++-------------- backend/domain/logic/submission.py | 28 +++++++++--------- backend/domain/logic/teacher.py | 16 +++++----- backend/domain/logic/user.py | 14 ++++----- 9 files changed, 87 insertions(+), 108 deletions(-) diff --git a/backend/domain/logic/admin.py b/backend/domain/logic/admin.py index 2a5d5418..9c95b01a 100644 --- a/backend/domain/logic/admin.py +++ b/backend/domain/logic/admin.py @@ -1,11 +1,10 @@ -from sqlalchemy.orm import Session +from sqlmodel import Session 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: +def create_admin(session: Session, name: str, email: str) -> Admin: """ This function is meant to create a new user that is an admin. It does not change the role of an existing user. """ @@ -15,15 +14,15 @@ def create_admin(session: Session, name: str, email: str) -> AdminDataclass: new_admin: Admin = Admin(id=new_user.id) session.add(new_admin) session.commit() - return new_admin.to_domain_model() + return new_admin -def get_admin(session: Session, admin_id: int) -> AdminDataclass: - return get(session, Admin, admin_id).to_domain_model() +def get_admin(session: Session, admin_id: int) -> Admin: + return get(session, Admin, admin_id) -def get_all_admins(session: Session) -> list[AdminDataclass]: - return [admin.to_domain_model() for admin in get_all(session, Admin)] +def get_all_admins(session: Session) -> list[Admin]: + return get_all(session, Admin) def is_user_admin(session: Session, user_id: int) -> bool: diff --git a/backend/domain/logic/basic_operations.py b/backend/domain/logic/basic_operations.py index 1e74d534..848309f0 100644 --- a/backend/domain/logic/basic_operations.py +++ b/backend/domain/logic/basic_operations.py @@ -1,13 +1,11 @@ from typing import TypeVar -from sqlalchemy import select -from sqlalchemy.orm import Session +from sqlmodel import Session, SQLModel, select from db.errors.database_errors import ItemNotFoundError -from db.models.models import AbstractModel # Create a generic type variable bound to subclasses of AbstractModel. -T = TypeVar("T", bound=AbstractModel) +T = TypeVar("T", bound=SQLModel) def get(session: Session, object_type: type[T], ident: int) -> T: diff --git a/backend/domain/logic/group.py b/backend/domain/logic/group.py index 4c7d5a5c..5839a03d 100644 --- a/backend/domain/logic/group.py +++ b/backend/domain/logic/group.py @@ -1,13 +1,11 @@ -from sqlalchemy.orm import Session +from sqlmodel import Session 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 -from domain.models.StudentDataclass import StudentDataclass -def create_group(session: Session, project_id: int) -> GroupDataclass: +def create_group(session: Session, project_id: int) -> Group: """ Create an empty group for a certain project. """ @@ -18,27 +16,25 @@ def create_group(session: Session, project_id: int) -> GroupDataclass: session.add(new_group) session.commit() - return new_group.to_domain_model() + return new_group -def get_group(session: Session, group_id: int) -> GroupDataclass: - return get(session, Group, group_id).to_domain_model() +def get_group(session: Session, group_id: int) -> Group: + return get(session, Group, group_id) -def get_all_groups(session: Session) -> list[GroupDataclass]: - return [group.to_domain_model() for group in get_all(session, Group)] +def get_all_groups(session: Session) -> list[Group]: + return get_all(session, Group) -def get_groups_of_project(session: Session, project_id: int) -> list[GroupDataclass]: +def get_groups_of_project(session: Session, project_id: int) -> list[Group]: project: Project = get(session, Project, project_id) - groups: list[Group] = project.groups - return [group.to_domain_model() for group in groups] + return project.groups -def get_groups_of_student(session: Session, student_id: int) -> list[GroupDataclass]: +def get_groups_of_student(session: Session, student_id: int) -> list[Group]: student: Student = get(session, Student, ident=student_id) - groups: list[Group] = student.groups - return [group.to_domain_model() for group in groups] + return student.groups def add_student_to_group(session: Session, student_id: int, group_id: int) -> None: @@ -68,16 +64,15 @@ def remove_student_from_group(session: Session, student_id: int, group_id: int) session.commit() -def get_students_of_group(session: Session, group_id: int) -> list[StudentDataclass]: +def get_students_of_group(session: Session, group_id: int) -> list[Student]: group: Group = get(session, Group, ident=group_id) - students: list[Student] = group.students - return [student.to_domain_model() for student in students] + return group.students -def get_group_for_student_and_project(session: Session, student_id: int, project_id: int) -> GroupDataclass | None: +def get_group_for_student_and_project(session: Session, student_id: int, project_id: int) -> Group | None: student: Student = get(session, Student, ident=student_id) project: Project = get(session, Project, ident=project_id) for group in project.groups: if student in group.students: - return group.to_domain_model() + return group return None diff --git a/backend/domain/logic/project.py b/backend/domain/logic/project.py index 10f76cb8..c469ce7f 100644 --- a/backend/domain/logic/project.py +++ b/backend/domain/logic/project.py @@ -1,10 +1,10 @@ from datetime import datetime -from sqlalchemy.orm import Session +from sqlmodel import Session from db.models.models import Project, Student, Subject, Teacher from domain.logic.basic_operations import get, get_all -from domain.models.ProjectDataclass import ProjectDataclass, ProjectInput +from domain.models.ProjectDataclass import ProjectInput def create_project( @@ -17,7 +17,7 @@ def create_project( requirements: str, visible: bool, max_students: int, -) -> ProjectDataclass: +) -> Project: """ Create a project for a certain subject. """ @@ -36,39 +36,38 @@ def create_project( subject.projects.append(new_project) session.commit() - return new_project.to_domain_model() + return new_project -def get_project(session: Session, project_id: int) -> ProjectDataclass: - return get(session, Project, project_id).to_domain_model() +def get_project(session: Session, project_id: int) -> Project: + return get(session, Project, project_id) -def get_all_projects(session: Session) -> list[ProjectDataclass]: - return [project.to_domain_model() for project in get_all(session, Project)] +def get_all_projects(session: Session) -> list[Project]: + return get_all(session, Project) -def get_projects_of_subject(session: Session, subject_id: int) -> list[ProjectDataclass]: +def get_projects_of_subject(session: Session, subject_id: int) -> list[Project]: subject: Subject = get(session, Subject, ident=subject_id) - projects: list[Project] = subject.projects - return [project.to_domain_model() for project in projects] + return subject.projects -def get_projects_of_student(session: Session, user_id: int) -> list[ProjectDataclass]: +def get_projects_of_student(session: Session, user_id: int) -> list[Project]: 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] + return projects -def get_projects_of_teacher(session: Session, user_id: int) -> list[ProjectDataclass]: +def get_projects_of_teacher(session: Session, user_id: int) -> list[Project]: 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] + return projects def update_project(session: Session, project_id: int, project: ProjectInput) -> None: diff --git a/backend/domain/logic/student.py b/backend/domain/logic/student.py index a42e1933..cae7f769 100644 --- a/backend/domain/logic/student.py +++ b/backend/domain/logic/student.py @@ -1,11 +1,10 @@ -from sqlalchemy.orm import Session +from sqlmodel import Session 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: +def create_student(session: Session, name: str, email: str) -> Student: """ This function is meant to create a new user that is a student. It does not change the role of an existing user. """ @@ -15,15 +14,15 @@ def create_student(session: Session, name: str, email: str) -> StudentDataclass: new_student: Student = Student(id=new_user.id) session.add(new_student) session.commit() - return new_student.to_domain_model() + return new_student -def get_student(session: Session, student_id: int) -> StudentDataclass: - return get(session, Student, student_id).to_domain_model() +def get_student(session: Session, student_id: int) -> Student: + return get(session, Student, student_id) -def get_all_students(session: Session) -> list[StudentDataclass]: - return [student.to_domain_model() for student in get_all(session, Student)] +def get_all_students(session: Session) -> list[Student]: + return get_all(session, Student) def is_user_student(session: Session, user_id: int) -> bool: diff --git a/backend/domain/logic/subject.py b/backend/domain/logic/subject.py index 6234b7f5..a3fc47d2 100644 --- a/backend/domain/logic/subject.py +++ b/backend/domain/logic/subject.py @@ -1,34 +1,30 @@ -from sqlalchemy.orm import Session +from sqlmodel import Session 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.StudentDataclass import StudentDataclass -from domain.models.SubjectDataclass import SubjectDataclass -from domain.models.TeacherDataclass import TeacherDataclass -def create_subject(session: Session, name: str) -> SubjectDataclass: +def create_subject(session: Session, name: str) -> Subject: new_subject = Subject(name=name) session.add(new_subject) session.commit() - return new_subject.to_domain_model() + return new_subject -def get_subject(session: Session, subject_id: int) -> SubjectDataclass: - return get(session, Subject, subject_id).to_domain_model() +def get_subject(session: Session, subject_id: int) -> Subject: + return get(session, Subject, subject_id) -def get_all_subjects(session: Session) -> list[SubjectDataclass]: - return [subject.to_domain_model() for subject in get_all(session, Subject)] +def get_all_subjects(session: Session) -> list[Subject]: + return get_all(session, Subject) -def get_subjects_of_teacher(session: Session, teacher_id: int) -> list[SubjectDataclass]: +def get_subjects_of_teacher(session: Session, teacher_id: int) -> list[Subject]: teacher: Teacher = get(session, Teacher, ident=teacher_id) - subjects: list[Subject] = teacher.subjects - return [vak.to_domain_model() for vak in subjects] + return teacher.subjects def add_student_to_subject(session: Session, student_id: int, subject_id: int) -> None: @@ -55,10 +51,9 @@ def add_teacher_to_subject(session: Session, teacher_id: int, subject_id: int) - session.commit() -def get_subjects_of_student(session: Session, student_id: int) -> list[SubjectDataclass]: +def get_subjects_of_student(session: Session, student_id: int) -> list[Subject]: student: Student = get(session, Student, ident=student_id) - subjects: list[Subject] = student.subjects - return [vak.to_domain_model() for vak in subjects] + return student.subjects def is_user_authorized_for_subject(subject_id: int, session: Session, uid: int) -> bool: @@ -72,13 +67,11 @@ def is_user_authorized_for_subject(subject_id: int, session: Session, uid: int) return False -def get_teachers_of_subject(session: Session, subject_id: int) -> list[TeacherDataclass]: +def get_teachers_of_subject(session: Session, subject_id: int) -> list[Teacher]: subject: Subject = get(session, Subject, ident=subject_id) - teachers = subject.teachers - return [teacher.to_domain_model() for teacher in teachers] + return subject.teachers -def get_students_of_subject(session: Session, subject_id: int) -> list[StudentDataclass]: +def get_students_of_subject(session: Session, subject_id: int) -> list[Student]: subject: Subject = get(session, Subject, ident=subject_id) - students = subject.students - return [student.to_domain_model() for student in students] + return subject.students diff --git a/backend/domain/logic/submission.py b/backend/domain/logic/submission.py index 5e4c13fb..524c9c14 100644 --- a/backend/domain/logic/submission.py +++ b/backend/domain/logic/submission.py @@ -1,10 +1,10 @@ from datetime import datetime -from sqlalchemy.orm import Session +from sqlmodel import Session from db.models.models import Group, Student, Submission from domain.logic.basic_operations import get, get_all -from domain.models.SubmissionDataclass import SubmissionDataclass, SubmissionState +from domain.models.SubmissionDataclass import SubmissionState def create_submission( @@ -15,7 +15,7 @@ def create_submission( state: SubmissionState, date_time: datetime, filename: str, -) -> SubmissionDataclass: +) -> Submission: """ Create a submission for a certain project by a certain group. """ @@ -32,29 +32,27 @@ def create_submission( ) session.add(new_submission) session.commit() - return new_submission.to_domain_model() + return new_submission -def get_submission(session: Session, submission_id: int) -> SubmissionDataclass: - return get(session, Submission, submission_id).to_domain_model() +def get_submission(session: Session, submission_id: int) -> Submission: + return get(session, Submission, submission_id) -def get_all_submissions(session: Session) -> list[SubmissionDataclass]: - return [submission.to_domain_model() for submission in get_all(session, Submission)] +def get_all_submissions(session: Session) -> list[Submission]: + return get_all(session, Submission) -def get_submissions_of_student(session: Session, student_id: int) -> list[SubmissionDataclass]: +def get_submissions_of_student(session: Session, student_id: int) -> list[Submission]: student: Student = get(session, Student, ident=student_id) - submissions: list[Submission] = student.submissions - return [submission.to_domain_model() for submission in submissions] + return student.submissions -def get_submissions_of_group(session: Session, group_id: int) -> list[SubmissionDataclass]: +def get_submissions_of_group(session: Session, group_id: int) -> list[Submission]: group: Group = get(session, Group, ident=group_id) - submissions: list[Submission] = group.submissions - return [submission.to_domain_model() for submission in submissions] + return group.submissions -def get_last_submission(session: Session, group_id: int) -> SubmissionDataclass: +def get_last_submission(session: Session, group_id: int) -> Submission: submissions = get_submissions_of_group(session, group_id) return max(submissions, key=lambda submission: submission.date_time) diff --git a/backend/domain/logic/teacher.py b/backend/domain/logic/teacher.py index 01e2d81b..acfbedd0 100644 --- a/backend/domain/logic/teacher.py +++ b/backend/domain/logic/teacher.py @@ -1,29 +1,29 @@ -from sqlalchemy.orm import Session +from sqlmodel import Session 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: +def create_teacher(session: Session, name: str, email: str) -> Teacher: """ This function is meant to create a new user that is a teacher. It does not change the role of an existing user. """ new_user: User = User(name=name, email=email) session.add(new_user) session.commit() + assert new_user.id is not None new_teacher: Teacher = Teacher(id=new_user.id) session.add(new_teacher) session.commit() - return new_teacher.to_domain_model() + return new_teacher -def get_teacher(session: Session, teacher_id: int) -> TeacherDataclass: - return get(session, Teacher, teacher_id).to_domain_model() +def get_teacher(session: Session, teacher_id: int) -> Teacher: + return get(session, Teacher, teacher_id) -def get_all_teachers(session: Session) -> list[TeacherDataclass]: - return [teacher.to_domain_model() for teacher in get_all(session, Teacher)] +def get_all_teachers(session: Session) -> list[Teacher]: + return get_all(session, Teacher) def is_user_teacher(session: Session, user_id: int) -> bool: diff --git a/backend/domain/logic/user.py b/backend/domain/logic/user.py index a0aa52e2..ce7286e1 100644 --- a/backend/domain/logic/user.py +++ b/backend/domain/logic/user.py @@ -1,5 +1,4 @@ -from sqlalchemy import select -from sqlalchemy.orm import Session +from sqlmodel import Session, select from db.models.models import Admin, Student, Teacher, User from domain.logic.admin import is_user_admin @@ -8,12 +7,11 @@ from domain.logic.student import is_user_student from domain.logic.teacher import is_user_teacher from domain.models.APIUser import APIUser -from domain.models.UserDataclass import UserDataclass -def convert_user(session: Session, user: UserDataclass) -> APIUser: +def convert_user(session: Session, user: User) -> APIUser: """ - Given a UserDataclass, check what roles that user has and fill those in to convert it to an APIUser. + Given a User, check what roles that user has and fill those in to convert it to an APIUser. """ api_user = APIUser(id=user.id, name=user.name, email=user.email, language=user.language, roles=[]) @@ -29,11 +27,11 @@ def convert_user(session: Session, user: UserDataclass) -> APIUser: return api_user -def get_user(session: Session, user_id: int) -> UserDataclass: +def get_user(session: Session, user_id: int) -> User: return get(session, User, user_id).to_domain_model() -def get_user_with_email(session: Session, email: str) -> UserDataclass | None: +def get_user_with_email(session: Session, email: str) -> User | None: stmt = select(User).where(User.email == email) result = session.execute(stmt) users = [r.to_domain_model() for r in result.scalars()] @@ -47,7 +45,7 @@ def get_user_with_email(session: Session, email: str) -> UserDataclass | None: return None -def get_all_users(session: Session) -> list[UserDataclass]: +def get_all_users(session: Session) -> list[User]: return [user.to_domain_model() for user in get_all(session, User)] From cd0cb291c9b0d14c14c4e797a2fb5ebf0d00b538 Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Sat, 6 Apr 2024 21:55:47 +0200 Subject: [PATCH 45/90] removed all dataclasses except APIUser #123 --- backend/domain/models/ProjectDataclass.py | 12 ------------ backend/domain/models/SubjectDataclass.py | 5 ----- backend/domain/models/SubmissionDataclass.py | 13 ------------- 3 files changed, 30 deletions(-) diff --git a/backend/domain/models/ProjectDataclass.py b/backend/domain/models/ProjectDataclass.py index b189d4d9..1fd875a8 100644 --- a/backend/domain/models/ProjectDataclass.py +++ b/backend/domain/models/ProjectDataclass.py @@ -3,18 +3,6 @@ from pydantic import BaseModel, PositiveInt -class ProjectDataclass(BaseModel): - id: int - name: str - deadline: datetime - archived: bool - description: str - requirements: str - visible: bool - max_students: PositiveInt - subject_id: int - - class ProjectInput(BaseModel): name: str deadline: datetime diff --git a/backend/domain/models/SubjectDataclass.py b/backend/domain/models/SubjectDataclass.py index cea578dc..d8264f36 100644 --- a/backend/domain/models/SubjectDataclass.py +++ b/backend/domain/models/SubjectDataclass.py @@ -1,10 +1,5 @@ from pydantic import BaseModel -class SubjectDataclass(BaseModel): - id: int - name: str - - class SubjectInput(BaseModel): name: str diff --git a/backend/domain/models/SubmissionDataclass.py b/backend/domain/models/SubmissionDataclass.py index 9b778460..cbc446f5 100644 --- a/backend/domain/models/SubmissionDataclass.py +++ b/backend/domain/models/SubmissionDataclass.py @@ -1,20 +1,7 @@ import enum -from datetime import datetime - -from pydantic import BaseModel class SubmissionState(enum.Enum): Pending = 1 Approved = 2 Rejected = 3 - - -class SubmissionDataclass(BaseModel): - id: int - date_time: datetime - group_id: int - student_id: int - state: SubmissionState - message: str - filename: str From 49185be02ae9a22e9b19c481d1efdc5d3e6c8b6a Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Sat, 6 Apr 2024 21:57:31 +0200 Subject: [PATCH 46/90] removed dataclasses #123 --- .../routes/dependencies/role_dependencies.py | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/backend/routes/dependencies/role_dependencies.py b/backend/routes/dependencies/role_dependencies.py index 56ecf34e..264971b8 100644 --- a/backend/routes/dependencies/role_dependencies.py +++ b/backend/routes/dependencies/role_dependencies.py @@ -1,6 +1,7 @@ from starlette.requests import Request from controllers.auth.token_controller import verify_token +from db.models.models import Admin, Student, Teacher from db.sessions import get_session from domain.logic.admin import get_admin, is_user_admin from domain.logic.group import get_group, get_students_of_group @@ -8,9 +9,6 @@ from domain.logic.student import get_student, is_user_student from domain.logic.subject import get_subjects_of_student, get_subjects_of_teacher, is_user_authorized_for_subject from domain.logic.teacher import get_teacher, is_user_teacher -from domain.models.AdminDataclass import AdminDataclass -from domain.models.StudentDataclass import StudentDataclass -from domain.models.TeacherDataclass import TeacherDataclass from routes.errors.authentication import ( InvalidAdminCredentialsError, InvalidAuthenticationError, @@ -32,7 +30,7 @@ def get_authenticated_user(request: Request) -> int: return uid -def get_authenticated_admin(request: Request) -> AdminDataclass: +def get_authenticated_admin(request: Request) -> Admin: session = next(get_session()) uid = get_authenticated_user(request) @@ -41,7 +39,7 @@ def get_authenticated_admin(request: Request) -> AdminDataclass: return get_admin(session, uid) -def get_authenticated_teacher(request: Request) -> TeacherDataclass: +def get_authenticated_teacher(request: Request) -> Teacher: session = next(get_session()) uid = get_authenticated_user(request) @@ -50,7 +48,7 @@ def get_authenticated_teacher(request: Request) -> TeacherDataclass: return get_teacher(session, uid) -def get_authenticated_student(request: Request) -> StudentDataclass: +def get_authenticated_student(request: Request) -> Student: session = next(get_session()) uid = get_authenticated_user(request) @@ -77,7 +75,7 @@ def ensure_user_authorized_for_project(request: Request, project_id: int) -> Non raise NoAccessToDataError -def ensure_student_authorized_for_subject(request: Request, subject_id: int) -> StudentDataclass: +def ensure_student_authorized_for_subject(request: Request, subject_id: int) -> Student: session = next(get_session()) student = get_authenticated_student(request) @@ -87,7 +85,7 @@ def ensure_student_authorized_for_subject(request: Request, subject_id: int) -> return student -def ensure_teacher_authorized_for_subject(request: Request, subject_id: int) -> TeacherDataclass: +def ensure_teacher_authorized_for_subject(request: Request, subject_id: int) -> Teacher: session = next(get_session()) teacher = get_authenticated_teacher(request) @@ -97,7 +95,7 @@ def ensure_teacher_authorized_for_subject(request: Request, subject_id: int) -> return teacher -def ensure_student_authorized_for_project(request: Request, project_id: int) -> StudentDataclass: +def ensure_student_authorized_for_project(request: Request, project_id: int) -> Student: session = next(get_session()) student = get_authenticated_student(request) @@ -107,7 +105,7 @@ def ensure_student_authorized_for_project(request: Request, project_id: int) -> return student -def ensure_teacher_authorized_for_project(request: Request, project_id: int) -> TeacherDataclass: +def ensure_teacher_authorized_for_project(request: Request, project_id: int) -> Teacher: session = next(get_session()) teacher = get_authenticated_teacher(request) @@ -117,7 +115,7 @@ def ensure_teacher_authorized_for_project(request: Request, project_id: int) -> return teacher -def ensure_student_authorized_for_group(request: Request, group_id: int) -> StudentDataclass: +def ensure_student_authorized_for_group(request: Request, group_id: int) -> Student: session = next(get_session()) student = get_authenticated_student(request) @@ -138,7 +136,7 @@ def ensure_user_authorized_for_group(request: Request, group_id: int) -> None: raise NoAccessToDataError -def ensure_student_in_group(request: Request, group_id: int) -> StudentDataclass: +def ensure_student_in_group(request: Request, group_id: int) -> Student: session = next(get_session()) student = get_authenticated_student(request) From 1abef4665e6ec0e2a874fad4c9820721343575b2 Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Sat, 6 Apr 2024 21:58:18 +0200 Subject: [PATCH 47/90] removed dataclasses #123 --- backend/routes/group.py | 7 +++---- backend/routes/login.py | 4 ++-- backend/routes/project.py | 12 ++++++------ backend/routes/student.py | 7 +++---- backend/routes/subject.py | 16 +++++++--------- backend/routes/submission.py | 7 ++++--- backend/routes/teacher.py | 10 +++++----- backend/routes/user.py | 5 ++--- 8 files changed, 32 insertions(+), 36 deletions(-) diff --git a/backend/routes/group.py b/backend/routes/group.py index c20d2b1c..f393a39b 100644 --- a/backend/routes/group.py +++ b/backend/routes/group.py @@ -1,14 +1,13 @@ from fastapi import APIRouter from starlette.requests import Request +from db.models.models import Group, Student from domain.logic.group import ( add_student_to_group, get_group_for_student_and_project, get_students_of_group, remove_student_from_group, ) -from domain.models.GroupDataclass import GroupDataclass -from domain.models.StudentDataclass import StudentDataclass from routes.dependencies.role_dependencies import ( ensure_student_authorized_for_group, ensure_student_authorized_for_project, @@ -34,14 +33,14 @@ def group_leave(request: Request, group_id: int) -> None: @group_router.get("/groups/{group_id}/members", tags=[Tags.GROUP], summary="List group members.") -def list_group_members(request: Request, group_id: int) -> list[StudentDataclass]: +def list_group_members(request: Request, group_id: int) -> list[Student]: ensure_user_authorized_for_group(request, group_id) session = request.state.session return get_students_of_group(session, group_id) @group_router.get("/projects/{project_id}/group", tags=[Tags.GROUP], summary="Get your group for a project.") -def project_get_group(request: Request, project_id: int) -> GroupDataclass | None: +def project_get_group(request: Request, project_id: int) -> Group | None: session = request.state.session student = ensure_student_authorized_for_project(request, project_id) return get_group_for_student_and_project(session, student.id, project_id) diff --git a/backend/routes/login.py b/backend/routes/login.py index c0b21ab2..6054c850 100644 --- a/backend/routes/login.py +++ b/backend/routes/login.py @@ -3,8 +3,8 @@ from controllers.auth.authentication_controller import authenticate_user from controllers.auth.token_controller import create_token, verify_token +from db.models.models import User from domain.models.APIUser import LoginResponse, ValidateResponse -from domain.models.UserDataclass import UserDataclass from routes.errors.authentication import InvalidAuthenticationError from routes.tags.swagger_tags import Tags @@ -35,7 +35,7 @@ def login(request: Request, ticket: str) -> LoginResponse: - Invalid Ticket: Response: with status_code 401 (unauthenticated) and an error message """ session = request.state.session - user: UserDataclass | None = authenticate_user(session, ticket) + user: User | None = authenticate_user(session, ticket) if not user: raise InvalidAuthenticationError return LoginResponse(token=create_token(user)) diff --git a/backend/routes/project.py b/backend/routes/project.py index d7bf1b9e..2966129e 100644 --- a/backend/routes/project.py +++ b/backend/routes/project.py @@ -1,10 +1,10 @@ from fastapi import APIRouter from starlette.requests import Request +from db.models.models import Group, Project from domain.logic.group import create_group, get_groups_of_project from domain.logic.project import get_project, update_project -from domain.models.GroupDataclass import GroupDataclass -from domain.models.ProjectDataclass import ProjectDataclass, ProjectInput +from domain.models.ProjectDataclass import ProjectInput from routes.dependencies.role_dependencies import ( ensure_teacher_authorized_for_project, ensure_user_authorized_for_project, @@ -15,22 +15,22 @@ @project_router.get("/projects/{project_id}", tags=[Tags.PROJECT], summary="Get a certain project.") -def project_get(request: Request, project_id: int) -> ProjectDataclass: +def project_get(request: Request, project_id: int) -> Project: session = request.state.session ensure_user_authorized_for_project(request, project_id) - project: ProjectDataclass = get_project(session, project_id) + project: Project = get_project(session, project_id) return project @project_router.get("/projects/{project_id}/groups", tags=[Tags.PROJECT], summary="Get all groups of a project.") -def project_get_groups(request: Request, project_id: int) -> list[GroupDataclass]: +def project_get_groups(request: Request, project_id: int) -> list[Group]: session = request.state.session ensure_user_authorized_for_project(request, project_id) return get_groups_of_project(session, project_id) @project_router.post("/projects/{project_id}/groups", tags=[Tags.PROJECT], summary="Create a group for a project.") -def project_create_group(request: Request, project_id: int) -> GroupDataclass: +def project_create_group(request: Request, project_id: int) -> Group: session = request.state.session ensure_teacher_authorized_for_project(request, project_id) return create_group(session, project_id) diff --git a/backend/routes/student.py b/backend/routes/student.py index d08eaf37..fd052576 100644 --- a/backend/routes/student.py +++ b/backend/routes/student.py @@ -1,10 +1,9 @@ from fastapi import APIRouter from starlette.requests import Request +from db.models.models import Project, Subject 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.SubjectDataclass import SubjectDataclass from routes.dependencies.role_dependencies import get_authenticated_student from routes.tags.swagger_tags import Tags @@ -12,13 +11,13 @@ @student_router.get("/student/subjects", tags=[Tags.STUDENT], summary="Get all subjects of the student.") -def subjects_of_student_get(request: Request) -> list[SubjectDataclass]: +def subjects_of_student_get(request: Request) -> list[Subject]: student = get_authenticated_student(request) return get_subjects_of_student(request.state.session, student.id) @student_router.get("/student/projects", tags=[Tags.STUDENT], summary="Get all projects of the student.") -def projects_of_student_get(request: Request) -> list[ProjectDataclass]: +def projects_of_student_get(request: Request) -> list[Project]: student = get_authenticated_student(request) return get_projects_of_student(request.state.session, student.id) diff --git a/backend/routes/subject.py b/backend/routes/subject.py index 6f1ab1a3..dec18c0d 100644 --- a/backend/routes/subject.py +++ b/backend/routes/subject.py @@ -1,12 +1,10 @@ from fastapi import APIRouter from starlette.requests import Request +from db.models.models import Project, Student, Subject, Teacher from domain.logic.project import create_project, get_projects_of_subject from domain.logic.subject import get_students_of_subject, get_subject, get_teachers_of_subject -from domain.models.ProjectDataclass import ProjectDataclass, ProjectInput -from domain.models.StudentDataclass import StudentDataclass -from domain.models.SubjectDataclass import SubjectDataclass -from domain.models.TeacherDataclass import TeacherDataclass +from domain.models.ProjectDataclass import ProjectInput from routes.dependencies.role_dependencies import ( ensure_teacher_authorized_for_subject, ensure_user_authorized_for_subject, @@ -18,35 +16,35 @@ @subject_router.get("/subjects/{subject_id}", tags=[Tags.SUBJECT], summary="Get a certain subject.") -def subject_get(request: Request, subject_id: int) -> SubjectDataclass: +def subject_get(request: Request, subject_id: int) -> Subject: session = request.state.session get_authenticated_user(request) return get_subject(session, subject_id) @subject_router.get("/subjects/{subject_id}/projects", tags=[Tags.SUBJECT], summary="Get all projects of subject.") -def get_subject_projects(request: Request, subject_id: int) -> list[ProjectDataclass]: +def get_subject_projects(request: Request, subject_id: int) -> list[Project]: session = request.state.session ensure_user_authorized_for_subject(request, subject_id) return get_projects_of_subject(session, subject_id) @subject_router.get("/subjects/{subject_id}/teachers", tags=[Tags.SUBJECT], summary="Get all teachers of subject.") -def get_subject_teachers(request: Request, subject_id: int) -> list[TeacherDataclass]: +def get_subject_teachers(request: Request, subject_id: int) -> list[Teacher]: session = request.state.session ensure_user_authorized_for_subject(request, subject_id) return get_teachers_of_subject(session, subject_id) @subject_router.get("/subjects/{subject_id}/students", tags=[Tags.SUBJECT], summary="Get all students of subject.") -def get_subject_students(request: Request, subject_id: int) -> list[StudentDataclass]: +def get_subject_students(request: Request, subject_id: int) -> list[Student]: session = request.state.session ensure_user_authorized_for_subject(request, subject_id) return get_students_of_subject(session, subject_id) @subject_router.post("/subjects/{subject_id}/projects", tags=[Tags.PROJECT], summary="Create project in a course.") -def new_project(request: Request, subject_id: int, project: ProjectInput) -> ProjectDataclass: +def new_project(request: Request, subject_id: int, project: ProjectInput) -> Project: session = request.state.session ensure_teacher_authorized_for_subject(request, subject_id) return create_project( diff --git a/backend/routes/submission.py b/backend/routes/submission.py index c71d3cd6..12601fdc 100644 --- a/backend/routes/submission.py +++ b/backend/routes/submission.py @@ -5,8 +5,9 @@ from fastapi import APIRouter, File, Response from starlette.requests import Request +from db.models.models import Submission from domain.logic.submission import create_submission, get_last_submission -from domain.models.SubmissionDataclass import SubmissionDataclass, SubmissionState +from domain.models.SubmissionDataclass import SubmissionState from routes.dependencies.role_dependencies import ensure_student_in_group, ensure_user_authorized_for_submission from routes.tags.swagger_tags import Tags @@ -14,7 +15,7 @@ @submission_router.post("/groups/{group_id}/submission", tags=[Tags.SUBMISSION], summary="Make a submission.") -def make_submission(request: Request, group_id: int, file: Annotated[bytes, File()]) -> SubmissionDataclass: +def make_submission(request: Request, group_id: int, file: Annotated[bytes, File()]) -> Submission: session = request.state.session student = ensure_student_in_group(request, group_id) @@ -34,7 +35,7 @@ def make_submission(request: Request, group_id: int, file: Annotated[bytes, File @submission_router.get("/groups/{group_id}/submission", tags=[Tags.SUBMISSION], summary="Get latest submission.") -def retrieve_submission(request: Request, group_id: int) -> SubmissionDataclass: +def retrieve_submission(request: Request, group_id: int) -> Submission: session = request.state.session ensure_user_authorized_for_submission(request, group_id) return get_last_submission(session, group_id) diff --git a/backend/routes/teacher.py b/backend/routes/teacher.py index 76f919bd..e933fa25 100644 --- a/backend/routes/teacher.py +++ b/backend/routes/teacher.py @@ -1,10 +1,10 @@ from fastapi import APIRouter from starlette.requests import Request +from db.models.models import Project, Subject from domain.logic.project import get_projects_of_teacher from domain.logic.subject import add_teacher_to_subject, create_subject, get_subjects_of_teacher -from domain.models.ProjectDataclass import ProjectDataclass -from domain.models.SubjectDataclass import SubjectDataclass, SubjectInput +from domain.models.SubjectDataclass import SubjectInput from routes.dependencies.role_dependencies import get_authenticated_teacher from routes.tags.swagger_tags import Tags @@ -12,21 +12,21 @@ @teacher_router.get("/teacher/subjects", tags=[Tags.TEACHER], summary="Get all subjects the teacher manages.") -def subjects_of_teacher_get(request: Request) -> list[SubjectDataclass]: +def subjects_of_teacher_get(request: Request) -> list[Subject]: session = request.state.session teacher = get_authenticated_teacher(request) return get_subjects_of_teacher(session, teacher.id) @teacher_router.get("/teacher/projects", tags=[Tags.TEACHER], summary="Get all projects of the teacher.") -def projects_of_teacher_get(request: Request) -> list[ProjectDataclass]: +def projects_of_teacher_get(request: Request) -> list[Project]: session = request.state.session teacher = get_authenticated_teacher(request) return get_projects_of_teacher(session, teacher.id) @teacher_router.post("/teacher/subjects", tags=[Tags.SUBJECT], summary="Create a new subject.") -def create_subject_post(request: Request, subject: SubjectInput) -> SubjectDataclass: +def create_subject_post(request: Request, subject: SubjectInput) -> Subject: session = request.state.session teacher = get_authenticated_teacher(request) diff --git a/backend/routes/user.py b/backend/routes/user.py index c5046364..b8c54728 100644 --- a/backend/routes/user.py +++ b/backend/routes/user.py @@ -6,7 +6,6 @@ from domain.logic.role_enum import Role from domain.logic.user import convert_user, get_user, modify_language, 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 from routes.tags.swagger_tags import Tags @@ -34,7 +33,7 @@ def get_users(request: Request) -> list[APIUser]: session = request.state.session get_authenticated_admin(request) - users: list[UserDataclass] = [user.to_domain_model() for user in get_all(session, User)] + users: list[User] = [user.to_domain_model() for user in get_all(session, User)] return [convert_user(session, user) for user in users] @@ -43,7 +42,7 @@ def admin_get_user(request: Request, uid: int) -> APIUser: session = request.state.session get_authenticated_admin(request) - user: UserDataclass = get(session, User, uid).to_domain_model() + user: User = get(session, User, uid).to_domain_model() return convert_user(session, user) From 37c7f2284061b3dbe37dc98500297023d659e57e Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Sat, 6 Apr 2024 21:58:59 +0200 Subject: [PATCH 48/90] migrate tests to sqlmodel #123 --- backend/tests/test_edge_cases.py | 10 +++++----- backend/tests/test_main.py | 16 ++++++++++++---- backend/tests/test_project.py | 10 +++++----- backend/tests/test_stress.py | 10 +++++----- backend/tests/test_student.py | 10 +++++----- backend/tests/test_subject.py | 10 +++++----- backend/tests/test_submission.py | 10 +++++----- backend/tests/test_teacher.py | 10 +++++----- 8 files changed, 47 insertions(+), 39 deletions(-) diff --git a/backend/tests/test_edge_cases.py b/backend/tests/test_edge_cases.py index 16cd8a3f..1d833672 100644 --- a/backend/tests/test_edge_cases.py +++ b/backend/tests/test_edge_cases.py @@ -2,19 +2,19 @@ import unittest from datetime import datetime -from test_main import SessionLocal, test_engine +from sqlmodel import SQLModel +from test_main import get_db, test_engine from db.errors.database_errors import ItemNotFoundError -from db.extensions import Base from domain.logic import group, student, submission from domain.models.SubmissionDataclass import SubmissionState class TestEdgeCases(unittest.TestCase): def setUp(self) -> None: - Base.metadata.drop_all(test_engine) - Base.metadata.create_all(test_engine) - self.session = SessionLocal() + SQLModel.metadata.drop_all(test_engine) + SQLModel.metadata.create_all(test_engine) + self.session = next(get_db()) def tearDown(self) -> None: self.session.rollback() diff --git a/backend/tests/test_main.py b/backend/tests/test_main.py index bd7c2d68..1676d591 100644 --- a/backend/tests/test_main.py +++ b/backend/tests/test_main.py @@ -1,8 +1,16 @@ import os +from collections.abc import Generator -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker +from sqlmodel import Session, create_engine -DB_TEST_URI = f"postgresql://{os.getenv("TEST_DB_USER", "postgres")}:postgres@localhost:5432/delphi-test" +# Construct the database connection string using environment variables +DB_TEST_URI = f"postgresql://{os.getenv('TEST_DB_USER', 'postgres')}:postgres@localhost:5432/delphi-test" + +# Create an engine using SQLModel's create_engine test_engine = create_engine(DB_TEST_URI) -SessionLocal = sessionmaker(autocommit=False, bind=test_engine) + + +# Function to get a new database session +def get_db() -> Generator[Session, None, None]: + with Session(test_engine) as session: + yield session diff --git a/backend/tests/test_project.py b/backend/tests/test_project.py index f33af391..2429e254 100644 --- a/backend/tests/test_project.py +++ b/backend/tests/test_project.py @@ -2,9 +2,9 @@ import unittest from datetime import datetime -from test_main import SessionLocal, test_engine +from sqlmodel import SQLModel +from test_main import get_db, test_engine -from db.extensions import Base from domain.logic.project import ( create_project, get_all_projects, @@ -20,9 +20,9 @@ class TestProject(unittest.TestCase): def setUp(self) -> None: - Base.metadata.drop_all(test_engine) - Base.metadata.create_all(test_engine) - self.session = SessionLocal() + SQLModel.metadata.drop_all(test_engine) + SQLModel.metadata.create_all(test_engine) + self.session = next(get_db()) def tearDown(self) -> None: self.session.rollback() diff --git a/backend/tests/test_stress.py b/backend/tests/test_stress.py index ca988df5..b80a5d48 100644 --- a/backend/tests/test_stress.py +++ b/backend/tests/test_stress.py @@ -2,18 +2,18 @@ import unittest from datetime import datetime -from test_main import SessionLocal, test_engine +from sqlmodel import SQLModel +from test_main import get_db, test_engine -from db.extensions import Base from domain.logic import admin, group, project, student, subject, submission, teacher from domain.models.SubmissionDataclass import SubmissionState class TestStress(unittest.TestCase): def setUp(self) -> None: - Base.metadata.drop_all(test_engine) - Base.metadata.create_all(test_engine) - self.session = SessionLocal() + SQLModel.metadata.drop_all(test_engine) + SQLModel.metadata.create_all(test_engine) + self.session = next(get_db()) def tearDown(self) -> None: self.session.rollback() diff --git a/backend/tests/test_student.py b/backend/tests/test_student.py index 95e8e680..49c4566e 100644 --- a/backend/tests/test_student.py +++ b/backend/tests/test_student.py @@ -1,18 +1,18 @@ # test_student.py import unittest -from test_main import SessionLocal, test_engine +from sqlmodel import SQLModel +from test_main import get_db, test_engine -from db.extensions import Base from domain.logic.student import create_student, get_all_students, get_student from domain.logic.subject import add_student_to_subject, create_subject, get_subjects_of_student class TestStudent(unittest.TestCase): def setUp(self) -> None: - Base.metadata.drop_all(test_engine) - Base.metadata.create_all(test_engine) - self.session = SessionLocal() + SQLModel.metadata.drop_all(test_engine) + SQLModel.metadata.create_all(test_engine) + self.session = next(get_db()) def tearDown(self) -> None: self.session.rollback() diff --git a/backend/tests/test_subject.py b/backend/tests/test_subject.py index d675053f..84c4c23b 100644 --- a/backend/tests/test_subject.py +++ b/backend/tests/test_subject.py @@ -1,9 +1,9 @@ # test_subject.py import unittest -from test_main import SessionLocal, test_engine +from sqlmodel import SQLModel +from test_main import get_db, test_engine -from db.extensions import Base from domain.logic.student import create_student from domain.logic.subject import ( add_student_to_subject, @@ -19,9 +19,9 @@ class TestSubject(unittest.TestCase): def setUp(self) -> None: - Base.metadata.drop_all(test_engine) - Base.metadata.create_all(test_engine) - self.session = SessionLocal() + SQLModel.metadata.drop_all(test_engine) + SQLModel.metadata.create_all(test_engine) + self.session = next(get_db()) def tearDown(self) -> None: self.session.rollback() diff --git a/backend/tests/test_submission.py b/backend/tests/test_submission.py index f4dff3b0..93e82450 100644 --- a/backend/tests/test_submission.py +++ b/backend/tests/test_submission.py @@ -2,9 +2,9 @@ import unittest from datetime import datetime -from test_main import SessionLocal, test_engine +from sqlmodel import SQLModel +from test_main import get_db, test_engine -from db.extensions import Base from domain.logic.group import create_group from domain.logic.project import create_project from domain.logic.student import create_student @@ -21,9 +21,9 @@ class TestSubmission(unittest.TestCase): def setUp(self) -> None: - Base.metadata.drop_all(test_engine) - Base.metadata.create_all(test_engine) - self.session = SessionLocal() + SQLModel.metadata.drop_all(test_engine) + SQLModel.metadata.create_all(test_engine) + self.session = next(get_db()) def tearDown(self) -> None: self.session.rollback() diff --git a/backend/tests/test_teacher.py b/backend/tests/test_teacher.py index 88925e0f..da45dcbe 100644 --- a/backend/tests/test_teacher.py +++ b/backend/tests/test_teacher.py @@ -1,17 +1,17 @@ # test_teacher.py import unittest -from test_main import SessionLocal, test_engine +from sqlmodel import SQLModel +from test_main import get_db, test_engine -from db.extensions import Base from domain.logic.teacher import create_teacher, get_all_teachers, get_teacher class TestTeacher(unittest.TestCase): def setUp(self) -> None: - Base.metadata.drop_all(test_engine) - Base.metadata.create_all(test_engine) - self.session = SessionLocal() + SQLModel.metadata.drop_all(test_engine) + SQLModel.metadata.create_all(test_engine) + self.session = next(get_db()) def tearDown(self) -> None: self.session.rollback() From 5de8268fac941fce7205624a64ac0820a51df164 Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Sat, 6 Apr 2024 22:01:33 +0200 Subject: [PATCH 49/90] remove dataclasses #123 --- .../auth/authentication_controller.py | 12 ++++++------ backend/controllers/auth/token_controller.py | 4 ++-- backend/poetry.lock | 17 ++++++++++++++++- backend/pyproject.toml | 2 +- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/backend/controllers/auth/authentication_controller.py b/backend/controllers/auth/authentication_controller.py index a8bb2748..44c9f930 100644 --- a/backend/controllers/auth/authentication_controller.py +++ b/backend/controllers/auth/authentication_controller.py @@ -4,12 +4,12 @@ import httpx from defusedxml.ElementTree import fromstring -from sqlalchemy.orm import Session +from sqlmodel import Session +from db.models.models import User from domain.logic.student import create_student from domain.logic.teacher import create_teacher from domain.logic.user import get_user_with_email -from domain.models.UserDataclass import UserDataclass if TYPE_CHECKING: from _elementtree import Element @@ -17,7 +17,7 @@ cas_service = os.getenv("CAS_URL", "https://localhost:8080/login") -def authenticate_user(session: Session, ticket: str) -> UserDataclass | None: +def authenticate_user(session: Session, ticket: str) -> User | None: """ This function will authenticate the user. If the use doesn't yet exist in the database, it will create an entry. @@ -35,12 +35,12 @@ def authenticate_user(session: Session, ticket: str) -> UserDataclass | None: if user_dict is None: return None - user: UserDataclass | None = get_user_with_email(session, user_dict["email"]) + user: User | None = get_user_with_email(session, user_dict["email"]) if user is None: if user_dict["role"] == "student": - user = create_student(session, user_dict["name"], user_dict["email"]) + user = create_student(session, user_dict["name"], user_dict["email"]).user elif user_dict["role"] == "teacher": - user = create_teacher(session, user_dict["name"], user_dict["email"]) + user = create_teacher(session, user_dict["name"], user_dict["email"]).user return user diff --git a/backend/controllers/auth/token_controller.py b/backend/controllers/auth/token_controller.py index ceefcb37..f16d2a15 100644 --- a/backend/controllers/auth/token_controller.py +++ b/backend/controllers/auth/token_controller.py @@ -4,7 +4,7 @@ import jwt -from domain.models.UserDataclass import UserDataclass +from db.models.models import User # Zeker aanpassen in production jwt_secret = os.getenv("JWT_SECRET", "secret") @@ -16,7 +16,7 @@ def verify_token(token: str) -> int | None: return payload.get("uid", None) -def create_token(user: UserDataclass) -> str: +def create_token(user: User) -> str: expire = datetime.now(UTC) + timedelta(days=1) to_encode: dict = { "uid": user.id, diff --git a/backend/poetry.lock b/backend/poetry.lock index 43a2273c..5eb875bf 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -837,6 +837,21 @@ test-all = ["Babel (>=1.3)", "Jinja2 (>=2.3)", "Pygments (>=1.2)", "arrow (>=0.3 timezone = ["python-dateutil"] url = ["furl (>=0.4.1)"] +[[package]] +name = "sqlmodel" +version = "0.0.16" +description = "SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness." +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "sqlmodel-0.0.16-py3-none-any.whl", hash = "sha256:b972f5d319580d6c37ecc417881f6ec4d1ad3ed3583d0ac0ed43234a28bf605a"}, + {file = "sqlmodel-0.0.16.tar.gz", hash = "sha256:966656f18a8e9a2d159eb215b07fb0cf5222acfae3362707ca611848a8a06bd1"}, +] + +[package.dependencies] +pydantic = ">=1.10.13,<3.0.0" +SQLAlchemy = ">=2.0.0,<2.1.0" + [[package]] name = "starlette" version = "0.37.2" @@ -906,4 +921,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "b3da44b22aad280c124d99bb01b41e4b88db5c8ef94d972d8f71899ebe0c25fe" +content-hash = "75cd95d27fcdd15fc32f8842d0c3ef008e0ff436c86ebccb78db39198832d69b" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index fd712117..e74ffecf 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -16,7 +16,6 @@ authors = [ [tool.poetry.dependencies] python = "^3.12" uvicorn = "^0.29.0" -sqlalchemy = "^2.0.29" ruff = "^0.3.5" python-multipart = "^0.0.9" pyright = "^1.1.357" @@ -28,6 +27,7 @@ email-validator = "^2.1.1" httpx = "^0.27.0" defusedxml = "^0.7.1" sqlalchemy-utils = "0.41.2" +sqlmodel = "^0.0.16" [build-system] requires = ["poetry-core"] From 90c3d50f2061089f8a99b1e2d9f300558a99ae3d Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Sat, 6 Apr 2024 22:19:24 +0200 Subject: [PATCH 50/90] added roles to user #123 --- backend/db/models/models.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/backend/db/models/models.py b/backend/db/models/models.py index 612007ea..6434016b 100644 --- a/backend/db/models/models.py +++ b/backend/db/models/models.py @@ -3,6 +3,7 @@ from sqlmodel import Field, Relationship, Session, SQLModel from db.extensions import engine +from domain.logic.role_enum import Role from domain.models.SubmissionDataclass import SubmissionState @@ -16,6 +17,17 @@ class User(SQLModel, table=True): teacher: "Teacher" = Relationship(back_populates="user") student: "Student" = Relationship(back_populates="user") + @property + def roles(self) -> list[Role]: + _roles = [] + if self.admin: + _roles.append(Role.ADMIN) + if self.teacher: + _roles.append(Role.TEACHER) + if self.student: + _roles.append(Role.STUDENT) + return _roles + class Admin(SQLModel, table=True): id: int = Field(default=-1, foreign_key="user.id", primary_key=True) From eae6bd0d6be170fa725da7bf1b9fd323b6034b88 Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Sat, 6 Apr 2024 22:28:07 +0200 Subject: [PATCH 51/90] removed API user #123 --- backend/domain/logic/user.py | 45 ++++++++++++-------------------- backend/domain/models/APIUser.py | 16 +----------- backend/routes/user.py | 17 +++++------- 3 files changed, 24 insertions(+), 54 deletions(-) diff --git a/backend/domain/logic/user.py b/backend/domain/logic/user.py index ce7286e1..93ca844d 100644 --- a/backend/domain/logic/user.py +++ b/backend/domain/logic/user.py @@ -1,40 +1,18 @@ from sqlmodel import Session, select 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 -from domain.logic.student import is_user_student -from domain.logic.teacher import is_user_teacher -from domain.models.APIUser import APIUser - - -def convert_user(session: Session, user: User) -> APIUser: - """ - Given a User, check what roles that user has and fill those in to convert it to an APIUser. - """ - api_user = APIUser(id=user.id, name=user.name, email=user.email, language=user.language, roles=[]) - - if is_user_teacher(session, user.id): - api_user.roles.append(Role.TEACHER) - - if is_user_admin(session, user.id): - api_user.roles.append(Role.ADMIN) - - if is_user_student(session, user.id): - api_user.roles.append(Role.STUDENT) - - return api_user def get_user(session: Session, user_id: int) -> User: - return get(session, User, user_id).to_domain_model() + return get(session, User, user_id) def get_user_with_email(session: Session, email: str) -> User | None: stmt = select(User).where(User.email == email) result = session.execute(stmt) - users = [r.to_domain_model() for r in result.scalars()] + users = result.scalars().all() if len(users) > 1: raise NotImplementedError @@ -46,22 +24,31 @@ def get_user_with_email(session: Session, email: str) -> User | None: def get_all_users(session: Session) -> list[User]: - return [user.to_domain_model() for user in get_all(session, User)] + return 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: + user: User = get_user(session, uid) + + # Add Student Role + if Role.STUDENT in roles and Role.STUDENT not in user.roles: student = Student(id=uid) session.add(student) - if Role.TEACHER in roles and session.get(Teacher, uid) is None: + + # Add Teacher Role + if Role.TEACHER in roles and Role.TEACHER not in user.roles: teacher = Teacher(id=uid) session.add(teacher) - if Role.ADMIN in roles and session.get(Admin, uid) is None: + + # Add Admin Role + if Role.ADMIN in roles and Role.ADMIN not in user.roles: admin = Admin(id=uid) session.add(admin) - if Role.ADMIN not in roles and session.get(Admin, uid) is not None: + + # Remove Admin Role + if Role.ADMIN not in roles and Role.ADMIN in user.roles: session.delete(get(session, Admin, uid)) session.commit() diff --git a/backend/domain/models/APIUser.py b/backend/domain/models/APIUser.py index 8f7617a8..54f08d1b 100644 --- a/backend/domain/models/APIUser.py +++ b/backend/domain/models/APIUser.py @@ -1,18 +1,4 @@ -from pydantic import BaseModel, EmailStr - -from domain.logic.role_enum import Role - - -class APIUser(BaseModel): - """ - Same as UserDataclass, but with the roles specified in a list. - """ - - id: int - name: str - language: str - email: EmailStr - roles: list[Role] +from pydantic import BaseModel class LoginResponse(BaseModel): diff --git a/backend/routes/user.py b/backend/routes/user.py index b8c54728..8fc5452e 100644 --- a/backend/routes/user.py +++ b/backend/routes/user.py @@ -4,8 +4,7 @@ from db.models.models import User from domain.logic.basic_operations import get, get_all from domain.logic.role_enum import Role -from domain.logic.user import convert_user, get_user, modify_language, modify_user_roles -from domain.models.APIUser import APIUser +from domain.logic.user import get_user, modify_language, modify_user_roles from routes.dependencies.role_dependencies import get_authenticated_admin, get_authenticated_user from routes.tags.swagger_tags import Tags @@ -13,10 +12,10 @@ @users_router.get("/user", tags=[Tags.USER], summary="Get the current user (and its roles).") -def get_current_user(request: Request) -> APIUser: +def get_current_user(request: Request) -> User: session = request.state.session uid = get_authenticated_user(request) - return convert_user(session, get_user(session, uid)) + return get_user(session, uid) @users_router.patch("/user", tags=[Tags.USER], summary="Modify the language of the user.") @@ -29,21 +28,19 @@ def modify_current_user(request: Request, language: str) -> Response: @users_router.get("/users", tags=[Tags.USER], summary="Get all users.") -def get_users(request: Request) -> list[APIUser]: +def get_users(request: Request) -> list[User]: session = request.state.session get_authenticated_admin(request) - users: list[User] = [user.to_domain_model() for user in get_all(session, User)] - return [convert_user(session, user) for user in users] + return get_all(session, User) @users_router.get("/users/{uid}", tags=[Tags.USER], summary="Get a certain user.") -def admin_get_user(request: Request, uid: int) -> APIUser: +def admin_get_user(request: Request, uid: int) -> User: session = request.state.session get_authenticated_admin(request) - user: User = get(session, User, uid).to_domain_model() - return convert_user(session, user) + return get(session, User, uid) @users_router.patch("/users/{uid}", tags=[Tags.USER], summary="Modify the roles of a certain user.") From 3f7838030065dd3bb11f6b163e442237a2e55c2c Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Sat, 6 Apr 2024 23:50:23 +0200 Subject: [PATCH 52/90] change default ID of models #123 --- backend/db/models/models.py | 115 ++++-------------------------------- 1 file changed, 13 insertions(+), 102 deletions(-) diff --git a/backend/db/models/models.py b/backend/db/models/models.py index 6434016b..f0900747 100644 --- a/backend/db/models/models.py +++ b/backend/db/models/models.py @@ -1,9 +1,7 @@ from datetime import datetime -from sqlmodel import Field, Relationship, Session, SQLModel +from sqlmodel import Field, Relationship, SQLModel -from db.extensions import engine -from domain.logic.role_enum import Role from domain.models.SubmissionDataclass import SubmissionState @@ -11,26 +9,15 @@ class User(SQLModel, table=True): name: str email: str language: str = "EN" - id: int = Field(default=-1, primary_key=True) + id: int = Field(default=None, primary_key=True) admin: "Admin" = Relationship(back_populates="user") teacher: "Teacher" = Relationship(back_populates="user") student: "Student" = Relationship(back_populates="user") - @property - def roles(self) -> list[Role]: - _roles = [] - if self.admin: - _roles.append(Role.ADMIN) - if self.teacher: - _roles.append(Role.TEACHER) - if self.student: - _roles.append(Role.STUDENT) - return _roles - class Admin(SQLModel, table=True): - id: int = Field(default=-1, foreign_key="user.id", primary_key=True) + id: int = Field(default=None, foreign_key="user.id", primary_key=True) user: User = Relationship(back_populates="admin") @@ -50,13 +37,13 @@ class StudentGroup(SQLModel, table=True): class Teacher(SQLModel, table=True): - id: int = Field(default=-1, foreign_key="user.id", primary_key=True) + id: int = Field(default=None, foreign_key="user.id", primary_key=True) user: User = Relationship(back_populates="teacher") subjects: list["Subject"] = Relationship(link_model=TeacherSubject, back_populates="teachers") class Student(SQLModel, table=True): - id: int = Field(default=-1, foreign_key="user.id", primary_key=True) + id: int = Field(default=None, foreign_key="user.id", primary_key=True) user: User = Relationship(back_populates="student") subjects: list["Subject"] = Relationship(link_model=StudentSubject, back_populates="students") groups: list["Group"] = Relationship(link_model=StudentGroup, back_populates="students") @@ -65,7 +52,7 @@ class Student(SQLModel, table=True): class Subject(SQLModel, table=True): name: str - id: int = Field(default=-1, primary_key=True) + id: int = Field(default=None, primary_key=True) teachers: list[Teacher] = Relationship(link_model=TeacherSubject, back_populates="subjects") students: list[Student] = Relationship(link_model=StudentSubject, back_populates="subjects") projects: list["Project"] = Relationship(back_populates="subject") @@ -79,15 +66,15 @@ class Project(SQLModel, table=True): requirements: str visible: bool max_students: int - id: int = Field(default=-1, primary_key=True) - subject_id: int = Field(default=-1, foreign_key="subject.id") + id: int = Field(default=None, primary_key=True) + subject_id: int = Field(default=None, foreign_key="subject.id") subject: Subject = Relationship(back_populates="projects") groups: list["Group"] = Relationship(back_populates="project") class Group(SQLModel, table=True): - id: int = Field(default=-1, primary_key=True) - project_id: int = Field(default=-1, foreign_key="project.id") + id: int = Field(default=None, primary_key=True) + project_id: int = Field(default=None, foreign_key="project.id") project: Project = Relationship(back_populates="groups") students: list[Student] = Relationship(link_model=StudentGroup, back_populates="groups") submissions: list["Submission"] = Relationship(back_populates="group") @@ -98,84 +85,8 @@ class Submission(SQLModel, table=True): state: SubmissionState message: str filename: str - id: int = Field(default=-1, primary_key=True) - group_id: int = Field(default=-1, foreign_key="group.id") + id: int = Field(default=None, primary_key=True) + group_id: int = Field(default=None, foreign_key="group.id") group: Group = Relationship(back_populates="submissions") - student_id: int = Field(default=-1, foreign_key="student.id") + student_id: int = Field(default=None, foreign_key="student.id") student: Student = Relationship(back_populates="submissions") - - -if __name__ == "__main__": - # Initialize database engine; replace connection string as needed - - # Create tables - SQLModel.metadata.drop_all(engine) - SQLModel.metadata.create_all(engine) - - # Insert mock data - with Session(engine) as session: - - user1 = User(name="John Doe", email="john@example.com") - user2 = User(name="Jane Doe", email="jane@example.com") - - teacher1 = Teacher(user=user1) - student1 = Student(user=user2) - - subject1 = Subject(name="Math") - subject2 = Subject(name="English") - - project1 = Project( - name="Math Project", - deadline=datetime.now(), - archived=False, - description="Math project description", - requirements="Math project requirements", - visible=True, - max_students=2, - subject=subject1, - ) - project2 = Project( - name="English Project", - deadline=datetime.now(), - archived=False, - description="English project description", - requirements="English project requirements", - visible=True, - max_students=2, - subject=subject2, - ) - - group1 = Group(project=project1) - group2 = Group(project=project2) - - submission1 = Submission( - date_time=datetime.now(), - state=SubmissionState.Pending, - message="Submission message", - filename="submission.txt", - group=group1, - student=student1, - ) - submission2 = Submission( - date_time=datetime.now(), - state=SubmissionState.Pending, - message="Submission message", - filename="submission.txt", - group=group2, - student=student1, - ) - - session.add(user1) - session.add(user2) - session.add(teacher1) - session.add(student1) - session.add(subject1) - session.add(subject2) - session.add(project1) - session.add(project2) - session.add(group1) - session.add(group2) - session.add(submission1) - session.add(submission2) - - session.commit() From 40ba6a5e237aa3546af8d6f23a3d359db0a1dd57 Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Sun, 7 Apr 2024 13:31:56 +0200 Subject: [PATCH 53/90] changed dependencies folder to authentication folder #123 --- .../{dependencies => authentication}/role_dependencies.py | 0 backend/routes/group.py | 2 +- backend/routes/project.py | 2 +- backend/routes/student.py | 2 +- backend/routes/subject.py | 2 +- backend/routes/submission.py | 2 +- backend/routes/teacher.py | 2 +- backend/routes/user.py | 2 +- 8 files changed, 7 insertions(+), 7 deletions(-) rename backend/routes/{dependencies => authentication}/role_dependencies.py (100%) diff --git a/backend/routes/dependencies/role_dependencies.py b/backend/routes/authentication/role_dependencies.py similarity index 100% rename from backend/routes/dependencies/role_dependencies.py rename to backend/routes/authentication/role_dependencies.py diff --git a/backend/routes/group.py b/backend/routes/group.py index f393a39b..6f7e183c 100644 --- a/backend/routes/group.py +++ b/backend/routes/group.py @@ -8,7 +8,7 @@ get_students_of_group, remove_student_from_group, ) -from routes.dependencies.role_dependencies import ( +from routes.authentication.role_dependencies import ( ensure_student_authorized_for_group, ensure_student_authorized_for_project, ensure_user_authorized_for_group, diff --git a/backend/routes/project.py b/backend/routes/project.py index 2966129e..8248b185 100644 --- a/backend/routes/project.py +++ b/backend/routes/project.py @@ -5,7 +5,7 @@ from domain.logic.group import create_group, get_groups_of_project from domain.logic.project import get_project, update_project from domain.models.ProjectDataclass import ProjectInput -from routes.dependencies.role_dependencies import ( +from routes.authentication.role_dependencies import ( ensure_teacher_authorized_for_project, ensure_user_authorized_for_project, ) diff --git a/backend/routes/student.py b/backend/routes/student.py index fd052576..cde0d28c 100644 --- a/backend/routes/student.py +++ b/backend/routes/student.py @@ -4,7 +4,7 @@ from db.models.models import Project, Subject from domain.logic.project import get_projects_of_student from domain.logic.subject import add_student_to_subject, get_subjects_of_student -from routes.dependencies.role_dependencies import get_authenticated_student +from routes.authentication.role_dependencies import get_authenticated_student from routes.tags.swagger_tags import Tags student_router = APIRouter() diff --git a/backend/routes/subject.py b/backend/routes/subject.py index dec18c0d..341eed60 100644 --- a/backend/routes/subject.py +++ b/backend/routes/subject.py @@ -5,7 +5,7 @@ from domain.logic.project import create_project, get_projects_of_subject from domain.logic.subject import get_students_of_subject, get_subject, get_teachers_of_subject from domain.models.ProjectDataclass import ProjectInput -from routes.dependencies.role_dependencies import ( +from routes.authentication.role_dependencies import ( ensure_teacher_authorized_for_subject, ensure_user_authorized_for_subject, get_authenticated_user, diff --git a/backend/routes/submission.py b/backend/routes/submission.py index 12601fdc..1a6b002b 100644 --- a/backend/routes/submission.py +++ b/backend/routes/submission.py @@ -8,7 +8,7 @@ from db.models.models import Submission from domain.logic.submission import create_submission, get_last_submission from domain.models.SubmissionDataclass import SubmissionState -from routes.dependencies.role_dependencies import ensure_student_in_group, ensure_user_authorized_for_submission +from routes.authentication.role_dependencies import ensure_student_in_group, ensure_user_authorized_for_submission from routes.tags.swagger_tags import Tags submission_router = APIRouter() diff --git a/backend/routes/teacher.py b/backend/routes/teacher.py index e933fa25..a3541040 100644 --- a/backend/routes/teacher.py +++ b/backend/routes/teacher.py @@ -5,7 +5,7 @@ from domain.logic.project import get_projects_of_teacher from domain.logic.subject import add_teacher_to_subject, create_subject, get_subjects_of_teacher from domain.models.SubjectDataclass import SubjectInput -from routes.dependencies.role_dependencies import get_authenticated_teacher +from routes.authentication.role_dependencies import get_authenticated_teacher from routes.tags.swagger_tags import Tags teacher_router = APIRouter() diff --git a/backend/routes/user.py b/backend/routes/user.py index 8fc5452e..dd13c197 100644 --- a/backend/routes/user.py +++ b/backend/routes/user.py @@ -5,7 +5,7 @@ from domain.logic.basic_operations import get, get_all from domain.logic.role_enum import Role from domain.logic.user import get_user, modify_language, modify_user_roles -from routes.dependencies.role_dependencies import get_authenticated_admin, get_authenticated_user +from routes.authentication.role_dependencies import get_authenticated_admin, get_authenticated_user from routes.tags.swagger_tags import Tags users_router = APIRouter() From 4fb0b7bbe0fffc62753d16cad2cf2df1d853eeb3 Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Sun, 7 Apr 2024 13:33:21 +0200 Subject: [PATCH 54/90] Moved authentication files from controller folder to authentication folder #123 --- .../authentication}/authentication_controller.py | 0 backend/routes/authentication/role_dependencies.py | 2 +- .../auth => routes/authentication}/token_controller.py | 0 backend/routes/login.py | 4 ++-- 4 files changed, 3 insertions(+), 3 deletions(-) rename backend/{controllers/auth => routes/authentication}/authentication_controller.py (100%) rename backend/{controllers/auth => routes/authentication}/token_controller.py (100%) diff --git a/backend/controllers/auth/authentication_controller.py b/backend/routes/authentication/authentication_controller.py similarity index 100% rename from backend/controllers/auth/authentication_controller.py rename to backend/routes/authentication/authentication_controller.py diff --git a/backend/routes/authentication/role_dependencies.py b/backend/routes/authentication/role_dependencies.py index 264971b8..ddbe05ae 100644 --- a/backend/routes/authentication/role_dependencies.py +++ b/backend/routes/authentication/role_dependencies.py @@ -1,6 +1,5 @@ from starlette.requests import Request -from controllers.auth.token_controller import verify_token from db.models.models import Admin, Student, Teacher from db.sessions import get_session from domain.logic.admin import get_admin, is_user_admin @@ -9,6 +8,7 @@ from domain.logic.student import get_student, is_user_student from domain.logic.subject import get_subjects_of_student, get_subjects_of_teacher, is_user_authorized_for_subject from domain.logic.teacher import get_teacher, is_user_teacher +from routes.authentication.token_controller import verify_token from routes.errors.authentication import ( InvalidAdminCredentialsError, InvalidAuthenticationError, diff --git a/backend/controllers/auth/token_controller.py b/backend/routes/authentication/token_controller.py similarity index 100% rename from backend/controllers/auth/token_controller.py rename to backend/routes/authentication/token_controller.py diff --git a/backend/routes/login.py b/backend/routes/login.py index 6054c850..ed6eb20d 100644 --- a/backend/routes/login.py +++ b/backend/routes/login.py @@ -1,10 +1,10 @@ from fastapi import APIRouter from starlette.requests import Request -from controllers.auth.authentication_controller import authenticate_user -from controllers.auth.token_controller import create_token, verify_token from db.models.models import User from domain.models.APIUser import LoginResponse, ValidateResponse +from routes.authentication.authentication_controller import authenticate_user +from routes.authentication.token_controller import create_token, verify_token from routes.errors.authentication import InvalidAuthenticationError from routes.tags.swagger_tags import Tags From a7599431e5af3b9ae702b5ad7eeb64a492ff62f9 Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Sun, 7 Apr 2024 13:34:47 +0200 Subject: [PATCH 55/90] Authentication errors to authentication folder #123 --- backend/app.py | 2 +- .../{errors/authentication.py => authentication/errors.py} | 0 backend/routes/authentication/role_dependencies.py | 4 ++-- backend/routes/login.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename backend/routes/{errors/authentication.py => authentication/errors.py} (100%) diff --git a/backend/app.py b/backend/app.py index a1799184..9d85685d 100644 --- a/backend/app.py +++ b/backend/app.py @@ -13,7 +13,7 @@ ItemNotFoundError, NoSuchRelationError, ) -from routes.errors.authentication import ( +from routes.authentication.errors import ( InvalidAuthenticationError, InvalidRoleCredentialsError, NoAccessToDataError, diff --git a/backend/routes/errors/authentication.py b/backend/routes/authentication/errors.py similarity index 100% rename from backend/routes/errors/authentication.py rename to backend/routes/authentication/errors.py diff --git a/backend/routes/authentication/role_dependencies.py b/backend/routes/authentication/role_dependencies.py index ddbe05ae..4c47ca67 100644 --- a/backend/routes/authentication/role_dependencies.py +++ b/backend/routes/authentication/role_dependencies.py @@ -8,14 +8,14 @@ from domain.logic.student import get_student, is_user_student from domain.logic.subject import get_subjects_of_student, get_subjects_of_teacher, is_user_authorized_for_subject from domain.logic.teacher import get_teacher, is_user_teacher -from routes.authentication.token_controller import verify_token -from routes.errors.authentication import ( +from routes.authentication.errors import ( InvalidAdminCredentialsError, InvalidAuthenticationError, InvalidStudentCredentialsError, InvalidTeacherCredentialsError, NoAccessToDataError, ) +from routes.authentication.token_controller import verify_token def get_authenticated_user(request: Request) -> int: diff --git a/backend/routes/login.py b/backend/routes/login.py index ed6eb20d..e6d9831f 100644 --- a/backend/routes/login.py +++ b/backend/routes/login.py @@ -4,8 +4,8 @@ from db.models.models import User from domain.models.APIUser import LoginResponse, ValidateResponse from routes.authentication.authentication_controller import authenticate_user +from routes.authentication.errors import InvalidAuthenticationError from routes.authentication.token_controller import create_token, verify_token -from routes.errors.authentication import InvalidAuthenticationError from routes.tags.swagger_tags import Tags # test url: https://login.ugent.be/login?service=https://localhost:8080/api/login From 50bac13f0c4dc733ca2ab79a469a37bffd5a3f8e Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Sun, 7 Apr 2024 13:39:53 +0200 Subject: [PATCH 56/90] removed folders with single files from db folder #123 --- backend/app.py | 2 +- backend/db/{errors => }/database_errors.py | 0 backend/db/{models => }/models.py | 0 backend/domain/logic/admin.py | 2 +- backend/domain/logic/basic_operations.py | 2 +- backend/domain/logic/group.py | 4 ++-- backend/domain/logic/project.py | 2 +- backend/domain/logic/student.py | 2 +- backend/domain/logic/subject.py | 4 ++-- backend/domain/logic/submission.py | 2 +- backend/domain/logic/teacher.py | 2 +- backend/domain/logic/user.py | 2 +- backend/routes/authentication/authentication_controller.py | 2 +- backend/routes/authentication/role_dependencies.py | 2 +- backend/routes/authentication/token_controller.py | 2 +- backend/routes/group.py | 2 +- backend/routes/login.py | 2 +- backend/routes/project.py | 2 +- backend/routes/student.py | 2 +- backend/routes/subject.py | 2 +- backend/routes/submission.py | 2 +- backend/routes/teacher.py | 2 +- backend/routes/user.py | 2 +- backend/tests/test_edge_cases.py | 2 +- 24 files changed, 24 insertions(+), 24 deletions(-) rename backend/db/{errors => }/database_errors.py (100%) rename backend/db/{models => }/models.py (100%) diff --git a/backend/app.py b/backend/app.py index 9d85685d..61cd72b2 100644 --- a/backend/app.py +++ b/backend/app.py @@ -7,7 +7,7 @@ from starlette.requests import Request from starlette.responses import JSONResponse -from db.errors.database_errors import ( +from db.database_errors import ( ActionAlreadyPerformedError, ConflictingRelationError, ItemNotFoundError, diff --git a/backend/db/errors/database_errors.py b/backend/db/database_errors.py similarity index 100% rename from backend/db/errors/database_errors.py rename to backend/db/database_errors.py diff --git a/backend/db/models/models.py b/backend/db/models.py similarity index 100% rename from backend/db/models/models.py rename to backend/db/models.py diff --git a/backend/domain/logic/admin.py b/backend/domain/logic/admin.py index 9c95b01a..e705e52b 100644 --- a/backend/domain/logic/admin.py +++ b/backend/domain/logic/admin.py @@ -1,6 +1,6 @@ from sqlmodel import Session -from db.models.models import Admin, User +from db.models import Admin, User from domain.logic.basic_operations import get, get_all diff --git a/backend/domain/logic/basic_operations.py b/backend/domain/logic/basic_operations.py index 848309f0..71eb5074 100644 --- a/backend/domain/logic/basic_operations.py +++ b/backend/domain/logic/basic_operations.py @@ -2,7 +2,7 @@ from sqlmodel import Session, SQLModel, select -from db.errors.database_errors import ItemNotFoundError +from db.database_errors import ItemNotFoundError # Create a generic type variable bound to subclasses of AbstractModel. T = TypeVar("T", bound=SQLModel) diff --git a/backend/domain/logic/group.py b/backend/domain/logic/group.py index 5839a03d..48493440 100644 --- a/backend/domain/logic/group.py +++ b/backend/domain/logic/group.py @@ -1,7 +1,7 @@ from sqlmodel import Session -from db.errors.database_errors import ActionAlreadyPerformedError, NoSuchRelationError -from db.models.models import Group, Project, Student +from db.database_errors import ActionAlreadyPerformedError, NoSuchRelationError +from db.models import Group, Project, Student from domain.logic.basic_operations import get, get_all diff --git a/backend/domain/logic/project.py b/backend/domain/logic/project.py index c469ce7f..fe5daa48 100644 --- a/backend/domain/logic/project.py +++ b/backend/domain/logic/project.py @@ -2,7 +2,7 @@ from sqlmodel import Session -from db.models.models import Project, Student, Subject, Teacher +from db.models import Project, Student, Subject, Teacher from domain.logic.basic_operations import get, get_all from domain.models.ProjectDataclass import ProjectInput diff --git a/backend/domain/logic/student.py b/backend/domain/logic/student.py index cae7f769..c8a652dd 100644 --- a/backend/domain/logic/student.py +++ b/backend/domain/logic/student.py @@ -1,6 +1,6 @@ from sqlmodel import Session -from db.models.models import Student, User +from db.models import Student, User from domain.logic.basic_operations import get, get_all diff --git a/backend/domain/logic/subject.py b/backend/domain/logic/subject.py index a3fc47d2..b30c497c 100644 --- a/backend/domain/logic/subject.py +++ b/backend/domain/logic/subject.py @@ -1,7 +1,7 @@ from sqlmodel import Session -from db.errors.database_errors import ActionAlreadyPerformedError -from db.models.models import Student, Subject, Teacher +from db.database_errors import ActionAlreadyPerformedError +from db.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 diff --git a/backend/domain/logic/submission.py b/backend/domain/logic/submission.py index 524c9c14..66a67a8f 100644 --- a/backend/domain/logic/submission.py +++ b/backend/domain/logic/submission.py @@ -2,7 +2,7 @@ from sqlmodel import Session -from db.models.models import Group, Student, Submission +from db.models import Group, Student, Submission from domain.logic.basic_operations import get, get_all from domain.models.SubmissionDataclass import SubmissionState diff --git a/backend/domain/logic/teacher.py b/backend/domain/logic/teacher.py index acfbedd0..d6ffe85f 100644 --- a/backend/domain/logic/teacher.py +++ b/backend/domain/logic/teacher.py @@ -1,6 +1,6 @@ from sqlmodel import Session -from db.models.models import Teacher, User +from db.models import Teacher, User from domain.logic.basic_operations import get, get_all diff --git a/backend/domain/logic/user.py b/backend/domain/logic/user.py index 93ca844d..0d0045fd 100644 --- a/backend/domain/logic/user.py +++ b/backend/domain/logic/user.py @@ -1,6 +1,6 @@ from sqlmodel import Session, select -from db.models.models import Admin, Student, Teacher, User +from db.models import Admin, Student, Teacher, User from domain.logic.basic_operations import get, get_all from domain.logic.role_enum import Role diff --git a/backend/routes/authentication/authentication_controller.py b/backend/routes/authentication/authentication_controller.py index 44c9f930..67538871 100644 --- a/backend/routes/authentication/authentication_controller.py +++ b/backend/routes/authentication/authentication_controller.py @@ -6,7 +6,7 @@ from defusedxml.ElementTree import fromstring from sqlmodel import Session -from db.models.models import User +from db.models import User from domain.logic.student import create_student from domain.logic.teacher import create_teacher from domain.logic.user import get_user_with_email diff --git a/backend/routes/authentication/role_dependencies.py b/backend/routes/authentication/role_dependencies.py index 4c47ca67..cae53607 100644 --- a/backend/routes/authentication/role_dependencies.py +++ b/backend/routes/authentication/role_dependencies.py @@ -1,6 +1,6 @@ from starlette.requests import Request -from db.models.models import Admin, Student, Teacher +from db.models import Admin, Student, Teacher from db.sessions import get_session from domain.logic.admin import get_admin, is_user_admin from domain.logic.group import get_group, get_students_of_group diff --git a/backend/routes/authentication/token_controller.py b/backend/routes/authentication/token_controller.py index f16d2a15..b00fd325 100644 --- a/backend/routes/authentication/token_controller.py +++ b/backend/routes/authentication/token_controller.py @@ -4,7 +4,7 @@ import jwt -from db.models.models import User +from db.models import User # Zeker aanpassen in production jwt_secret = os.getenv("JWT_SECRET", "secret") diff --git a/backend/routes/group.py b/backend/routes/group.py index 6f7e183c..942f6dda 100644 --- a/backend/routes/group.py +++ b/backend/routes/group.py @@ -1,7 +1,7 @@ from fastapi import APIRouter from starlette.requests import Request -from db.models.models import Group, Student +from db.models import Group, Student from domain.logic.group import ( add_student_to_group, get_group_for_student_and_project, diff --git a/backend/routes/login.py b/backend/routes/login.py index e6d9831f..bd1d732c 100644 --- a/backend/routes/login.py +++ b/backend/routes/login.py @@ -1,7 +1,7 @@ from fastapi import APIRouter from starlette.requests import Request -from db.models.models import User +from db.models import User from domain.models.APIUser import LoginResponse, ValidateResponse from routes.authentication.authentication_controller import authenticate_user from routes.authentication.errors import InvalidAuthenticationError diff --git a/backend/routes/project.py b/backend/routes/project.py index 8248b185..9df02383 100644 --- a/backend/routes/project.py +++ b/backend/routes/project.py @@ -1,7 +1,7 @@ from fastapi import APIRouter from starlette.requests import Request -from db.models.models import Group, Project +from db.models import Group, Project from domain.logic.group import create_group, get_groups_of_project from domain.logic.project import get_project, update_project from domain.models.ProjectDataclass import ProjectInput diff --git a/backend/routes/student.py b/backend/routes/student.py index cde0d28c..a8fbc067 100644 --- a/backend/routes/student.py +++ b/backend/routes/student.py @@ -1,7 +1,7 @@ from fastapi import APIRouter from starlette.requests import Request -from db.models.models import Project, Subject +from db.models import Project, Subject from domain.logic.project import get_projects_of_student from domain.logic.subject import add_student_to_subject, get_subjects_of_student from routes.authentication.role_dependencies import get_authenticated_student diff --git a/backend/routes/subject.py b/backend/routes/subject.py index 341eed60..fa6c42e7 100644 --- a/backend/routes/subject.py +++ b/backend/routes/subject.py @@ -1,7 +1,7 @@ from fastapi import APIRouter from starlette.requests import Request -from db.models.models import Project, Student, Subject, Teacher +from db.models import Project, Student, Subject, Teacher from domain.logic.project import create_project, get_projects_of_subject from domain.logic.subject import get_students_of_subject, get_subject, get_teachers_of_subject from domain.models.ProjectDataclass import ProjectInput diff --git a/backend/routes/submission.py b/backend/routes/submission.py index 1a6b002b..503b8a8a 100644 --- a/backend/routes/submission.py +++ b/backend/routes/submission.py @@ -5,7 +5,7 @@ from fastapi import APIRouter, File, Response from starlette.requests import Request -from db.models.models import Submission +from db.models import Submission from domain.logic.submission import create_submission, get_last_submission from domain.models.SubmissionDataclass import SubmissionState from routes.authentication.role_dependencies import ensure_student_in_group, ensure_user_authorized_for_submission diff --git a/backend/routes/teacher.py b/backend/routes/teacher.py index a3541040..09108abd 100644 --- a/backend/routes/teacher.py +++ b/backend/routes/teacher.py @@ -1,7 +1,7 @@ from fastapi import APIRouter from starlette.requests import Request -from db.models.models import Project, Subject +from db.models import Project, Subject from domain.logic.project import get_projects_of_teacher from domain.logic.subject import add_teacher_to_subject, create_subject, get_subjects_of_teacher from domain.models.SubjectDataclass import SubjectInput diff --git a/backend/routes/user.py b/backend/routes/user.py index dd13c197..e672960b 100644 --- a/backend/routes/user.py +++ b/backend/routes/user.py @@ -1,7 +1,7 @@ from fastapi import APIRouter, Response, status from starlette.requests import Request -from db.models.models import User +from db.models import User from domain.logic.basic_operations import get, get_all from domain.logic.role_enum import Role from domain.logic.user import get_user, modify_language, modify_user_roles diff --git a/backend/tests/test_edge_cases.py b/backend/tests/test_edge_cases.py index 1d833672..609f9093 100644 --- a/backend/tests/test_edge_cases.py +++ b/backend/tests/test_edge_cases.py @@ -5,7 +5,7 @@ from sqlmodel import SQLModel from test_main import get_db, test_engine -from db.errors.database_errors import ItemNotFoundError +from db.database_errors import ItemNotFoundError from domain.logic import group, student, submission from domain.models.SubmissionDataclass import SubmissionState From ea573b364801078191d7557f07a48474031307d6 Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Sun, 7 Apr 2024 13:52:23 +0200 Subject: [PATCH 57/90] move submission state #123 --- backend/db/fill_database_mock.py | 2 +- backend/db/models.py | 9 +++++++-- backend/domain/logic/project.py | 2 +- backend/domain/logic/submission.py | 3 +-- backend/domain/models/APIUser.py | 9 --------- .../{ProjectDataclass.py => ProjectInput.py} | 0 .../{SubjectDataclass.py => SubjectInput.py} | 0 backend/domain/models/SubmissionDataclass.py | 7 ------- backend/routes/login.py | 14 ++++++++++---- backend/routes/project.py | 2 +- backend/routes/subject.py | 2 +- backend/routes/submission.py | 3 +-- backend/routes/teacher.py | 2 +- backend/tests/test_edge_cases.py | 2 +- backend/tests/test_stress.py | 2 +- backend/tests/test_submission.py | 2 +- 16 files changed, 27 insertions(+), 34 deletions(-) delete mode 100644 backend/domain/models/APIUser.py rename backend/domain/models/{ProjectDataclass.py => ProjectInput.py} (100%) rename backend/domain/models/{SubjectDataclass.py => SubjectInput.py} (100%) delete mode 100644 backend/domain/models/SubmissionDataclass.py diff --git a/backend/db/fill_database_mock.py b/backend/db/fill_database_mock.py index bbd0f94d..796d871c 100644 --- a/backend/db/fill_database_mock.py +++ b/backend/db/fill_database_mock.py @@ -5,6 +5,7 @@ from sqlmodel import Session, SQLModel from db.extensions import engine +from db.models import SubmissionState from domain.logic.admin import create_admin from domain.logic.group import add_student_to_group, create_group from domain.logic.project import create_project @@ -14,7 +15,6 @@ from domain.logic.submission import create_submission from domain.logic.teacher import create_teacher from domain.logic.user import modify_user_roles -from domain.models.SubmissionDataclass import SubmissionState if __name__ == "__main__": with Session(engine) as session: diff --git a/backend/db/models.py b/backend/db/models.py index f0900747..cb0d1ea2 100644 --- a/backend/db/models.py +++ b/backend/db/models.py @@ -1,9 +1,8 @@ +import enum from datetime import datetime from sqlmodel import Field, Relationship, SQLModel -from domain.models.SubmissionDataclass import SubmissionState - class User(SQLModel, table=True): name: str @@ -80,6 +79,12 @@ class Group(SQLModel, table=True): submissions: list["Submission"] = Relationship(back_populates="group") +class SubmissionState(enum.Enum): + Pending = 1 + Approved = 2 + Rejected = 3 + + class Submission(SQLModel, table=True): date_time: datetime state: SubmissionState diff --git a/backend/domain/logic/project.py b/backend/domain/logic/project.py index fe5daa48..be63834f 100644 --- a/backend/domain/logic/project.py +++ b/backend/domain/logic/project.py @@ -4,7 +4,7 @@ from db.models import Project, Student, Subject, Teacher from domain.logic.basic_operations import get, get_all -from domain.models.ProjectDataclass import ProjectInput +from domain.models.ProjectInput import ProjectInput def create_project( diff --git a/backend/domain/logic/submission.py b/backend/domain/logic/submission.py index 66a67a8f..b5e996ad 100644 --- a/backend/domain/logic/submission.py +++ b/backend/domain/logic/submission.py @@ -2,9 +2,8 @@ from sqlmodel import Session -from db.models import Group, Student, Submission +from db.models import Group, Student, Submission, SubmissionState from domain.logic.basic_operations import get, get_all -from domain.models.SubmissionDataclass import SubmissionState def create_submission( diff --git a/backend/domain/models/APIUser.py b/backend/domain/models/APIUser.py deleted file mode 100644 index 54f08d1b..00000000 --- a/backend/domain/models/APIUser.py +++ /dev/null @@ -1,9 +0,0 @@ -from pydantic import BaseModel - - -class LoginResponse(BaseModel): - token: str - - -class ValidateResponse(BaseModel): - valid: bool diff --git a/backend/domain/models/ProjectDataclass.py b/backend/domain/models/ProjectInput.py similarity index 100% rename from backend/domain/models/ProjectDataclass.py rename to backend/domain/models/ProjectInput.py diff --git a/backend/domain/models/SubjectDataclass.py b/backend/domain/models/SubjectInput.py similarity index 100% rename from backend/domain/models/SubjectDataclass.py rename to backend/domain/models/SubjectInput.py diff --git a/backend/domain/models/SubmissionDataclass.py b/backend/domain/models/SubmissionDataclass.py deleted file mode 100644 index cbc446f5..00000000 --- a/backend/domain/models/SubmissionDataclass.py +++ /dev/null @@ -1,7 +0,0 @@ -import enum - - -class SubmissionState(enum.Enum): - Pending = 1 - Approved = 2 - Rejected = 3 diff --git a/backend/routes/login.py b/backend/routes/login.py index bd1d732c..241d28a6 100644 --- a/backend/routes/login.py +++ b/backend/routes/login.py @@ -1,8 +1,8 @@ from fastapi import APIRouter +from pydantic import BaseModel from starlette.requests import Request from db.models import User -from domain.models.APIUser import LoginResponse, ValidateResponse from routes.authentication.authentication_controller import authenticate_user from routes.authentication.errors import InvalidAuthenticationError from routes.authentication.token_controller import create_token, verify_token @@ -12,10 +12,16 @@ login_router = APIRouter() +class LoginResponse(BaseModel): + token: str + + +class ValidateResponse(BaseModel): + valid: bool + + @login_router.post("/validate", tags=[Tags.LOGIN], summary="Validate a session token.") -def validate_token( - token: str, -) -> ValidateResponse: +def validate_token(token: str) -> ValidateResponse: uid = verify_token(token) if uid: return ValidateResponse(valid=True) diff --git a/backend/routes/project.py b/backend/routes/project.py index 9df02383..f2509e67 100644 --- a/backend/routes/project.py +++ b/backend/routes/project.py @@ -4,7 +4,7 @@ from db.models import Group, Project from domain.logic.group import create_group, get_groups_of_project from domain.logic.project import get_project, update_project -from domain.models.ProjectDataclass import ProjectInput +from domain.models.ProjectInput import ProjectInput from routes.authentication.role_dependencies import ( ensure_teacher_authorized_for_project, ensure_user_authorized_for_project, diff --git a/backend/routes/subject.py b/backend/routes/subject.py index fa6c42e7..c080f984 100644 --- a/backend/routes/subject.py +++ b/backend/routes/subject.py @@ -4,7 +4,7 @@ from db.models import Project, Student, Subject, Teacher from domain.logic.project import create_project, get_projects_of_subject from domain.logic.subject import get_students_of_subject, get_subject, get_teachers_of_subject -from domain.models.ProjectDataclass import ProjectInput +from domain.models.ProjectInput import ProjectInput from routes.authentication.role_dependencies import ( ensure_teacher_authorized_for_subject, ensure_user_authorized_for_subject, diff --git a/backend/routes/submission.py b/backend/routes/submission.py index 503b8a8a..0a134e18 100644 --- a/backend/routes/submission.py +++ b/backend/routes/submission.py @@ -5,9 +5,8 @@ from fastapi import APIRouter, File, Response from starlette.requests import Request -from db.models import Submission +from db.models import Submission, SubmissionState from domain.logic.submission import create_submission, get_last_submission -from domain.models.SubmissionDataclass import SubmissionState from routes.authentication.role_dependencies import ensure_student_in_group, ensure_user_authorized_for_submission from routes.tags.swagger_tags import Tags diff --git a/backend/routes/teacher.py b/backend/routes/teacher.py index 09108abd..a903f6fd 100644 --- a/backend/routes/teacher.py +++ b/backend/routes/teacher.py @@ -4,7 +4,7 @@ from db.models import Project, Subject from domain.logic.project import get_projects_of_teacher from domain.logic.subject import add_teacher_to_subject, create_subject, get_subjects_of_teacher -from domain.models.SubjectDataclass import SubjectInput +from domain.models.SubjectInput import SubjectInput from routes.authentication.role_dependencies import get_authenticated_teacher from routes.tags.swagger_tags import Tags diff --git a/backend/tests/test_edge_cases.py b/backend/tests/test_edge_cases.py index 609f9093..237caf8b 100644 --- a/backend/tests/test_edge_cases.py +++ b/backend/tests/test_edge_cases.py @@ -6,8 +6,8 @@ from test_main import get_db, test_engine from db.database_errors import ItemNotFoundError +from db.models import SubmissionState from domain.logic import group, student, submission -from domain.models.SubmissionDataclass import SubmissionState class TestEdgeCases(unittest.TestCase): diff --git a/backend/tests/test_stress.py b/backend/tests/test_stress.py index b80a5d48..02ce653c 100644 --- a/backend/tests/test_stress.py +++ b/backend/tests/test_stress.py @@ -5,8 +5,8 @@ from sqlmodel import SQLModel from test_main import get_db, test_engine +from db.models import SubmissionState from domain.logic import admin, group, project, student, subject, submission, teacher -from domain.models.SubmissionDataclass import SubmissionState class TestStress(unittest.TestCase): diff --git a/backend/tests/test_submission.py b/backend/tests/test_submission.py index 93e82450..620e5477 100644 --- a/backend/tests/test_submission.py +++ b/backend/tests/test_submission.py @@ -5,6 +5,7 @@ from sqlmodel import SQLModel from test_main import get_db, test_engine +from db.models import SubmissionState from domain.logic.group import create_group from domain.logic.project import create_project from domain.logic.student import create_student @@ -16,7 +17,6 @@ get_submissions_of_group, get_submissions_of_student, ) -from domain.models.SubmissionDataclass import SubmissionState class TestSubmission(unittest.TestCase): From 97580cc12fbef29c7c1329fb2781a5544c2ab059 Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Sun, 7 Apr 2024 14:02:25 +0200 Subject: [PATCH 58/90] use inheritance for ProjectInput and move to db models #123 --- backend/db/models.py | 5 ++++- backend/domain/logic/project.py | 3 +-- backend/domain/models/ProjectInput.py | 13 ------------- backend/routes/project.py | 3 +-- backend/routes/subject.py | 3 +-- 5 files changed, 7 insertions(+), 20 deletions(-) delete mode 100644 backend/domain/models/ProjectInput.py diff --git a/backend/db/models.py b/backend/db/models.py index cb0d1ea2..49d9bfba 100644 --- a/backend/db/models.py +++ b/backend/db/models.py @@ -57,7 +57,7 @@ class Subject(SQLModel, table=True): projects: list["Project"] = Relationship(back_populates="subject") -class Project(SQLModel, table=True): +class ProjectInput(SQLModel): name: str deadline: datetime archived: bool @@ -65,6 +65,9 @@ class Project(SQLModel, table=True): requirements: str visible: bool max_students: int + + +class Project(ProjectInput, table=True): # Inherits from ProjectInput id: int = Field(default=None, primary_key=True) subject_id: int = Field(default=None, foreign_key="subject.id") subject: Subject = Relationship(back_populates="projects") diff --git a/backend/domain/logic/project.py b/backend/domain/logic/project.py index be63834f..e9cf6c70 100644 --- a/backend/domain/logic/project.py +++ b/backend/domain/logic/project.py @@ -2,9 +2,8 @@ from sqlmodel import Session -from db.models import Project, Student, Subject, Teacher +from db.models import Project, ProjectInput, Student, Subject, Teacher from domain.logic.basic_operations import get, get_all -from domain.models.ProjectInput import ProjectInput def create_project( diff --git a/backend/domain/models/ProjectInput.py b/backend/domain/models/ProjectInput.py deleted file mode 100644 index 1fd875a8..00000000 --- a/backend/domain/models/ProjectInput.py +++ /dev/null @@ -1,13 +0,0 @@ -from datetime import datetime - -from pydantic import BaseModel, PositiveInt - - -class ProjectInput(BaseModel): - name: str - deadline: datetime - archived: bool - description: str - requirements: str - visible: bool - max_students: PositiveInt diff --git a/backend/routes/project.py b/backend/routes/project.py index f2509e67..acdf51af 100644 --- a/backend/routes/project.py +++ b/backend/routes/project.py @@ -1,10 +1,9 @@ from fastapi import APIRouter from starlette.requests import Request -from db.models import Group, Project +from db.models import Group, Project, ProjectInput from domain.logic.group import create_group, get_groups_of_project from domain.logic.project import get_project, update_project -from domain.models.ProjectInput import ProjectInput from routes.authentication.role_dependencies import ( ensure_teacher_authorized_for_project, ensure_user_authorized_for_project, diff --git a/backend/routes/subject.py b/backend/routes/subject.py index c080f984..c1505b96 100644 --- a/backend/routes/subject.py +++ b/backend/routes/subject.py @@ -1,10 +1,9 @@ from fastapi import APIRouter from starlette.requests import Request -from db.models import Project, Student, Subject, Teacher +from db.models import Project, ProjectInput, Student, Subject, Teacher from domain.logic.project import create_project, get_projects_of_subject from domain.logic.subject import get_students_of_subject, get_subject, get_teachers_of_subject -from domain.models.ProjectInput import ProjectInput from routes.authentication.role_dependencies import ( ensure_teacher_authorized_for_subject, ensure_user_authorized_for_subject, From 3e0af960dee7bb3347c57d8fde4332f69cf47527 Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Sun, 7 Apr 2024 14:11:31 +0200 Subject: [PATCH 59/90] use inheritance for SubjectInput and move to db folder #123 --- backend/db/models.py | 5 ++++- backend/domain/models/SubjectInput.py | 5 ----- backend/routes/teacher.py | 3 +-- 3 files changed, 5 insertions(+), 8 deletions(-) delete mode 100644 backend/domain/models/SubjectInput.py diff --git a/backend/db/models.py b/backend/db/models.py index 49d9bfba..dc7a04dc 100644 --- a/backend/db/models.py +++ b/backend/db/models.py @@ -49,8 +49,11 @@ class Student(SQLModel, table=True): submissions: list["Submission"] = Relationship(back_populates="student") -class Subject(SQLModel, table=True): +class SubjectInput(SQLModel): name: str + + +class Subject(SubjectInput, table=True): # Inherits from SubjectInput id: int = Field(default=None, primary_key=True) teachers: list[Teacher] = Relationship(link_model=TeacherSubject, back_populates="subjects") students: list[Student] = Relationship(link_model=StudentSubject, back_populates="subjects") diff --git a/backend/domain/models/SubjectInput.py b/backend/domain/models/SubjectInput.py deleted file mode 100644 index d8264f36..00000000 --- a/backend/domain/models/SubjectInput.py +++ /dev/null @@ -1,5 +0,0 @@ -from pydantic import BaseModel - - -class SubjectInput(BaseModel): - name: str diff --git a/backend/routes/teacher.py b/backend/routes/teacher.py index a903f6fd..4abc8c61 100644 --- a/backend/routes/teacher.py +++ b/backend/routes/teacher.py @@ -1,10 +1,9 @@ from fastapi import APIRouter from starlette.requests import Request -from db.models import Project, Subject +from db.models import Project, Subject, SubjectInput from domain.logic.project import get_projects_of_teacher from domain.logic.subject import add_teacher_to_subject, create_subject, get_subjects_of_teacher -from domain.models.SubjectInput import SubjectInput from routes.authentication.role_dependencies import get_authenticated_teacher from routes.tags.swagger_tags import Tags From 82e7c19424d28999490f7889b46f96e2f406d61a Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Sun, 7 Apr 2024 14:42:17 +0200 Subject: [PATCH 60/90] Tests are now in package so unittest can discover them --- backend/tests/crud/__init__.py | 0 backend/tests/{ => crud}/authentication_test.py | 0 backend/tests/{ => crud}/test_edge_cases.py | 2 +- backend/tests/{ => crud}/test_main.py | 0 backend/tests/{ => crud}/test_project.py | 2 +- backend/tests/{ => crud}/test_stress.py | 2 +- backend/tests/{ => crud}/test_student.py | 2 +- backend/tests/{ => crud}/test_subject.py | 2 +- backend/tests/{ => crud}/test_submission.py | 2 +- backend/tests/{ => crud}/test_teacher.py | 2 +- backend/tests/test_simple_submissions/__init__.py | 0 11 files changed, 7 insertions(+), 7 deletions(-) create mode 100644 backend/tests/crud/__init__.py rename backend/tests/{ => crud}/authentication_test.py (100%) rename backend/tests/{ => crud}/test_edge_cases.py (96%) rename backend/tests/{ => crud}/test_main.py (100%) rename backend/tests/{ => crud}/test_project.py (98%) rename backend/tests/{ => crud}/test_stress.py (98%) rename backend/tests/{ => crud}/test_student.py (96%) rename backend/tests/{ => crud}/test_subject.py (97%) rename backend/tests/{ => crud}/test_submission.py (99%) rename backend/tests/{ => crud}/test_teacher.py (95%) create mode 100644 backend/tests/test_simple_submissions/__init__.py diff --git a/backend/tests/crud/__init__.py b/backend/tests/crud/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/tests/authentication_test.py b/backend/tests/crud/authentication_test.py similarity index 100% rename from backend/tests/authentication_test.py rename to backend/tests/crud/authentication_test.py diff --git a/backend/tests/test_edge_cases.py b/backend/tests/crud/test_edge_cases.py similarity index 96% rename from backend/tests/test_edge_cases.py rename to backend/tests/crud/test_edge_cases.py index 237caf8b..68f13023 100644 --- a/backend/tests/test_edge_cases.py +++ b/backend/tests/crud/test_edge_cases.py @@ -3,11 +3,11 @@ from datetime import datetime from sqlmodel import SQLModel -from test_main import get_db, test_engine from db.database_errors import ItemNotFoundError from db.models import SubmissionState from domain.logic import group, student, submission +from tests.crud.test_main import get_db, test_engine class TestEdgeCases(unittest.TestCase): diff --git a/backend/tests/test_main.py b/backend/tests/crud/test_main.py similarity index 100% rename from backend/tests/test_main.py rename to backend/tests/crud/test_main.py diff --git a/backend/tests/test_project.py b/backend/tests/crud/test_project.py similarity index 98% rename from backend/tests/test_project.py rename to backend/tests/crud/test_project.py index 2429e254..39717101 100644 --- a/backend/tests/test_project.py +++ b/backend/tests/crud/test_project.py @@ -3,7 +3,6 @@ from datetime import datetime from sqlmodel import SQLModel -from test_main import get_db, test_engine from domain.logic.project import ( create_project, @@ -16,6 +15,7 @@ from domain.logic.student import create_student from domain.logic.subject import add_student_to_subject, add_teacher_to_subject, create_subject from domain.logic.teacher import create_teacher +from tests.crud.test_main import get_db, test_engine class TestProject(unittest.TestCase): diff --git a/backend/tests/test_stress.py b/backend/tests/crud/test_stress.py similarity index 98% rename from backend/tests/test_stress.py rename to backend/tests/crud/test_stress.py index 02ce653c..eeb872fc 100644 --- a/backend/tests/test_stress.py +++ b/backend/tests/crud/test_stress.py @@ -3,10 +3,10 @@ from datetime import datetime from sqlmodel import SQLModel -from test_main import get_db, test_engine from db.models import SubmissionState from domain.logic import admin, group, project, student, subject, submission, teacher +from tests.crud.test_main import get_db, test_engine class TestStress(unittest.TestCase): diff --git a/backend/tests/test_student.py b/backend/tests/crud/test_student.py similarity index 96% rename from backend/tests/test_student.py rename to backend/tests/crud/test_student.py index 49c4566e..56759f7f 100644 --- a/backend/tests/test_student.py +++ b/backend/tests/crud/test_student.py @@ -2,10 +2,10 @@ import unittest from sqlmodel import SQLModel -from test_main import get_db, test_engine from domain.logic.student import create_student, get_all_students, get_student from domain.logic.subject import add_student_to_subject, create_subject, get_subjects_of_student +from tests.crud.test_main import get_db, test_engine class TestStudent(unittest.TestCase): diff --git a/backend/tests/test_subject.py b/backend/tests/crud/test_subject.py similarity index 97% rename from backend/tests/test_subject.py rename to backend/tests/crud/test_subject.py index 84c4c23b..5dbb9cfa 100644 --- a/backend/tests/test_subject.py +++ b/backend/tests/crud/test_subject.py @@ -2,7 +2,6 @@ import unittest from sqlmodel import SQLModel -from test_main import get_db, test_engine from domain.logic.student import create_student from domain.logic.subject import ( @@ -15,6 +14,7 @@ get_subjects_of_teacher, ) from domain.logic.teacher import create_teacher +from tests.crud.test_main import get_db, test_engine class TestSubject(unittest.TestCase): diff --git a/backend/tests/test_submission.py b/backend/tests/crud/test_submission.py similarity index 99% rename from backend/tests/test_submission.py rename to backend/tests/crud/test_submission.py index 620e5477..31c55f11 100644 --- a/backend/tests/test_submission.py +++ b/backend/tests/crud/test_submission.py @@ -3,7 +3,6 @@ from datetime import datetime from sqlmodel import SQLModel -from test_main import get_db, test_engine from db.models import SubmissionState from domain.logic.group import create_group @@ -17,6 +16,7 @@ get_submissions_of_group, get_submissions_of_student, ) +from tests.crud.test_main import get_db, test_engine class TestSubmission(unittest.TestCase): diff --git a/backend/tests/test_teacher.py b/backend/tests/crud/test_teacher.py similarity index 95% rename from backend/tests/test_teacher.py rename to backend/tests/crud/test_teacher.py index da45dcbe..b95eb245 100644 --- a/backend/tests/test_teacher.py +++ b/backend/tests/crud/test_teacher.py @@ -2,9 +2,9 @@ import unittest from sqlmodel import SQLModel -from test_main import get_db, test_engine from domain.logic.teacher import create_teacher, get_all_teachers, get_teacher +from tests.crud.test_main import get_db, test_engine class TestTeacher(unittest.TestCase): diff --git a/backend/tests/test_simple_submissions/__init__.py b/backend/tests/test_simple_submissions/__init__.py new file mode 100644 index 00000000..e69de29b From 43e8a23dad36341dc504cd311d5e8d8bc90242d2 Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Sun, 7 Apr 2024 14:45:28 +0200 Subject: [PATCH 61/90] routes are now in controller folder --- backend/app.py | 32 +++++++++---------- .../authentication_controller.py | 0 .../authentication/errors.py | 0 .../authentication/role_dependencies.py | 16 +++++----- .../authentication/token_controller.py | 0 .../middleware => controllers}/middleware.py | 0 backend/{ => controllers}/routes/group.py | 12 +++---- backend/{ => controllers}/routes/login.py | 8 ++--- backend/{ => controllers}/routes/project.py | 10 +++--- backend/{ => controllers}/routes/student.py | 4 +-- backend/{ => controllers}/routes/subject.py | 10 +++--- .../{ => controllers}/routes/submission.py | 4 +-- backend/{ => controllers}/routes/teacher.py | 4 +-- backend/{ => controllers}/routes/user.py | 4 +-- .../tags => controllers}/swagger_tags.py | 0 15 files changed, 52 insertions(+), 52 deletions(-) rename backend/{routes => controllers}/authentication/authentication_controller.py (100%) rename backend/{routes => controllers}/authentication/errors.py (100%) rename backend/{routes => controllers}/authentication/role_dependencies.py (97%) rename backend/{routes => controllers}/authentication/token_controller.py (100%) rename backend/{routes/middleware => controllers}/middleware.py (100%) rename backend/{ => controllers}/routes/group.py (94%) rename backend/{ => controllers}/routes/login.py (83%) rename backend/{ => controllers}/routes/project.py (94%) rename backend/{ => controllers}/routes/student.py (90%) rename backend/{ => controllers}/routes/subject.py (95%) rename backend/{ => controllers}/routes/submission.py (91%) rename backend/{ => controllers}/routes/teacher.py (91%) rename backend/{ => controllers}/routes/user.py (92%) rename backend/{routes/tags => controllers}/swagger_tags.py (100%) diff --git a/backend/app.py b/backend/app.py index 61cd72b2..a1eb9cb9 100644 --- a/backend/app.py +++ b/backend/app.py @@ -7,27 +7,27 @@ from starlette.requests import Request from starlette.responses import JSONResponse +from controllers.authentication.errors import ( + InvalidAuthenticationError, + InvalidRoleCredentialsError, + NoAccessToDataError, +) +from controllers.middleware import DatabaseSessionMiddleware +from controllers.routes.group import group_router +from controllers.routes.login import login_router +from controllers.routes.project import project_router +from controllers.routes.student import student_router +from controllers.routes.subject import subject_router +from controllers.routes.submission import submission_router +from controllers.routes.teacher import teacher_router +from controllers.routes.user import users_router +from controllers.swagger_tags import tags_metadata from db.database_errors import ( ActionAlreadyPerformedError, ConflictingRelationError, ItemNotFoundError, NoSuchRelationError, ) -from routes.authentication.errors import ( - InvalidAuthenticationError, - InvalidRoleCredentialsError, - NoAccessToDataError, -) -from routes.group import group_router -from routes.login import login_router -from routes.middleware.middleware import DatabaseSessionMiddleware -from routes.project import project_router -from routes.student import student_router -from routes.subject import subject_router -from routes.submission import submission_router -from routes.tags.swagger_tags import tags_metadata -from routes.teacher import teacher_router -from routes.user import users_router pathlib.Path.mkdir(pathlib.Path("submissions"), exist_ok=True) app = FastAPI( @@ -36,7 +36,7 @@ dependencies=[Depends(APIKeyHeader(name="cas", auto_error=False))], # To authenticate via Swagger UI ) -# Koppel routes uit andere modules. +# Koppel controllers uit andere modules. app.include_router(login_router, prefix="/api") app.include_router(student_router, prefix="/api") app.include_router(teacher_router, prefix="/api") diff --git a/backend/routes/authentication/authentication_controller.py b/backend/controllers/authentication/authentication_controller.py similarity index 100% rename from backend/routes/authentication/authentication_controller.py rename to backend/controllers/authentication/authentication_controller.py diff --git a/backend/routes/authentication/errors.py b/backend/controllers/authentication/errors.py similarity index 100% rename from backend/routes/authentication/errors.py rename to backend/controllers/authentication/errors.py diff --git a/backend/routes/authentication/role_dependencies.py b/backend/controllers/authentication/role_dependencies.py similarity index 97% rename from backend/routes/authentication/role_dependencies.py rename to backend/controllers/authentication/role_dependencies.py index cae53607..03b30c7d 100644 --- a/backend/routes/authentication/role_dependencies.py +++ b/backend/controllers/authentication/role_dependencies.py @@ -1,5 +1,13 @@ from starlette.requests import Request +from controllers.authentication.errors import ( + InvalidAdminCredentialsError, + InvalidAuthenticationError, + InvalidStudentCredentialsError, + InvalidTeacherCredentialsError, + NoAccessToDataError, +) +from controllers.authentication.token_controller import verify_token from db.models import Admin, Student, Teacher from db.sessions import get_session from domain.logic.admin import get_admin, is_user_admin @@ -8,14 +16,6 @@ from domain.logic.student import get_student, is_user_student from domain.logic.subject import get_subjects_of_student, get_subjects_of_teacher, is_user_authorized_for_subject from domain.logic.teacher import get_teacher, is_user_teacher -from routes.authentication.errors import ( - InvalidAdminCredentialsError, - InvalidAuthenticationError, - InvalidStudentCredentialsError, - InvalidTeacherCredentialsError, - NoAccessToDataError, -) -from routes.authentication.token_controller import verify_token def get_authenticated_user(request: Request) -> int: diff --git a/backend/routes/authentication/token_controller.py b/backend/controllers/authentication/token_controller.py similarity index 100% rename from backend/routes/authentication/token_controller.py rename to backend/controllers/authentication/token_controller.py diff --git a/backend/routes/middleware/middleware.py b/backend/controllers/middleware.py similarity index 100% rename from backend/routes/middleware/middleware.py rename to backend/controllers/middleware.py diff --git a/backend/routes/group.py b/backend/controllers/routes/group.py similarity index 94% rename from backend/routes/group.py rename to backend/controllers/routes/group.py index 942f6dda..1ff89ab5 100644 --- a/backend/routes/group.py +++ b/backend/controllers/routes/group.py @@ -1,6 +1,12 @@ from fastapi import APIRouter from starlette.requests import Request +from controllers.authentication.role_dependencies import ( + ensure_student_authorized_for_group, + ensure_student_authorized_for_project, + ensure_user_authorized_for_group, +) +from controllers.swagger_tags import Tags from db.models import Group, Student from domain.logic.group import ( add_student_to_group, @@ -8,12 +14,6 @@ get_students_of_group, remove_student_from_group, ) -from routes.authentication.role_dependencies import ( - ensure_student_authorized_for_group, - ensure_student_authorized_for_project, - ensure_user_authorized_for_group, -) -from routes.tags.swagger_tags import Tags group_router = APIRouter() diff --git a/backend/routes/login.py b/backend/controllers/routes/login.py similarity index 83% rename from backend/routes/login.py rename to backend/controllers/routes/login.py index 241d28a6..ec2d4348 100644 --- a/backend/routes/login.py +++ b/backend/controllers/routes/login.py @@ -2,11 +2,11 @@ from pydantic import BaseModel from starlette.requests import Request +from controllers.authentication.authentication_controller import authenticate_user +from controllers.authentication.errors import InvalidAuthenticationError +from controllers.authentication.token_controller import create_token, verify_token +from controllers.swagger_tags import Tags from db.models import User -from routes.authentication.authentication_controller import authenticate_user -from routes.authentication.errors import InvalidAuthenticationError -from routes.authentication.token_controller import create_token, verify_token -from routes.tags.swagger_tags import Tags # test url: https://login.ugent.be/login?service=https://localhost:8080/api/login login_router = APIRouter() diff --git a/backend/routes/project.py b/backend/controllers/routes/project.py similarity index 94% rename from backend/routes/project.py rename to backend/controllers/routes/project.py index acdf51af..3128eea8 100644 --- a/backend/routes/project.py +++ b/backend/controllers/routes/project.py @@ -1,14 +1,14 @@ from fastapi import APIRouter from starlette.requests import Request -from db.models import Group, Project, ProjectInput -from domain.logic.group import create_group, get_groups_of_project -from domain.logic.project import get_project, update_project -from routes.authentication.role_dependencies import ( +from controllers.authentication.role_dependencies import ( ensure_teacher_authorized_for_project, ensure_user_authorized_for_project, ) -from routes.tags.swagger_tags import Tags +from controllers.swagger_tags import Tags +from db.models import Group, Project, ProjectInput +from domain.logic.group import create_group, get_groups_of_project +from domain.logic.project import get_project, update_project project_router = APIRouter() diff --git a/backend/routes/student.py b/backend/controllers/routes/student.py similarity index 90% rename from backend/routes/student.py rename to backend/controllers/routes/student.py index a8fbc067..82168426 100644 --- a/backend/routes/student.py +++ b/backend/controllers/routes/student.py @@ -1,11 +1,11 @@ from fastapi import APIRouter from starlette.requests import Request +from controllers.authentication.role_dependencies import get_authenticated_student +from controllers.swagger_tags import Tags from db.models import Project, Subject from domain.logic.project import get_projects_of_student from domain.logic.subject import add_student_to_subject, get_subjects_of_student -from routes.authentication.role_dependencies import get_authenticated_student -from routes.tags.swagger_tags import Tags student_router = APIRouter() diff --git a/backend/routes/subject.py b/backend/controllers/routes/subject.py similarity index 95% rename from backend/routes/subject.py rename to backend/controllers/routes/subject.py index c1505b96..be7198e9 100644 --- a/backend/routes/subject.py +++ b/backend/controllers/routes/subject.py @@ -1,15 +1,15 @@ from fastapi import APIRouter from starlette.requests import Request -from db.models import Project, ProjectInput, Student, Subject, Teacher -from domain.logic.project import create_project, get_projects_of_subject -from domain.logic.subject import get_students_of_subject, get_subject, get_teachers_of_subject -from routes.authentication.role_dependencies import ( +from controllers.authentication.role_dependencies import ( ensure_teacher_authorized_for_subject, ensure_user_authorized_for_subject, get_authenticated_user, ) -from routes.tags.swagger_tags import Tags +from controllers.swagger_tags import Tags +from db.models import Project, ProjectInput, Student, Subject, Teacher +from domain.logic.project import create_project, get_projects_of_subject +from domain.logic.subject import get_students_of_subject, get_subject, get_teachers_of_subject subject_router = APIRouter() diff --git a/backend/routes/submission.py b/backend/controllers/routes/submission.py similarity index 91% rename from backend/routes/submission.py rename to backend/controllers/routes/submission.py index 0a134e18..e4d3940e 100644 --- a/backend/routes/submission.py +++ b/backend/controllers/routes/submission.py @@ -5,10 +5,10 @@ from fastapi import APIRouter, File, Response from starlette.requests import Request +from controllers.authentication.role_dependencies import ensure_student_in_group, ensure_user_authorized_for_submission +from controllers.swagger_tags import Tags from db.models import Submission, SubmissionState from domain.logic.submission import create_submission, get_last_submission -from routes.authentication.role_dependencies import ensure_student_in_group, ensure_user_authorized_for_submission -from routes.tags.swagger_tags import Tags submission_router = APIRouter() diff --git a/backend/routes/teacher.py b/backend/controllers/routes/teacher.py similarity index 91% rename from backend/routes/teacher.py rename to backend/controllers/routes/teacher.py index 4abc8c61..d1ab2557 100644 --- a/backend/routes/teacher.py +++ b/backend/controllers/routes/teacher.py @@ -1,11 +1,11 @@ from fastapi import APIRouter from starlette.requests import Request +from controllers.authentication.role_dependencies import get_authenticated_teacher +from controllers.swagger_tags import Tags from db.models import Project, Subject, SubjectInput from domain.logic.project import get_projects_of_teacher from domain.logic.subject import add_teacher_to_subject, create_subject, get_subjects_of_teacher -from routes.authentication.role_dependencies import get_authenticated_teacher -from routes.tags.swagger_tags import Tags teacher_router = APIRouter() diff --git a/backend/routes/user.py b/backend/controllers/routes/user.py similarity index 92% rename from backend/routes/user.py rename to backend/controllers/routes/user.py index e672960b..0c2ad8bf 100644 --- a/backend/routes/user.py +++ b/backend/controllers/routes/user.py @@ -1,12 +1,12 @@ from fastapi import APIRouter, Response, status from starlette.requests import Request +from controllers.authentication.role_dependencies import get_authenticated_admin, get_authenticated_user +from controllers.swagger_tags import Tags from db.models import User from domain.logic.basic_operations import get, get_all from domain.logic.role_enum import Role from domain.logic.user import get_user, modify_language, modify_user_roles -from routes.authentication.role_dependencies import get_authenticated_admin, get_authenticated_user -from routes.tags.swagger_tags import Tags users_router = APIRouter() diff --git a/backend/routes/tags/swagger_tags.py b/backend/controllers/swagger_tags.py similarity index 100% rename from backend/routes/tags/swagger_tags.py rename to backend/controllers/swagger_tags.py From cf62f1798180a63de8fae921becea6a01fd6a680 Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Mon, 8 Apr 2024 15:13:05 +0200 Subject: [PATCH 62/90] fill_database_mock is now back #123 --- backend/create_database_tables.py | 19 +++++++++++++++++++ backend/{db => }/fill_database_mock.py | 13 ++++--------- 2 files changed, 23 insertions(+), 9 deletions(-) create mode 100644 backend/create_database_tables.py rename backend/{db => }/fill_database_mock.py (97%) diff --git a/backend/create_database_tables.py b/backend/create_database_tables.py new file mode 100644 index 00000000..872a3531 --- /dev/null +++ b/backend/create_database_tables.py @@ -0,0 +1,19 @@ +from sqlalchemy import Engine +from sqlalchemy_utils import create_database, database_exists +from sqlmodel import Session, SQLModel + +from db.extensions import engine + + +def initialize_tables(session_instance: Session, engine_instance: Engine) -> None: + if not database_exists(engine_instance.url): + create_database(engine_instance.url) + SQLModel.metadata.drop_all(engine_instance) + SQLModel.metadata.create_all(engine_instance) + session_instance.commit() + + +if __name__ == "__main__": + with Session(engine) as session: + initialize_tables(session, engine) + session.close() diff --git a/backend/db/fill_database_mock.py b/backend/fill_database_mock.py similarity index 97% rename from backend/db/fill_database_mock.py rename to backend/fill_database_mock.py index 796d871c..a5b420c6 100644 --- a/backend/db/fill_database_mock.py +++ b/backend/fill_database_mock.py @@ -1,9 +1,9 @@ from datetime import datetime from psycopg2 import tz -from sqlalchemy_utils import create_database, database_exists -from sqlmodel import Session, SQLModel +from sqlmodel import Session +from create_database_tables import initialize_tables from db.extensions import engine from db.models import SubmissionState from domain.logic.admin import create_admin @@ -17,14 +17,9 @@ from domain.logic.user import modify_user_roles if __name__ == "__main__": - with Session(engine) as session: - if not database_exists(engine.url): - create_database(engine.url) - - SQLModel.metadata.drop_all(engine) - SQLModel.metadata.create_all(engine) - session.commit() + with Session(engine) as session: + initialize_tables(session, engine) # Create subjects objeprog = create_subject(session, name="Objectgericht Programmeren") From 348cfc8683313b106b2d553a565def49fda6e4d4 Mon Sep 17 00:00:00 2001 From: Lukas Barragan Torres Date: Mon, 8 Apr 2024 15:57:19 +0200 Subject: [PATCH 63/90] make roles a computed field #123 --- backend/db/models.py | 45 +++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/backend/db/models.py b/backend/db/models.py index dc7a04dc..cb486efd 100644 --- a/backend/db/models.py +++ b/backend/db/models.py @@ -1,23 +1,10 @@ import enum from datetime import datetime +from pydantic import computed_field from sqlmodel import Field, Relationship, SQLModel - -class User(SQLModel, table=True): - name: str - email: str - language: str = "EN" - id: int = Field(default=None, primary_key=True) - - admin: "Admin" = Relationship(back_populates="user") - teacher: "Teacher" = Relationship(back_populates="user") - student: "Student" = Relationship(back_populates="user") - - -class Admin(SQLModel, table=True): - id: int = Field(default=None, foreign_key="user.id", primary_key=True) - user: User = Relationship(back_populates="admin") +from domain.logic.role_enum import Role class TeacherSubject(SQLModel, table=True): @@ -35,6 +22,34 @@ class StudentGroup(SQLModel, table=True): group_id: int = Field(foreign_key="group.id", primary_key=True) +class User(SQLModel, table=True): + name: str + email: str + language: str = "EN" + id: int = Field(default=None, primary_key=True) + + admin: "Admin" = Relationship(back_populates="user") + teacher: "Teacher" = Relationship(back_populates="user") + student: "Student" = Relationship(back_populates="user") + + @computed_field + @property + def roles(self) -> list[Role]: + roles = [] + if self.admin: + roles.append(Role.ADMIN) + if self.teacher: + roles.append(Role.TEACHER) + if self.student: + roles.append(Role.STUDENT) + return roles + + +class Admin(SQLModel, table=True): + id: int = Field(default=None, foreign_key="user.id", primary_key=True) + user: User = Relationship(back_populates="admin") + + class Teacher(SQLModel, table=True): id: int = Field(default=None, foreign_key="user.id", primary_key=True) user: User = Relationship(back_populates="teacher") From 109a27fe5e707def1f5e3f67e4ed5cb1baa65845 Mon Sep 17 00:00:00 2001 From: EmmaVandewalle Date: Fri, 5 Apr 2024 16:03:33 +0200 Subject: [PATCH 64/90] feat: last pages added --- ...ponent.tsx => ProjectStudentComponent.tsx} | 2 +- ...ponent.tsx => ProjectTeacherComponent.tsx} | 4 +- frontend/src/components/Table.tsx | 50 +++++------ frontend/src/dataloaders/SharedFunctions.ts | 40 --------- frontend/src/main.tsx | 22 +++++ .../src/pages/student/CourseViewStudent.tsx | 59 +++++++++++++ .../src/pages/student/CoursesViewStudent.tsx | 24 ++++-- .../src/pages/student/ProjectViewStudent.tsx | 33 +++---- .../src/pages/student/ProjectsViewStudent.tsx | 41 +++++++-- .../src/pages/teacher/CourseViewTeacher.tsx | 86 +++++++++++++++++++ .../src/pages/teacher/CoursesViewTeacher.tsx | 28 ++++-- frontend/src/pages/teacher/CreateProject.tsx | 6 +- .../src/pages/teacher/ProjectViewTeacher.tsx | 6 +- .../src/pages/teacher/ProjectsViewTeacher.tsx | 42 ++++++--- frontend/src/types/tableRows.ts | 39 +++++++++ 15 files changed, 358 insertions(+), 124 deletions(-) rename frontend/src/components/{ViewProjectStudentComponent.tsx => ProjectStudentComponent.tsx} (97%) rename frontend/src/components/{ViewProjectTeacherComponent.tsx => ProjectTeacherComponent.tsx} (98%) create mode 100644 frontend/src/pages/student/CourseViewStudent.tsx create mode 100644 frontend/src/pages/teacher/CourseViewTeacher.tsx create mode 100644 frontend/src/types/tableRows.ts diff --git a/frontend/src/components/ViewProjectStudentComponent.tsx b/frontend/src/components/ProjectStudentComponent.tsx similarity index 97% rename from frontend/src/components/ViewProjectStudentComponent.tsx rename to frontend/src/components/ProjectStudentComponent.tsx index 44be3c46..c2df8c01 100644 --- a/frontend/src/components/ViewProjectStudentComponent.tsx +++ b/frontend/src/components/ProjectStudentComponent.tsx @@ -4,7 +4,7 @@ import {FaCheck, FaUpload} from "react-icons/fa"; import {FaDownload} from "react-icons/fa6"; import {ProjectStatus, ProjectStudent} from "../types/project.ts"; -export default function ViewProjectStudentComponent(props: { project: ProjectStudent }): JSX.Element { +export default function ProjectStudentComponent(props: { project: ProjectStudent }): JSX.Element { return ( <> diff --git a/frontend/src/components/ViewProjectTeacherComponent.tsx b/frontend/src/components/ProjectTeacherComponent.tsx similarity index 98% rename from frontend/src/components/ViewProjectTeacherComponent.tsx rename to frontend/src/components/ProjectTeacherComponent.tsx index 8b158ac9..60e9ffa8 100644 --- a/frontend/src/components/ViewProjectTeacherComponent.tsx +++ b/frontend/src/components/ProjectTeacherComponent.tsx @@ -7,9 +7,7 @@ import {FaUpload} from "react-icons/fa"; import {ProjectTeacher, Value} from "../types/project.ts"; import "../assets/styles/teacher_components.css" -export function ViewProjectTeacherComponent(props: { - project: ProjectTeacher -}): JSX.Element { +export function ProjectTeacherComponent(props: {project: ProjectTeacher }): JSX.Element { const [projectName, setProjectName] = useState(props.project.projectName) const [courseName, setCourseName] = useState(props.project.courseName) diff --git a/frontend/src/components/Table.tsx b/frontend/src/components/Table.tsx index 63fefef6..ff3d7bb0 100644 --- a/frontend/src/components/Table.tsx +++ b/frontend/src/components/Table.tsx @@ -1,29 +1,33 @@ import {JSX} from "react"; import "../assets/styles/table.css" +import {Link} from "react-router-dom"; +import {TableRow} from "../types/tableRows.ts"; -export interface TableRow { - name: string -} +const firstFieldWidth: number = 40; +const otherFieldsWidth = (keysLength: number) => 1 / (keysLength - 1) * 100 - firstFieldWidth / (keysLength - 1); -export interface TableRowProjects extends TableRow { - course: string, - deadline: string | null, - status: string | null, // for student - numberOfSubmissions: number | null // for teacher and only for visible projects -} +function TableDataElement(props: {home: string, row: T, colIndex: number, keys: string[], index: number}): JSX.Element { + const widthElement = props.colIndex == 0 ? firstFieldWidth : otherFieldsWidth(props.keys.length) + const values: any[] = Object.values(props.row) -export interface TableRowCourses extends TableRow { - numberOfProjects: number | null; // not for archived projects for teachers - shortestDeadline: string | null; // only if not archived + return ( +
+ ) } -export function Table(props: { title: string, data: T[], ignoreKeys: string[] }): JSX.Element { +export function Table(props: { title: string, data: T[], ignoreKeys: string[], home: string }): JSX.Element { // todo: translate the keys for i18n way // right now only the name of the key will be written as it will be completely different with i18n const keys = props.data.length > 0 ? Object.keys(props.data[0]) : []; - const firstFieldWidth: number = 40; - const otherFieldsWidth: number = 1 / (keys.length - 1) * 100 - firstFieldWidth / (keys.length - 1); return (
@@ -33,12 +37,12 @@ export function Table(props: { title: string, data: T[], ign
{keys.map((field, index) => ( !props.ignoreKeys.some(item => field === item) ? - field === "name" ? + index === 0 ? : - - : + : ))} @@ -47,13 +51,9 @@ export function Table(props: { title: string, data: T[], ign {keys.map((field, colIndex) => ( !props.ignoreKeys.some(item => field === item) ? - colIndex == 0 ? - - : - - : + + : + ))} ))} diff --git a/frontend/src/dataloaders/SharedFunctions.ts b/frontend/src/dataloaders/SharedFunctions.ts index 69569c6f..5da45272 100644 --- a/frontend/src/dataloaders/SharedFunctions.ts +++ b/frontend/src/dataloaders/SharedFunctions.ts @@ -41,46 +41,6 @@ export async function coursesLoader(role: teacherStudentRole): Promise { - const {subjects, projects} = await getAllProjectsAndSubjects(role); - if (! Array.isArray(projects) || ! Array.isArray(subjects)) { - throw Error("Problem loading projects or courses."); - } - - //TODO aanpassen, dit geeft gelijk alle groepen terug - const groupPromises: Promise[] = projects.map(async project => { - return (await apiFetch(`/projects/${project.id}/group`)) as Group; - }); - - const submissionPromises: Promise[] = (await Promise.all(groupPromises)).map(async group => { - if (group) { - return (await apiFetch(`/groups/${group.id}/submission`)) as Submission; - } - return undefined; - }); - - const groups: Group[] = (await Promise.all(groupPromises)).filter(group => group !== null) as Group[]; - const submissions: Submission[] = (await Promise.all(submissionPromises)) as Submission[]; - - return projects.map((project, index) => { - const group = groups[index]; - const submission = submissions[index]; - const subject = subjects.find(subject => subject.id === project.subject_id); - if (!subject) { - throw Error("Subject not found for project."); - } - return { - ...project, - ...group, - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - submission_state: submission?.state - } - }).filter(project => project.submission_state !== undefined); // filter alles eruit waar je niets mee te maken hebt -} - -======= ->>>>>>> d865193 (move project dataloader for student to other file) export interface projectsAndSubjects { projects: Project[], subjects: Subject[] diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 2014e4b9..49fd334d 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -24,10 +24,17 @@ import ProjectsViewTeacher from "./pages/teacher/ProjectsViewTeacher.tsx"; import CoursesViewTeacher from "./pages/teacher/CoursesViewTeacher.tsx"; import {CreateProject} from "./pages/teacher/CreateProject.tsx"; import CreateCourse from "./pages/teacher/CreateCourse.tsx"; +<<<<<<< HEAD import projectsTeacherLoader, {PROJECTS_TEACHER_ROUTER_ID} from "./dataloaders/projectsTeacherLoader.ts"; import projectsStudentLoader, {PROJECTS_STUDENT_ROUTER_ID} from "./dataloaders/ProjectsStudentLoader.ts"; import coursesStudentLoader, {COURSES_STUDENT_ROUTER_ID} from './dataloaders/CoursesStudentLoader.ts'; import coursesTeacherLoader, {COURSES_TEACHER_ROUTER_ID} from "./dataloaders/CoursesTeacherLoader.ts"; +======= +import ProjectViewStudent from "./pages/student/ProjectViewStudent.tsx"; +import ProjectViewTeacher from "./pages/teacher/ProjectViewTeacher.tsx"; +import CourseViewStudent from "./pages/student/CourseViewStudent.tsx"; +import CourseViewTeacher from "./pages/teacher/CourseViewTeacher.tsx"; +>>>>>>> 685d6cb (feat: last pages added) const router = createBrowserRouter( createRoutesFromElements( @@ -48,19 +55,34 @@ const router = createBrowserRouter( }> } loader={studentLoader}/> +<<<<<<< HEAD } loader={projectsStudentLoader}/> } loader={coursesStudentLoader}/> +======= + }/> + }/> + }/> + }/> +>>>>>>> 685d6cb (feat: last pages added) }> } loader={teacherLoader}/> +<<<<<<< HEAD } loader={projectsTeacherLoader}/> }/> } loader={coursesTeacherLoader}/> +======= + }/> + }/> + }/> + } /*loader={subjectsTeacherLoader} id={SUBJECT_TEACHER_ROUTER_ID}*//> + }/> +>>>>>>> 685d6cb (feat: last pages added) }/> diff --git a/frontend/src/pages/student/CourseViewStudent.tsx b/frontend/src/pages/student/CourseViewStudent.tsx new file mode 100644 index 00000000..b4ec9c37 --- /dev/null +++ b/frontend/src/pages/student/CourseViewStudent.tsx @@ -0,0 +1,59 @@ +import {JSX} from "react"; +import {TableRowOverviewProjects, TableRowPeople} from "../../types/tableRows.ts"; +import {Header} from "../../components/Header.tsx"; +import {Sidebar} from "../../components/Sidebar.tsx"; +import {Table} from "../../components/Table.tsx"; + +export default function CourseViewStudent(): JSX.Element { + const tableProjects: TableRowOverviewProjects[] = [ + { + project: { + name: "RSA security", + id: 1234 + }, + status: null, + deadline: "23:59 - 27/02/2024" + }, + { + project: { + name: "Symmetric encryption", + id: 5897 + }, + status: null, + deadline: "23:59 - 03/03/2024" + } + ]; + + const teachers: TableRowPeople[] = [ + { + name: "Maarten Vermeiren", + email: "maarten.vermeiren@ugent.be" + }, + { + name: "Anke De Groot", + email: "anke.degroot@ugent.be" + } + ]; + + return ( + <> +
+
+
+
+
+ +
+
+
+
+
+ { typeof values[props.index] === "object" ? ( + + {values[props.index]["name"]} + + ) : ( + <>{values[props.index]} + )} +
{field}{field}
{Object.values(row)[keys.indexOf(field)]}{Object.values(row)[keys.indexOf(field)]}
+
+
+ + + + + + ) +} \ No newline at end of file diff --git a/frontend/src/pages/student/CoursesViewStudent.tsx b/frontend/src/pages/student/CoursesViewStudent.tsx index 4ecd5f16..c8af559e 100644 --- a/frontend/src/pages/student/CoursesViewStudent.tsx +++ b/frontend/src/pages/student/CoursesViewStudent.tsx @@ -3,9 +3,14 @@ import {Header} from "../../components/Header.tsx"; import {Sidebar} from "../../components/Sidebar.tsx"; import '../../assets/styles/students_components.css' import {SearchBar} from "../../components/SearchBar.tsx"; +<<<<<<< HEAD import {Table, TableRowCourses} from "../../components/Table.tsx"; import {useRouteLoaderData} from "react-router-dom"; import {COURSES_STUDENT_ROUTER_ID, coursesStudentLoaderObject} from "../../dataloaders/CoursesStudentLoader.ts"; +======= +import {Table} from "../../components/Table.tsx"; +import {TableRowCourses} from "../../types/tableRows.ts"; +>>>>>>> 685d6cb (feat: last pages added) export default function CoursesViewStudent(): JSX.Element { @@ -14,19 +19,28 @@ export default function CoursesViewStudent(): JSX.Element { const tableCoursesActive: TableRowCourses[] = [ { - name: "Automaten, berekenbaarheid & complexiteit", + course: { + name: "Automaten, berekenbaarheid & complexiteit", + id: 5896 + }, shortestDeadline: "17:00 - 23/02/2024", numberOfProjects: 1 }, { - name: "Computationele Biologie", + course: { + name: "Computationele Biologie", + id: 5741 + }, shortestDeadline: "19:00 - 25/02/2024", numberOfProjects: 2 }, ]; const tableCoursesArchived: TableRowCourses[] = [ { - name: "Logisch Programmeren", + course: { + name: "Logisch Programmeren", + id: 2569 + }, shortestDeadline: null, numberOfProjects: 1, } @@ -44,9 +58,9 @@ export default function CoursesViewStudent(): JSX.Element {
-
+
-
+
diff --git a/frontend/src/pages/student/ProjectViewStudent.tsx b/frontend/src/pages/student/ProjectViewStudent.tsx index 1e77696c..035911da 100644 --- a/frontend/src/pages/student/ProjectViewStudent.tsx +++ b/frontend/src/pages/student/ProjectViewStudent.tsx @@ -1,45 +1,38 @@ import {JSX} from "react"; import {Header} from "../../components/Header.tsx"; import {Sidebar} from "../../components/Sidebar.tsx"; -import ViewProjectStudent from "../../components/ViewProjectStudentComponent.tsx"; +import ViewProjectStudent from "../../components/ProjectStudentComponent.tsx"; import {ProjectStatus, ProjectStudent} from "../../types/project.ts"; export default function ProjectViewStudent(): JSX.Element { - const projectName: string = "Markov Decision Diagram" - const courseName: string = "Automaten, berekenbaarheid en complexiteit" - const deadline: string = "17:00 - 23/02/2024" - const status: ProjectStatus = ProjectStatus.FAILED - const description: string = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut vel arcu sit amet quam scelerisque vestibulum. Nulla lectus ipsum, convallis ut odio sit amet, auctor dictum felis. Phasellus libero sapien, tempus eu fringilla eu, facilisis vel purus. Quisque odio elit, viverra id tortor eu, blandit luctus turpis. Vestibulum libero felis, condimentum finibus posuere sed, lobortis non tellus. Phasellus laoreet, metus a semper vulputate, mi dui lobortis augue, quis fringilla ipsum felis eu mauris. Donec sem dolor, porta ultrices venenatis eget, ullamcorper id turpis. Nulla quis lacinia sapien. Mauris dignissim nisi id quam vulputate molestie. Fusce eleifend sagittis dolor sit amet aliquam. Aenean in sapien diam. Donec iaculis nunc eu enim pulvinar ultricies. Suspendisse potenti. Etiam quis viverra nunc. Nulla tempus in erat vitae tincidunt. Vestibulum et iaculis nulla." - const requiredFiles: string[] = ["Diagram.dgr", "verslag.pdf"] const groupMembers: { name: string, email: string, lastSubmission: boolean }[] = [ {name: "jan", email: "jan@ugent.be", lastSubmission: false}, {name: "erik", email: "erik@ugent.be", lastSubmission: false}, - {name: "peter", email: "peter@ugent.be", lastSubmission: true}] - const maxGroupMembers: number = 4 - const submission: string | null = "submission.zip" + {name: "peter", email: "peter@ugent.be", lastSubmission: true} + ] const project: ProjectStudent = { - courseName: courseName, - deadline: deadline, - status: status, - description: description, + projectName: "Markov Decision Diagram", + courseName: "Automaten, berekenbaarheid en complexiteit", + deadline: "17:00 - 23/02/2024", + status: ProjectStatus.FAILED, + description: "Lorem ipsum dolor sit amet.", + requiredFiles: ["Diagram.dgr", "verslag.pdf"], groupMembers: groupMembers, - maxGroupMembers: maxGroupMembers, - projectName: projectName, - requiredFiles: requiredFiles, - submission: submission + maxGroupMembers: 4, + submission: "submission.zip" } return ( <>
-
+
-
+
diff --git a/frontend/src/pages/student/ProjectsViewStudent.tsx b/frontend/src/pages/student/ProjectsViewStudent.tsx index b034d1a7..92a41419 100644 --- a/frontend/src/pages/student/ProjectsViewStudent.tsx +++ b/frontend/src/pages/student/ProjectsViewStudent.tsx @@ -2,10 +2,15 @@ import {JSX} from "react"; import {Header} from "../../components/Header.tsx"; import {Sidebar} from "../../components/Sidebar.tsx"; import {SearchBar} from "../../components/SearchBar.tsx"; +<<<<<<< HEAD import {Table, TableRowProjects} from "../../components/Table.tsx"; import {useRouteLoaderData} from "react-router-dom"; import {PROJECTS_STUDENT_ROUTER_ID} from "../../dataloaders/ProjectsStudentLoader.ts"; import {studentLoaderObject} from "../../dataloaders/StudentLoader.ts"; +======= +import {Table} from "../../components/Table.tsx"; +import {TableRowProjects} from "../../types/tableRows.ts"; +>>>>>>> 685d6cb (feat: last pages added) export default function ProjectsViewStudent(): JSX.Element { @@ -14,15 +19,27 @@ export default function ProjectsViewStudent(): JSX.Element { const tableProjectsActive: TableRowProjects[] = [ { - name: "Markov Decision Diagram", - course: "Automaten, berekenbaarheid & complexiteit", + project: { + name: "Markov Decision Diagram", + id: 1234 + }, + course: { + name: "Automaten, berekenbaarheid & complexiteit", + id: 9876 + }, numberOfSubmissions: null, deadline: "17:00 - 23/02/2024", status: "FAILED" }, { - name: "HPC", - course: "Computationele Biologie", + project: { + name: "HPC", + id: 4321 + }, + course: { + name: "Computationele Biologie", + id: 6789 + }, numberOfSubmissions: null, deadline: "19:00 - 25/02/2024", status: "SUCCES" @@ -31,8 +48,14 @@ export default function ProjectsViewStudent(): JSX.Element { const tableProjectsArchived: TableRowProjects[] = [ { - name: "HPC", - course: "Computationele Biologie", + project: { + name: "HPC", + id: 5478 + }, + course: { + name: "Computationele Biologie", + id: 6789 + }, numberOfSubmissions: null, deadline: null, status: "SUCCES" @@ -51,10 +74,14 @@ export default function ProjectsViewStudent(): JSX.Element {
-
+
+<<<<<<< HEAD
+======= +
+>>>>>>> 685d6cb (feat: last pages added) diff --git a/frontend/src/pages/teacher/CourseViewTeacher.tsx b/frontend/src/pages/teacher/CourseViewTeacher.tsx new file mode 100644 index 00000000..226bd2d6 --- /dev/null +++ b/frontend/src/pages/teacher/CourseViewTeacher.tsx @@ -0,0 +1,86 @@ +import {JSX} from "react"; +import {Header} from "../../components/Header.tsx"; +import {Sidebar} from "../../components/Sidebar.tsx"; +import {RegularATag} from "../../components/RegularATag.tsx"; +import {Table} from "../../components/Table.tsx"; +import {IoExitOutline} from "react-icons/io5"; +import {FaArchive} from "react-icons/fa"; +import {CiLink} from "react-icons/ci"; +import {MdManageAccounts} from "react-icons/md"; +import {TableRowOverviewProjects, TableRowPeople} from "../../types/tableRows.ts"; + +export default function CourseViewTeacher(): JSX.Element { + const tableProjects: TableRowOverviewProjects[] = [ + { + project: { + name: "RSA security", + id: 1234 + }, + status: null, + deadline: "23:59 - 27/02/2024" + }, + { + project: { + name: "Symmetric encryption", + id: 5897 + }, + status: null, + deadline: "23:59 - 03/03/2024" + } + ]; + + const teachers: TableRowPeople[] = [ + { + name: "Maarten Vermeiren", + email: "maarten.vermeiren@ugent.be" + }, + { + name: "Anke De Groot", + email: "anke.degroot@ugent.be" + } + ]; + + const students: TableRowPeople[] = [ + { + name: "Bart De Jong", + email: "bart.dejong@ugent.be" + }, + { + name: "Siemen Janssens", + email: "siemen.janssens@ugent.be" + } + ]; + + + return ( + <> +
+
+
+
+
+ +
+
+
+
+
+ + + + +
+ +
+
+
+
+
+
+
+ + + + + ) +} \ No newline at end of file diff --git a/frontend/src/pages/teacher/CoursesViewTeacher.tsx b/frontend/src/pages/teacher/CoursesViewTeacher.tsx index def0ed6b..4eb50fa0 100644 --- a/frontend/src/pages/teacher/CoursesViewTeacher.tsx +++ b/frontend/src/pages/teacher/CoursesViewTeacher.tsx @@ -1,11 +1,18 @@ import {JSX} from "react"; -import {Table, TableRowCourses} from "../../components/Table.tsx"; +import {Table} from "../../components/Table.tsx"; import {Header} from "../../components/Header.tsx"; import {Sidebar} from "../../components/Sidebar.tsx"; import {SearchBar} from "../../components/SearchBar.tsx"; import {RegularATag} from "../../components/RegularATag.tsx"; +<<<<<<< HEAD import {COURSES_TEACHER_ROUTER_ID, coursesTeacherLoaderObject} from "../../dataloaders/CoursesTeacherLoader.ts"; import {useRouteLoaderData} from "react-router-dom"; +======= +import {TableRowCourses} from "../../types/tableRows.ts"; + +// import {useRouteLoaderData} from "react-router-dom"; +// import {SUBJECT_TEACHER_ROUTER_ID, subjectsTeacherLoaderObject} from "../../dataloaders/SubjectsTeacherLoader.ts"; +>>>>>>> 685d6cb (feat: last pages added) export default function CoursesViewTeacher(): JSX.Element { @@ -14,19 +21,28 @@ export default function CoursesViewTeacher(): JSX.Element { const tableCoursesActive: TableRowCourses[] = [ { - name: "Information Security", + course: { + name: "Information Security", + id: 5241 + }, shortestDeadline: "23:59 - 05/03/2024", numberOfProjects: 2 }, { - name: "Rechtsgeschiedenis", + course: { + name: "Rechtsgeschiedenis", + id: 5897 + }, shortestDeadline: "23:59 - 21/03/2024", numberOfProjects: 2 }, ]; const tableCoursesArchived: TableRowCourses[] = [ { - name: "Moderne talen", + course: { + name: "Moderne talen", + id: 5896 + }, shortestDeadline: null, numberOfProjects: null, } @@ -47,10 +63,10 @@ export default function CoursesViewTeacher(): JSX.Element { -
+
+ ignoreKeys={["shortestDeadline", "numberOfProjects"]} home={"teacher"}/> diff --git a/frontend/src/pages/teacher/CreateProject.tsx b/frontend/src/pages/teacher/CreateProject.tsx index a17d2daf..50f88d06 100644 --- a/frontend/src/pages/teacher/CreateProject.tsx +++ b/frontend/src/pages/teacher/CreateProject.tsx @@ -1,11 +1,11 @@ import {JSX} from "react"; import {ProjectTeacher} from "../../types/project.ts"; -import {ViewProjectTeacherComponent} from "../../components/ViewProjectTeacherComponent.tsx"; +import {ProjectTeacherComponent} from "../../components/ProjectTeacherComponent.tsx"; import {Header} from "../../components/Header.tsx"; import {Sidebar} from "../../components/Sidebar.tsx"; export function CreateProject(): JSX.Element { - const emptyProjectTeacher: ProjectTeacher = { + const emptyProjectTeacher: ProjectTeacherComponent = { courseName: "", deadline: new Date(Date.now()), description: "", @@ -27,7 +27,7 @@ export function CreateProject(): JSX.Element {
- +
diff --git a/frontend/src/pages/teacher/ProjectViewTeacher.tsx b/frontend/src/pages/teacher/ProjectViewTeacher.tsx index 0f1fb7d9..ce745efc 100644 --- a/frontend/src/pages/teacher/ProjectViewTeacher.tsx +++ b/frontend/src/pages/teacher/ProjectViewTeacher.tsx @@ -1,12 +1,11 @@ import {Header} from "../../components/Header.tsx"; import {Sidebar} from "../../components/Sidebar.tsx"; -import {ViewProjectTeacherComponent} from "../../components/ViewProjectTeacherComponent.tsx"; +import {ProjectTeacherComponent} from "../../components/ProjectTeacherComponent.tsx"; import {ProjectTeacher} from "../../types/project.ts"; import Statistics from "../../components/Statistics.tsx"; export default function ProjectViewTeacher() { - const project: ProjectTeacher = { projectName: "RSA security", courseName: "Information Security", @@ -19,7 +18,6 @@ export default function ProjectViewTeacher() { groupProject: true, } - return ( <>
@@ -31,7 +29,7 @@ export default function ProjectViewTeacher() {
- +
diff --git a/frontend/src/pages/teacher/ProjectsViewTeacher.tsx b/frontend/src/pages/teacher/ProjectsViewTeacher.tsx index 3b78985a..abc288a7 100644 --- a/frontend/src/pages/teacher/ProjectsViewTeacher.tsx +++ b/frontend/src/pages/teacher/ProjectsViewTeacher.tsx @@ -2,24 +2,40 @@ import {JSX} from "react"; import {Header} from "../../components/Header.tsx"; import {Sidebar} from "../../components/Sidebar.tsx"; import {SearchBar} from "../../components/SearchBar.tsx"; -import {Table, TableRowProjects} from "../../components/Table.tsx"; +import {Table} from "../../components/Table.tsx"; import {RegularATag} from "../../components/RegularATag.tsx"; +<<<<<<< HEAD import {useRouteLoaderData} from "react-router-dom"; import {teacherLoaderObject} from "../../dataloaders/TeacherLoader.ts"; import {PROJECTS_TEACHER_ROUTER_ID} from "../../dataloaders/projectsTeacherLoader.ts"; +======= +import {TableRowProjects} from "../../types/tableRows.ts"; +>>>>>>> 685d6cb (feat: last pages added) export default function ProjectsViewTeacher(): JSX.Element { const tableProjectsActive: TableRowProjects[] = [ { - name: "RSA security", - course: "Information Security", + project: { + name: "RSA security", + id: 1478 + }, + course: { + name: "Information Security", + id: 9632, + }, status: null, numberOfSubmissions: 35, deadline: "23:59 - 23/02/2024" }, { - name: "Bachelorproef", - course: "Rechtsgeschiedenis", + project: { + name: "Bachelorproef", + id: 7536 + }, + course: { + name: "Rechtsgeschiedenis", + id: 4521, + }, status: null, numberOfSubmissions: 3, deadline: "23:59 - 21/03/2024" @@ -33,8 +49,14 @@ export default function ProjectsViewTeacher(): JSX.Element { const tableProjectsArchived: TableRowProjects[] = [ { - name: "samenvatting \"The Social Contract\"", - course: "Rechtsgeschiedenis", + project: { + name: "samenvatting \"The Social Contract\"", + id: 6874 + }, + course: { + name: "Rechtsgeschiedenis", + id: 4521, + }, status: null, numberOfSubmissions: null, deadline: null @@ -57,13 +79,13 @@ export default function ProjectsViewTeacher(): JSX.Element { -
+
+ ignoreKeys={["status", "numberOfSubmissions"]} home={"teacher"}/>
+ ignoreKeys={["status", "numberOfSubmissions", "deadline"]} home={"teacher"}/> diff --git a/frontend/src/types/tableRows.ts b/frontend/src/types/tableRows.ts new file mode 100644 index 00000000..f58466d7 --- /dev/null +++ b/frontend/src/types/tableRows.ts @@ -0,0 +1,39 @@ +export interface TableRow {} + + +export interface TableRowProjects extends TableRow { + project: { + name: string, + id: number, + } + course: { + name: string, + id: number, + } + deadline: string | null, + status: string | null, // for student + numberOfSubmissions: number | null // for teacher and only for visible projects +} + +export interface TableRowOverviewProjects extends TableRow { + project: { + name: string, + id: number, + }, + deadline: string | null, + status: string | null, // for student +} + +export interface TableRowCourses extends TableRow { + course: { + name: string, + id: number + } + numberOfProjects: number | null; // not for archived projects for teachers + shortestDeadline: string | null; // only if not archived +} + +export interface TableRowPeople extends TableRow { + name: string, + email: string +} \ No newline at end of file From c80d951ddc685cf301759ead85fda5e6eaff4cb5 Mon Sep 17 00:00:00 2001 From: EmmaVandewalle Date: Fri, 5 Apr 2024 16:37:18 +0200 Subject: [PATCH 65/90] fix: linter --- frontend/src/components/Table.tsx | 19 +++++++++++-------- frontend/src/pages/teacher/CreateProject.tsx | 4 ++-- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/Table.tsx b/frontend/src/components/Table.tsx index ff3d7bb0..8cce864a 100644 --- a/frontend/src/components/Table.tsx +++ b/frontend/src/components/Table.tsx @@ -4,17 +4,20 @@ import {Link} from "react-router-dom"; import {TableRow} from "../types/tableRows.ts"; const firstFieldWidth: number = 40; -const otherFieldsWidth = (keysLength: number) => 1 / (keysLength - 1) * 100 - firstFieldWidth / (keysLength - 1); + +function otherFieldsWidth(keysLength: number): number { + return 1 / (keysLength - 1) * 100 - firstFieldWidth / (keysLength - 1); +} function TableDataElement(props: {home: string, row: T, colIndex: number, keys: string[], index: number}): JSX.Element { const widthElement = props.colIndex == 0 ? firstFieldWidth : otherFieldsWidth(props.keys.length) - const values: any[] = Object.values(props.row) + const values = Object.values(props.row) return ( - {keys.map((field, colIndex) => ( !props.ignoreKeys.some(item => field === item) ? - + : - + ))} ))} diff --git a/frontend/src/pages/teacher/CreateProject.tsx b/frontend/src/pages/teacher/CreateProject.tsx index 50f88d06..a549cc78 100644 --- a/frontend/src/pages/teacher/CreateProject.tsx +++ b/frontend/src/pages/teacher/CreateProject.tsx @@ -1,11 +1,11 @@ import {JSX} from "react"; -import {ProjectTeacher} from "../../types/project.ts"; import {ProjectTeacherComponent} from "../../components/ProjectTeacherComponent.tsx"; import {Header} from "../../components/Header.tsx"; import {Sidebar} from "../../components/Sidebar.tsx"; +import {ProjectTeacher} from "../../types/project.ts"; export function CreateProject(): JSX.Element { - const emptyProjectTeacher: ProjectTeacherComponent = { + const emptyProjectTeacher: ProjectTeacher = { courseName: "", deadline: new Date(Date.now()), description: "", From efe0d798243685ce3c41da54e5bc1a923acdd2d0 Mon Sep 17 00:00:00 2001 From: EmmaVandewalle Date: Tue, 9 Apr 2024 11:24:27 +0200 Subject: [PATCH 66/90] fix: png import and deadline --- .../components/ProjectTeacherComponent.tsx | 33 ++++++++++--------- frontend/src/components/Statistics.tsx | 2 +- .../components/authentication/LoginForm.tsx | 3 +- frontend/src/pages/teacher/CreateProject.tsx | 6 +++- .../src/pages/teacher/ProjectViewTeacher.tsx | 9 +++-- 5 files changed, 31 insertions(+), 22 deletions(-) diff --git a/frontend/src/components/ProjectTeacherComponent.tsx b/frontend/src/components/ProjectTeacherComponent.tsx index 60e9ffa8..d32a505b 100644 --- a/frontend/src/components/ProjectTeacherComponent.tsx +++ b/frontend/src/components/ProjectTeacherComponent.tsx @@ -7,7 +7,7 @@ import {FaUpload} from "react-icons/fa"; import {ProjectTeacher, Value} from "../types/project.ts"; import "../assets/styles/teacher_components.css" -export function ProjectTeacherComponent(props: {project: ProjectTeacher }): JSX.Element { +export function ProjectTeacherComponent(props: { project: ProjectTeacher }): JSX.Element { const [projectName, setProjectName] = useState(props.project.projectName) const [courseName, setCourseName] = useState(props.project.courseName) @@ -63,22 +63,23 @@ export function ProjectTeacherComponent(props: {project: ProjectTeacher }): JSX.
-
-
diff --git a/frontend/src/components/Statistics.tsx b/frontend/src/components/Statistics.tsx index 959d7166..a57410b7 100644 --- a/frontend/src/components/Statistics.tsx +++ b/frontend/src/components/Statistics.tsx @@ -21,7 +21,7 @@ export default function Statistics(): JSX.Element { return ( <> - +
) +} \ No newline at end of file diff --git a/frontend/src/pages/login/LoginScreen.tsx b/frontend/src/pages/login/LoginScreen.tsx index 99323787..6de90a40 100644 --- a/frontend/src/pages/login/LoginScreen.tsx +++ b/frontend/src/pages/login/LoginScreen.tsx @@ -1,10 +1,10 @@ import React, {JSX, useEffect} from "react"; import {Navigate, useLocation, useRouteLoaderData} from 'react-router-dom'; import useAuth from "../../hooks/useAuth.ts"; -import loginLoader, {LOGIN_ROUTER_ID, loginLoaderObject} from "../../dataloaders/LoginLoader.tsx"; +import loginLoader, {LOGIN_ROUTER_ID, loginLoaderObject} from "../../dataloaders/LoginLoader.ts"; import LoginForm from "../../components/authentication/LoginForm.tsx"; +import {DEBUG} from "../root.tsx"; import {Token, User} from "../../utils/ApiInterfaces.ts"; -import {post_ticket} from "../../utils/api/Login.ts"; interface location_type { search?: { ticket?: string }, @@ -13,12 +13,17 @@ interface location_type { } const ticketLogin = async (ticket: string, setUser: React.Dispatch>) => { - const token: Token = await post_ticket(ticket) + let url = '/api/login?ticket=' + ticket + if (DEBUG) { + url = 'http://127.0.0.1:8000/api/login?ticket=' + ticket + } + const token = await (await fetch(url, {method: 'POST', headers: {'Content-Type': 'application/json'}})) + .json() as Token if (token.token) { localStorage.setItem('token', token.token) const result: loginLoaderObject = await loginLoader() - if (isUser(result.user)) { + if (result.user) { setUser(result.user) }else{ localStorage.removeItem('token') @@ -29,6 +34,7 @@ const ticketLogin = async (ticket: string, setUser: React.Dispatch { @@ -44,6 +50,8 @@ const isUser = (data?: User) => { } >>>>>>> 81deb20 (Functies user, projects, subjects, group, course) +======= +>>>>>>> 135265f (Revert "Functies user, projects, subjects, group, course") export default function LoginScreen(): JSX.Element { const {user, setUser} = useAuth(); const location = useLocation() as location_type; @@ -62,13 +70,9 @@ export default function LoginScreen(): JSX.Element { useEffect(() => { // If the saved token is valid => the user will be logged in if (data && data.user) { - if (isUser(data.user)) { - setUser(data.user) - }else{ - setUser(undefined) - localStorage.removeItem('token') - } - } else if (!user && ticket) { + setUser(data.user) + } + else if (!user && ticket) { void ticketLogin(ticket, setUser); } }, [data, setUser, ticket, user]); diff --git a/frontend/src/utils/InputInterfaces.ts b/frontend/src/utils/InputInterfaces.ts deleted file mode 100644 index a2514a70..00000000 --- a/frontend/src/utils/InputInterfaces.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface ProjectInput{ - name: string, - deadline: Date, - archived: boolean, - description: string, - requirements: string, - visible: boolean, - max_students: number, -} \ No newline at end of file diff --git a/frontend/src/utils/ApiFetch.ts b/frontend/src/utils/api/ApiFetch.ts similarity index 91% rename from frontend/src/utils/ApiFetch.ts rename to frontend/src/utils/api/ApiFetch.ts index 19c8d250..e4934a1d 100644 --- a/frontend/src/utils/ApiFetch.ts +++ b/frontend/src/utils/api/ApiFetch.ts @@ -1,4 +1,4 @@ -import {DEBUG} from "../pages/root.tsx"; +import {DEBUG} from "../../pages/root.tsx"; const ApiFetch = async (url: string, options?: RequestInit) => { diff --git a/frontend/src/utils/api/Groups.ts b/frontend/src/utils/api/Groups.ts index 2edec938..54bc3af5 100644 --- a/frontend/src/utils/api/Groups.ts +++ b/frontend/src/utils/api/Groups.ts @@ -1,4 +1,4 @@ -import ApiFetch from "../ApiFetch.ts"; +import ApiFetch from "./ApiFetch.ts"; export function joinGroup(groupId: number) { void ApiFetch(`/groups/${groupId}/join`, @@ -8,4 +8,16 @@ export function joinGroup(groupId: number) { export function leaveGroup(groupId: number) { void ApiFetch(`/groups/${groupId}/leave`, {method: 'POST', headers: {'Content-Type': 'application/json'}}); +} + +export async function listGroupMembers(groupId: number) { + let members = await ApiFetch(`/groups/${groupId}/members`, + {method: 'GET', headers: {'Content-Type': 'application/json'}}); + return members; +} + +export function projectGroup(projectId: number) { + let group = void ApiFetch(`/projects/${projectId}/group`, + {method: 'GET', headers: {'Content-Type': 'application/json'}}); + return group; } \ No newline at end of file diff --git a/frontend/src/utils/api/Login.ts b/frontend/src/utils/api/Login.ts index af35a0f5..e69de29b 100644 --- a/frontend/src/utils/api/Login.ts +++ b/frontend/src/utils/api/Login.ts @@ -1,16 +0,0 @@ -import {DEBUG} from "../../pages/root.tsx"; -import {Token} from "../ApiInterfaces.ts"; - -export async function post_ticket(ticket: string){ - let url = '/api/login?ticket=' + ticket - if (DEBUG) { - url = 'http://127.0.0.1:8000/api/login?ticket=' + ticket - } - - return await (await fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - },})) - .json() as Token -} \ No newline at end of file diff --git a/frontend/src/utils/api/Project.ts b/frontend/src/utils/api/Project.ts index 04e62b87..e69de29b 100644 --- a/frontend/src/utils/api/Project.ts +++ b/frontend/src/utils/api/Project.ts @@ -1,36 +0,0 @@ -import apiFetch from "../ApiFetch.ts"; -import {ProjectInput} from "../InputInterfaces.ts"; -import {Backend_group, Backend_Project} from "../BackendInterfaces.ts"; -import {mapGroup} from "../ApiTypesMapper.ts"; - -export async function project_create_group(project_id: number){ - const groupData = await apiFetch(`/projects/${project_id}/groups`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - } - }) as Backend_group - return mapGroup(groupData) -} - -export async function update_project(project_id: number, projectInput: ProjectInput){ - const projectData = await apiFetch(`/projects/${project_id}`, { - method: 'PATCH', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(projectInput) - }) as Backend_Project - return { - project_id: projectData.id, - project_name: projectData.name, - project_deadline: projectData.deadline, - project_archived: projectData.archived, - project_description: projectData.description, - project_requirements: projectData.requirements, - project_visible: projectData.visible, - project_max_students: projectData.max_students, - subject_id: projectData.subject_id - } -} - diff --git a/frontend/src/utils/api/Student.ts b/frontend/src/utils/api/Student.ts index 2269b84b..e69de29b 100644 --- a/frontend/src/utils/api/Student.ts +++ b/frontend/src/utils/api/Student.ts @@ -1,7 +0,0 @@ -import apiFetch from "../ApiFetch.ts"; - -export function join_subject(subjectId: number){ - void apiFetch(`/student/subjects/${subjectId}/join`, { - method: 'POST' - }) -} \ No newline at end of file diff --git a/frontend/src/utils/api/Subject.ts b/frontend/src/utils/api/Subject.ts index fa9850f3..e69de29b 100644 --- a/frontend/src/utils/api/Subject.ts +++ b/frontend/src/utils/api/Subject.ts @@ -1,25 +0,0 @@ -import {ProjectInput} from "../InputInterfaces.ts"; -import ApiFetch from "../ApiFetch.ts"; -import {Backend_Project} from "../BackendInterfaces.ts"; - -export async function subject_create_project(subjectId: number, projectInput: ProjectInput) { - const projectData: Backend_Project = (await ApiFetch(`/subjects/${subjectId}/projects`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(projectInput) - }) as Backend_Project) - - return { - project_id: projectData.id, - project_name: projectData.name, - project_deadline: projectData.deadline, - project_archived: projectData.archived, - project_description: projectData.description, - project_requirements: projectData.requirements, - project_visible: projectData.visible, - project_max_students: projectData.max_students, - subject_id: projectData.subject_id - } -} \ No newline at end of file diff --git a/frontend/src/utils/api/Teacher.ts b/frontend/src/utils/api/Teacher.ts index ab4a1a09..e69de29b 100644 --- a/frontend/src/utils/api/Teacher.ts +++ b/frontend/src/utils/api/Teacher.ts @@ -1,13 +0,0 @@ -import {Backend_Subject} from "../BackendInterfaces.ts"; -import apiFetch from "../ApiFetch.ts"; - -export async function createSubject(name: string) { - return (await apiFetch('/teacher/subjects', - { - headers: { - 'Content-Type': 'application/json' - }, - method: 'POST', - body: JSON.stringify({name: name}) - })) as Backend_Subject -} \ No newline at end of file diff --git a/frontend/src/utils/api/User.ts b/frontend/src/utils/api/User.ts index f731bc77..e69de29b 100644 --- a/frontend/src/utils/api/User.ts +++ b/frontend/src/utils/api/User.ts @@ -1,8 +0,0 @@ -import {Language} from "../../components/Settings.tsx"; -import apiFetch from "../ApiFetch.ts"; - -export function modify_language(language: Language){ - void apiFetch(`/user?language=${language}`, { - method: 'PATCH' - }) -} \ No newline at end of file From 1466c693298b5712e14f5b045f137d9cb1e32185 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stef=20Oss=C3=A9?= Date: Sat, 13 Apr 2024 10:02:04 +0200 Subject: [PATCH 68/90] rebase --- .../components/authentication/RequireAuth.tsx | 2 +- frontend/src/dataloaders/LoginLoader.ts | 9 ---- .../src/dataloaders/ProjectsStudentLoader.ts | 2 +- frontend/src/dataloaders/SharedFunctions.ts | 48 +++++++++++++++---- .../src/dataloaders/projectsTeacherLoader.ts | 2 +- frontend/src/main.tsx | 8 ---- frontend/src/pages/Test.tsx | 2 - frontend/src/pages/login/LoginScreen.tsx | 18 ------- frontend/src/pages/root.tsx | 8 ++-- .../src/pages/student/CoursesViewStudent.tsx | 13 ++--- .../src/pages/student/ProjectsViewStudent.tsx | 15 ++---- .../src/pages/teacher/CoursesViewTeacher.tsx | 7 +-- .../src/pages/teacher/ProjectsViewTeacher.tsx | 7 +-- frontend/src/utils/{api => }/ApiFetch.ts | 2 +- frontend/src/utils/InputInterfaces.ts | 9 ++++ frontend/src/utils/api/Groups.ts | 14 +----- frontend/src/utils/api/Login.ts | 16 +++++++ frontend/src/utils/api/Project.ts | 35 ++++++++++++++ frontend/src/utils/api/Student.ts | 7 +++ frontend/src/utils/api/Subject.ts | 25 ++++++++++ frontend/src/utils/api/Teacher.ts | 13 +++++ frontend/src/utils/api/User.ts | 8 ++++ 22 files changed, 173 insertions(+), 97 deletions(-) rename frontend/src/utils/{api => }/ApiFetch.ts (91%) create mode 100644 frontend/src/utils/InputInterfaces.ts diff --git a/frontend/src/components/authentication/RequireAuth.tsx b/frontend/src/components/authentication/RequireAuth.tsx index 3491ea11..7841130b 100644 --- a/frontend/src/components/authentication/RequireAuth.tsx +++ b/frontend/src/components/authentication/RequireAuth.tsx @@ -10,7 +10,7 @@ const RequireAuth = ({allowedRoles}: Props) => { const location = useLocation(); if (user) { return ( - (allowedRoles && user.roles.find(role => allowedRoles.includes(role))) + (allowedRoles && user.user_roles.find(role => allowedRoles.includes(role))) ? : ); diff --git a/frontend/src/dataloaders/LoginLoader.ts b/frontend/src/dataloaders/LoginLoader.ts index afc1022e..43711402 100644 --- a/frontend/src/dataloaders/LoginLoader.ts +++ b/frontend/src/dataloaders/LoginLoader.ts @@ -1,15 +1,7 @@ import {User} from "../utils/ApiInterfaces.ts"; -import apiFetch from "../utils/ApiFetch.ts"; import {Backend_user} from "../utils/BackendInterfaces.ts"; import {mapUser} from "../utils/ApiTypesMapper.ts"; -<<<<<<< HEAD:frontend/src/dataloaders/LoginLoader.tsx -<<<<<<< HEAD:frontend/src/dataloaders/LoginLoader.ts -======= import apiFetch from "../utils/ApiFetch.ts"; ->>>>>>> 81deb20 (Functies user, projects, subjects, group, course):frontend/src/dataloaders/LoginLoader.tsx -======= -import apiFetch from "../utils/api/ApiFetch.ts"; ->>>>>>> 135265f (Revert "Functies user, projects, subjects, group, course"):frontend/src/dataloaders/LoginLoader.ts export const LOGIN_ROUTER_ID = "login"; @@ -19,7 +11,6 @@ export interface loginLoaderObject { const isUser = (data?: Backend_user) => { return (data && data.id && data.name && data.email && data.roles && data.language); - } export default async function loginLoader(): Promise { diff --git a/frontend/src/dataloaders/ProjectsStudentLoader.ts b/frontend/src/dataloaders/ProjectsStudentLoader.ts index 43b29101..9e782310 100644 --- a/frontend/src/dataloaders/ProjectsStudentLoader.ts +++ b/frontend/src/dataloaders/ProjectsStudentLoader.ts @@ -1,6 +1,6 @@ import {CompleteProject, Group, Submission} from "../utils/ApiInterfaces.ts"; import {getAllProjectsAndSubjects, teacherStudentRole} from "./SharedFunctions.ts"; -import apiFetch from "../utils/api/ApiFetch.ts"; +import apiFetch from "../utils/ApiFetch.ts"; import {Backend_group, Backend_submission} from "../utils/BackendInterfaces.ts"; import {mapGroup, mapSubmission} from "../utils/ApiTypesMapper.ts"; diff --git a/frontend/src/dataloaders/SharedFunctions.ts b/frontend/src/dataloaders/SharedFunctions.ts index 13517a33..a14535b6 100644 --- a/frontend/src/dataloaders/SharedFunctions.ts +++ b/frontend/src/dataloaders/SharedFunctions.ts @@ -1,20 +1,47 @@ import {Project, properSubject, Subject} from "../utils/ApiInterfaces.ts"; import apiFetch from "../utils/ApiFetch.ts"; -import {mapProjectList, mapSubjectList} from "../utils/ApiTypesMapper.ts"; import {Backend_Project, Backend_Subject} from "../utils/BackendInterfaces.ts"; -import apiFetch from "../utils/api/ApiFetch.ts"; +import {mapProjectList, mapSubjectList} from "../utils/ApiTypesMapper.ts"; export enum teacherStudentRole { STUDENT = "student", TEACHER = "teacher" } -export async function coursesLoader(role: teacherStudentRole): Promise { - const {subjects, projects} = await getAllProjectsAndSubjects(role); - if (!Array.isArray(projects) || !Array.isArray(subjects)) { +export interface CourseLoaderObject { + course?: properSubject +} + +export async function parse_id_and_get_item(id: string | undefined, loader: (id: number) => Promise): Promise { + if (!id || isNaN(parseInt(id))) { + return undefined; + } + const parsed_id = parseInt(id); + return (await loader(parsed_id)).find(() => true); +} + +export async function courseLoader(role: teacherStudentRole, course_id: string | undefined): Promise { + return { + course: await parse_id_and_get_item( + course_id, + (id) => coursesLoader(role, id) + ) + }; +} + +export async function coursesLoader(role: teacherStudentRole, course_id?: number): Promise { + const temp = await getAllProjectsAndSubjects(role); + let courses = temp.subjects; + const projects = temp.projects; + if (!Array.isArray(projects) || !Array.isArray(courses)) { throw Error("Problem loading projects or courses."); } - return subjects.map((subject) => { + + if (course_id) { + courses = courses.filter(course => course.subject_id === course_id); + } + + return courses.map((subject) => { const subjectProjects = projects.filter(project => project.subject_id === subject.subject_id); if (subjectProjects.length === 0) { return { @@ -46,10 +73,13 @@ export interface projectsAndSubjects { subjects: Subject[] } -export async function getAllProjectsAndSubjects(role: teacherStudentRole): Promise { +export async function getAllProjectsAndSubjects(role: teacherStudentRole, filter_on_current: boolean = false): Promise { const apiSubjects = (await apiFetch(`/${role}/subjects`)) as Backend_Subject[]; const apiProjects = (await apiFetch(`/${role}/projects`)) as Backend_Project[]; - const projects: Project[] = mapProjectList(apiProjects); + let projects: Project[] = mapProjectList(apiProjects); + if (filter_on_current) { + projects = projects.filter(project => project.project_visible && !project.project_archived) + } const subjects: Subject[] = mapSubjectList(apiSubjects); return {projects, subjects} -} +} \ No newline at end of file diff --git a/frontend/src/dataloaders/projectsTeacherLoader.ts b/frontend/src/dataloaders/projectsTeacherLoader.ts index a709ecf1..87fedcea 100644 --- a/frontend/src/dataloaders/projectsTeacherLoader.ts +++ b/frontend/src/dataloaders/projectsTeacherLoader.ts @@ -1,6 +1,6 @@ import {CompleteProjectTeacher, Group, Submission} from "../utils/ApiInterfaces.ts"; import {getAllProjectsAndSubjects, teacherStudentRole} from "./SharedFunctions.ts"; -import apiFetch from "../utils/api/ApiFetch.ts"; +import apiFetch from "../utils/ApiFetch.ts"; import {Backend_group} from "../utils/BackendInterfaces.ts"; import {mapGroupList} from "../utils/ApiTypesMapper.ts"; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 86dfc611..414e121b 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -56,19 +56,11 @@ const router = createBrowserRouter( }> } loader={teacherLoader}/> -<<<<<<< HEAD - } - loader={projectsTeacherLoader}/> - }/> - } loader={coursesTeacherLoader}/> -======= }/> }/> }/> } /*loader={subjectsTeacherLoader} id={SUBJECT_TEACHER_ROUTER_ID}*//> }/> ->>>>>>> 685d6cb (feat: last pages added) }/> diff --git a/frontend/src/pages/Test.tsx b/frontend/src/pages/Test.tsx index 626d973e..b243570a 100644 --- a/frontend/src/pages/Test.tsx +++ b/frontend/src/pages/Test.tsx @@ -1,8 +1,6 @@ -import {projectGroup} from "../utils/api/Groups.ts"; export default function Test () { function clickEvent () { - console.log(projectGroup(2)) } return (
diff --git a/frontend/src/pages/login/LoginScreen.tsx b/frontend/src/pages/login/LoginScreen.tsx index 6de90a40..7b9ec838 100644 --- a/frontend/src/pages/login/LoginScreen.tsx +++ b/frontend/src/pages/login/LoginScreen.tsx @@ -34,24 +34,6 @@ const ticketLogin = async (ticket: string, setUser: React.Dispatch { - return (data && data.id && data.name && data.email && data.roles); - -} - -======= ->>>>>>> a25cbf2 (mapping backend_user <> user) -======= -const isUser = (data?: User) => { - return (data && data.user_id && data.user_name && data.user_email && data.user_roles); -} - ->>>>>>> 81deb20 (Functies user, projects, subjects, group, course) -======= ->>>>>>> 135265f (Revert "Functies user, projects, subjects, group, course") export default function LoginScreen(): JSX.Element { const {user, setUser} = useAuth(); const location = useLocation() as location_type; diff --git a/frontend/src/pages/root.tsx b/frontend/src/pages/root.tsx index b87b4bb4..747c296a 100644 --- a/frontend/src/pages/root.tsx +++ b/frontend/src/pages/root.tsx @@ -2,17 +2,17 @@ import {JSX} from "react"; import useAuth from "../hooks/useAuth.ts"; import {Navigate} from "react-router-dom"; -export const DEBUG: boolean = false; // should always be false on the repo. +export const DEBUG: boolean = true; // should always be false on the repo. export default function Root(): JSX.Element { const {user} = useAuth() let to: string = "/error" - if (user?.roles.includes('TEACHER')) { + if (user?.user_roles.includes('TEACHER')) { to = "/teacher"; - } else if (user?.roles.includes('STUDENT')) { + } else if (user?.user_roles.includes('STUDENT')) { to = "/student"; - } else if (user?.roles.includes('ADMIN')) { + } else if (user?.user_roles.includes('ADMIN')) { to = "/admin"; } diff --git a/frontend/src/pages/student/CoursesViewStudent.tsx b/frontend/src/pages/student/CoursesViewStudent.tsx index c8af559e..fa90deb6 100644 --- a/frontend/src/pages/student/CoursesViewStudent.tsx +++ b/frontend/src/pages/student/CoursesViewStudent.tsx @@ -3,19 +3,15 @@ import {Header} from "../../components/Header.tsx"; import {Sidebar} from "../../components/Sidebar.tsx"; import '../../assets/styles/students_components.css' import {SearchBar} from "../../components/SearchBar.tsx"; -<<<<<<< HEAD -import {Table, TableRowCourses} from "../../components/Table.tsx"; -import {useRouteLoaderData} from "react-router-dom"; -import {COURSES_STUDENT_ROUTER_ID, coursesStudentLoaderObject} from "../../dataloaders/CoursesStudentLoader.ts"; -======= import {Table} from "../../components/Table.tsx"; import {TableRowCourses} from "../../types/tableRows.ts"; ->>>>>>> 685d6cb (feat: last pages added) +import {COURSES_STUDENT_ROUTER_ID, coursesStudentLoaderObject} from "../../dataloaders/CoursesStudentLoader.ts"; +import {useRouteLoaderData} from "react-router-dom"; export default function CoursesViewStudent(): JSX.Element { const data: coursesStudentLoaderObject = useRouteLoaderData(COURSES_STUDENT_ROUTER_ID) as coursesStudentLoaderObject - console.log(data.courses) + console.log(data) const tableCoursesActive: TableRowCourses[] = [ { @@ -60,7 +56,8 @@ export default function CoursesViewStudent(): JSX.Element {
- { typeof values[props.index] === "object" ? ( - - {values[props.index]["name"]} + + { typeof values[props.index] === "object" && "id" in values[props.index] ? ( + + {(values[props.index] as {name: string, id: number}).name} ) : ( <>{values[props.index]} @@ -51,9 +54,9 @@ export function Table(props: { title: string, data: T[], ign
-
+
diff --git a/frontend/src/pages/student/ProjectsViewStudent.tsx b/frontend/src/pages/student/ProjectsViewStudent.tsx index 92a41419..a2cddc96 100644 --- a/frontend/src/pages/student/ProjectsViewStudent.tsx +++ b/frontend/src/pages/student/ProjectsViewStudent.tsx @@ -2,15 +2,11 @@ import {JSX} from "react"; import {Header} from "../../components/Header.tsx"; import {Sidebar} from "../../components/Sidebar.tsx"; import {SearchBar} from "../../components/SearchBar.tsx"; -<<<<<<< HEAD -import {Table, TableRowProjects} from "../../components/Table.tsx"; -import {useRouteLoaderData} from "react-router-dom"; -import {PROJECTS_STUDENT_ROUTER_ID} from "../../dataloaders/ProjectsStudentLoader.ts"; -import {studentLoaderObject} from "../../dataloaders/StudentLoader.ts"; -======= import {Table} from "../../components/Table.tsx"; import {TableRowProjects} from "../../types/tableRows.ts"; ->>>>>>> 685d6cb (feat: last pages added) +import {studentLoaderObject} from "../../dataloaders/StudentLoader.ts"; +import {useRouteLoaderData} from "react-router-dom"; +import {PROJECTS_STUDENT_ROUTER_ID} from "../../dataloaders/ProjectsStudentLoader.ts"; export default function ProjectsViewStudent(): JSX.Element { @@ -76,12 +72,7 @@ export default function ProjectsViewStudent(): JSX.Element {
-<<<<<<< HEAD -
-=======
->>>>>>> 685d6cb (feat: last pages added) diff --git a/frontend/src/pages/teacher/CoursesViewTeacher.tsx b/frontend/src/pages/teacher/CoursesViewTeacher.tsx index 4eb50fa0..9e25d558 100644 --- a/frontend/src/pages/teacher/CoursesViewTeacher.tsx +++ b/frontend/src/pages/teacher/CoursesViewTeacher.tsx @@ -4,15 +4,12 @@ import {Header} from "../../components/Header.tsx"; import {Sidebar} from "../../components/Sidebar.tsx"; import {SearchBar} from "../../components/SearchBar.tsx"; import {RegularATag} from "../../components/RegularATag.tsx"; -<<<<<<< HEAD -import {COURSES_TEACHER_ROUTER_ID, coursesTeacherLoaderObject} from "../../dataloaders/CoursesTeacherLoader.ts"; -import {useRouteLoaderData} from "react-router-dom"; -======= import {TableRowCourses} from "../../types/tableRows.ts"; +import {useRouteLoaderData} from "react-router-dom"; +import {COURSES_TEACHER_ROUTER_ID, coursesTeacherLoaderObject} from "../../dataloaders/CoursesTeacherLoader.ts"; // import {useRouteLoaderData} from "react-router-dom"; // import {SUBJECT_TEACHER_ROUTER_ID, subjectsTeacherLoaderObject} from "../../dataloaders/SubjectsTeacherLoader.ts"; ->>>>>>> 685d6cb (feat: last pages added) export default function CoursesViewTeacher(): JSX.Element { diff --git a/frontend/src/pages/teacher/ProjectsViewTeacher.tsx b/frontend/src/pages/teacher/ProjectsViewTeacher.tsx index abc288a7..ae0d1d89 100644 --- a/frontend/src/pages/teacher/ProjectsViewTeacher.tsx +++ b/frontend/src/pages/teacher/ProjectsViewTeacher.tsx @@ -4,13 +4,10 @@ import {Sidebar} from "../../components/Sidebar.tsx"; import {SearchBar} from "../../components/SearchBar.tsx"; import {Table} from "../../components/Table.tsx"; import {RegularATag} from "../../components/RegularATag.tsx"; -<<<<<<< HEAD -import {useRouteLoaderData} from "react-router-dom"; +import {TableRowProjects} from "../../types/tableRows.ts"; import {teacherLoaderObject} from "../../dataloaders/TeacherLoader.ts"; +import {useRouteLoaderData} from "react-router-dom"; import {PROJECTS_TEACHER_ROUTER_ID} from "../../dataloaders/projectsTeacherLoader.ts"; -======= -import {TableRowProjects} from "../../types/tableRows.ts"; ->>>>>>> 685d6cb (feat: last pages added) export default function ProjectsViewTeacher(): JSX.Element { const tableProjectsActive: TableRowProjects[] = [ diff --git a/frontend/src/utils/api/ApiFetch.ts b/frontend/src/utils/ApiFetch.ts similarity index 91% rename from frontend/src/utils/api/ApiFetch.ts rename to frontend/src/utils/ApiFetch.ts index e4934a1d..19c8d250 100644 --- a/frontend/src/utils/api/ApiFetch.ts +++ b/frontend/src/utils/ApiFetch.ts @@ -1,4 +1,4 @@ -import {DEBUG} from "../../pages/root.tsx"; +import {DEBUG} from "../pages/root.tsx"; const ApiFetch = async (url: string, options?: RequestInit) => { diff --git a/frontend/src/utils/InputInterfaces.ts b/frontend/src/utils/InputInterfaces.ts new file mode 100644 index 00000000..a2514a70 --- /dev/null +++ b/frontend/src/utils/InputInterfaces.ts @@ -0,0 +1,9 @@ +export interface ProjectInput{ + name: string, + deadline: Date, + archived: boolean, + description: string, + requirements: string, + visible: boolean, + max_students: number, +} \ No newline at end of file diff --git a/frontend/src/utils/api/Groups.ts b/frontend/src/utils/api/Groups.ts index 54bc3af5..2edec938 100644 --- a/frontend/src/utils/api/Groups.ts +++ b/frontend/src/utils/api/Groups.ts @@ -1,4 +1,4 @@ -import ApiFetch from "./ApiFetch.ts"; +import ApiFetch from "../ApiFetch.ts"; export function joinGroup(groupId: number) { void ApiFetch(`/groups/${groupId}/join`, @@ -8,16 +8,4 @@ export function joinGroup(groupId: number) { export function leaveGroup(groupId: number) { void ApiFetch(`/groups/${groupId}/leave`, {method: 'POST', headers: {'Content-Type': 'application/json'}}); -} - -export async function listGroupMembers(groupId: number) { - let members = await ApiFetch(`/groups/${groupId}/members`, - {method: 'GET', headers: {'Content-Type': 'application/json'}}); - return members; -} - -export function projectGroup(projectId: number) { - let group = void ApiFetch(`/projects/${projectId}/group`, - {method: 'GET', headers: {'Content-Type': 'application/json'}}); - return group; } \ No newline at end of file diff --git a/frontend/src/utils/api/Login.ts b/frontend/src/utils/api/Login.ts index e69de29b..af35a0f5 100644 --- a/frontend/src/utils/api/Login.ts +++ b/frontend/src/utils/api/Login.ts @@ -0,0 +1,16 @@ +import {DEBUG} from "../../pages/root.tsx"; +import {Token} from "../ApiInterfaces.ts"; + +export async function post_ticket(ticket: string){ + let url = '/api/login?ticket=' + ticket + if (DEBUG) { + url = 'http://127.0.0.1:8000/api/login?ticket=' + ticket + } + + return await (await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + },})) + .json() as Token +} \ No newline at end of file diff --git a/frontend/src/utils/api/Project.ts b/frontend/src/utils/api/Project.ts index e69de29b..e41d5836 100644 --- a/frontend/src/utils/api/Project.ts +++ b/frontend/src/utils/api/Project.ts @@ -0,0 +1,35 @@ +import apiFetch from "../ApiFetch.ts"; +import {Backend_group, Backend_Project} from "../BackendInterfaces.ts"; +import {mapGroup} from "../ApiTypesMapper.ts"; +import {ProjectInput} from "../InputInterfaces.ts"; + +export async function project_create_group(project_id: number){ + const groupData = await apiFetch(`/projects/${project_id}/groups`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }) as Backend_group + return mapGroup(groupData) +} + +export async function update_project(project_id: number, projectInput: ProjectInput){ + const projectData = await apiFetch(`/projects/${project_id}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(projectInput) + }) as Backend_Project + return { + project_id: projectData.id, + project_name: projectData.name, + project_deadline: projectData.deadline, + project_archived: projectData.archived, + project_description: projectData.description, + project_requirements: projectData.requirements, + project_visible: projectData.visible, + project_max_students: projectData.max_students, + subject_id: projectData.subject_id + } +} diff --git a/frontend/src/utils/api/Student.ts b/frontend/src/utils/api/Student.ts index e69de29b..2269b84b 100644 --- a/frontend/src/utils/api/Student.ts +++ b/frontend/src/utils/api/Student.ts @@ -0,0 +1,7 @@ +import apiFetch from "../ApiFetch.ts"; + +export function join_subject(subjectId: number){ + void apiFetch(`/student/subjects/${subjectId}/join`, { + method: 'POST' + }) +} \ No newline at end of file diff --git a/frontend/src/utils/api/Subject.ts b/frontend/src/utils/api/Subject.ts index e69de29b..fa9850f3 100644 --- a/frontend/src/utils/api/Subject.ts +++ b/frontend/src/utils/api/Subject.ts @@ -0,0 +1,25 @@ +import {ProjectInput} from "../InputInterfaces.ts"; +import ApiFetch from "../ApiFetch.ts"; +import {Backend_Project} from "../BackendInterfaces.ts"; + +export async function subject_create_project(subjectId: number, projectInput: ProjectInput) { + const projectData: Backend_Project = (await ApiFetch(`/subjects/${subjectId}/projects`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(projectInput) + }) as Backend_Project) + + return { + project_id: projectData.id, + project_name: projectData.name, + project_deadline: projectData.deadline, + project_archived: projectData.archived, + project_description: projectData.description, + project_requirements: projectData.requirements, + project_visible: projectData.visible, + project_max_students: projectData.max_students, + subject_id: projectData.subject_id + } +} \ No newline at end of file diff --git a/frontend/src/utils/api/Teacher.ts b/frontend/src/utils/api/Teacher.ts index e69de29b..ab4a1a09 100644 --- a/frontend/src/utils/api/Teacher.ts +++ b/frontend/src/utils/api/Teacher.ts @@ -0,0 +1,13 @@ +import {Backend_Subject} from "../BackendInterfaces.ts"; +import apiFetch from "../ApiFetch.ts"; + +export async function createSubject(name: string) { + return (await apiFetch('/teacher/subjects', + { + headers: { + 'Content-Type': 'application/json' + }, + method: 'POST', + body: JSON.stringify({name: name}) + })) as Backend_Subject +} \ No newline at end of file diff --git a/frontend/src/utils/api/User.ts b/frontend/src/utils/api/User.ts index e69de29b..f731bc77 100644 --- a/frontend/src/utils/api/User.ts +++ b/frontend/src/utils/api/User.ts @@ -0,0 +1,8 @@ +import {Language} from "../../components/Settings.tsx"; +import apiFetch from "../ApiFetch.ts"; + +export function modify_language(language: Language){ + void apiFetch(`/user?language=${language}`, { + method: 'PATCH' + }) +} \ No newline at end of file From 791d42ef5e3bc85c6fb31a71eb43dfdd8f782475 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stef=20Oss=C3=A9?= Date: Tue, 9 Apr 2024 21:39:56 +0200 Subject: [PATCH 69/90] Functies user, projects, subjects, group, course --- .../components/authentication/ErrorLogin.tsx | 2 +- .../{LoginLoader.ts => LoginLoader.tsx} | 0 frontend/src/main.tsx | 6 ++--- frontend/src/pages/login/LoginScreen.tsx | 27 ++++++++++--------- 4 files changed, 19 insertions(+), 16 deletions(-) rename frontend/src/dataloaders/{LoginLoader.ts => LoginLoader.tsx} (100%) diff --git a/frontend/src/components/authentication/ErrorLogin.tsx b/frontend/src/components/authentication/ErrorLogin.tsx index 0ab4aa45..1e0ab292 100644 --- a/frontend/src/components/authentication/ErrorLogin.tsx +++ b/frontend/src/components/authentication/ErrorLogin.tsx @@ -1,5 +1,5 @@ import {JSX} from "react"; -import delphi_full from "../../../public/delphi_full.png"; +import delphi_full from "/delphi_full.png"; export default function ErrorLogin(): JSX.Element { diff --git a/frontend/src/dataloaders/LoginLoader.ts b/frontend/src/dataloaders/LoginLoader.tsx similarity index 100% rename from frontend/src/dataloaders/LoginLoader.ts rename to frontend/src/dataloaders/LoginLoader.tsx diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 414e121b..064a8f0c 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -12,7 +12,7 @@ import studentLoader, {STUDENT_ROUTER_ID} from "./dataloaders/StudentLoader.ts"; import Unauthorized from "./components/authentication/Unauthorized.tsx"; import teacherLoader, {TEACHER_ROUTER_ID} from "./dataloaders/TeacherLoader.ts"; // import coursesTeacherLoader, {SUBJECT_TEACHER_ROUTER_ID} from "./dataloaders/CoursesTeacherLoader.ts"; -import loginLoader, {LOGIN_ROUTER_ID} from "./dataloaders/LoginLoader.ts"; +import loginLoader, {LOGIN_ROUTER_ID} from "./dataloaders/LoginLoader.tsx"; import ErrorLogin from "./components/authentication/ErrorLogin.tsx"; import ProjectsViewStudent from "./pages/student/ProjectsViewStudent.tsx"; import CoursesViewStudent from "./pages/student/CoursesViewStudent.tsx"; @@ -46,7 +46,7 @@ const router = createBrowserRouter( }/> - }> + }> } loader={studentLoader}/> }/> }/> @@ -54,7 +54,7 @@ const router = createBrowserRouter( }/> - }> + }> } loader={teacherLoader}/> }/> }/> diff --git a/frontend/src/pages/login/LoginScreen.tsx b/frontend/src/pages/login/LoginScreen.tsx index 7b9ec838..f02eea18 100644 --- a/frontend/src/pages/login/LoginScreen.tsx +++ b/frontend/src/pages/login/LoginScreen.tsx @@ -1,10 +1,10 @@ import React, {JSX, useEffect} from "react"; import {Navigate, useLocation, useRouteLoaderData} from 'react-router-dom'; import useAuth from "../../hooks/useAuth.ts"; -import loginLoader, {LOGIN_ROUTER_ID, loginLoaderObject} from "../../dataloaders/LoginLoader.ts"; +import loginLoader, {LOGIN_ROUTER_ID, loginLoaderObject} from "../../dataloaders/LoginLoader.tsx"; import LoginForm from "../../components/authentication/LoginForm.tsx"; -import {DEBUG} from "../root.tsx"; import {Token, User} from "../../utils/ApiInterfaces.ts"; +import {post_ticket} from "../../utils/api/Login.ts"; interface location_type { search?: { ticket?: string }, @@ -13,17 +13,12 @@ interface location_type { } const ticketLogin = async (ticket: string, setUser: React.Dispatch>) => { - let url = '/api/login?ticket=' + ticket - if (DEBUG) { - url = 'http://127.0.0.1:8000/api/login?ticket=' + ticket - } - const token = await (await fetch(url, {method: 'POST', headers: {'Content-Type': 'application/json'}})) - .json() as Token + const token: Token = await post_ticket(ticket) if (token.token) { localStorage.setItem('token', token.token) const result: loginLoaderObject = await loginLoader() - if (result.user) { + if (isUser(result.user)) { setUser(result.user) }else{ localStorage.removeItem('token') @@ -34,6 +29,10 @@ const ticketLogin = async (ticket: string, setUser: React.Dispatch { + return (data && data.user_id && data.user_name && data.user_email && data.user_roles); +} + export default function LoginScreen(): JSX.Element { const {user, setUser} = useAuth(); const location = useLocation() as location_type; @@ -52,9 +51,13 @@ export default function LoginScreen(): JSX.Element { useEffect(() => { // If the saved token is valid => the user will be logged in if (data && data.user) { - setUser(data.user) - } - else if (!user && ticket) { + if (isUser(data.user)) { + setUser(data.user) + }else{ + setUser(undefined) + localStorage.removeItem('token') + } + } else if (!user && ticket) { void ticketLogin(ticket, setUser); } }, [data, setUser, ticket, user]); From 5c1239e876ec524c8fd392180e4aff777b4944ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stef=20Oss=C3=A9?= Date: Tue, 9 Apr 2024 22:28:48 +0200 Subject: [PATCH 70/90] debug false --- frontend/src/pages/root.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/root.tsx b/frontend/src/pages/root.tsx index 747c296a..a9c2d7b2 100644 --- a/frontend/src/pages/root.tsx +++ b/frontend/src/pages/root.tsx @@ -2,7 +2,7 @@ import {JSX} from "react"; import useAuth from "../hooks/useAuth.ts"; import {Navigate} from "react-router-dom"; -export const DEBUG: boolean = true; // should always be false on the repo. +export const DEBUG: boolean = false; // should always be false on the repo. export default function Root(): JSX.Element { const {user} = useAuth() From 4ae0c316cdf1bccdb2da66e0d78ee4a00f9986f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stef=20Oss=C3=A9?= Date: Sat, 13 Apr 2024 09:15:40 +0200 Subject: [PATCH 71/90] small fix --- frontend/src/dataloaders/{LoginLoader.tsx => LoginLoader.ts} | 0 frontend/src/pages/login/LoginScreen.tsx | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename frontend/src/dataloaders/{LoginLoader.tsx => LoginLoader.ts} (100%) diff --git a/frontend/src/dataloaders/LoginLoader.tsx b/frontend/src/dataloaders/LoginLoader.ts similarity index 100% rename from frontend/src/dataloaders/LoginLoader.tsx rename to frontend/src/dataloaders/LoginLoader.ts diff --git a/frontend/src/pages/login/LoginScreen.tsx b/frontend/src/pages/login/LoginScreen.tsx index f02eea18..9e12869a 100644 --- a/frontend/src/pages/login/LoginScreen.tsx +++ b/frontend/src/pages/login/LoginScreen.tsx @@ -1,7 +1,7 @@ import React, {JSX, useEffect} from "react"; import {Navigate, useLocation, useRouteLoaderData} from 'react-router-dom'; import useAuth from "../../hooks/useAuth.ts"; -import loginLoader, {LOGIN_ROUTER_ID, loginLoaderObject} from "../../dataloaders/LoginLoader.tsx"; +import loginLoader, {LOGIN_ROUTER_ID, loginLoaderObject} from "../../dataloaders/LoginLoader.ts"; import LoginForm from "../../components/authentication/LoginForm.tsx"; import {Token, User} from "../../utils/ApiInterfaces.ts"; import {post_ticket} from "../../utils/api/Login.ts"; From 02ec4995d708fc04fe0f133ad02982d6486cd2e2 Mon Sep 17 00:00:00 2001 From: Matthias Seghers Date: Thu, 4 Apr 2024 15:25:14 +0200 Subject: [PATCH 72/90] merging main --- frontend/src/dataloaders/LoginLoader.ts | 2 +- frontend/src/pages/login/LoginScreen.tsx | 16 ++---- .../src/pages/student/CourseViewStudent.tsx | 53 +++++++++---------- 3 files changed, 31 insertions(+), 40 deletions(-) diff --git a/frontend/src/dataloaders/LoginLoader.ts b/frontend/src/dataloaders/LoginLoader.ts index 7c96b78e..76c4405d 100644 --- a/frontend/src/dataloaders/LoginLoader.ts +++ b/frontend/src/dataloaders/LoginLoader.ts @@ -1,7 +1,7 @@ import {User} from "../utils/ApiInterfaces.ts"; +import apiFetch from "../utils/ApiFetch.ts"; import {Backend_user} from "../utils/BackendInterfaces.ts"; import {mapUser} from "../utils/ApiTypesMapper.ts"; -import apiFetch from "../utils/ApiFetch.ts"; export const LOGIN_ROUTER_ID = "login"; diff --git a/frontend/src/pages/login/LoginScreen.tsx b/frontend/src/pages/login/LoginScreen.tsx index 9e12869a..1255e1a0 100644 --- a/frontend/src/pages/login/LoginScreen.tsx +++ b/frontend/src/pages/login/LoginScreen.tsx @@ -18,7 +18,7 @@ const ticketLogin = async (ticket: string, setUser: React.Dispatch { - return (data && data.user_id && data.user_name && data.user_email && data.user_roles); -} - export default function LoginScreen(): JSX.Element { const {user, setUser} = useAuth(); const location = useLocation() as location_type; @@ -51,13 +47,9 @@ export default function LoginScreen(): JSX.Element { useEffect(() => { // If the saved token is valid => the user will be logged in if (data && data.user) { - if (isUser(data.user)) { - setUser(data.user) - }else{ - setUser(undefined) - localStorage.removeItem('token') - } - } else if (!user && ticket) { + setUser(data.user) + } + else if (!user && ticket) { void ticketLogin(ticket, setUser); } }, [data, setUser, ticket, user]); diff --git a/frontend/src/pages/student/CourseViewStudent.tsx b/frontend/src/pages/student/CourseViewStudent.tsx index 001fb1e4..b4ec9c37 100644 --- a/frontend/src/pages/student/CourseViewStudent.tsx +++ b/frontend/src/pages/student/CourseViewStudent.tsx @@ -1,39 +1,37 @@ import {JSX} from "react"; +import {TableRowOverviewProjects, TableRowPeople} from "../../types/tableRows.ts"; import {Header} from "../../components/Header.tsx"; import {Sidebar} from "../../components/Sidebar.tsx"; -import '../../assets/styles/students_components.css' -import {SearchBar} from "../../components/SearchBar.tsx"; import {Table} from "../../components/Table.tsx"; -import {TableRowCourses} from "../../types/tableRows.ts"; -export default function CoursesViewStudent(): JSX.Element { - - const tableCoursesActive: TableRowCourses[] = [ +export default function CourseViewStudent(): JSX.Element { + const tableProjects: TableRowOverviewProjects[] = [ { - course: { - name: "Automaten, berekenbaarheid & complexiteit", - id: 5896 + project: { + name: "RSA security", + id: 1234 }, - shortestDeadline: "17:00 - 23/02/2024", - numberOfProjects: 1 + status: null, + deadline: "23:59 - 27/02/2024" }, { - course: { - name: "Computationele Biologie", - id: 5741 + project: { + name: "Symmetric encryption", + id: 5897 }, - shortestDeadline: "19:00 - 25/02/2024", - numberOfProjects: 2 - }, + status: null, + deadline: "23:59 - 03/03/2024" + } ]; - const tableCoursesArchived: TableRowCourses[] = [ + + const teachers: TableRowPeople[] = [ { - course: { - name: "Logisch Programmeren", - id: 2569 - }, - shortestDeadline: null, - numberOfProjects: 1, + name: "Maarten Vermeiren", + email: "maarten.vermeiren@ugent.be" + }, + { + name: "Anke De Groot", + email: "anke.degroot@ugent.be" } ]; @@ -48,13 +46,14 @@ export default function CoursesViewStudent(): JSX.Element {
- -
-
+
+
+
+ ) } \ No newline at end of file From 4928ece234d9093b1bc0d424984dfba285d28999 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stef=20Oss=C3=A9?= Date: Sat, 13 Apr 2024 11:33:49 +0200 Subject: [PATCH 73/90] Added return types --- frontend/src/pages/Test.tsx | 10 ---------- frontend/src/pages/root.tsx | 2 +- frontend/src/utils/ApiTypesMapper.ts | 22 +++++++++++++++------- frontend/src/utils/api/Groups.ts | 4 ++-- frontend/src/utils/api/Login.ts | 8 ++++---- frontend/src/utils/api/Project.ts | 25 ++++++++----------------- frontend/src/utils/api/Student.ts | 4 ++-- frontend/src/utils/api/Subject.ts | 18 +++++------------- frontend/src/utils/api/Teacher.ts | 9 ++++++--- frontend/src/utils/api/User.ts | 4 ++-- 10 files changed, 45 insertions(+), 61 deletions(-) delete mode 100644 frontend/src/pages/Test.tsx diff --git a/frontend/src/pages/Test.tsx b/frontend/src/pages/Test.tsx deleted file mode 100644 index b243570a..00000000 --- a/frontend/src/pages/Test.tsx +++ /dev/null @@ -1,10 +0,0 @@ - -export default function Test () { - function clickEvent () { - } - - return (
-

Welcome on this test page, click the button below to test te function

- -
) -} \ No newline at end of file diff --git a/frontend/src/pages/root.tsx b/frontend/src/pages/root.tsx index a9c2d7b2..747c296a 100644 --- a/frontend/src/pages/root.tsx +++ b/frontend/src/pages/root.tsx @@ -2,7 +2,7 @@ import {JSX} from "react"; import useAuth from "../hooks/useAuth.ts"; import {Navigate} from "react-router-dom"; -export const DEBUG: boolean = false; // should always be false on the repo. +export const DEBUG: boolean = true; // should always be false on the repo. export default function Root(): JSX.Element { const {user} = useAuth() diff --git a/frontend/src/utils/ApiTypesMapper.ts b/frontend/src/utils/ApiTypesMapper.ts index cb7b4c28..6bf12fe9 100644 --- a/frontend/src/utils/ApiTypesMapper.ts +++ b/frontend/src/utils/ApiTypesMapper.ts @@ -7,18 +7,22 @@ import { Backend_user } from "./BackendInterfaces.ts"; +export function mapSubject(subjectData: Backend_Subject): Subject{ + return { + subject_id: subjectData.id, + subject_name: subjectData.name, + } +} + export function mapSubjectList(subjectList: Backend_Subject[]): Subject[] { if (!Array.isArray(subjectList)) { throw new Error('projectList is not an array'); } - return subjectList.map(subjectData => ({ - subject_id: subjectData.id, - subject_name: subjectData.name, - })); + return subjectList.map(subjectData => mapSubject(subjectData)); } -export function mapProjectList(projectList: Backend_Project[]): Project[] { - return projectList.map(projectData => ({ +export function mapProject(projectData: Backend_Project): Project{ + return { project_id: projectData.id, project_name: projectData.name, project_deadline: projectData.deadline, @@ -28,7 +32,11 @@ export function mapProjectList(projectList: Backend_Project[]): Project[] { project_visible: projectData.visible, project_max_students: projectData.max_students, subject_id: projectData.subject_id, - })); + } +} + +export function mapProjectList(projectList: Backend_Project[]): Project[] { + return projectList.map(projectData => mapProject(projectData)); } export function mapSubmission(submission: Backend_submission): Submission { diff --git a/frontend/src/utils/api/Groups.ts b/frontend/src/utils/api/Groups.ts index 2edec938..109e884a 100644 --- a/frontend/src/utils/api/Groups.ts +++ b/frontend/src/utils/api/Groups.ts @@ -1,11 +1,11 @@ import ApiFetch from "../ApiFetch.ts"; -export function joinGroup(groupId: number) { +export function joinGroup(groupId: number): void { void ApiFetch(`/groups/${groupId}/join`, {method: 'POST', headers: {'Content-Type': 'application/json'}}); } -export function leaveGroup(groupId: number) { +export function leaveGroup(groupId: number): void { void ApiFetch(`/groups/${groupId}/leave`, {method: 'POST', headers: {'Content-Type': 'application/json'}}); } \ No newline at end of file diff --git a/frontend/src/utils/api/Login.ts b/frontend/src/utils/api/Login.ts index af35a0f5..60dae755 100644 --- a/frontend/src/utils/api/Login.ts +++ b/frontend/src/utils/api/Login.ts @@ -1,10 +1,10 @@ import {DEBUG} from "../../pages/root.tsx"; import {Token} from "../ApiInterfaces.ts"; -export async function post_ticket(ticket: string){ - let url = '/api/login?ticket=' + ticket +export async function post_ticket(ticket: string): Promise{ + let url = '/api/login?ticket=' + ticket; if (DEBUG) { - url = 'http://127.0.0.1:8000/api/login?ticket=' + ticket + url = 'http://127.0.0.1:8000/api/login?ticket=' + ticket; } return await (await fetch(url, { @@ -12,5 +12,5 @@ export async function post_ticket(ticket: string){ headers: { 'Content-Type': 'application/json' },})) - .json() as Token + .json() as Token; } \ No newline at end of file diff --git a/frontend/src/utils/api/Project.ts b/frontend/src/utils/api/Project.ts index 04e62b87..c982c7c5 100644 --- a/frontend/src/utils/api/Project.ts +++ b/frontend/src/utils/api/Project.ts @@ -1,36 +1,27 @@ import apiFetch from "../ApiFetch.ts"; import {ProjectInput} from "../InputInterfaces.ts"; import {Backend_group, Backend_Project} from "../BackendInterfaces.ts"; -import {mapGroup} from "../ApiTypesMapper.ts"; +import {mapGroup, mapProject} from "../ApiTypesMapper.ts"; +import {Group, Project} from "../ApiInterfaces.ts"; -export async function project_create_group(project_id: number){ +export async function project_create_group(project_id: number): Promise{ const groupData = await apiFetch(`/projects/${project_id}/groups`, { method: 'POST', headers: { 'Content-Type': 'application/json' } - }) as Backend_group - return mapGroup(groupData) + }) as Backend_group; + return mapGroup(groupData); } -export async function update_project(project_id: number, projectInput: ProjectInput){ +export async function update_project(project_id: number, projectInput: ProjectInput): Promise{ const projectData = await apiFetch(`/projects/${project_id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(projectInput) - }) as Backend_Project - return { - project_id: projectData.id, - project_name: projectData.name, - project_deadline: projectData.deadline, - project_archived: projectData.archived, - project_description: projectData.description, - project_requirements: projectData.requirements, - project_visible: projectData.visible, - project_max_students: projectData.max_students, - subject_id: projectData.subject_id - } + }) as Backend_Project; + return mapProject(projectData); } diff --git a/frontend/src/utils/api/Student.ts b/frontend/src/utils/api/Student.ts index 2269b84b..5baf225c 100644 --- a/frontend/src/utils/api/Student.ts +++ b/frontend/src/utils/api/Student.ts @@ -1,7 +1,7 @@ import apiFetch from "../ApiFetch.ts"; -export function join_subject(subjectId: number){ +export function join_subject(subjectId: number): void{ void apiFetch(`/student/subjects/${subjectId}/join`, { method: 'POST' - }) + }); } \ No newline at end of file diff --git a/frontend/src/utils/api/Subject.ts b/frontend/src/utils/api/Subject.ts index fa9850f3..78dc8591 100644 --- a/frontend/src/utils/api/Subject.ts +++ b/frontend/src/utils/api/Subject.ts @@ -1,25 +1,17 @@ import {ProjectInput} from "../InputInterfaces.ts"; import ApiFetch from "../ApiFetch.ts"; import {Backend_Project} from "../BackendInterfaces.ts"; +import {Project} from "../ApiInterfaces.ts"; +import {mapProject} from "../ApiTypesMapper.ts"; -export async function subject_create_project(subjectId: number, projectInput: ProjectInput) { +export async function subject_create_project(subjectId: number, projectInput: ProjectInput): Promise { const projectData: Backend_Project = (await ApiFetch(`/subjects/${subjectId}/projects`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(projectInput) - }) as Backend_Project) + }) as Backend_Project); - return { - project_id: projectData.id, - project_name: projectData.name, - project_deadline: projectData.deadline, - project_archived: projectData.archived, - project_description: projectData.description, - project_requirements: projectData.requirements, - project_visible: projectData.visible, - project_max_students: projectData.max_students, - subject_id: projectData.subject_id - } + return mapProject(projectData); } \ No newline at end of file diff --git a/frontend/src/utils/api/Teacher.ts b/frontend/src/utils/api/Teacher.ts index ab4a1a09..b9a5fc85 100644 --- a/frontend/src/utils/api/Teacher.ts +++ b/frontend/src/utils/api/Teacher.ts @@ -1,13 +1,16 @@ import {Backend_Subject} from "../BackendInterfaces.ts"; import apiFetch from "../ApiFetch.ts"; +import {Subject} from "../ApiInterfaces.ts"; +import {mapSubject} from "../ApiTypesMapper.ts"; -export async function createSubject(name: string) { - return (await apiFetch('/teacher/subjects', +export async function createSubject(name: string): Promise { + const subjectData = (await apiFetch('/teacher/subjects', { headers: { 'Content-Type': 'application/json' }, method: 'POST', body: JSON.stringify({name: name}) - })) as Backend_Subject + })) as Backend_Subject; + return mapSubject(subjectData); } \ No newline at end of file diff --git a/frontend/src/utils/api/User.ts b/frontend/src/utils/api/User.ts index f731bc77..c6d0e05d 100644 --- a/frontend/src/utils/api/User.ts +++ b/frontend/src/utils/api/User.ts @@ -1,8 +1,8 @@ import {Language} from "../../components/Settings.tsx"; import apiFetch from "../ApiFetch.ts"; -export function modify_language(language: Language){ +export function modify_language(language: Language): void{ void apiFetch(`/user?language=${language}`, { method: 'PATCH' - }) + }); } \ No newline at end of file From abde22c0c107be2a0bffc347a3df6ade29a50579 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stef=20Oss=C3=A9?= Date: Sat, 13 Apr 2024 13:53:49 +0200 Subject: [PATCH 74/90] ApiFetch, changed return + added todos for error handling --- frontend/src/dataloaders/LoginLoader.ts | 8 +++--- .../src/dataloaders/ProjectsStudentLoader.ts | 18 ++++++++----- .../loader_helpers/SharedFunctions.ts | 26 ++++++++++++++----- .../src/dataloaders/projectsTeacherLoader.ts | 19 +++++++++----- frontend/src/pages/login/LoginScreen.tsx | 20 +++++++------- frontend/src/pages/root.tsx | 2 +- frontend/src/utils/ApiFetch.ts | 14 ++++++++-- frontend/src/utils/api/Login.ts | 13 +++++++--- frontend/src/utils/api/Project.ts | 26 +++++++++++++------ frontend/src/utils/api/Subject.ts | 12 ++++++--- frontend/src/utils/api/Teacher.ts | 12 ++++++--- 11 files changed, 116 insertions(+), 54 deletions(-) diff --git a/frontend/src/dataloaders/LoginLoader.ts b/frontend/src/dataloaders/LoginLoader.ts index 76c4405d..48f58576 100644 --- a/frontend/src/dataloaders/LoginLoader.ts +++ b/frontend/src/dataloaders/LoginLoader.ts @@ -17,9 +17,11 @@ const isUser = (data?: Backend_user) => { export default async function loginLoader(): Promise { const token = localStorage.getItem('token') if (token) { - const user = (await apiFetch('/user')) as Backend_user; - if (isUser(user)) { - return {user: mapUser(user)} + const user_request = (await apiFetch('/user')); + if (user_request.ok && isUser(user_request.content)) { + return {user: mapUser(user_request.content)} + }else{ + // TODO error handling } } return {user: undefined} diff --git a/frontend/src/dataloaders/ProjectsStudentLoader.ts b/frontend/src/dataloaders/ProjectsStudentLoader.ts index 7f59674e..ca9084e3 100644 --- a/frontend/src/dataloaders/ProjectsStudentLoader.ts +++ b/frontend/src/dataloaders/ProjectsStudentLoader.ts @@ -32,16 +32,22 @@ export async function LoadProjectsForStudent(filter_on_current: boolean = false, //TODO aanpassen, dit geeft gelijk alle groepen terug const groupPromises: Promise[] = projects.map(async project => { - const apiGroup = await apiFetch(`/projects/${project.project_id}/group`) as Backend_group; - if (apiGroup) { - return mapGroup(apiGroup); - } else return undefined; + const apiGroup = await apiFetch(`/projects/${project.project_id}/group`); + if (apiGroup.ok && apiGroup.content) { + return mapGroup(apiGroup.content); + } + // TODO: error handling + return undefined; }); const submissionPromises: Promise[] = (await Promise.all(groupPromises)).map(async group => { if (group) { - const apiSubmission = await apiFetch(`/groups/${group.group_id}/submission`) as Backend_submission; - return mapSubmission(apiSubmission); + const apiSubmission = await apiFetch(`/groups/${group.group_id}/submission`); + if (apiSubmission.ok) { + return mapSubmission(apiSubmission.content); + }else{ + // TODO: error handling + } } return undefined; }); diff --git a/frontend/src/dataloaders/loader_helpers/SharedFunctions.ts b/frontend/src/dataloaders/loader_helpers/SharedFunctions.ts index 14d67249..9d499f6c 100644 --- a/frontend/src/dataloaders/loader_helpers/SharedFunctions.ts +++ b/frontend/src/dataloaders/loader_helpers/SharedFunctions.ts @@ -74,12 +74,26 @@ export interface projectsAndSubjects { } export async function getAllProjectsAndSubjects(role: teacherStudentRole, filter_on_current: boolean = false): Promise { - const apiSubjects = (await apiFetch(`/${role}/subjects`)) as Backend_Subject[]; - const apiProjects = (await apiFetch(`/${role}/projects`)) as Backend_Project[]; - let projects: Project[] = mapProjectList(apiProjects); - if (filter_on_current) { - projects = projects.filter(project => project.project_visible && !project.project_archived) + const apiProjects = (await apiFetch(`/${role}/projects`)); + let projects: Project[] = [] + + if (apiProjects.ok) { + projects = mapProjectList(apiProjects.content); + if (filter_on_current) { + projects = projects.filter(project => project.project_visible && !project.project_archived) + } + }else{ + // TODO error handling + } + + const apiSubjects = (await apiFetch(`/${role}/subjects`)); + let subjects: Subject[] = [] + + if (apiSubjects.ok){ + subjects = mapSubjectList(apiSubjects.content); + }else{ + // TODO error handling } - const subjects: Subject[] = mapSubjectList(apiSubjects); + return {projects, subjects} } diff --git a/frontend/src/dataloaders/projectsTeacherLoader.ts b/frontend/src/dataloaders/projectsTeacherLoader.ts index f8b9a636..ae7735ed 100644 --- a/frontend/src/dataloaders/projectsTeacherLoader.ts +++ b/frontend/src/dataloaders/projectsTeacherLoader.ts @@ -1,8 +1,8 @@ -import {CompleteProjectTeacher, Group, Submission} from "../utils/ApiInterfaces.ts"; +import {CompleteProjectTeacher, Group} from "../utils/ApiInterfaces.ts"; import {getAllProjectsAndSubjects, teacherStudentRole} from "./loader_helpers/SharedFunctions.ts"; import apiFetch from "../utils/ApiFetch.ts"; -import {Backend_group} from "../utils/BackendInterfaces.ts"; -import {mapGroupList} from "../utils/ApiTypesMapper.ts"; +import {Backend_group, Backend_submission} from "../utils/BackendInterfaces.ts"; +import {mapGroupList, mapSubmission} from "../utils/ApiTypesMapper.ts"; export interface projectsTeacherLoaderObject { projects: CompleteProjectTeacher[] @@ -31,8 +31,13 @@ export async function LoadProjectsForTeacher(filter_on_current: boolean = false, } const groupPromises: Promise = Promise.all(projects.map(async project => { - const groups = await apiFetch(`/projects/${project.project_id}/groups`) as Backend_group[]; - return mapGroupList(groups); + const groups = await apiFetch(`/projects/${project.project_id}/groups`); + if (groups.ok){ + return mapGroupList(groups.content); + }else{ + // TODO: error handling + return [] + } })); const groups: Group[][] = (await groupPromises) @@ -41,8 +46,8 @@ export async function LoadProjectsForTeacher(filter_on_current: boolean = false, let amount = 0 for (const group of groupArray) { try { - const submission = await apiFetch(`/groups/${group.group_id}/submission`) as Submission; - if (submission) { + const submission = await apiFetch(`/groups/${group.group_id}/submission`); + if (submission.ok && mapSubmission(submission.content)) { amount++; } } catch (e) { diff --git a/frontend/src/pages/login/LoginScreen.tsx b/frontend/src/pages/login/LoginScreen.tsx index 1255e1a0..d36be21a 100644 --- a/frontend/src/pages/login/LoginScreen.tsx +++ b/frontend/src/pages/login/LoginScreen.tsx @@ -12,21 +12,18 @@ interface location_type { pathname: string } -const ticketLogin = async (ticket: string, setUser: React.Dispatch>) => { - const token: Token = await post_ticket(ticket) - - if (token.token) { +const ticketLogin = async (ticket: string, setUser: React.Dispatch>)=> { + const token: Token | undefined = await post_ticket(ticket) + if (token?.token) { localStorage.setItem('token', token.token) const result: loginLoaderObject = await loginLoader() if (result.user) { setUser(result.user) - }else{ + } else { localStorage.removeItem('token') setUser(undefined) } } - - return token; } export default function LoginScreen(): JSX.Element { @@ -48,9 +45,12 @@ export default function LoginScreen(): JSX.Element { // If the saved token is valid => the user will be logged in if (data && data.user) { setUser(data.user) - } - else if (!user && ticket) { - void ticketLogin(ticket, setUser); + } else if (!user && ticket) { + try { + void ticketLogin(ticket, setUser); + } catch (error) { + console.log("Ticket wasn't accepted") + } } }, [data, setUser, ticket, user]); diff --git a/frontend/src/pages/root.tsx b/frontend/src/pages/root.tsx index 747c296a..a9c2d7b2 100644 --- a/frontend/src/pages/root.tsx +++ b/frontend/src/pages/root.tsx @@ -2,7 +2,7 @@ import {JSX} from "react"; import useAuth from "../hooks/useAuth.ts"; import {Navigate} from "react-router-dom"; -export const DEBUG: boolean = true; // should always be false on the repo. +export const DEBUG: boolean = false; // should always be false on the repo. export default function Root(): JSX.Element { const {user} = useAuth() diff --git a/frontend/src/utils/ApiFetch.ts b/frontend/src/utils/ApiFetch.ts index c9232cbd..0b925898 100644 --- a/frontend/src/utils/ApiFetch.ts +++ b/frontend/src/utils/ApiFetch.ts @@ -1,7 +1,12 @@ import {DEBUG} from "../pages/root.tsx"; +export interface ApiFetchResponse{ + ok: boolean, + status_code: number, + content: Type +} -const ApiFetch = async (url: string, options?: RequestInit) => { +export async function ApiFetch (url: string, options?: RequestInit) { if (typeof options === 'undefined') { options = {} } @@ -18,7 +23,12 @@ const ApiFetch = async (url: string, options?: RequestInit) => { if (DEBUG) { url = "http://127.0.0.1:8000" + url; } - return (await fetch(url, options)).json() + const response = await fetch(url, options) + return { + ok: response.ok, + status_code: response.status, + content: await response.json() as Type + } as ApiFetchResponse } export default ApiFetch diff --git a/frontend/src/utils/api/Login.ts b/frontend/src/utils/api/Login.ts index 60dae755..84b9be54 100644 --- a/frontend/src/utils/api/Login.ts +++ b/frontend/src/utils/api/Login.ts @@ -1,16 +1,21 @@ import {DEBUG} from "../../pages/root.tsx"; import {Token} from "../ApiInterfaces.ts"; -export async function post_ticket(ticket: string): Promise{ +export async function post_ticket(ticket: string): Promise { let url = '/api/login?ticket=' + ticket; if (DEBUG) { url = 'http://127.0.0.1:8000/api/login?ticket=' + ticket; } - return await (await fetch(url, { + const tokenData = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' - },})) - .json() as Token; + }, + }) + if (tokenData.ok) { + return await tokenData.json() as Token + } + + return undefined } \ No newline at end of file diff --git a/frontend/src/utils/api/Project.ts b/frontend/src/utils/api/Project.ts index c982c7c5..d1292963 100644 --- a/frontend/src/utils/api/Project.ts +++ b/frontend/src/utils/api/Project.ts @@ -4,24 +4,34 @@ import {Backend_group, Backend_Project} from "../BackendInterfaces.ts"; import {mapGroup, mapProject} from "../ApiTypesMapper.ts"; import {Group, Project} from "../ApiInterfaces.ts"; -export async function project_create_group(project_id: number): Promise{ - const groupData = await apiFetch(`/projects/${project_id}/groups`, { +export async function project_create_group(project_id: number): Promise { + const groupData = await apiFetch(`/projects/${project_id}/groups`, { method: 'POST', headers: { 'Content-Type': 'application/json' } - }) as Backend_group; - return mapGroup(groupData); + }); + if (groupData.ok){ + return mapGroup(groupData.content); + }else{ + // TODO: error handling + throw groupData.status_code + } } -export async function update_project(project_id: number, projectInput: ProjectInput): Promise{ - const projectData = await apiFetch(`/projects/${project_id}`, { +export async function update_project(project_id: number, projectInput: ProjectInput): Promise { + const projectData = await apiFetch(`/projects/${project_id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(projectInput) - }) as Backend_Project; - return mapProject(projectData); + }); + + if (projectData.ok){ + return mapProject(projectData.content) + }else{ + throw projectData.status_code + } } diff --git a/frontend/src/utils/api/Subject.ts b/frontend/src/utils/api/Subject.ts index 78dc8591..c20a991f 100644 --- a/frontend/src/utils/api/Subject.ts +++ b/frontend/src/utils/api/Subject.ts @@ -5,13 +5,17 @@ import {Project} from "../ApiInterfaces.ts"; import {mapProject} from "../ApiTypesMapper.ts"; export async function subject_create_project(subjectId: number, projectInput: ProjectInput): Promise { - const projectData: Backend_Project = (await ApiFetch(`/subjects/${subjectId}/projects`, { + const projectData = (await ApiFetch(`/subjects/${subjectId}/projects`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(projectInput) - }) as Backend_Project); - - return mapProject(projectData); + })); + if (projectData.ok) { + return mapProject(projectData.content); + }else{ + // TODO: error handling + throw projectData.status_code + } } \ No newline at end of file diff --git a/frontend/src/utils/api/Teacher.ts b/frontend/src/utils/api/Teacher.ts index b9a5fc85..06742236 100644 --- a/frontend/src/utils/api/Teacher.ts +++ b/frontend/src/utils/api/Teacher.ts @@ -4,13 +4,19 @@ import {Subject} from "../ApiInterfaces.ts"; import {mapSubject} from "../ApiTypesMapper.ts"; export async function createSubject(name: string): Promise { - const subjectData = (await apiFetch('/teacher/subjects', + const subjectData = (await apiFetch('/teacher/subjects', { headers: { 'Content-Type': 'application/json' }, method: 'POST', body: JSON.stringify({name: name}) - })) as Backend_Subject; - return mapSubject(subjectData); + })); + + if (subjectData.ok){ + return mapSubject(subjectData.content); + }else{ + // TODO: error handling + throw subjectData.status_code + } } \ No newline at end of file From 69ea77bdd13a7bb7310ae3b28703b690c282e83f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stef=20Oss=C3=A9?= Date: Wed, 17 Apr 2024 15:13:40 +0200 Subject: [PATCH 75/90] main -> backend --- backend/app.py | 37 ++++++++++++++++++- .../authentication/role_dependencies.py | 33 ++++++++++------- backend/controllers/routes/group.py | 32 +++++++++++++--- backend/controllers/routes/login.py | 8 ++-- backend/controllers/routes/project.py | 12 +++--- backend/controllers/routes/student.py | 16 +++++--- backend/controllers/routes/subject.py | 23 ++++++++---- backend/controllers/routes/submission.py | 8 ++-- backend/controllers/routes/teacher.py | 36 +++++++++++++++--- backend/controllers/routes/user.py | 16 ++++---- backend/create_database_tables.py | 1 + backend/db/models.py | 1 + backend/domain/logic/errors.py | 15 +++++++- backend/domain/logic/subject.py | 37 +++++++++++++++++-- backend/domain/logic/submission.py | 10 +++-- backend/poetry.lock | 2 +- 16 files changed, 217 insertions(+), 70 deletions(-) diff --git a/backend/app.py b/backend/app.py index 790c56e3..2df34290 100644 --- a/backend/app.py +++ b/backend/app.py @@ -29,12 +29,19 @@ NoSuchRelationError, ) from debug import DEBUG -from domain.logic.errors import InvalidConstraintsError, InvalidSubmissionError +from domain.logic.errors import ( + ArchivedError, + InvalidConstraintsError, + InvalidSubmissionError, + NotATeacherError, + UserNotEnrolledError, +) pathlib.Path.mkdir(pathlib.Path("submissions"), exist_ok=True) app = FastAPI( docs_url="/api/docs", openapi_tags=tags_metadata, + swagger_ui_parameters={"persistAuthorization": True}, dependencies=[Depends(HTTPBearer(auto_error=False))], # To authenticate via Swagger UI ) @@ -126,13 +133,15 @@ def conflicting_relation_error_handler(request: Request, exc: ConflictingRelatio content={"detail": str(exc)}, ) + @app.exception_handler(InvalidConstraintsError) def invalid_constraints_error_handler(request: Request, exc: InvalidConstraintsError) -> JSONResponse: return JSONResponse( status_code=status.HTTP_400_BAD_REQUEST, - content={"detail": str(exc)}, + content={"detail": exc.ERROR_MESSAGE}, ) + @app.exception_handler(InvalidSubmissionError) def invalid_submission_error_handler(request: Request, exc: InvalidSubmissionError) -> JSONResponse: return JSONResponse( @@ -141,5 +150,29 @@ def invalid_submission_error_handler(request: Request, exc: InvalidSubmissionErr ) +@app.exception_handler(UserNotEnrolledError) +def user_not_enrolled_error_handler(request: Request, exc: UserNotEnrolledError) -> JSONResponse: + return JSONResponse( + status_code=status.HTTP_400_BAD_REQUEST, + content={"detail": exc.ERROR_MESSAGE}, + ) + + +@app.exception_handler(ArchivedError) +def archived_error_handler(request: Request, exc: ArchivedError) -> JSONResponse: + return JSONResponse( + status_code=status.HTTP_400_BAD_REQUEST, + content={"detail": exc.ERROR_MESSAGE}, + ) + + +@app.exception_handler(NotATeacherError) +def not_a_teacher_error_handler(request: Request, exc: NotATeacherError) -> JSONResponse: + return JSONResponse( + status_code=status.HTTP_400_BAD_REQUEST, + content={"detail": exc.ERROR_MESSAGE}, + ) + + if __name__ == "__main__": uvicorn.run("app:app") diff --git a/backend/controllers/authentication/role_dependencies.py b/backend/controllers/authentication/role_dependencies.py index 90f24c4b..55d0e98b 100644 --- a/backend/controllers/authentication/role_dependencies.py +++ b/backend/controllers/authentication/role_dependencies.py @@ -9,7 +9,6 @@ ) from controllers.authentication.token_controller import verify_token from db.models import Admin, Student, Teacher -from db.sessions import get_session from domain.logic.admin import get_admin, is_user_admin from domain.logic.group import get_group, get_students_of_group from domain.logic.project import get_project, get_projects_of_teacher @@ -30,7 +29,7 @@ def get_authenticated_user(request: Request) -> int: def get_authenticated_admin(request: Request) -> Admin: - session = next(get_session()) + session = request.state.session uid = get_authenticated_user(request) if not is_user_admin(session, uid): @@ -39,7 +38,7 @@ def get_authenticated_admin(request: Request) -> Admin: def get_authenticated_teacher(request: Request) -> Teacher: - session = next(get_session()) + session = request.state.session uid = get_authenticated_user(request) if not is_user_teacher(session, uid): @@ -48,7 +47,7 @@ def get_authenticated_teacher(request: Request) -> Teacher: def get_authenticated_student(request: Request) -> Student: - session = next(get_session()) + session = request.state.session uid = get_authenticated_user(request) if not is_user_student(session, uid): @@ -58,7 +57,7 @@ def get_authenticated_student(request: Request) -> Student: def ensure_user_authorized_for_subject(request: Request, subject_id: int) -> None: - session = next(get_session()) + session = request.state.session uid = get_authenticated_user(request) if not is_user_authorized_for_subject(subject_id, session, uid): @@ -66,13 +65,13 @@ def ensure_user_authorized_for_subject(request: Request, subject_id: int) -> Non def ensure_user_authorized_for_project(request: Request, project_id: int) -> None: - session = next(get_session()) + session = request.state.session project = get_project(session, project_id) return ensure_user_authorized_for_subject(request, project.subject_id) def ensure_student_authorized_for_subject(request: Request, subject_id: int) -> Student: - session = next(get_session()) + session = request.state.session student = get_authenticated_student(request) subjects_of_student = get_subjects_of_student(session, student.id) @@ -82,7 +81,7 @@ def ensure_student_authorized_for_subject(request: Request, subject_id: int) -> def ensure_teacher_authorized_for_subject(request: Request, subject_id: int) -> Teacher: - session = next(get_session()) + session = request.state.session teacher = get_authenticated_teacher(request) subjects_of_teacher = get_subjects_of_teacher(session, teacher.id) @@ -92,31 +91,37 @@ def ensure_teacher_authorized_for_subject(request: Request, subject_id: int) -> def ensure_student_authorized_for_project(request: Request, project_id: int) -> Student: - session = next(get_session()) + session = request.state.session project = get_project(session, project_id) return ensure_student_authorized_for_subject(request, project.subject_id) def ensure_teacher_authorized_for_project(request: Request, project_id: int) -> Teacher: - session = next(get_session()) + session = request.state.session project = get_project(session, project_id) return ensure_teacher_authorized_for_subject(request, project.subject_id) def ensure_student_authorized_for_group(request: Request, group_id: int) -> Student: - session = next(get_session()) + session = request.state.session group = get_group(session, group_id) return ensure_student_authorized_for_project(request, group.project_id) +def ensure_teacher_authorized_for_group(request: Request, group_id: int) -> Teacher: + session = request.state.session + group = get_group(session, group_id) + return ensure_teacher_authorized_for_project(request, group.project_id) + + def ensure_user_authorized_for_group(request: Request, group_id: int) -> None: - session = next(get_session()) + session = request.state.session group = get_group(session, group_id) ensure_user_authorized_for_project(request, group.project_id) def ensure_student_in_group(request: Request, group_id: int) -> Student: - session = next(get_session()) + session = request.state.session student = get_authenticated_student(request) if student not in get_students_of_group(session, group_id): @@ -125,7 +130,7 @@ def ensure_student_in_group(request: Request, group_id: int) -> Student: def ensure_user_authorized_for_submission(request: Request, group_id: int) -> None: - session = next(get_session()) + session = request.state.session uid = get_authenticated_user(request) group = get_group(session, group_id) diff --git a/backend/controllers/routes/group.py b/backend/controllers/routes/group.py index 1ff89ab5..3e249b8b 100644 --- a/backend/controllers/routes/group.py +++ b/backend/controllers/routes/group.py @@ -4,43 +4,65 @@ from controllers.authentication.role_dependencies import ( ensure_student_authorized_for_group, ensure_student_authorized_for_project, + ensure_teacher_authorized_for_group, ensure_user_authorized_for_group, ) from controllers.swagger_tags import Tags from db.models import Group, Student +from domain.logic.errors import UserNotEnrolledError from domain.logic.group import ( add_student_to_group, + get_group, get_group_for_student_and_project, get_students_of_group, remove_student_from_group, ) +from domain.logic.project import get_project, get_projects_of_student -group_router = APIRouter() +group_router = APIRouter(tags=[Tags.GROUP]) -@group_router.post("/groups/{group_id}/join", tags=[Tags.GROUP], summary="Join a certain group.") +@group_router.post("/groups/{group_id}/join", summary="Join a certain group.") def group_join(request: Request, group_id: int) -> None: session = request.state.session student = ensure_student_authorized_for_group(request, group_id) add_student_to_group(session, student.id, group_id) -@group_router.post("/groups/{group_id}/leave", tags=[Tags.GROUP], summary="Leave a certain group.") +@group_router.post("/groups/{group_id}/leave", summary="Leave a certain group.") def group_leave(request: Request, group_id: int) -> None: session = request.state.session student = ensure_student_authorized_for_group(request, group_id) remove_student_from_group(session, student.id, group_id) -@group_router.get("/groups/{group_id}/members", tags=[Tags.GROUP], summary="List group members.") +@group_router.get("/groups/{group_id}/members", summary="List group members.") def list_group_members(request: Request, group_id: int) -> list[Student]: ensure_user_authorized_for_group(request, group_id) session = request.state.session return get_students_of_group(session, group_id) -@group_router.get("/projects/{project_id}/group", tags=[Tags.GROUP], summary="Get your group for a project.") +@group_router.get("/projects/{project_id}/group", summary="Get your group for a project.") def project_get_group(request: Request, project_id: int) -> Group | None: session = request.state.session student = ensure_student_authorized_for_project(request, project_id) return get_group_for_student_and_project(session, student.id, project_id) + + +@group_router.post("/groups/{group_id}/add", summary="Add a student to a group.") +def group_add(request: Request, group_id: int, uid: int) -> None: + session = request.state.session + ensure_teacher_authorized_for_group(request, group_id) + group = get_group(session, group_id) + project = get_project(session, group.project_id) + if project not in get_projects_of_student(session, uid): + raise UserNotEnrolledError + add_student_to_group(session, uid, group_id) + + +@group_router.post("/groups/{group_id}/remove", summary="Remove a student from a group.") +def group_remove(request: Request, group_id: int, uid: int) -> None: + session = request.state.session + ensure_teacher_authorized_for_group(request, group_id) + remove_student_from_group(session, uid, group_id) diff --git a/backend/controllers/routes/login.py b/backend/controllers/routes/login.py index f25335a2..33f588df 100644 --- a/backend/controllers/routes/login.py +++ b/backend/controllers/routes/login.py @@ -11,7 +11,7 @@ from domain.logic.user import get_user # test url: https://login.ugent.be/login?service=https://localhost:8080/api/login -login_router = APIRouter() +login_router = APIRouter(tags=[Tags.LOGIN]) class LoginResponse(BaseModel): @@ -22,7 +22,7 @@ class ValidateResponse(BaseModel): valid: bool -@login_router.post("/validate", tags=[Tags.LOGIN], summary="Validate a session token.") +@login_router.post("/validate", summary="Validate a session token.") def validate_token(token: str) -> ValidateResponse: uid = verify_token(token) if uid: @@ -30,7 +30,7 @@ def validate_token(token: str) -> ValidateResponse: return ValidateResponse(valid=False) -@login_router.post("/login", tags=[Tags.LOGIN], summary="Starts a session for the user.") +@login_router.post("/login", summary="Starts a session for the user.") def login(request: Request, ticket: str) -> LoginResponse: """ This function starts a session for the user. @@ -51,7 +51,7 @@ def login(request: Request, ticket: str) -> LoginResponse: if DEBUG: - @login_router.post("/fake-login", tags=[Tags.LOGIN], summary="Development only: Generate a login token for a user.") + @login_router.post("/fake-login", summary="Development only: Generate a login token for a user.") def fake_login(request: Request, uid: int) -> LoginResponse: session = request.state.session user = get_user(session, uid) diff --git a/backend/controllers/routes/project.py b/backend/controllers/routes/project.py index a538a2f5..53774c51 100644 --- a/backend/controllers/routes/project.py +++ b/backend/controllers/routes/project.py @@ -10,10 +10,10 @@ from domain.logic.group import create_group, get_groups_of_project from domain.logic.project import get_project, update_project, validate_constraints -project_router = APIRouter() +project_router = APIRouter(tags=[Tags.PROJECT]) -@project_router.get("/projects/{project_id}", tags=[Tags.PROJECT], summary="Get a certain project.") +@project_router.get("/projects/{project_id}", summary="Get a certain project.") def project_get(request: Request, project_id: int) -> Project: session = request.state.session ensure_user_authorized_for_project(request, project_id) @@ -21,22 +21,22 @@ def project_get(request: Request, project_id: int) -> Project: return project -@project_router.get("/projects/{project_id}/groups", tags=[Tags.PROJECT], summary="Get all groups of a project.") +@project_router.get("/projects/{project_id}/groups", summary="Get all groups of a project.") def project_get_groups(request: Request, project_id: int) -> list[Group]: session = request.state.session ensure_user_authorized_for_project(request, project_id) return get_groups_of_project(session, project_id) -@project_router.post("/projects/{project_id}/groups", tags=[Tags.PROJECT], summary="Create a group for a project.") +@project_router.post("/projects/{project_id}/groups", summary="Create a group for a project.") def project_create_group(request: Request, project_id: int) -> Group: session = request.state.session ensure_teacher_authorized_for_project(request, project_id) return create_group(session, project_id) -@project_router.patch("/projects/{project_id}", tags=[Tags.PROJECT], summary="Update a project.") -def patch_update_project(request: Request, project_id: int, project: ProjectInput) -> None: +@project_router.put("/projects/{project_id}", summary="Update a project.") +def put_update_project(request: Request, project_id: int, project: ProjectInput) -> None: session = request.state.session ensure_teacher_authorized_for_project(request, project_id) if project.requirements: diff --git a/backend/controllers/routes/student.py b/backend/controllers/routes/student.py index 82168426..3a29d057 100644 --- a/backend/controllers/routes/student.py +++ b/backend/controllers/routes/student.py @@ -5,24 +5,30 @@ from controllers.swagger_tags import Tags from db.models import Project, Subject from domain.logic.project import get_projects_of_student -from domain.logic.subject import add_student_to_subject, get_subjects_of_student +from domain.logic.subject import add_student_to_subject, get_subjects_of_student, remove_student_from_subject -student_router = APIRouter() +student_router = APIRouter(tags=[Tags.STUDENT]) -@student_router.get("/student/subjects", tags=[Tags.STUDENT], summary="Get all subjects of the student.") +@student_router.get("/student/subjects", summary="Get all subjects of the student.") def subjects_of_student_get(request: Request) -> list[Subject]: student = get_authenticated_student(request) return get_subjects_of_student(request.state.session, student.id) -@student_router.get("/student/projects", tags=[Tags.STUDENT], summary="Get all projects of the student.") +@student_router.get("/student/projects", summary="Get all projects of the student.") def projects_of_student_get(request: Request) -> list[Project]: student = get_authenticated_student(request) return get_projects_of_student(request.state.session, student.id) -@student_router.post("/student/subjects/{subject_id}/join", tags=[Tags.STUDENT], summary="Join a subject.") +@student_router.post("/student/subjects/{subject_id}/join", summary="Join a subject.") def student_subject_join(request: Request, subject_id: int) -> None: student = get_authenticated_student(request) add_student_to_subject(request.state.session, student.id, subject_id) + + +@student_router.post("/student/subjects/{subject_id}/leave", summary="Leave a subject.") +def student_subject_leave(request: Request, subject_id: int) -> None: + student = get_authenticated_student(request) + remove_student_from_subject(request.state.session, student.id, subject_id) diff --git a/backend/controllers/routes/subject.py b/backend/controllers/routes/subject.py index 7c789aee..8d9cd552 100644 --- a/backend/controllers/routes/subject.py +++ b/backend/controllers/routes/subject.py @@ -7,42 +7,42 @@ get_authenticated_user, ) from controllers.swagger_tags import Tags -from db.models import Project, ProjectInput, Student, Subject, Teacher +from db.models import Project, ProjectInput, Student, Subject, SubjectInput, Teacher from domain.logic.project import create_project, get_projects_of_subject, validate_constraints -from domain.logic.subject import get_students_of_subject, get_subject, get_teachers_of_subject +from domain.logic.subject import get_students_of_subject, get_subject, get_teachers_of_subject, update_subject -subject_router = APIRouter() +subject_router = APIRouter(tags=[Tags.SUBJECT]) -@subject_router.get("/subjects/{subject_id}", tags=[Tags.SUBJECT], summary="Get a certain subject.") +@subject_router.get("/subjects/{subject_id}", summary="Get a certain subject.") def subject_get(request: Request, subject_id: int) -> Subject: session = request.state.session get_authenticated_user(request) return get_subject(session, subject_id) -@subject_router.get("/subjects/{subject_id}/projects", tags=[Tags.SUBJECT], summary="Get all projects of subject.") +@subject_router.get("/subjects/{subject_id}/projects", summary="Get all projects of subject.") def get_subject_projects(request: Request, subject_id: int) -> list[Project]: session = request.state.session ensure_user_authorized_for_subject(request, subject_id) return get_projects_of_subject(session, subject_id) -@subject_router.get("/subjects/{subject_id}/teachers", tags=[Tags.SUBJECT], summary="Get all teachers of subject.") +@subject_router.get("/subjects/{subject_id}/teachers", summary="Get all teachers of subject.") def get_subject_teachers(request: Request, subject_id: int) -> list[Teacher]: session = request.state.session ensure_user_authorized_for_subject(request, subject_id) return get_teachers_of_subject(session, subject_id) -@subject_router.get("/subjects/{subject_id}/students", tags=[Tags.SUBJECT], summary="Get all students of subject.") +@subject_router.get("/subjects/{subject_id}/students", summary="Get all students of subject.") def get_subject_students(request: Request, subject_id: int) -> list[Student]: session = request.state.session ensure_user_authorized_for_subject(request, subject_id) return get_students_of_subject(session, subject_id) -@subject_router.post("/subjects/{subject_id}/projects", tags=[Tags.PROJECT], summary="Create project in a course.") +@subject_router.post("/subjects/{subject_id}/projects", summary="Create project in a course.") def new_project(request: Request, subject_id: int, project: ProjectInput) -> Project: session = request.state.session ensure_teacher_authorized_for_subject(request, subject_id) @@ -59,3 +59,10 @@ def new_project(request: Request, subject_id: int, project: ProjectInput) -> Pro visible=project.visible, max_students=project.max_students, ) + + +@subject_router.put("/subjects/{subject_id}", summary="Update a subject") +def put_update_subject(request: Request, subject_id: int, subject: SubjectInput) -> None: + session = request.state.session + ensure_teacher_authorized_for_subject(request, subject_id) + update_subject(session, subject_id, subject) diff --git a/backend/controllers/routes/submission.py b/backend/controllers/routes/submission.py index f2a4cee0..4a9d9f6c 100644 --- a/backend/controllers/routes/submission.py +++ b/backend/controllers/routes/submission.py @@ -11,10 +11,10 @@ from domain.logic.errors import InvalidSubmissionError from domain.logic.submission import check_submission, create_submission, get_last_submission -submission_router = APIRouter() +submission_router = APIRouter(tags=[Tags.SUBMISSION]) -@submission_router.post("/groups/{group_id}/submission", tags=[Tags.SUBMISSION], summary="Make a submission.") +@submission_router.post("/groups/{group_id}/submission", summary="Make a submission.") def make_submission(request: Request, group_id: int, file: UploadFile) -> Submission: session = request.state.session student = ensure_student_in_group(request, group_id) @@ -43,14 +43,14 @@ def make_submission(request: Request, group_id: int, file: UploadFile) -> Submis ) -@submission_router.get("/groups/{group_id}/submission", tags=[Tags.SUBMISSION], summary="Get latest submission.") +@submission_router.get("/groups/{group_id}/submission", summary="Get latest submission.") def retrieve_submission(request: Request, group_id: int) -> Submission: session = request.state.session ensure_user_authorized_for_submission(request, group_id) return get_last_submission(session, group_id) -@submission_router.get("/groups/{group_id}/submission/file", tags=[Tags.SUBMISSION], summary="Get last submission") +@submission_router.get("/groups/{group_id}/submission/file", summary="Get last submission") def retrieve_submission_file(request: Request, group_id: int) -> Response: session = request.state.session ensure_user_authorized_for_submission(request, group_id) diff --git a/backend/controllers/routes/teacher.py b/backend/controllers/routes/teacher.py index d1ab2557..154abde9 100644 --- a/backend/controllers/routes/teacher.py +++ b/backend/controllers/routes/teacher.py @@ -1,30 +1,40 @@ from fastapi import APIRouter from starlette.requests import Request -from controllers.authentication.role_dependencies import get_authenticated_teacher +from controllers.authentication.role_dependencies import ( + ensure_teacher_authorized_for_subject, + get_authenticated_teacher, +) from controllers.swagger_tags import Tags from db.models import Project, Subject, SubjectInput +from domain.logic.errors import NotATeacherError from domain.logic.project import get_projects_of_teacher -from domain.logic.subject import add_teacher_to_subject, create_subject, get_subjects_of_teacher +from domain.logic.subject import ( + add_teacher_to_subject, + create_subject, + get_subjects_of_teacher, + remove_teacher_from_subject, +) +from domain.logic.teacher import is_user_teacher -teacher_router = APIRouter() +teacher_router = APIRouter(tags=[Tags.TEACHER]) -@teacher_router.get("/teacher/subjects", tags=[Tags.TEACHER], summary="Get all subjects the teacher manages.") +@teacher_router.get("/teacher/subjects", summary="Get all subjects the teacher manages.") def subjects_of_teacher_get(request: Request) -> list[Subject]: session = request.state.session teacher = get_authenticated_teacher(request) return get_subjects_of_teacher(session, teacher.id) -@teacher_router.get("/teacher/projects", tags=[Tags.TEACHER], summary="Get all projects of the teacher.") +@teacher_router.get("/teacher/projects", summary="Get all projects of the teacher.") def projects_of_teacher_get(request: Request) -> list[Project]: session = request.state.session teacher = get_authenticated_teacher(request) return get_projects_of_teacher(session, teacher.id) -@teacher_router.post("/teacher/subjects", tags=[Tags.SUBJECT], summary="Create a new subject.") +@teacher_router.post("/teacher/subjects", summary="Create a new subject.") def create_subject_post(request: Request, subject: SubjectInput) -> Subject: session = request.state.session teacher = get_authenticated_teacher(request) @@ -32,3 +42,17 @@ def create_subject_post(request: Request, subject: SubjectInput) -> Subject: 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 + + +@teacher_router.post("/teacher/subjects/{subject_id}/leave", summary="Leave a subject.") +def teacher_subject_leave(request: Request, subject_id: int) -> None: + teacher = ensure_teacher_authorized_for_subject(request, subject_id) + remove_teacher_from_subject(request.state.session, teacher.id, subject_id) + + +@teacher_router.post("/teacher/subjects/{subject_id}/add", summary="Add a teacher to a subject.") +def teacher_subject_add(request: Request, subject_id: int, teacher_id: int) -> None: + ensure_teacher_authorized_for_subject(request, subject_id) + if not is_user_teacher(request.state.session, teacher_id): + raise NotATeacherError + add_teacher_to_subject(request.state.session, teacher_id, subject_id) diff --git a/backend/controllers/routes/user.py b/backend/controllers/routes/user.py index 0c2ad8bf..c3d07ebd 100644 --- a/backend/controllers/routes/user.py +++ b/backend/controllers/routes/user.py @@ -8,17 +8,17 @@ from domain.logic.role_enum import Role from domain.logic.user import get_user, modify_language, modify_user_roles -users_router = APIRouter() +users_router = APIRouter(tags=[Tags.USER]) -@users_router.get("/user", tags=[Tags.USER], summary="Get the current user (and its roles).") +@users_router.get("/user", summary="Get the current user (and its roles).") def get_current_user(request: Request) -> User: session = request.state.session uid = get_authenticated_user(request) return get_user(session, uid) -@users_router.patch("/user", tags=[Tags.USER], summary="Modify the language of the user.") +@users_router.put("/user", summary="Modify the the current user") def modify_current_user(request: Request, language: str) -> Response: session = request.state.session uid = get_authenticated_user(request) @@ -27,23 +27,23 @@ def modify_current_user(request: Request, language: str) -> Response: return Response(status_code=status.HTTP_204_NO_CONTENT) -@users_router.get("/users", tags=[Tags.USER], summary="Get all users.") +@users_router.get("/users", summary="Get all users.") def get_users(request: Request) -> list[User]: session = request.state.session - get_authenticated_admin(request) + get_authenticated_user(request) return get_all(session, User) -@users_router.get("/users/{uid}", tags=[Tags.USER], summary="Get a certain user.") +@users_router.get("/users/{uid}", summary="Get a certain user.") def admin_get_user(request: Request, uid: int) -> User: session = request.state.session - get_authenticated_admin(request) + get_authenticated_user(request) return get(session, User, uid) -@users_router.patch("/users/{uid}", tags=[Tags.USER], summary="Modify the roles of a certain user.") +@users_router.put("/users/{uid}", summary="Modify the roles of a certain user.") def modify_user(request: Request, uid: int, roles: list[Role]) -> Response: session = request.state.session get_authenticated_admin(request) diff --git a/backend/create_database_tables.py b/backend/create_database_tables.py index 872a3531..30de3c96 100644 --- a/backend/create_database_tables.py +++ b/backend/create_database_tables.py @@ -2,6 +2,7 @@ from sqlalchemy_utils import create_database, database_exists from sqlmodel import Session, SQLModel +import db.models # noqa: F401 from db.extensions import engine diff --git a/backend/db/models.py b/backend/db/models.py index cb486efd..83f90f80 100644 --- a/backend/db/models.py +++ b/backend/db/models.py @@ -66,6 +66,7 @@ class Student(SQLModel, table=True): class SubjectInput(SQLModel): name: str + archived: bool class Subject(SubjectInput, table=True): # Inherits from SubjectInput diff --git a/backend/domain/logic/errors.py b/backend/domain/logic/errors.py index 5b102b7d..246f5ca7 100644 --- a/backend/domain/logic/errors.py +++ b/backend/domain/logic/errors.py @@ -1,5 +1,18 @@ class InvalidConstraintsError(Exception): ERROR_MESSAGE = "The constraints are invalid" + class InvalidSubmissionError(Exception): - ERROR_MESSAGE = "Invalid submission content" + ERROR_MESSAGE = "Invalid submission content" + + +class UserNotEnrolledError(Exception): + ERROR_MESSAGE = "User is not enrolled for this subject" + + +class ArchivedError(Exception): + ERROR_MESSAGE = "Can't modify archived data" + + +class NotATeacherError(Exception): + ERROR_MESSAGE = "User isn't a teacher" diff --git a/backend/domain/logic/subject.py b/backend/domain/logic/subject.py index b30c497c..8326713d 100644 --- a/backend/domain/logic/subject.py +++ b/backend/domain/logic/subject.py @@ -1,14 +1,14 @@ from sqlmodel import Session -from db.database_errors import ActionAlreadyPerformedError -from db.models import Student, Subject, Teacher +from db.database_errors import ActionAlreadyPerformedError, NoSuchRelationError +from db.models import Student, Subject, SubjectInput, 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 def create_subject(session: Session, name: str) -> Subject: - new_subject = Subject(name=name) + new_subject = Subject(name=name, archived=False) session.add(new_subject) session.commit() return new_subject @@ -75,3 +75,34 @@ def get_teachers_of_subject(session: Session, subject_id: int) -> list[Teacher]: def get_students_of_subject(session: Session, subject_id: int) -> list[Student]: subject: Subject = get(session, Subject, ident=subject_id) return subject.students + + +def remove_student_from_subject(session: Session, student_id: int, subject_id: int) -> None: + student = get(session, Student, ident=student_id) + subject = get(session, Subject, ident=subject_id) + + if subject not in student.subjects: + msg = "Student is not enrolled in subject" + raise NoSuchRelationError(msg) + + student.subjects.remove(subject) + session.commit() + + +def remove_teacher_from_subject(session: Session, teacher_id: int, subject_id: int) -> None: + teacher = get(session, Teacher, ident=teacher_id) + subject = get(session, Subject, ident=subject_id) + + if subject not in teacher.subjects: + msg = "Teacher doesn't teach subject" + raise NoSuchRelationError(msg) + + teacher.subjects.remove(subject) + session.commit() + + +def update_subject(session: Session, subject_id: int, subject: SubjectInput) -> None: + subject_db = get(session, Subject, subject_id) + subject_db.archived = subject.archived + subject_db.name = subject.name + session.commit() diff --git a/backend/domain/logic/submission.py b/backend/domain/logic/submission.py index 49c5f64f..cba2941d 100644 --- a/backend/domain/logic/submission.py +++ b/backend/domain/logic/submission.py @@ -5,7 +5,7 @@ from db.models import Group, Student, Submission, SubmissionState from domain.logic.basic_operations import get, get_all -from domain.logic.errors import InvalidSubmissionError +from domain.logic.errors import ArchivedError, InvalidSubmissionError from domain.simple_submission_checks.constraints.submission_constraint import create_constraint_from_json @@ -24,6 +24,9 @@ def create_submission( student: Student = get(session, Student, ident=student_id) group: Group = get(session, Group, ident=group_id) + if group.project.archived: + raise ArchivedError + new_submission: Submission = Submission( student_id=student.id, group_id=group.id, @@ -64,5 +67,6 @@ def check_submission(session: Session, group_id: int, path: str) -> None: group = get(session, Group, group_id) project = group.project constraints = create_constraint_from_json(project.requirements) - if not constraints.validate_constraint(Path(path)).is_ok: - raise InvalidSubmissionError + validation = constraints.validate_constraint(Path(path)) + if not validation.is_ok: + raise InvalidSubmissionError(validation) diff --git a/backend/poetry.lock b/backend/poetry.lock index 10f7e4ea..0c650fbd 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "annotated-types" From 620efb1d1238526c51fd5f47c8c7033985fcfb46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stef=20Oss=C3=A9?= Date: Wed, 17 Apr 2024 15:33:13 +0200 Subject: [PATCH 76/90] updating branch to main --- frontend/src/components/Header.tsx | 2 +- .../components/ProjectStudentComponent.tsx | 17 +--- .../components/ProjectTeacherComponent.tsx | 77 +++++++++---------- frontend/src/components/RegularButton.tsx | 4 +- frontend/src/components/Settings.tsx | 3 +- frontend/src/components/Sidebar.tsx | 32 +++----- frontend/src/components/Statistics.tsx | 2 +- frontend/src/components/Table.tsx | 51 ++++++------ .../components/authentication/ErrorLogin.tsx | 3 +- frontend/src/dataloaders/SharedFunctions.ts | 46 +++++++++++ frontend/src/dataloaders/StudentLoader.ts | 5 +- frontend/src/dataloaders/TeacherLoader.ts | 4 +- frontend/src/main.tsx | 1 + frontend/src/utils/ApiInterfaces.ts | 1 + 14 files changed, 135 insertions(+), 113 deletions(-) create mode 100644 frontend/src/dataloaders/SharedFunctions.ts diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index 204ed32c..eb041bf0 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -11,7 +11,7 @@ export function Header(props: { page_title: string, home: string }): JSX.Element {"image"}/ -

{props.page_title}

+

{props.page_title}

- ) +export interface TableRowCourses extends TableRow { + numberOfProjects: number | null; // not for archived projects for teachers + shortestDeadline: string | null; // only if not archived } -export function Table(props: { title: string, data: T[], ignoreKeys: string[], home: string }): JSX.Element { +export function Table(props: { title: string, data: T[], ignoreKeys: string[] }): JSX.Element { // todo: translate the keys for i18n way // right now only the name of the key will be written as it will be completely different with i18n const keys = props.data.length > 0 ? Object.keys(props.data[0]) : []; + const firstFieldWidth: number = 40; + const otherFieldsWidth: number = 1 / (keys.length - 1) * 100 - firstFieldWidth / (keys.length - 1); return (
@@ -40,12 +33,12 @@ export function Table(props: { title: string, data: T[], ign
{keys.map((field, index) => ( !props.ignoreKeys.some(item => field === item) ? - index === 0 ? + field === "name" ? : - - : + : ))} @@ -54,9 +47,13 @@ export function Table(props: { title: string, data: T[], ign {keys.map((field, colIndex) => ( !props.ignoreKeys.some(item => field === item) ? - - : - + colIndex == 0 ? + + : + + : ))} ))} diff --git a/frontend/src/components/authentication/ErrorLogin.tsx b/frontend/src/components/authentication/ErrorLogin.tsx index 1e0ab292..64bf7d96 100644 --- a/frontend/src/components/authentication/ErrorLogin.tsx +++ b/frontend/src/components/authentication/ErrorLogin.tsx @@ -1,12 +1,11 @@ import {JSX} from "react"; -import delphi_full from "/delphi_full.png"; export default function ErrorLogin(): JSX.Element { return (
- Delphi logo + Delphi logo

There was an error logging in!

{"To retry, please press the button below"}

diff --git a/frontend/src/dataloaders/SharedFunctions.ts b/frontend/src/dataloaders/SharedFunctions.ts new file mode 100644 index 00000000..52e1e647 --- /dev/null +++ b/frontend/src/dataloaders/SharedFunctions.ts @@ -0,0 +1,46 @@ +import {Project, Subject} from "../utils/ApiInterfaces.ts"; +import apiFetch, {ApiFetchResponse} from "../utils/ApiFetch.ts"; +import {Backend_Project, Backend_Subject} from "../utils/BackendInterfaces.ts"; +import {mapProjectList, mapSubjectList} from "../utils/ApiTypesMapper.ts"; + +export enum teacherStudentRole { + STUDENT = "student", + TEACHER = "teacher" +} + +export async function projectsLoader(role: teacherStudentRole): Promise { + const getter = await getAllProjectsAndSubjects(role); + const subjects = getter.subjects; + const projects = getter.projects; + // TODO: add submission data + for (let i = 0; i < projects.length; i++) { + const subject: Subject | undefined = subjects.find(subject => subject.subject_id === projects[i].subject_id); + if (subject !== undefined) { + projects[i].subject_name = subject.subject_name; + } + } + return projects; +} + +export interface projectsAndSubjects { + projects: Project[], + subjects: Subject[] +} + +export async function getAllProjectsAndSubjects(role: teacherStudentRole): Promise { + let projects: Project[] = []; + let subjects: Subject[] = []; + + const projectsData: ApiFetchResponse = (await apiFetch(`/${role}/projects`)); + const subjectsData: ApiFetchResponse = (await apiFetch(`/${role}/subjects`)); + + if (projectsData.ok){ + projects = mapProjectList(projectsData.content) + } + + if (subjectsData.ok){ + subjects = mapSubjectList(subjectsData.content) + } + + return {projects, subjects} +} diff --git a/frontend/src/dataloaders/StudentLoader.ts b/frontend/src/dataloaders/StudentLoader.ts index 9fa42d45..3f643c02 100644 --- a/frontend/src/dataloaders/StudentLoader.ts +++ b/frontend/src/dataloaders/StudentLoader.ts @@ -1,12 +1,13 @@ -import {CompleteProjectStudent} from "../utils/ApiInterfaces.ts"; +import {CompleteProjectStudent, Project} from "../utils/ApiInterfaces.ts"; import {LoadProjectsForStudent} from "./ProjectsStudentLoader.ts"; export interface studentLoaderObject { - projects: CompleteProjectStudent[] + projects: Project[] } export const STUDENT_ROUTER_ID = "student"; + export default async function studentLoader(): Promise { const projects: CompleteProjectStudent[] = await LoadProjectsForStudent(true); return {projects}; diff --git a/frontend/src/dataloaders/TeacherLoader.ts b/frontend/src/dataloaders/TeacherLoader.ts index 9d80fcc7..6d9d75a9 100644 --- a/frontend/src/dataloaders/TeacherLoader.ts +++ b/frontend/src/dataloaders/TeacherLoader.ts @@ -1,8 +1,8 @@ -import {CompleteProjectTeacher} from "../utils/ApiInterfaces.ts"; +import {CompleteProjectTeacher, Project} from "../utils/ApiInterfaces.ts"; import {LoadProjectsForTeacher} from "./projectsTeacherLoader.ts"; export interface teacherLoaderObject { - projects: CompleteProjectTeacher[] + projects: Project[] } export const TEACHER_ROUTER_ID = "teacher"; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index ccd44a96..f9dd494a 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -11,6 +11,7 @@ import HomeTeacher from "./pages/teacher/HomeTeacher.tsx"; import studentLoader, {STUDENT_ROUTER_ID} from "./dataloaders/StudentLoader.ts"; import Unauthorized from "./components/authentication/Unauthorized.tsx"; import teacherLoader, {TEACHER_ROUTER_ID} from "./dataloaders/TeacherLoader.ts"; +// import subjectsTeacherLoader, {SUBJECT_TEACHER_ROUTER_ID} from "./dataloaders/SubjectsTeacherLoader.ts"; import loginLoader, {LOGIN_ROUTER_ID} from "./dataloaders/LoginLoader.ts"; import ErrorLogin from "./components/authentication/ErrorLogin.tsx"; import ProjectsViewStudent from "./pages/student/ProjectsViewStudent.tsx"; diff --git a/frontend/src/utils/ApiInterfaces.ts b/frontend/src/utils/ApiInterfaces.ts index 00393a6a..1e954bbf 100644 --- a/frontend/src/utils/ApiInterfaces.ts +++ b/frontend/src/utils/ApiInterfaces.ts @@ -13,6 +13,7 @@ export interface Project { project_visible: boolean, project_max_students: number, subject_id: number, + subject_name: string } From 51e53783a508deadeeb8f341f4d8388e3087c6cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stef=20Oss=C3=A9?= Date: Wed, 17 Apr 2024 16:01:21 +0200 Subject: [PATCH 77/90] trying to set branch ready --- frontend/src/components/RegularButton.tsx | 4 +-- frontend/src/components/Sidebar.tsx | 32 ++++++++++++------- frontend/src/components/Statistics.tsx | 2 +- frontend/src/dataloaders/StudentLoader.ts | 5 ++- frontend/src/dataloaders/TeacherLoader.ts | 4 +-- .../src/dataloaders/projectsTeacherLoader.ts | 2 +- frontend/src/main.tsx | 1 - frontend/src/utils/ApiInterfaces.ts | 1 - 8 files changed, 29 insertions(+), 22 deletions(-) diff --git a/frontend/src/components/RegularButton.tsx b/frontend/src/components/RegularButton.tsx index bfbe1d0d..62f768c9 100644 --- a/frontend/src/components/RegularButton.tsx +++ b/frontend/src/components/RegularButton.tsx @@ -1,9 +1,9 @@ import {JSX} from "react"; import {IoMdAdd} from "react-icons/io"; -export function RegularButton(props: { placeholder: string, add: boolean, onClick: () => void }): JSX.Element { +export function RegularButton(props: { placeholder: string, add: boolean, onClick: () => void, styling?: string }): JSX.Element { return ( -
- { typeof values[props.index] === "object" && "id" in values[props.index] ? ( - - {(values[props.index] as {name: string, id: number}).name} - - ) : ( - <>{values[props.index]} - )} -
{field}{field}
{Object.values(row)[keys.indexOf(field)]}{Object.values(row)[keys.indexOf(field)]}