Skip to content

Commit

Permalink
Add functionality to assign thesis advisors, reject and accept applic…
Browse files Browse the repository at this point in the history
…ations
  • Loading branch information
airelawaleria committed Oct 2, 2023
1 parent 50dfb3f commit 6b766f6
Show file tree
Hide file tree
Showing 17 changed files with 787 additions and 260 deletions.
559 changes: 309 additions & 250 deletions client/src/forms/ThesisApplicationForm.tsx

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import jwtDecode from 'jwt-decode'
import { setAuthState } from '../../redux/authSlice/authSlice'
import { ThesisApplicationsDatatable } from './components/ThesisApplicationsDatatable'
import { Center } from '@mantine/core'
import { updateThesisAdvisorList } from '../../redux/thesisApplicationsSlice/thunks/updateThesisAdvisorList'

export const ThesisApplicationsManagementConsole = (): JSX.Element => {
const dispatch = useDispatch<AppDispatch>()
Expand Down Expand Up @@ -48,6 +49,17 @@ export const ThesisApplicationsManagementConsole = (): JSX.Element => {
mgmtAccess: keycloak.hasResourceRole('chair-member', 'prompt-server'),
}),
)

if (keycloak.hasResourceRole('chair-member', 'prompt-server')) {
void dispatch(
updateThesisAdvisorList({
firstName: decodedJwt.given_name,
lastName: decodedJwt.family_name,
email: decodedJwt.email,
tumId: decodedJwt.preferred_username,
}),
)
}
}
} catch (error) {
dispatch(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import {
} from '../applicationsSlice/applicationsSlice'
import { fetchThesisApplications } from './thunks/fetchThesisApplications'
import { assessThesisApplication } from './thunks/assessThesisApplication'
import { updateThesisAdvisorList } from './thunks/updateThesisAdvisorList'
import { acceptThesisApplication } from './thunks/acceptThesisApplication'
import { rejectThesisApplication } from './thunks/rejectThesisApplication'
import { assignThesisAdvisor } from './thunks/assignThesisAdvisor'

enum ResearchArea {
EDUCATION_TECHNOLOGIES = 'Education Technologies',
Expand Down Expand Up @@ -53,6 +57,14 @@ enum FocusTopic {
HW_SW_CO_DESIGN = 'HW/SW Co-Design',
}

interface ThesisAdvisor {
id?: string
firstName: string
lastName: string
email: string
tumId: string
}

interface ThesisApplication {
id: string
student: Student
Expand All @@ -74,18 +86,21 @@ interface ThesisApplication {
applicationStatus: keyof typeof ApplicationStatus
assessmentComment?: string
createdAt?: Date
thesisAdvisor?: ThesisAdvisor
}

interface ThesisApplicationsSliceState {
status: string
error: string | null
applications: ThesisApplication[]
thesisAdvisors: ThesisAdvisor[]
}

const initialState: ThesisApplicationsSliceState = {
status: 'idle',
error: null,
applications: [],
thesisAdvisors: [],
}

export const thesisApplicationsSlice = createSlice({
Expand Down Expand Up @@ -124,8 +139,74 @@ export const thesisApplicationsSlice = createSlice({
if (payload) state.error = 'error'
state.status = 'idle'
})

builder.addCase(acceptThesisApplication.pending, (state) => {
state.status = 'loading'
state.error = null
})

builder.addCase(acceptThesisApplication.fulfilled, (state, { payload }) => {
state.applications = state.applications.map((application) =>
application.id === payload.id ? payload : application,
)
state.status = 'idle'
})

builder.addCase(acceptThesisApplication.rejected, (state, { payload }) => {
if (payload) state.error = 'error'
state.status = 'idle'
})

builder.addCase(rejectThesisApplication.pending, (state) => {
state.status = 'loading'
state.error = null
})

builder.addCase(rejectThesisApplication.fulfilled, (state, { payload }) => {
state.applications = state.applications.map((application) =>
application.id === payload.id ? payload : application,
)
state.status = 'idle'
})

builder.addCase(rejectThesisApplication.rejected, (state, { payload }) => {
if (payload) state.error = 'error'
state.status = 'idle'
})

builder.addCase(assignThesisAdvisor.pending, (state) => {
state.status = 'loading'
state.error = null
})

builder.addCase(assignThesisAdvisor.fulfilled, (state, { payload }) => {
state.applications = state.applications.map((application) =>
application.id === payload.id ? payload : application,
)
state.status = 'idle'
})

builder.addCase(assignThesisAdvisor.rejected, (state, { payload }) => {
if (payload) state.error = 'error'
state.status = 'idle'
})

builder.addCase(updateThesisAdvisorList.pending, (state) => {
state.status = 'loading'
state.error = null
})

builder.addCase(updateThesisAdvisorList.fulfilled, (state, { payload }) => {
state.thesisAdvisors = payload
state.status = 'idle'
})

builder.addCase(updateThesisAdvisorList.rejected, (state, { payload }) => {
if (payload) state.error = 'error'
state.status = 'idle'
})
},
})

export default thesisApplicationsSlice.reducer
export { type ThesisApplication, ResearchArea, FocusTopic }
export { type ThesisApplication, ResearchArea, FocusTopic, type ThesisAdvisor }
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { createAsyncThunk } from '@reduxjs/toolkit'
import axios from 'axios'
import { serverBaseUrl } from '../../../service/configService'

export const acceptThesisApplication = createAsyncThunk(
'thesisApplications/acceptThesisApplication',

async (thesisApplicationId: string, { rejectWithValue }) => {
try {
return (
await axios.post(
`${serverBaseUrl}/api/thesis-applications/${thesisApplicationId}/accept`,
{},
{
headers: {
Authorization: `Bearer ${localStorage.getItem('jwt_token') ?? ''}`,
},
},
)
).data
} catch (err) {
return rejectWithValue(err)
}
},
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { createAsyncThunk } from '@reduxjs/toolkit'
import axios from 'axios'
import { serverBaseUrl } from '../../../service/configService'

export const assignThesisAdvisor = createAsyncThunk(
'thesisApplications/assignThesisAdvisor',

async (
{
thesisApplicationId,
thesisAdvisorId,
}: { thesisApplicationId: string; thesisAdvisorId: string },
{ rejectWithValue },
) => {
try {
return (
await axios.post(
`${serverBaseUrl}/api/thesis-applications/${thesisApplicationId}/thesis-advisor/${thesisAdvisorId}`,
{},
{
headers: {
Authorization: `Bearer ${localStorage.getItem('jwt_token') ?? ''}`,
},
},
)
).data
} catch (err) {
return rejectWithValue(err)
}
},
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { createAsyncThunk } from '@reduxjs/toolkit'
import axios from 'axios'
import { serverBaseUrl } from '../../../service/configService'

export const rejectThesisApplication = createAsyncThunk(
'thesisApplications/rejectThesisApplication',

async (thesisApplicationId: string, { rejectWithValue }) => {
try {
return (
await axios.post(
`${serverBaseUrl}/api/thesis-applications/${thesisApplicationId}/reject`,
{},
{
headers: {
Authorization: `Bearer ${localStorage.getItem('jwt_token') ?? ''}`,
},
},
)
).data
} catch (err) {
return rejectWithValue(err)
}
},
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { createAsyncThunk } from '@reduxjs/toolkit'
import axios from 'axios'
import { serverBaseUrl } from '../../../service/configService'
import { type ThesisAdvisor } from '../thesisApplicationsSlice'

export const updateThesisAdvisorList = createAsyncThunk(
'thesisApplications/updateThesisAdvisorList',

async (thesisAdvisor: ThesisAdvisor, { rejectWithValue }) => {
try {
return (
await axios.put(`${serverBaseUrl}/api/thesis-applications/thesis-advisors`, thesisAdvisor, {
headers: {
Authorization: `Bearer ${localStorage.getItem('jwt_token') ?? ''}`,
},
})
).data
} catch (err) {
return rejectWithValue(err)
}
},
)
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import prompt.ls1.controller.payload.ThesisApplicationAssessment;
import prompt.ls1.model.ThesisAdvisor;
import prompt.ls1.model.ThesisApplication;
import prompt.ls1.service.MailingService;
import prompt.ls1.service.ThesisApplicationService;
Expand Down Expand Up @@ -64,7 +66,7 @@ public ResponseEntity<List<ThesisApplication>> getAllNotAssessed() {

@GetMapping("/{thesisApplicationId}/examination-report")
@PreAuthorize("hasRole('chair-member')")
public ResponseEntity<Resource> getExaminationReport(@PathVariable final UUID thesisApplicationId) throws IOException {
public ResponseEntity<Resource> getExaminationReport(@PathVariable final UUID thesisApplicationId) {
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_PDF)
.header(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment; filename=examination_report_%s.pdf", thesisApplicationId))
Expand All @@ -73,7 +75,7 @@ public ResponseEntity<Resource> getExaminationReport(@PathVariable final UUID th

@GetMapping("/{thesisApplicationId}/cv")
@PreAuthorize("hasRole('chair-member')")
public ResponseEntity<Resource> getCV(@PathVariable final UUID thesisApplicationId) throws IOException {
public ResponseEntity<Resource> getCV(@PathVariable final UUID thesisApplicationId) {
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_PDF)
.header(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment; filename=cv_%s.pdf", thesisApplicationId))
Expand All @@ -82,7 +84,7 @@ public ResponseEntity<Resource> getCV(@PathVariable final UUID thesisApplication

@GetMapping("/{thesisApplicationId}/bachelor-report")
@PreAuthorize("hasRole('chair-member')")
public ResponseEntity<Resource> getBachelorReport(@PathVariable final UUID thesisApplicationId) throws IOException {
public ResponseEntity<Resource> getBachelorReport(@PathVariable final UUID thesisApplicationId) {
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_PDF)
.header(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment; filename=bachelor_report_%s.pdf", thesisApplicationId))
Expand Down Expand Up @@ -111,4 +113,29 @@ public ResponseEntity<ThesisApplication> assess(@PathVariable final UUID thesisA
@RequestBody final ThesisApplicationAssessment assessment) {
return ResponseEntity.ok(thesisApplicationService.assess(thesisApplicationId, assessment.getStatus(), assessment.getAssessmentComment()));
}

@PutMapping("/thesis-advisors")
@PreAuthorize("hasRole('chair-member')")
public ResponseEntity<List<ThesisAdvisor>> updateThesisAdvisorList(@RequestBody final ThesisAdvisor thesisAdvisor) {
return ResponseEntity.ok(thesisApplicationService.updateThesisAdvisorList(thesisAdvisor));
}

@PostMapping("/{thesisApplicationId}/thesis-advisor/{thesisAdvisorId}")
@PreAuthorize("hasRole('chair-member')")
public ResponseEntity<ThesisApplication> assignThesisAdvisor(@PathVariable final UUID thesisApplicationId,
@PathVariable final UUID thesisAdvisorId) {
return ResponseEntity.ok(thesisApplicationService.assignThesisAdvisor(thesisApplicationId, thesisAdvisorId));
}

@PostMapping("/{thesisApplicationId}/accept")
@PreAuthorize("hasRole('chair-member')")
public ResponseEntity<ThesisApplication> acceptThesisApplication(@PathVariable final UUID thesisApplicationId) {
return ResponseEntity.ok(thesisApplicationService.accept(thesisApplicationId));
}

@PostMapping("/{thesisApplicationId}/reject")
@PreAuthorize("hasRole('chair-member')")
public ResponseEntity<ThesisApplication> rejectThesisApplication(@PathVariable final UUID thesisApplicationId) {
return ResponseEntity.ok(thesisApplicationService.reject(thesisApplicationId));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ protected ResponseEntity<Object> handleNotFound(
@ExceptionHandler(value
= { ParseException.class, ResourceInvalidParametersException.class,
JsonParseException.class, JsonProcessingException.class, RuntimeException.class,
UnirestRequestException.class })
UnirestRequestException.class, FailedMailSend.class })
protected ResponseEntity<Object> handleBadRequest(
RuntimeException ex, WebRequest request) {
return handleExceptionInternal(ex, ex.getMessage(),
Expand Down
8 changes: 8 additions & 0 deletions server/src/main/java/prompt/ls1/exception/FailedMailSend.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package prompt.ls1.exception;

public class FailedMailSend extends RuntimeException{

public FailedMailSend(String message) {
super(message);
}
}
36 changes: 36 additions & 0 deletions server/src/main/java/prompt/ls1/model/ThesisAdvisor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package prompt.ls1.model;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import jakarta.validation.constraints.Email;
import lombok.Data;
import org.hibernate.validator.constraints.Length;

import java.util.UUID;

@Data
@Entity
@Table(uniqueConstraints = { @UniqueConstraint(columnNames = { "email" }),
@UniqueConstraint(columnNames = { "tumId" }) })
public class ThesisAdvisor {

@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;

private String firstName;

private String lastName;

@Column(length = 50)
@Length(max = 50)
private String tumId;
@Email
@Column(unique = true)
private String email;
}
4 changes: 4 additions & 0 deletions server/src/main/java/prompt/ls1/model/ThesisApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,8 @@ public class ThesisApplication implements Serializable {
private String cvFilename;

private String bachelorReportFilename;

@ManyToOne
@JoinColumn(name = "thesis_advisor_id")
private ThesisAdvisor thesisAdvisor;
}
Loading

0 comments on commit 6b766f6

Please sign in to comment.