Skip to content

Commit 8decdf8

Browse files
sachmiigithub-actions[bot]az108
authored
Development: Request access form for employees (#1043)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: aniruddhzaveri <aniruddh.zaveri@tum.de>
1 parent 1502e18 commit 8decdf8

File tree

15 files changed

+447
-12
lines changed

15 files changed

+447
-12
lines changed

openapi/openapi.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -964,6 +964,17 @@ paths:
964964
content:
965965
application/json:
966966
schema: {$ref: '#/components/schemas/PageResponseDTOResearchGroupDTO'}
967+
/api/research-groups/employee-request:
968+
post:
969+
tags: [research-group-resource]
970+
operationId: createEmployeeResearchGroupRequest
971+
requestBody:
972+
content:
973+
application/json:
974+
schema: {$ref: '#/components/schemas/EmployeeResearchGroupRequestDTO'}
975+
required: true
976+
responses:
977+
'200': {description: OK}
967978
/api/research-groups/members:
968979
get:
969980
tags: [research-group-resource]
@@ -1447,6 +1458,11 @@ components:
14471458
properties:
14481459
body: {type: string}
14491460
subject: {type: string}
1461+
EmployeeResearchGroupRequestDTO:
1462+
type: object
1463+
properties:
1464+
professorName: {type: string, minLength: 1}
1465+
required: [professorName]
14501466
InternalComment:
14511467
type: object
14521468
properties:
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package de.tum.cit.aet.usermanagement.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonInclude;
4+
import jakarta.validation.constraints.NotBlank;
5+
6+
/**
7+
* DTO for employee research group access requests during onboarding.
8+
* Contains the professor name that the employee is working for.
9+
* This is a temporary solution until the employee role is added.
10+
*/
11+
@JsonInclude(JsonInclude.Include.NON_EMPTY)
12+
public record EmployeeResearchGroupRequestDTO(@NotBlank(message = "Professor name is required") String professorName) {}

src/main/java/de/tum/cit/aet/usermanagement/service/ResearchGroupService.java

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package de.tum.cit.aet.usermanagement.service;
22

3+
import de.tum.cit.aet.core.constants.Language;
34
import de.tum.cit.aet.core.dto.PageDTO;
45
import de.tum.cit.aet.core.dto.PageResponseDTO;
56
import de.tum.cit.aet.core.exception.AccessDeniedException;
@@ -8,6 +9,8 @@
89
import de.tum.cit.aet.core.exception.ResourceAlreadyExistsException;
910
import de.tum.cit.aet.core.service.CurrentUserService;
1011
import de.tum.cit.aet.core.util.StringUtil;
12+
import de.tum.cit.aet.notification.service.AsyncEmailSender;
13+
import de.tum.cit.aet.notification.service.mail.Email;
1114
import de.tum.cit.aet.usermanagement.constants.ResearchGroupState;
1215
import de.tum.cit.aet.usermanagement.constants.UserRole;
1316
import de.tum.cit.aet.usermanagement.domain.ResearchGroup;
@@ -22,6 +25,7 @@
2225
import java.util.UUID;
2326
import lombok.RequiredArgsConstructor;
2427
import lombok.extern.slf4j.Slf4j;
28+
import org.springframework.beans.factory.annotation.Value;
2529
import org.springframework.data.domain.Page;
2630
import org.springframework.data.domain.PageRequest;
2731
import org.springframework.data.domain.Pageable;
@@ -42,6 +46,10 @@ public class ResearchGroupService {
4246
private final ResearchGroupRepository researchGroupRepository;
4347

4448
private final UserResearchGroupRoleRepository userResearchGroupRoleRepository;
49+
private final AsyncEmailSender emailSender;
50+
51+
@Value("${aet.contact-email:tum-apply.aet@xcit.tum.de}")
52+
private String supportEmail;
4553

4654
/**
4755
* Get all members of the current user's research group.
@@ -360,4 +368,64 @@ public ResearchGroup createProfessorResearchGroupRequest(ProfessorResearchGroupR
360368

361369
return saved;
362370
}
371+
372+
/**
373+
* Creates an employee research group access request.
374+
* Sends an email to support/administrators with user information and professor name.
375+
* This is a temporary solution until the employee role is implemented.
376+
*
377+
* @param request the employee's research group request containing professor name
378+
*/
379+
@Transactional
380+
public void createEmployeeResearchGroupRequest(EmployeeResearchGroupRequestDTO request) {
381+
User currentUser = currentUserService.getUser();
382+
383+
// Create a dummy admin user for sending the email to support
384+
User supportUser = new User();
385+
supportUser.setEmail(supportEmail);
386+
supportUser.setSelectedLanguage(Language.ENGLISH.getCode());
387+
388+
String emailBody = String.format(
389+
"""
390+
<html>
391+
<body>
392+
<h2>Employee Research Group Access Request</h2>
393+
<p>A user has requested access to a research group as an employee.</p>
394+
395+
<h3>User Information:</h3>
396+
<ul>
397+
<li><strong>Name:</strong> %s %s</li>
398+
<li><strong>Email:</strong> %s</li>
399+
<li><strong>User ID:</strong> %s</li>
400+
<li><strong>University ID:</strong> %s</li>
401+
</ul>
402+
403+
<h3>Professor Information:</h3>
404+
<ul>
405+
<li><strong>Professor Name:</strong> %s</li>
406+
</ul>
407+
408+
<p>Please assign this user to the appropriate research group.</p>
409+
</body>
410+
</html>
411+
""",
412+
currentUser.getFirstName() != null ? currentUser.getFirstName() : "N/A",
413+
currentUser.getLastName() != null ? currentUser.getLastName() : "N/A",
414+
currentUser.getEmail(),
415+
currentUser.getUserId(),
416+
currentUser.getUniversityId() != null ? currentUser.getUniversityId() : "Not provided",
417+
request.professorName()
418+
);
419+
420+
Email email = Email.builder()
421+
.to(supportUser)
422+
.customSubject("Employee Research Group Access Request - " + currentUser.getEmail())
423+
.customBody(emailBody)
424+
.sendAlways(true)
425+
.language(Language.ENGLISH)
426+
.build();
427+
428+
emailSender.sendAsync(email);
429+
log.info("Employee access request sent to support: userId={} professorName={}", currentUser.getUserId(), request.professorName());
430+
}
363431
}

src/main/java/de/tum/cit/aet/usermanagement/web/ResearchGroupResource.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import de.tum.cit.aet.core.security.annotations.Authenticated;
88
import de.tum.cit.aet.core.security.annotations.ProfessorOrAdmin;
99
import de.tum.cit.aet.usermanagement.domain.ResearchGroup;
10+
import de.tum.cit.aet.usermanagement.dto.EmployeeResearchGroupRequestDTO;
1011
import de.tum.cit.aet.usermanagement.dto.ProfessorResearchGroupRequestDTO;
1112
import de.tum.cit.aet.usermanagement.dto.ResearchGroupDTO;
1213
import de.tum.cit.aet.usermanagement.dto.ResearchGroupLargeDTO;
@@ -135,14 +136,29 @@ public ResponseEntity<ResearchGroupDTO> createProfessorResearchGroupRequest(
135136
return ResponseEntity.status(HttpStatus.CREATED).body(ResearchGroupDTO.getFromEntity(created));
136137
}
137138

139+
/**
140+
* Creates an employee research group access request during onboarding.
141+
* Sends an email to administrators with user and professor information.
142+
*
143+
* @param request the employee's research group request
144+
* @return HTTP 204 No Content on success
145+
*/
146+
@PostMapping("/employee-request")
147+
@Authenticated
148+
public ResponseEntity<Void> createEmployeeResearchGroupRequest(@Valid @RequestBody EmployeeResearchGroupRequestDTO request) {
149+
log.info("POST /api/research-groups/employee-request professorName={}", request.professorName());
150+
researchGroupService.createEmployeeResearchGroupRequest(request);
151+
return ResponseEntity.noContent().build();
152+
}
153+
138154
/**
139155
* Gets all DRAFT research groups for admin review.
140156
*
141157
* @param pageDTO the pagination parameters
142158
* @return paginated list of DRAFT research groups
143159
*/
144160
@GetMapping("/draft")
145-
@PreAuthorize("hasRole('ADMIN')")
161+
@Admin
146162
public ResponseEntity<PageResponseDTO<ResearchGroupDTO>> getDraftResearchGroups(
147163
@ParameterObject @Valid @ModelAttribute PageDTO pageDTO
148164
) {

src/main/webapp/app/generated/.openapi-generator/FILES

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ model/emailSettingDTO.ts
4242
model/emailTemplateDTO.ts
4343
model/emailTemplateOverviewDTO.ts
4444
model/emailTemplateTranslationDTO.ts
45+
model/employeeResearchGroupRequestDTO.ts
4546
model/internalComment.ts
4647
model/internalCommentDTO.ts
4748
model/internalCommentUpdateDTO.ts

src/main/webapp/app/generated/api/researchGroupResourceApi.service.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import { HttpClient, HttpParams,
1515
} from '@angular/common/http';
1616
import { Observable } from 'rxjs';
1717

18+
// @ts-ignore
19+
import { EmployeeResearchGroupRequestDTO } from '../model/employeeResearchGroupRequestDTO';
1820
// @ts-ignore
1921
import { PageResponseDTOResearchGroupDTO } from '../model/pageResponseDTOResearchGroupDTO';
2022
// @ts-ignore
@@ -94,6 +96,67 @@ export class ResearchGroupResourceApiService extends BaseService {
9496
);
9597
}
9698

99+
/**
100+
* @param employeeResearchGroupRequestDTO
101+
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
102+
* @param reportProgress flag to report request and response progress.
103+
*/
104+
public createEmployeeResearchGroupRequest(employeeResearchGroupRequestDTO: EmployeeResearchGroupRequestDTO, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable<any>;
105+
public createEmployeeResearchGroupRequest(employeeResearchGroupRequestDTO: EmployeeResearchGroupRequestDTO, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable<HttpResponse<any>>;
106+
public createEmployeeResearchGroupRequest(employeeResearchGroupRequestDTO: EmployeeResearchGroupRequestDTO, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable<HttpEvent<any>>;
107+
public createEmployeeResearchGroupRequest(employeeResearchGroupRequestDTO: EmployeeResearchGroupRequestDTO, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable<any> {
108+
if (employeeResearchGroupRequestDTO === null || employeeResearchGroupRequestDTO === undefined) {
109+
throw new Error('Required parameter employeeResearchGroupRequestDTO was null or undefined when calling createEmployeeResearchGroupRequest.');
110+
}
111+
112+
let localVarHeaders = this.defaultHeaders;
113+
114+
const localVarHttpHeaderAcceptSelected: string | undefined = options?.httpHeaderAccept ?? this.configuration.selectHeaderAccept([
115+
]);
116+
if (localVarHttpHeaderAcceptSelected !== undefined) {
117+
localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected);
118+
}
119+
120+
const localVarHttpContext: HttpContext = options?.context ?? new HttpContext();
121+
122+
const localVarTransferCache: boolean = options?.transferCache ?? true;
123+
124+
125+
// to determine the Content-Type header
126+
const consumes: string[] = [
127+
'application/json'
128+
];
129+
const httpContentTypeSelected: string | undefined = this.configuration.selectHeaderContentType(consumes);
130+
if (httpContentTypeSelected !== undefined) {
131+
localVarHeaders = localVarHeaders.set('Content-Type', httpContentTypeSelected);
132+
}
133+
134+
let responseType_: 'text' | 'json' | 'blob' = 'json';
135+
if (localVarHttpHeaderAcceptSelected) {
136+
if (localVarHttpHeaderAcceptSelected.startsWith('text')) {
137+
responseType_ = 'text';
138+
} else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) {
139+
responseType_ = 'json';
140+
} else {
141+
responseType_ = 'blob';
142+
}
143+
}
144+
145+
let localVarPath = `/api/research-groups/employee-request`;
146+
return this.httpClient.request<any>('post', `${this.configuration.basePath}${localVarPath}`,
147+
{
148+
context: localVarHttpContext,
149+
body: employeeResearchGroupRequestDTO,
150+
responseType: <any>responseType_,
151+
withCredentials: this.configuration.withCredentials,
152+
headers: localVarHeaders,
153+
observe: observe,
154+
transferCache: localVarTransferCache,
155+
reportProgress: reportProgress
156+
}
157+
);
158+
}
159+
97160
/**
98161
* @param professorResearchGroupRequestDTO
99162
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* OpenAPI definition
3+
*
4+
*
5+
*
6+
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
7+
* https://openapi-generator.tech
8+
* Do not edit the class manually.
9+
*/
10+
11+
12+
export interface EmployeeResearchGroupRequestDTO {
13+
professorName: string;
14+
}
15+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<!-- Confirmation Dialog -->
2+
<jhi-confirm-dialog
3+
#confirmDialog
4+
[header]="'onboarding.employeeRequest.confirmDialog.header' | translate"
5+
[message]="'onboarding.employeeRequest.confirmDialog.message' | translate"
6+
[label]="'onboarding.employeeRequest.confirmDialog.confirm' | translate"
7+
severity="primary"
8+
confirmIcon="paper-plane"
9+
[showOpenButton]="false"
10+
(confirmed)="onConfirmSubmit()"
11+
/>
12+
13+
<form [formGroup]="employeeForm" (ngSubmit)="onSubmit()" novalidate class="flex flex-col gap-4 py-4">
14+
<!-- Professor Name Input -->
15+
<div class="form-field">
16+
<jhi-string-input
17+
#firstInput
18+
label="onboarding.employeeRequest.professorName.label"
19+
placeholder="onboarding.employeeRequest.professorName.placeholder"
20+
[required]="true"
21+
[model]="employeeForm.get('professorName')?.value"
22+
[control]="employeeForm.get('professorName') ?? undefined"
23+
[shouldTranslate]="true"
24+
/>
25+
</div>
26+
27+
<!-- Information Notice -->
28+
<div class="flex items-start gap-3 p-4 bg-blue-50 border-l-4 border-[var(--p-primary-color)] rounded-r-lg mt-2">
29+
<div class="flex-1">
30+
<p class="m-0 text-sm" jhiTranslate="onboarding.employeeRequest.notice"></p>
31+
</div>
32+
</div>
33+
34+
<!-- Form Actions -->
35+
<div class="flex justify-between gap-4 mt-6 pt-4 border-t border-gray-200">
36+
<jhi-button
37+
type="button"
38+
(click)="onBack()"
39+
[shouldTranslate]="true"
40+
label="entity.action.back"
41+
variant="outlined"
42+
icon="arrow-left"
43+
[disabled]="isSubmitting()"
44+
/>
45+
<div class="flex gap-4">
46+
<jhi-button
47+
type="button"
48+
(click)="onCancel()"
49+
[shouldTranslate]="true"
50+
label="entity.action.cancel"
51+
variant="outlined"
52+
[disabled]="isSubmitting()"
53+
/>
54+
<jhi-button
55+
type="submit"
56+
[disabled]="employeeForm.invalid || isSubmitting()"
57+
[shouldTranslate]="true"
58+
[label]="isSubmitting() ? 'onboarding.employeeRequest.submitting' : 'onboarding.employeeRequest.submit'"
59+
severity="primary"
60+
[icon]="isSubmitting() ? 'spinner' : 'paper-plane'"
61+
/>
62+
</div>
63+
</div>
64+
</form>

0 commit comments

Comments
 (0)