diff --git a/src/main/java/edu/handong/csee/histudy/controller/AdminController.java b/src/main/java/edu/handong/csee/histudy/controller/AdminController.java index 2ec034a..e98e5c0 100644 --- a/src/main/java/edu/handong/csee/histudy/controller/AdminController.java +++ b/src/main/java/edu/handong/csee/histudy/controller/AdminController.java @@ -56,7 +56,6 @@ public ResponseEntity getTeamReports( @RequestAttribute Claims claims) { if (Role.isAuthorized(claims, Role.ADMIN)) { TeamReportDto res = teamService.getTeamReports(id, claims.getSubject()); - return ResponseEntity.ok(res); } throw new ForbiddenException(); @@ -111,6 +110,7 @@ public ResponseEntity> getUnmatchedUsers(@RequestAttribut public void deleteForm(@RequestParam String sid, @RequestAttribute Claims claims) { if (Role.isAuthorized(claims, Role.ADMIN)) { userService.deleteUserForm(sid); + return; } throw new ForbiddenException(); } @@ -120,6 +120,7 @@ public void deleteForm(@RequestParam String sid, @RequestAttribute Claims claims public void editUser(@RequestBody UserDto.UserEdit form, @RequestAttribute Claims claims) { if (Role.isAuthorized(claims, Role.ADMIN)) { userService.editUser(form); + return; } throw new ForbiddenException(); } diff --git a/src/main/java/edu/handong/csee/histudy/controller/TeamController.java b/src/main/java/edu/handong/csee/histudy/controller/TeamController.java index dd00bc8..f9062d5 100644 --- a/src/main/java/edu/handong/csee/histudy/controller/TeamController.java +++ b/src/main/java/edu/handong/csee/histudy/controller/TeamController.java @@ -1,16 +1,11 @@ package edu.handong.csee.histudy.controller; import edu.handong.csee.histudy.controller.form.ReportForm; -import edu.handong.csee.histudy.domain.AcademicTerm; import edu.handong.csee.histudy.domain.Role; -import edu.handong.csee.histudy.domain.StudyGroup; -import edu.handong.csee.histudy.domain.User; import edu.handong.csee.histudy.dto.CourseDto; import edu.handong.csee.histudy.dto.ReportDto; import edu.handong.csee.histudy.dto.UserDto; import edu.handong.csee.histudy.exception.ForbiddenException; -import edu.handong.csee.histudy.exception.NoCurrentTermFoundException; -import edu.handong.csee.histudy.exception.UserNotFoundException; import edu.handong.csee.histudy.repository.AcademicTermRepository; import edu.handong.csee.histudy.repository.StudyGroupRepository; import edu.handong.csee.histudy.repository.UserRepository; @@ -156,18 +151,7 @@ public ResponseEntity> uploadImage( @RequestParam MultipartFile image, @RequestAttribute Claims claims) { if (Role.isAuthorized(claims, Role.MEMBER)) { - AcademicTerm currentTerm = - academicTermRepository - .findCurrentSemester() - .orElseThrow(NoCurrentTermFoundException::new); - User user = - userRepository - .findUserByEmail(claims.getSubject()) - .orElseThrow(UserNotFoundException::new); - StudyGroup studyGroup = - studyGroupRepository.findByUserAndTerm(user, currentTerm).orElseThrow(); - - String filename = imageService.getImagePaths(image, studyGroup.getTag(), reportIdOr); + String filename = imageService.getImagePaths(claims.getSubject(), image, reportIdOr); Map response = Map.of("imagePath", filename); return ResponseEntity.ok(response); } diff --git a/src/main/java/edu/handong/csee/histudy/domain/StudyApplicant.java b/src/main/java/edu/handong/csee/histudy/domain/StudyApplicant.java index 719d70f..82800f3 100644 --- a/src/main/java/edu/handong/csee/histudy/domain/StudyApplicant.java +++ b/src/main/java/edu/handong/csee/histudy/domain/StudyApplicant.java @@ -26,7 +26,7 @@ public class StudyApplicant extends BaseTime { @JoinColumn(name = "user_id") private User user; - @ManyToOne(fetch = FetchType.LAZY) + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) @JoinColumn(name = "study_group_id") private StudyGroup studyGroup; diff --git a/src/main/java/edu/handong/csee/histudy/domain/StudyGroup.java b/src/main/java/edu/handong/csee/histudy/domain/StudyGroup.java index b9b61d6..cc5a281 100644 --- a/src/main/java/edu/handong/csee/histudy/domain/StudyGroup.java +++ b/src/main/java/edu/handong/csee/histudy/domain/StudyGroup.java @@ -50,21 +50,12 @@ public static StudyGroup of(Integer tag, AcademicTerm current, List findCommonCourses(StudyApplicant... applicants) { if (!this.courses.isEmpty()) { this.courses.clear(); @@ -94,13 +85,13 @@ private List findCommonCourses(StudyApplicant... applicants) { public StudyGroup assignMembers(StudyApplicant... applicants) { Arrays.stream(applicants) .forEach( - form -> { - if (isInSameGroup(form)) { + applicant -> { + if (isInSameGroup(applicant)) { return; - } else if (isAlreadyInOtherGroup(form)) { - form.leaveGroup(); + } else if (isAlreadyInOtherGroup(applicant)) { + applicant.leaveGroup(); } - GroupMember.of(this, form); + GroupMember.of(this, applicant); }); assignCommonCourses(applicants); return this; @@ -114,14 +105,15 @@ public void removeMember(User member) { } private boolean isAlreadyInOtherGroup(StudyApplicant applicant) { - return applicant != null && !applicant.getStudyGroup().equals(this); + return applicant.getStudyGroup() != null && !applicant.getStudyGroup().equals(this); } public boolean isInSameGroup(StudyApplicant applicant) { - return applicant != null && applicant.getStudyGroup().equals(this); + return applicant.getStudyGroup() != null && applicant.getStudyGroup().equals(this); } protected void assignCommonCourses(StudyApplicant... applicants) { + if (isSameMemberExact(applicants)) return; if (this.members.isEmpty()) { this.courses.clear(); this.tag = -1; @@ -132,6 +124,16 @@ protected void assignCommonCourses(StudyApplicant... applicants) { .forEach(course -> new GroupCourse(this, course)); } + private boolean isSameMemberExact(StudyApplicant... applicants) { + if (this.courses.isEmpty()) { + return false; + } + Set users = + Arrays.stream(applicants).map(StudyApplicant::getUser).collect(Collectors.toSet()); + Set members = this.members.stream().map(GroupMember::getUser).collect(Collectors.toSet()); + return members.containsAll(users); + } + private boolean isNotInGroupCourse(Course course) { return this.courses.stream().map(GroupCourse::getCourse).noneMatch(c -> c.equals(course)); } diff --git a/src/main/java/edu/handong/csee/histudy/dto/TeamDto.java b/src/main/java/edu/handong/csee/histudy/dto/TeamDto.java index 04e6764..d5bf0e7 100644 --- a/src/main/java/edu/handong/csee/histudy/dto/TeamDto.java +++ b/src/main/java/edu/handong/csee/histudy/dto/TeamDto.java @@ -37,7 +37,9 @@ public TeamDto( .map( user -> { StudyApplicant applicant = applicantMap.get(user); - return new UserDto.UserInfo(user, applicant); + return (applicant == null) + ? new UserDto.UserInfo(user) + : new UserDto.UserInfo(user, applicant); }) .toList(); this.reports = reports.size(); diff --git a/src/main/java/edu/handong/csee/histudy/dto/UserDto.java b/src/main/java/edu/handong/csee/histudy/dto/UserDto.java index 9c0cd01..73afe72 100644 --- a/src/main/java/edu/handong/csee/histudy/dto/UserDto.java +++ b/src/main/java/edu/handong/csee/histudy/dto/UserDto.java @@ -1,7 +1,7 @@ package edu.handong.csee.histudy.dto; -import edu.handong.csee.histudy.domain.StudyApplicant; import edu.handong.csee.histudy.domain.PreferredCourse; +import edu.handong.csee.histudy.domain.StudyApplicant; import edu.handong.csee.histudy.domain.StudyPartnerRequest; import edu.handong.csee.histudy.domain.User; import edu.handong.csee.histudy.jwt.JwtPair; @@ -112,16 +112,20 @@ public UserInfo(User user, StudyApplicant applicant) { this.email = user.getEmail(); this.group = (applicant.getStudyGroup() == null) ? null : applicant.getStudyGroup().getTag(); this.friends = - applicant.getPartnerRequests().stream() - .filter(StudyPartnerRequest::isAccepted) - .map(StudyPartnerRequest::getReceiver) - .map(UserBasic::new) - .toList(); + (applicant.getPartnerRequests() == null) + ? Collections.emptyList() + : applicant.getPartnerRequests().stream() + .filter(StudyPartnerRequest::isAccepted) + .map(StudyPartnerRequest::getReceiver) + .map(UserBasic::new) + .toList(); this.courses = - applicant.getPreferredCourses().stream() - .sorted(Comparator.comparing(PreferredCourse::getPriority)) - .map(c -> new CourseDto.BasicCourseInfo(c.getCourse())) - .toList(); + (applicant.getPreferredCourses() == null) + ? Collections.emptyList() + : applicant.getPreferredCourses().stream() + .sorted(Comparator.comparing(PreferredCourse::getPriority)) + .map(c -> new CourseDto.BasicCourseInfo(c.getCourse())) + .toList(); } public UserInfo(User user) { diff --git a/src/main/java/edu/handong/csee/histudy/repository/StudyApplicantRepository.java b/src/main/java/edu/handong/csee/histudy/repository/StudyApplicantRepository.java index 8377e2c..acd239c 100644 --- a/src/main/java/edu/handong/csee/histudy/repository/StudyApplicantRepository.java +++ b/src/main/java/edu/handong/csee/histudy/repository/StudyApplicantRepository.java @@ -15,27 +15,27 @@ public interface StudyApplicantRepository extends JpaRepository { @Query( - "select s from StudyApplicant s join fetch s.partnerRequests " - + "where s.user = :applicant and s.academicTerm = :currentTerm") + "select s from StudyApplicant s left join fetch s.preferredCourses " + + "where s.user = :applicant and s.academicTerm = :currentTerm") Optional findByUserAndTerm(User applicant, AcademicTerm currentTerm); @Query( - "select s from StudyApplicant s join fetch s.partnerRequests " + "select s from StudyApplicant s left join fetch s.preferredCourses " + "where s.academicTerm = :currentTerm and s.studyGroup is null") List findUnassignedApplicants(AcademicTerm currentTerm); @Query( - "select s from StudyApplicant s join fetch s.partnerRequests " + "select s from StudyApplicant s join fetch s.user " + "where s.academicTerm = :currentTerm and s.studyGroup is not null") List findAssignedApplicants(AcademicTerm currentTerm); @Query( - "select s from StudyApplicant s join fetch s.partnerRequests " + "select s from StudyApplicant s left join fetch s.preferredCourses " + "where s.academicTerm = :currentTerm") List findAllByTerm(AcademicTerm currentTerm); @Query( - "select s from StudyApplicant s join fetch s.partnerRequests " + "select s from StudyApplicant s left join fetch s.preferredCourses " + "where s.studyGroup = :studyGroup") - List findAllByStudyGroup(@Param("studyGroup") StudyGroup group); + List findAllByStudyGroup(@Param("studyGroup") StudyGroup group); } diff --git a/src/main/java/edu/handong/csee/histudy/repository/StudyGroupRepository.java b/src/main/java/edu/handong/csee/histudy/repository/StudyGroupRepository.java index fccaf34..609c6d1 100644 --- a/src/main/java/edu/handong/csee/histudy/repository/StudyGroupRepository.java +++ b/src/main/java/edu/handong/csee/histudy/repository/StudyGroupRepository.java @@ -26,7 +26,9 @@ public interface StudyGroupRepository extends JpaRepository { List findAllByAcademicTerm(@Param("academicTerm") AcademicTerm academicTerm); @Query( - "select s from StudyGroup s where :user member of s.members and s.academicTerm = :currentTerm") + "select s from StudyGroup s " + + "join s.members m " + + "where m.user = :user and s.academicTerm = :currentTerm") Optional findByUserAndTerm( @Param("user") User user, @Param("currentTerm") AcademicTerm currentTerm); } diff --git a/src/main/java/edu/handong/csee/histudy/service/CourseService.java b/src/main/java/edu/handong/csee/histudy/service/CourseService.java index 38f0933..a660030 100644 --- a/src/main/java/edu/handong/csee/histudy/service/CourseService.java +++ b/src/main/java/edu/handong/csee/histudy/service/CourseService.java @@ -4,11 +4,9 @@ import edu.handong.csee.histudy.dto.CourseDto; import edu.handong.csee.histudy.dto.CourseIdDto; import edu.handong.csee.histudy.exception.NoCurrentTermFoundException; +import edu.handong.csee.histudy.exception.StudyGroupNotFoundException; import edu.handong.csee.histudy.exception.UserNotFoundException; -import edu.handong.csee.histudy.repository.AcademicTermRepository; -import edu.handong.csee.histudy.repository.CourseRepository; -import edu.handong.csee.histudy.repository.StudyGroupRepository; -import edu.handong.csee.histudy.repository.UserRepository; +import edu.handong.csee.histudy.repository.*; import edu.handong.csee.histudy.util.CSVResolver; import edu.handong.csee.histudy.util.CourseCSV; import java.io.IOException; @@ -24,6 +22,7 @@ public class CourseService { private final UserRepository userRepository; private final AcademicTermRepository academicTermRepository; private final StudyGroupRepository studyGroupRepository; + private final StudyApplicantRepository studyApplicantRepository; public void readCourseCSV(MultipartFile file) throws IOException { CSVResolver resolver = CSVResolver.of(file.getInputStream()); @@ -65,10 +64,13 @@ public List getTeamCourses(String email) { User user = userRepository.findUserByEmail(email).orElseThrow(UserNotFoundException::new); AcademicTerm currentTerm = academicTermRepository.findCurrentSemester().orElseThrow(NoCurrentTermFoundException::new); + StudyGroup studyGroup = + studyGroupRepository + .findByUserAndTerm(user, currentTerm) + .orElseThrow(StudyGroupNotFoundException::new); - StudyGroup studyGroup = studyGroupRepository.findByUserAndTerm(user, currentTerm).orElseThrow(); - List courses = - studyGroup.getCourses().stream().map(GroupCourse::getCourse).toList(); + studyGroupRepository.findByUserAndTerm(user, currentTerm).orElseThrow(); + List courses = studyGroup.getCourses().stream().map(GroupCourse::getCourse).toList(); return courses.stream().map(CourseDto.CourseInfo::new).toList(); } diff --git a/src/main/java/edu/handong/csee/histudy/service/ImageService.java b/src/main/java/edu/handong/csee/histudy/service/ImageService.java index ff73d33..e7cd209 100644 --- a/src/main/java/edu/handong/csee/histudy/service/ImageService.java +++ b/src/main/java/edu/handong/csee/histudy/service/ImageService.java @@ -2,10 +2,12 @@ import static org.springframework.util.ResourceUtils.isUrl; +import edu.handong.csee.histudy.domain.AcademicTerm; import edu.handong.csee.histudy.domain.ReportImage; -import edu.handong.csee.histudy.exception.FileTransferException; -import edu.handong.csee.histudy.exception.ReportNotFoundException; -import edu.handong.csee.histudy.repository.StudyReportRepository; +import edu.handong.csee.histudy.domain.StudyGroup; +import edu.handong.csee.histudy.domain.User; +import edu.handong.csee.histudy.exception.*; +import edu.handong.csee.histudy.repository.*; import edu.handong.csee.histudy.util.ImagePathMapper; import edu.handong.csee.histudy.util.Utils; import java.io.File; @@ -34,11 +36,24 @@ public class ImageService { @Value("${custom.resource.location}") private String imageBaseLocation; + private final AcademicTermRepository academicTermRepository; + private final UserRepository userRepository; private final StudyReportRepository studyReportRepository; + private final StudyApplicantRepository studyApplicantRepository; + private final ImagePathMapper imagePathMapper; + private final StudyGroupRepository studyGroupRepository; public String getImagePaths( - MultipartFile imageAsFormData, Integer tag, Optional reportIdOr) { + String email, MultipartFile imageAsFormData, Optional reportIdOr) { + AcademicTerm currentTerm = + academicTermRepository.findCurrentSemester().orElseThrow(NoCurrentTermFoundException::new); + User user = userRepository.findUserByEmail(email).orElseThrow(UserNotFoundException::new); + StudyGroup studyGroup = + studyGroupRepository + .findByUserAndTerm(user, currentTerm) + .orElseThrow(StudyGroupNotFoundException::new); + if (reportIdOr.isPresent()) { Long id = reportIdOr.get(); Optional sameResource = getSameContent(imageAsFormData, id); @@ -58,7 +73,8 @@ public String getImagePaths( // e.g. 2023-2-group1-report_20230923_123456.jpg String pathname = String.format( - "%d-%d-group%02d-report_%s%s", year, semester, tag, formattedDateTime, extension); + "%d-%d-group%02d-report_%s%s", + year, semester, studyGroup.getTag(), formattedDateTime, extension); String savedImagePath = saveImage(imageAsFormData, pathname); return imagePathMapper.getFullPath(savedImagePath); diff --git a/src/main/java/edu/handong/csee/histudy/service/TeamService.java b/src/main/java/edu/handong/csee/histudy/service/TeamService.java index e768302..17a7840 100644 --- a/src/main/java/edu/handong/csee/histudy/service/TeamService.java +++ b/src/main/java/edu/handong/csee/histudy/service/TeamService.java @@ -40,20 +40,15 @@ public List getTeams(String email) { studyReportRepository.findAllByStudyGroupOrderByCreatedDateDesc(group); List applicants = studyApplicantRepository.findAllByStudyGroup(group); - Map formMap = + Map applicantMap = applicants.stream() - .filter(form -> isFormRelevantToGroup(form, group)) .collect(Collectors.toMap(StudyApplicant::getUser, Function.identity())); - return new TeamDto(group, reports, formMap); + return new TeamDto(group, reports, applicantMap); }) .toList(); } - private boolean isFormRelevantToGroup(StudyApplicant form, StudyGroup group) { - return form.getStudyGroup().equals(group); - } - public int deleteTeam(TeamIdDto dto, String email) { if (studyGroupRepository.existsById(dto.getGroupId())) { studyGroupRepository.deleteById(dto.getGroupId()); @@ -161,7 +156,7 @@ public void matchTeam() { matchCourseSecond(applicants, tag, current); } - public List matchFriendFirst( + public Set matchFriendFirst( List applicants, AtomicInteger tag, AcademicTerm current) { // First matching // Make teams with friends @@ -177,13 +172,12 @@ public List matchFriendFirst( .orElseThrow(NoStudyApplicationFound::new); return StudyGroup.of(tag, current, partnerRequest.getSender(), receiver); }) - .distinct() - .toList(); + .collect(Collectors.toSet()); } - public List matchCourseFirst( + public Set matchCourseFirst( List applicants, AtomicInteger tag, AcademicTerm current) { - List results = new ArrayList<>(); + Set results = new HashSet<>(); List preferredCourses = applicants.stream() @@ -213,17 +207,17 @@ public List matchCourseFirst( Collectors.mapping(PreferredCourse::getApplicant, Collectors.toList()))); courseToUserMap.forEach( - (course, applicant) -> { - List matchedGroupList = createGroup(applicant, tag, current); - results.addAll(matchedGroupList); + (course, _applicants) -> { + Set matchedGroups = createGroup(_applicants, tag, current); + results.addAll(matchedGroups); }); }); return results; } - private List createGroup( + private Set createGroup( List applicants, AtomicInteger tag, AcademicTerm current) { - List matchedGroupList = new ArrayList<>(); + Set matchedGroups = new HashSet<>(); while (applicants.size() >= 5) { // If the group has more than 5 elements, split the group @@ -233,7 +227,7 @@ private List createGroup( // Create a group with only 5 elements StudyGroup studyGroup = StudyGroup.of(tag.getAndIncrement(), current, subGroup); - matchedGroupList.add(studyGroup); + matchedGroups.add(studyGroup); // Remove the elements that have already been added to the group applicants.removeAll(subGroup); @@ -242,13 +236,14 @@ private List createGroup( // If the remaining elements are 3 ~ 4 // Create a group with 3 ~ 4 elements StudyGroup studyGroup = StudyGroup.of(tag.getAndIncrement(), current, applicants); - matchedGroupList.add(studyGroup); + matchedGroups.add(studyGroup); } - return matchedGroupList; + return matchedGroups; } - private void matchCourseSecond( + private Set matchCourseSecond( List applicants, AtomicInteger tag, AcademicTerm current) { + Set matchedGroups = new HashSet<>(); Map> courseToUserByPriority = preparePriorityQueueOfUsers(applicants); @@ -261,8 +256,10 @@ private void matchCourseSecond( .filter(StudyApplicant::isNotMarkedAsGrouped) .sorted(queue.comparator()) .collect(Collectors.toList()); - createGroup(group, tag, current); + Set groups = createGroup(group, tag, current); + matchedGroups.addAll(groups); }); + return matchedGroups; } private Map> preparePriorityQueueOfUsers( diff --git a/src/main/java/edu/handong/csee/histudy/service/UserService.java b/src/main/java/edu/handong/csee/histudy/service/UserService.java index a83c8fd..b6e1a65 100644 --- a/src/main/java/edu/handong/csee/histudy/service/UserService.java +++ b/src/main/java/edu/handong/csee/histudy/service/UserService.java @@ -206,11 +206,17 @@ public void editUser(UserDto.UserEdit form) { .ifPresentOrElse( tag -> { StudyApplicant applicant = - applicantOr.orElse(StudyApplicant.of(currentTerm, user, List.of(), List.of())); + applicantOr.orElseGet( + () -> { + StudyApplicant _applicant = + StudyApplicant.of(currentTerm, user, List.of(), List.of()); + return studyApplicantRepository.save(_applicant); + }); studyGroupRepository .findByTagAndAcademicTerm(tag, currentTerm) - .orElse(StudyGroup.of(tag, currentTerm)) - .assignMembers(applicant); + .ifPresentOrElse( + group -> group.assignMembers(applicant), + () -> StudyGroup.of(tag, currentTerm, List.of(applicant))); }, () -> applicantOr.ifPresent(