diff --git a/src/main/java/com/example/solidconnection/admin/university/controller/AdminHomeUniversityController.java b/src/main/java/com/example/solidconnection/admin/university/controller/AdminHomeUniversityController.java new file mode 100644 index 000000000..b0de5627f --- /dev/null +++ b/src/main/java/com/example/solidconnection/admin/university/controller/AdminHomeUniversityController.java @@ -0,0 +1,61 @@ +package com.example.solidconnection.admin.university.controller; + +import com.example.solidconnection.admin.university.dto.AdminHomeUniversityCreateRequest; +import com.example.solidconnection.admin.university.dto.AdminHomeUniversityResponse; +import com.example.solidconnection.admin.university.dto.AdminHomeUniversityUpdateRequest; +import com.example.solidconnection.admin.university.service.AdminHomeUniversityService; +import jakarta.validation.Valid; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +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.RestController; + +@RequiredArgsConstructor +@RequestMapping("/admin/home-universities") +@RestController +public class AdminHomeUniversityController { + + private final AdminHomeUniversityService adminHomeUniversityService; + + @GetMapping + public ResponseEntity> getHomeUniversities() { + return ResponseEntity.ok(adminHomeUniversityService.getAllHomeUniversities()); + } + + @GetMapping("/{home-university-id}") + public ResponseEntity getHomeUniversity( + @PathVariable("home-university-id") Long homeUniversityId + ) { + return ResponseEntity.ok(adminHomeUniversityService.getHomeUniversity(homeUniversityId)); + } + + @PostMapping + public ResponseEntity createHomeUniversity( + @Valid @RequestBody AdminHomeUniversityCreateRequest request + ) { + return ResponseEntity.ok(adminHomeUniversityService.createHomeUniversity(request)); + } + + @PutMapping("/{home-university-id}") + public ResponseEntity updateHomeUniversity( + @PathVariable("home-university-id") Long homeUniversityId, + @Valid @RequestBody AdminHomeUniversityUpdateRequest request + ) { + return ResponseEntity.ok(adminHomeUniversityService.updateHomeUniversity(homeUniversityId, request)); + } + + @DeleteMapping("/{home-university-id}") + public ResponseEntity deleteHomeUniversity( + @PathVariable("home-university-id") Long homeUniversityId + ) { + adminHomeUniversityService.deleteHomeUniversity(homeUniversityId); + return ResponseEntity.ok().build(); + } +} diff --git a/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityCreateRequest.java b/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityCreateRequest.java new file mode 100644 index 000000000..14df833e5 --- /dev/null +++ b/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityCreateRequest.java @@ -0,0 +1,12 @@ +package com.example.solidconnection.admin.university.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public record AdminHomeUniversityCreateRequest( + @NotBlank(message = "협정 대학명은 필수입니다") + @Size(max = 100, message = "협정 대학명은 100자 이하여야 합니다") + String name +) { + +} diff --git a/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityResponse.java b/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityResponse.java new file mode 100644 index 000000000..ea74a02ba --- /dev/null +++ b/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityResponse.java @@ -0,0 +1,16 @@ +package com.example.solidconnection.admin.university.dto; + +import com.example.solidconnection.university.domain.HomeUniversity; + +public record AdminHomeUniversityResponse( + Long id, + String name +) { + + public static AdminHomeUniversityResponse from(HomeUniversity homeUniversity) { + return new AdminHomeUniversityResponse( + homeUniversity.getId(), + homeUniversity.getName() + ); + } +} diff --git a/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityUpdateRequest.java b/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityUpdateRequest.java new file mode 100644 index 000000000..e22473099 --- /dev/null +++ b/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityUpdateRequest.java @@ -0,0 +1,12 @@ +package com.example.solidconnection.admin.university.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public record AdminHomeUniversityUpdateRequest( + @NotBlank(message = "협정 대학명은 필수입니다") + @Size(max = 100, message = "협정 대학명은 100자 이하여야 합니다") + String name +) { + +} diff --git a/src/main/java/com/example/solidconnection/admin/university/service/AdminHomeUniversityService.java b/src/main/java/com/example/solidconnection/admin/university/service/AdminHomeUniversityService.java new file mode 100644 index 000000000..b14be29ff --- /dev/null +++ b/src/main/java/com/example/solidconnection/admin/university/service/AdminHomeUniversityService.java @@ -0,0 +1,104 @@ +package com.example.solidconnection.admin.university.service; + +import static com.example.solidconnection.common.exception.ErrorCode.HOME_UNIVERSITY_ALREADY_EXISTS; +import static com.example.solidconnection.common.exception.ErrorCode.HOME_UNIVERSITY_HAS_REFERENCES; +import static com.example.solidconnection.common.exception.ErrorCode.HOME_UNIVERSITY_NOT_FOUND; + +import com.example.solidconnection.admin.university.dto.AdminHomeUniversityCreateRequest; +import com.example.solidconnection.admin.university.dto.AdminHomeUniversityResponse; +import com.example.solidconnection.admin.university.dto.AdminHomeUniversityUpdateRequest; +import com.example.solidconnection.cache.annotation.DefaultCacheOut; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.university.domain.HomeUniversity; +import com.example.solidconnection.university.repository.HomeUniversityRepository; +import com.example.solidconnection.university.repository.UnivApplyInfoRepository; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class AdminHomeUniversityService { + + private final HomeUniversityRepository homeUniversityRepository; + private final UnivApplyInfoRepository univApplyInfoRepository; + private final SiteUserRepository siteUserRepository; + + @Transactional(readOnly = true) + public List getAllHomeUniversities() { + return homeUniversityRepository.findAll().stream() + .map(AdminHomeUniversityResponse::from) + .toList(); + } + + @Transactional(readOnly = true) + public AdminHomeUniversityResponse getHomeUniversity(Long id) { + HomeUniversity homeUniversity = homeUniversityRepository.findById(id) + .orElseThrow(() -> new CustomException(HOME_UNIVERSITY_NOT_FOUND)); + return AdminHomeUniversityResponse.from(homeUniversity); + } + + @Transactional + @DefaultCacheOut( + key = {"univApplyInfoTextSearch", "university:recommend:general"}, + cacheManager = "customCacheManager", + prefix = true + ) + public AdminHomeUniversityResponse createHomeUniversity(AdminHomeUniversityCreateRequest request) { + validateNameNotExists(request.name()); + HomeUniversity homeUniversity = new HomeUniversity(null, request.name()); + return AdminHomeUniversityResponse.from(homeUniversityRepository.save(homeUniversity)); + } + + @Transactional + @DefaultCacheOut( + key = {"univApplyInfoTextSearch", "university:recommend:general"}, + cacheManager = "customCacheManager", + prefix = true + ) + public AdminHomeUniversityResponse updateHomeUniversity(Long id, AdminHomeUniversityUpdateRequest request) { + HomeUniversity homeUniversity = homeUniversityRepository.findById(id) + .orElseThrow(() -> new CustomException(HOME_UNIVERSITY_NOT_FOUND)); + validateNameNotDuplicated(request.name(), id); + homeUniversity.update(request.name()); + return AdminHomeUniversityResponse.from(homeUniversity); + } + + @Transactional + @DefaultCacheOut( + key = {"univApplyInfoTextSearch", "university:recommend:general"}, + cacheManager = "customCacheManager", + prefix = true + ) + public void deleteHomeUniversity(Long id) { + HomeUniversity homeUniversity = homeUniversityRepository.findById(id) + .orElseThrow(() -> new CustomException(HOME_UNIVERSITY_NOT_FOUND)); + validateNoReferences(id); + homeUniversityRepository.delete(homeUniversity); + } + + private void validateNameNotExists(String name) { + homeUniversityRepository.findByName(name) + .ifPresent(existing -> { + throw new CustomException(HOME_UNIVERSITY_ALREADY_EXISTS); + }); + } + + private void validateNameNotDuplicated(String name, Long excludeId) { + homeUniversityRepository.findByName(name) + .ifPresent(existing -> { + if (!existing.getId().equals(excludeId)) { + throw new CustomException(HOME_UNIVERSITY_ALREADY_EXISTS); + } + }); + } + + private void validateNoReferences(Long id) { + if (univApplyInfoRepository.existsByHomeUniversityId(id) + || siteUserRepository.existsByHomeUniversityId(id)) { + throw new CustomException(HOME_UNIVERSITY_HAS_REFERENCES); + } + } +} diff --git a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java index 78b653da8..8dc4ea70e 100644 --- a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java @@ -45,6 +45,9 @@ public enum ErrorCode { COUNTRY_ALREADY_EXISTS(HttpStatus.CONFLICT.value(), "이미 존재하는 국가입니다."), HOST_UNIVERSITY_ALREADY_EXISTS(HttpStatus.CONFLICT.value(), "이미 존재하는 파견 대학입니다."), HOST_UNIVERSITY_HAS_REFERENCES(HttpStatus.CONFLICT.value(), "해당 파견 대학을 참조하는 대학 지원 정보가 존재합니다."), + HOME_UNIVERSITY_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "협정 대학교를 찾을 수 없습니다."), + HOME_UNIVERSITY_ALREADY_EXISTS(HttpStatus.CONFLICT.value(), "이미 존재하는 협정 대학입니다."), + HOME_UNIVERSITY_HAS_REFERENCES(HttpStatus.CONFLICT.value(), "해당 협정 대학을 참조하는 데이터가 존재합니다."), COUNTRY_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "국가를 찾을 수 없습니다."), COUNTRY_NOT_FOUND_BY_KOREAN_NAME(HttpStatus.NOT_FOUND.value(), "이름에 해당하는 국가를 찾을 수 없습니다."), GPA_SCORE_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "존재하지 않는 학점입니다."), diff --git a/src/main/java/com/example/solidconnection/siteuser/repository/SiteUserRepository.java b/src/main/java/com/example/solidconnection/siteuser/repository/SiteUserRepository.java index b151ade94..3d02c77e5 100644 --- a/src/main/java/com/example/solidconnection/siteuser/repository/SiteUserRepository.java +++ b/src/main/java/com/example/solidconnection/siteuser/repository/SiteUserRepository.java @@ -25,6 +25,8 @@ public interface SiteUserRepository extends JpaRepository, SiteU List findAllByIdIn(List ids); + boolean existsByHomeUniversityId(Long homeUniversityId); + @Modifying @Query("UPDATE SiteUser u SET u.userStatus = :status WHERE u.id IN :userIds") void bulkUpdateUserStatus(@Param("userIds") List userIds, @Param("status") UserStatus status); diff --git a/src/main/java/com/example/solidconnection/university/domain/HomeUniversity.java b/src/main/java/com/example/solidconnection/university/domain/HomeUniversity.java index f39378e97..506491c0f 100644 --- a/src/main/java/com/example/solidconnection/university/domain/HomeUniversity.java +++ b/src/main/java/com/example/solidconnection/university/domain/HomeUniversity.java @@ -24,4 +24,8 @@ public class HomeUniversity extends BaseEntity { @Column(name = "name", nullable = false, unique = true, length = 100) private String name; + + public void update(String name) { + this.name = name; + } } diff --git a/src/main/java/com/example/solidconnection/university/repository/UnivApplyInfoRepository.java b/src/main/java/com/example/solidconnection/university/repository/UnivApplyInfoRepository.java index 0b016750c..4c2b6578e 100644 --- a/src/main/java/com/example/solidconnection/university/repository/UnivApplyInfoRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/UnivApplyInfoRepository.java @@ -69,6 +69,8 @@ default UnivApplyInfo getUnivApplyInfoById(Long id) { boolean existsByUniversityId(Long universityId); + boolean existsByHomeUniversityId(Long homeUniversityId); + @Query(""" SELECT uai.id FROM UnivApplyInfo uai diff --git a/src/test/java/com/example/solidconnection/admin/university/service/AdminHomeUniversityServiceTest.java b/src/test/java/com/example/solidconnection/admin/university/service/AdminHomeUniversityServiceTest.java new file mode 100644 index 000000000..2e6a8ff55 --- /dev/null +++ b/src/test/java/com/example/solidconnection/admin/university/service/AdminHomeUniversityServiceTest.java @@ -0,0 +1,237 @@ +package com.example.solidconnection.admin.university.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.example.solidconnection.admin.university.dto.AdminHomeUniversityCreateRequest; +import com.example.solidconnection.admin.university.dto.AdminHomeUniversityResponse; +import com.example.solidconnection.admin.university.dto.AdminHomeUniversityUpdateRequest; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.common.exception.ErrorCode; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import com.example.solidconnection.university.domain.HomeUniversity; +import com.example.solidconnection.university.fixture.HomeUniversityFixture; +import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; +import com.example.solidconnection.university.repository.HomeUniversityRepository; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +@TestContainerSpringBootTest +@DisplayName("협정 대학 관리자 서비스 테스트") +class AdminHomeUniversityServiceTest { + + @Autowired + private AdminHomeUniversityService adminHomeUniversityService; + + @Autowired + private HomeUniversityRepository homeUniversityRepository; + + @Autowired + private HomeUniversityFixture homeUniversityFixture; + + @Autowired + private UnivApplyInfoFixture univApplyInfoFixture; + + @Autowired + private SiteUserFixture siteUserFixture; + + @Nested + class 전체_협정대학_조회 { + + @Test + void 협정대학이_없으면_빈_목록을_반환한다() { + // when + List responses = adminHomeUniversityService.getAllHomeUniversities(); + + // then + assertThat(responses).isEmpty(); + } + + @Test + void 저장된_모든_협정대학을_반환한다() { + // given + HomeUniversity homeUniversity1 = homeUniversityFixture.인하대학교(); + HomeUniversity homeUniversity2 = homeUniversityRepository.save(new HomeUniversity(null, "연세대학교")); + + // when + List responses = adminHomeUniversityService.getAllHomeUniversities(); + + // then + assertThat(responses) + .hasSize(2) + .extracting(AdminHomeUniversityResponse::name) + .containsExactlyInAnyOrder(homeUniversity1.getName(), homeUniversity2.getName()); + } + } + + @Nested + class 협정대학_단건_조회 { + + @Test + void 존재하는_협정대학을_조회하면_성공한다() { + // given + HomeUniversity homeUniversity = homeUniversityFixture.인하대학교(); + + // when + AdminHomeUniversityResponse response = adminHomeUniversityService.getHomeUniversity(homeUniversity.getId()); + + // then + assertAll( + () -> assertThat(response.id()).isEqualTo(homeUniversity.getId()), + () -> assertThat(response.name()).isEqualTo(homeUniversity.getName()) + ); + } + + @Test + void 존재하지_않는_협정대학을_조회하면_예외가_발생한다() { + // when & then + assertThatCode(() -> adminHomeUniversityService.getHomeUniversity(999L)) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.HOME_UNIVERSITY_NOT_FOUND.getMessage()); + } + } + + @Nested + class 협정대학_생성 { + + @Test + void 유효한_요청으로_협정대학을_생성하면_성공한다() { + // given + AdminHomeUniversityCreateRequest request = new AdminHomeUniversityCreateRequest("인하대학교"); + + // when + AdminHomeUniversityResponse response = adminHomeUniversityService.createHomeUniversity(request); + + // then + HomeUniversity saved = homeUniversityRepository.findById(response.id()).orElseThrow(); + assertAll( + () -> assertThat(response.name()).isEqualTo("인하대학교"), + () -> assertThat(saved.getName()).isEqualTo("인하대학교") + ); + } + + @Test + void 이미_존재하는_이름으로_생성하면_예외가_발생한다() { + // given + homeUniversityFixture.인하대학교(); + AdminHomeUniversityCreateRequest request = new AdminHomeUniversityCreateRequest("인하대학교"); + + // when & then + assertThatCode(() -> adminHomeUniversityService.createHomeUniversity(request)) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.HOME_UNIVERSITY_ALREADY_EXISTS.getMessage()); + } + } + + @Nested + class 협정대학_수정 { + + @Test + void 유효한_요청으로_협정대학을_수정하면_성공한다() { + // given + HomeUniversity homeUniversity = homeUniversityFixture.인하대학교(); + AdminHomeUniversityUpdateRequest request = new AdminHomeUniversityUpdateRequest("연세대학교"); + + // when + AdminHomeUniversityResponse response = adminHomeUniversityService.updateHomeUniversity(homeUniversity.getId(), request); + + // then + HomeUniversity updated = homeUniversityRepository.findById(homeUniversity.getId()).orElseThrow(); + assertAll( + () -> assertThat(response.name()).isEqualTo("연세대학교"), + () -> assertThat(updated.getName()).isEqualTo("연세대학교") + ); + } + + @Test + void 존재하지_않는_협정대학을_수정하면_예외가_발생한다() { + // given + AdminHomeUniversityUpdateRequest request = new AdminHomeUniversityUpdateRequest("연세대학교"); + + // when & then + assertThatCode(() -> adminHomeUniversityService.updateHomeUniversity(999L, request)) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.HOME_UNIVERSITY_NOT_FOUND.getMessage()); + } + + @Test + void 다른_협정대학의_이름으로_수정하면_예외가_발생한다() { + // given + homeUniversityFixture.인하대학교(); + HomeUniversity other = homeUniversityRepository.save(new HomeUniversity(null, "연세대학교")); + AdminHomeUniversityUpdateRequest request = new AdminHomeUniversityUpdateRequest("인하대학교"); + + // when & then + assertThatCode(() -> adminHomeUniversityService.updateHomeUniversity(other.getId(), request)) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.HOME_UNIVERSITY_ALREADY_EXISTS.getMessage()); + } + + @Test + void 같은_이름으로_수정하면_성공한다() { + // given + HomeUniversity homeUniversity = homeUniversityFixture.인하대학교(); + AdminHomeUniversityUpdateRequest request = new AdminHomeUniversityUpdateRequest("인하대학교"); + + // when + AdminHomeUniversityResponse response = adminHomeUniversityService.updateHomeUniversity(homeUniversity.getId(), request); + + // then + assertThat(response.name()).isEqualTo("인하대학교"); + } + } + + @Nested + class 협정대학_삭제 { + + @Test + void 참조가_없는_협정대학을_삭제하면_성공한다() { + // given + HomeUniversity homeUniversity = homeUniversityFixture.인하대학교(); + + // when + adminHomeUniversityService.deleteHomeUniversity(homeUniversity.getId()); + + // then + assertThat(homeUniversityRepository.findById(homeUniversity.getId())).isEmpty(); + } + + @Test + void 존재하지_않는_협정대학을_삭제하면_예외가_발생한다() { + // when & then + assertThatCode(() -> adminHomeUniversityService.deleteHomeUniversity(999L)) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.HOME_UNIVERSITY_NOT_FOUND.getMessage()); + } + + @Test + void UnivApplyInfo가_참조하는_협정대학을_삭제하면_예외가_발생한다() { + // given + HomeUniversity homeUniversity = homeUniversityFixture.인하대학교(); + univApplyInfoFixture.괌대학_A_지원_정보(1L); + + // when & then + assertThatCode(() -> adminHomeUniversityService.deleteHomeUniversity(homeUniversity.getId())) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.HOME_UNIVERSITY_HAS_REFERENCES.getMessage()); + } + + @Test + void SiteUser가_참조하는_협정대학을_삭제하면_예외가_발생한다() { + // given + HomeUniversity homeUniversity = homeUniversityFixture.인하대학교(); + SiteUser siteUser = siteUserFixture.국내_대학_정보_소지_사용자(homeUniversity.getId()); + + // when & then + assertThatCode(() -> adminHomeUniversityService.deleteHomeUniversity(homeUniversity.getId())) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.HOME_UNIVERSITY_HAS_REFERENCES.getMessage()); + } + } +}