Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ public record ApplicantDataExportDTO(
String masterGrade,
String masterUniversity,
Set<DocumentExportDTO> documents,
List<ApplicationExportDTO> applications
List<ApplicationExportDTO> applications,
List<IntervieweeExportDTO> interviewees
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package de.tum.cit.aet.core.dto.exportdata;

import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record InterviewProcessExportDTO(String jobTitle) {}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
import com.fasterxml.jackson.annotation.JsonInclude;
import java.time.Instant;

@JsonInclude(JsonInclude.Include.NON_NULL)
public record InterviewSlotExportDTO(Instant start, Instant end) {}
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record InterviewSlotExportDTO(String jobTitle, Instant start, Instant end, String location, String streamLink, Boolean isBooked) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package de.tum.cit.aet.core.dto.exportdata;

import com.fasterxml.jackson.annotation.JsonInclude;
import java.time.Instant;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record IntervieweeExportDTO(String jobTitle, Instant lastInvited) {}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ public record StaffDataDTO(
List<ResearchGroupRoleExportDTO> researchGroupRoles,
List<ApplicationReviewExportDTO> reviews,
List<InternalCommentExportDTO> comments,
List<RatingExportDTO> ratings
List<RatingExportDTO> ratings,
List<InterviewProcessExportDTO> interviewProcesses,
List<InterviewSlotExportDTO> interviewSlots
) {}
123 changes: 111 additions & 12 deletions src/main/java/de/tum/cit/aet/core/service/UserDataExportService.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import de.tum.cit.aet.core.dto.exportdata.ApplicationReviewExportDTO;
import de.tum.cit.aet.core.dto.exportdata.DocumentExportDTO;
import de.tum.cit.aet.core.dto.exportdata.InternalCommentExportDTO;
import de.tum.cit.aet.core.dto.exportdata.InterviewProcessExportDTO;
import de.tum.cit.aet.core.dto.exportdata.InterviewSlotExportDTO;
import de.tum.cit.aet.core.dto.exportdata.IntervieweeExportDTO;
import de.tum.cit.aet.core.dto.exportdata.RatingExportDTO;
import de.tum.cit.aet.core.dto.exportdata.ResearchGroupRoleExportDTO;
import de.tum.cit.aet.core.dto.exportdata.StaffDataDTO;
Expand All @@ -21,11 +24,19 @@
import de.tum.cit.aet.evaluation.repository.ApplicationReviewRepository;
import de.tum.cit.aet.evaluation.repository.InternalCommentRepository;
import de.tum.cit.aet.evaluation.repository.RatingRepository;
import de.tum.cit.aet.interview.domain.InterviewProcess;
import de.tum.cit.aet.interview.domain.InterviewSlot;
import de.tum.cit.aet.interview.domain.Interviewee;
import de.tum.cit.aet.interview.repository.InterviewProcessRepository;
import de.tum.cit.aet.interview.repository.InterviewSlotRepository;
import de.tum.cit.aet.interview.repository.IntervieweeRepository;
import de.tum.cit.aet.job.domain.Job;
import de.tum.cit.aet.notification.dto.EmailSettingDTO;
import de.tum.cit.aet.notification.repository.EmailSettingRepository;
import de.tum.cit.aet.usermanagement.constants.UserRole;
import de.tum.cit.aet.usermanagement.domain.Applicant;
import de.tum.cit.aet.usermanagement.domain.User;
import de.tum.cit.aet.usermanagement.domain.UserResearchGroupRole;
import de.tum.cit.aet.usermanagement.repository.ApplicantRepository;
import de.tum.cit.aet.usermanagement.repository.UserRepository;
import de.tum.cit.aet.usermanagement.repository.UserResearchGroupRoleRepository;
Expand All @@ -50,17 +61,21 @@
@RequiredArgsConstructor
public class UserDataExportService {

private final UserRepository userRepository;
private final ApplicantRepository applicantRepository;
private final UserSettingRepository userSettingRepository;
private final EmailSettingRepository emailSettingRepository;
private final ApplicationRepository applicationRepository;
private final DocumentDictionaryRepository documentDictionaryRepository;
private final UserResearchGroupRoleRepository userResearchGroupRoleRepository;
private final ApplicationReviewRepository applicationReviewRepository;
private final DocumentDictionaryRepository documentDictionaryRepository;
private final InternalCommentRepository internalCommentRepository;
private final RatingRepository ratingRepository;
private final IntervieweeRepository intervieweeRepository;
private final InterviewProcessRepository interviewProcessRepository;
private final InterviewSlotRepository interviewSlotRepository;
private final DocumentRepository documentRepository;
private final EmailSettingRepository emailSettingRepository;
private final RatingRepository ratingRepository;
private final UserRepository userRepository;
private final UserResearchGroupRoleRepository userResearchGroupRoleRepository;
private final UserSettingRepository userSettingRepository;

private final ZipExportService zipExportService;
private final ObjectMapper objectMapper;
private final PlatformTransactionManager transactionManager;
Expand Down Expand Up @@ -111,19 +126,37 @@ public void exportUserData(UUID userId, HttpServletResponse response) throws IOE
}
}

// ------------------------------------ Private helper methods ------------------------------------

/**
* Collects all exportable data for the given user, including profile, settings, email settings,
* and role-specific data (applicant or staff) when applicable.
*
* @param userId the ID of the user whose data should be collected
* @return a {@link UserDataExportDTO} containing all available export data for the user
* @throws IllegalArgumentException if the user cannot be found
*/
private UserDataExportDTO collectUserData(UUID userId) {
User user = userRepository.findById(userId).orElseThrow(() -> new IllegalArgumentException("User not found"));

boolean hasApplicantRole = hasRole(user, UserRole.APPLICANT);
boolean hasStaffRole = hasRole(user, UserRole.PROFESSOR) || hasRole(user, UserRole.EMPLOYEE) || hasRole(user, UserRole.ADMIN);

UserProfileExportDTO profile = getUserProfile(user);
List<UserSettingDTO> settings = getUserSettings(userId);
List<EmailSettingDTO> emailSettings = getEmailSettings(user);
ApplicantDataExportDTO applicantData = applicantRepository.existsById(userId) ? getApplicantData(userId) : null;
StaffDataDTO staffData = getStaffData(user);
ApplicantDataExportDTO applicantData = hasApplicantRole && applicantRepository.existsById(userId) ? getApplicantData(userId) : null;
StaffDataDTO staffData = hasStaffRole ? getStaffData(user) : null;

return new UserDataExportDTO(profile, settings, emailSettings, applicantData, staffData);
}

// Private helper methods
private boolean hasRole(User user, UserRole role) {
if (user.getResearchGroupRoles() == null || user.getResearchGroupRoles().isEmpty()) {
return false;
}
return user.getResearchGroupRoles().stream().map(UserResearchGroupRole::getRole).anyMatch(role::equals);
}

private UserProfileExportDTO getUserProfile(User user) {
return new UserProfileExportDTO(
Expand Down Expand Up @@ -184,6 +217,8 @@ private ApplicantDataExportDTO getApplicantData(UUID userId) {
)
.toList();

List<IntervieweeExportDTO> interviewees = getInterviewees(userId);

return new ApplicantDataExportDTO(
applicant.getStreet(),
applicant.getPostalCode(),
Expand All @@ -200,7 +235,8 @@ private ApplicantDataExportDTO getApplicantData(UUID userId) {
applicant.getMasterGrade(),
applicant.getMasterUniversity(),
documents,
applications
applications,
interviewees
);
}

Expand All @@ -211,12 +247,64 @@ private StaffDataDTO getStaffData(User user) {
List<ApplicationReviewExportDTO> reviews = getReviews(user);
List<InternalCommentExportDTO> comments = getComments(user);
List<RatingExportDTO> ratings = getRatings(user);
List<InterviewProcess> interviewProcessEntities = getInterviewProcesses(user);
List<InterviewProcessExportDTO> interviewProcesses = mapInterviewProcesses(interviewProcessEntities);
List<InterviewSlotExportDTO> interviewSlots = getInterviewSlots(interviewProcessEntities);

if (supervisedJobs.isEmpty() && researchGroupRoles.isEmpty() && reviews.isEmpty() && comments.isEmpty() && ratings.isEmpty()) {
if (
supervisedJobs.isEmpty() &&
researchGroupRoles.isEmpty() &&
reviews.isEmpty() &&
comments.isEmpty() &&
ratings.isEmpty() &&
interviewProcesses.isEmpty() &&
interviewSlots.isEmpty()
) {
return null;
}

return new StaffDataDTO(supervisedJobs, researchGroupRoles, reviews, comments, ratings);
return new StaffDataDTO(supervisedJobs, researchGroupRoles, reviews, comments, ratings, interviewProcesses, interviewSlots);
}

private List<IntervieweeExportDTO> getInterviewees(UUID userId) {
List<Interviewee> interviewees = intervieweeRepository.findByApplicantUserIdWithDetails(userId);
if (interviewees == null || interviewees.isEmpty()) {
return List.of();
}

return interviewees
.stream()
.map(interviewee ->
new IntervieweeExportDTO(interviewee.getInterviewProcess().getJob().getTitle(), interviewee.getLastInvited())
)
.toList();
}

private List<InterviewProcess> getInterviewProcesses(User user) {
List<InterviewProcess> processes = interviewProcessRepository.findAllByProfessorId(user.getUserId());
return processes == null ? List.of() : processes;
}

private List<InterviewProcessExportDTO> mapInterviewProcesses(List<InterviewProcess> processes) {
if (processes == null || processes.isEmpty()) {
return List.of();
}
return processes
.stream()
.map(process -> new InterviewProcessExportDTO(process.getJob().getTitle()))
.toList();
}

private List<InterviewSlotExportDTO> getInterviewSlots(List<InterviewProcess> interviewProcesses) {
if (interviewProcesses == null || interviewProcesses.isEmpty()) {
return List.of();
}
List<UUID> processIds = interviewProcesses.stream().map(InterviewProcess::getId).toList();
List<InterviewSlot> slots = interviewSlotRepository.findByInterviewProcessIdInWithJob(processIds);
if (slots == null || slots.isEmpty()) {
return List.of();
}
return slots.stream().map(this::mapInterviewSlot).toList();
}

private List<ResearchGroupRoleExportDTO> getResearchGroupRoles(User user) {
Expand Down Expand Up @@ -284,6 +372,17 @@ private List<RatingExportDTO> getRatings(User user) {
.toList();
}

private InterviewSlotExportDTO mapInterviewSlot(InterviewSlot slot) {
return new InterviewSlotExportDTO(
slot.getInterviewProcess().getJob().getTitle(),
slot.getStartDateTime(),
slot.getEndDateTime(),
slot.getLocation(),
slot.getStreamLink(),
slot.getIsBooked()
);
}

private void addDocumentToZip(ZipOutputStream zipOut, UUID documentId, String entryPath) {
try {
Document document = documentRepository
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ public interface InterviewProcessRepository extends JpaRepository<InterviewProce
"""
SELECT ip
FROM InterviewProcess ip
WHERE ip.job.supervisingProfessor.userId = :professorId
JOIN FETCH ip.job job
WHERE job.supervisingProfessor.userId = :professorId
"""
)
List<InterviewProcess> findAllByProfessorId(@Param("professorId") UUID professorId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,22 @@ public interface InterviewSlotRepository extends JpaRepository<InterviewSlot, UU
@Query("SELECT s FROM InterviewSlot s WHERE s.id = :slotId")
Optional<InterviewSlot> findByIdWithJob(@Param("slotId") UUID slotId);

/**
* Finds all slots for the given interview process ids with job data.
*
* @param processIds interview process ids
* @return list of slots ordered by start time
*/
@EntityGraph(attributePaths = { "interviewProcess", "interviewProcess.job" })
@Query(
"""
SELECT s FROM InterviewSlot s
WHERE s.interviewProcess.id IN :processIds
ORDER BY s.startDateTime ASC
"""
)
List<InterviewSlot> findByInterviewProcessIdInWithJob(@Param("processIds") List<UUID> processIds);

/**
* Counts all interview slots associated with a specific interview process.
* Finds all interview slots of a given professor that overlap with a specified
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,23 @@ public interface IntervieweeRepository extends TumApplyJpaRepository<Interviewee
)
List<Interviewee> findByInterviewProcessIdInWithSlots(@Param("processIds") List<UUID> processIds);

/**
* Finds all interviewees for a given applicant user id with process, job and slot data.
*
* @param userId the applicant user id
* @return list of interviewees with process/job and slot data
*/
@Query(
"""
SELECT DISTINCT i FROM Interviewee i
JOIN FETCH i.interviewProcess ip
JOIN FETCH ip.job j
LEFT JOIN FETCH i.slots
WHERE i.application.applicant.user.userId = :userId
"""
)
List<Interviewee> findByApplicantUserIdWithDetails(@Param("userId") UUID userId);

/**
* Finds a single interviewee by ID within a process.
*
Expand Down
Loading
Loading