Skip to content

Commit e673bb2

Browse files
Gyuhyeok99claude
andauthored
feat: region 관련 관리 기능 추가 (#561)
* feat: 지역 생성 기능 구현 (AdminRegion) 지역을 생성하는 기능을 구현했습니다: - AdminRegionCreateRequest: 지역 생성 요청 DTO - AdminRegionResponse: 지역 응답 DTO - AdminRegionService.createRegion(): 중복 검사를 포함한 지역 생성 로직 - AdminRegionController.createRegion(): HTTP POST 엔드포인트 중복 검사: - 지역 코드 중복 확인 - 한글명 중복 확인 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * feat: 지역 수정/삭제/조회 기능 구현 및 테스트 추가 (AdminRegion) 지역 관리 기능을 완성했습니다: 구현 기능: - AdminRegionUpdateRequest: 지역 수정 요청 DTO - AdminRegionService.updateRegion(): 한글명 중복 검사를 포함한 지역 수정 - AdminRegionService.deleteRegion(): 지역 삭제 - AdminRegionService.getAllRegions(): 전체 지역 조회 - AdminRegionController: 수정/삭제/조회 HTTP 엔드포인트 테스트 코드 (AdminRegionServiceTest): - CREATE: 정상 생성, 코드 중복, 한글명 중복 테스트 - UPDATE: 정상 수정, NOT_FOUND, 중복 한글명, 동일 한글명 테스트 - DELETE: 정상 삭제, NOT_FOUND 테스트 - READ: 빈 목록, 전체 조회 테스트 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: 지역 관리 관련 에러코드 추가 (ErrorCode) 지역 관리 기능에 필요한 에러코드를 추가했습니다: - REGION_NOT_FOUND: 존재하지 않는 지역 - REGION_ALREADY_EXISTS: 이미 존재하는 지역 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: jpa 오류 수정 * refactor: 코드리뷰 반영 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 3c342a8 commit e673bb2

File tree

9 files changed

+386
-1
lines changed

9 files changed

+386
-1
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.example.solidconnection.admin.location.region.controller;
2+
3+
import com.example.solidconnection.admin.location.region.dto.AdminRegionCreateRequest;
4+
import com.example.solidconnection.admin.location.region.dto.AdminRegionResponse;
5+
import com.example.solidconnection.admin.location.region.dto.AdminRegionUpdateRequest;
6+
import com.example.solidconnection.admin.location.region.service.AdminRegionService;
7+
import jakarta.validation.Valid;
8+
import java.util.List;
9+
import lombok.RequiredArgsConstructor;
10+
import org.springframework.http.HttpStatus;
11+
import org.springframework.http.ResponseEntity;
12+
import org.springframework.web.bind.annotation.DeleteMapping;
13+
import org.springframework.web.bind.annotation.GetMapping;
14+
import org.springframework.web.bind.annotation.PathVariable;
15+
import org.springframework.web.bind.annotation.PostMapping;
16+
import org.springframework.web.bind.annotation.PutMapping;
17+
import org.springframework.web.bind.annotation.RequestBody;
18+
import org.springframework.web.bind.annotation.RequestMapping;
19+
import org.springframework.web.bind.annotation.RestController;
20+
21+
@RequiredArgsConstructor
22+
@RequestMapping("/admin/regions")
23+
@RestController
24+
public class AdminRegionController {
25+
26+
private final AdminRegionService adminRegionService;
27+
28+
@GetMapping
29+
public ResponseEntity<List<AdminRegionResponse>> getAllRegions() {
30+
List<AdminRegionResponse> responses = adminRegionService.getAllRegions();
31+
return ResponseEntity.ok(responses);
32+
}
33+
34+
@PostMapping
35+
public ResponseEntity<AdminRegionResponse> createRegion(
36+
@Valid @RequestBody AdminRegionCreateRequest request
37+
) {
38+
AdminRegionResponse response = adminRegionService.createRegion(request);
39+
return ResponseEntity.status(HttpStatus.CREATED).body(response);
40+
}
41+
42+
@PutMapping("/{code}")
43+
public ResponseEntity<AdminRegionResponse> updateRegion(
44+
@PathVariable String code,
45+
@Valid @RequestBody AdminRegionUpdateRequest request
46+
) {
47+
AdminRegionResponse response = adminRegionService.updateRegion(code, request);
48+
return ResponseEntity.ok(response);
49+
}
50+
51+
@DeleteMapping("/{code}")
52+
public ResponseEntity<Void> deleteRegion(
53+
@PathVariable String code
54+
) {
55+
adminRegionService.deleteRegion(code);
56+
return ResponseEntity.noContent().build();
57+
}
58+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.example.solidconnection.admin.location.region.dto;
2+
3+
import jakarta.validation.constraints.NotBlank;
4+
import jakarta.validation.constraints.Size;
5+
6+
public record AdminRegionCreateRequest(
7+
@NotBlank(message = "지역 코드는 필수입니다")
8+
@Size(min = 1, max = 10, message = "지역 코드는 1자 이상 10자 이하여야 합니다")
9+
String code,
10+
11+
@NotBlank(message = "한글 지역명은 필수입니다")
12+
@Size(min = 1, max = 100, message = "한글 지역명은 1자 이상 100자 이하여야 합니다")
13+
String koreanName
14+
) {
15+
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.example.solidconnection.admin.location.region.dto;
2+
3+
import com.example.solidconnection.location.region.domain.Region;
4+
5+
public record AdminRegionResponse(
6+
String code,
7+
String koreanName
8+
) {
9+
10+
public static AdminRegionResponse from(Region region) {
11+
return new AdminRegionResponse(
12+
region.getCode(),
13+
region.getKoreanName()
14+
);
15+
}
16+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.example.solidconnection.admin.location.region.dto;
2+
3+
import jakarta.validation.constraints.NotBlank;
4+
import jakarta.validation.constraints.Size;
5+
6+
public record AdminRegionUpdateRequest(
7+
@NotBlank(message = "한글 지역명은 필수입니다")
8+
@Size(min = 1, max = 100, message = "한글 지역명은 1자 이상 100자 이하여야 합니다")
9+
String koreanName
10+
) {
11+
12+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package com.example.solidconnection.admin.location.region.service;
2+
3+
import com.example.solidconnection.admin.location.region.dto.AdminRegionCreateRequest;
4+
import com.example.solidconnection.admin.location.region.dto.AdminRegionResponse;
5+
import com.example.solidconnection.admin.location.region.dto.AdminRegionUpdateRequest;
6+
import com.example.solidconnection.common.exception.CustomException;
7+
import com.example.solidconnection.common.exception.ErrorCode;
8+
import com.example.solidconnection.location.region.domain.Region;
9+
import com.example.solidconnection.location.region.repository.RegionRepository;
10+
import java.util.List;
11+
import lombok.RequiredArgsConstructor;
12+
import org.springframework.stereotype.Service;
13+
import org.springframework.transaction.annotation.Transactional;
14+
15+
@Service
16+
@RequiredArgsConstructor
17+
public class AdminRegionService {
18+
19+
private final RegionRepository regionRepository;
20+
21+
@Transactional(readOnly = true)
22+
public List<AdminRegionResponse> getAllRegions() {
23+
return regionRepository.findAll()
24+
.stream()
25+
.map(AdminRegionResponse::from)
26+
.toList();
27+
}
28+
29+
@Transactional
30+
public AdminRegionResponse createRegion(AdminRegionCreateRequest request) {
31+
validateCodeNotExists(request.code());
32+
validateKoreanNameNotExists(request.koreanName());
33+
34+
Region region = new Region(request.code(), request.koreanName());
35+
Region savedRegion = regionRepository.save(region);
36+
37+
return AdminRegionResponse.from(savedRegion);
38+
}
39+
40+
private void validateCodeNotExists(String code) {
41+
regionRepository.findById(code)
42+
.ifPresent(region -> {
43+
throw new CustomException(ErrorCode.REGION_ALREADY_EXISTS);
44+
});
45+
}
46+
47+
private void validateKoreanNameNotExists(String koreanName) {
48+
regionRepository.findByKoreanName(koreanName)
49+
.ifPresent(region -> {
50+
throw new CustomException(ErrorCode.REGION_ALREADY_EXISTS);
51+
});
52+
}
53+
54+
@Transactional
55+
public AdminRegionResponse updateRegion(String code, AdminRegionUpdateRequest request) {
56+
Region region = regionRepository.findById(code)
57+
.orElseThrow(() -> new CustomException(ErrorCode.REGION_NOT_FOUND));
58+
59+
validateKoreanNameNotDuplicated(request.koreanName(), code);
60+
61+
region.updateKoreanName(request.koreanName());
62+
return AdminRegionResponse.from(region);
63+
}
64+
65+
private void validateKoreanNameNotDuplicated(String koreanName, String excludeCode) {
66+
regionRepository.findByKoreanName(koreanName)
67+
.ifPresent(existingRegion -> {
68+
if (!existingRegion.getCode().equals(excludeCode)) {
69+
throw new CustomException(ErrorCode.REGION_ALREADY_EXISTS);
70+
}
71+
});
72+
}
73+
74+
@Transactional
75+
public void deleteRegion(String code) {
76+
Region region = regionRepository.findById(code)
77+
.orElseThrow(() -> new CustomException(ErrorCode.REGION_NOT_FOUND));
78+
79+
regionRepository.delete(region);
80+
}
81+
}

src/main/java/com/example/solidconnection/common/exception/ErrorCode.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ public enum ErrorCode {
3838
APPLICATION_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "사용자의 대학 지원 정보를 찾을 수 없습니다."),
3939
USER_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "회원을 찾을 수 없습니다."),
4040
UNIVERSITY_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "대학교를 찾을 수 없습니다."),
41+
REGION_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "지역을 찾을 수 없습니다."),
4142
REGION_NOT_FOUND_BY_KOREAN_NAME(HttpStatus.NOT_FOUND.value(), "이름에 해당하는 지역을 찾을 수 없습니다."),
43+
REGION_ALREADY_EXISTS(HttpStatus.CONFLICT.value(), "이미 존재하는 지역입니다."),
4244
COUNTRY_NOT_FOUND_BY_KOREAN_NAME(HttpStatus.NOT_FOUND.value(), "이름에 해당하는 국가를 찾을 수 없습니다."),
4345
GPA_SCORE_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "존재하지 않는 학점입니다."),
4446
LANGUAGE_TEST_SCORE_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "존재하지 않는 어학성적입니다."),

src/main/java/com/example/solidconnection/location/region/domain/Region.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,8 @@ public Region(String code, String koreanName) {
2525
this.code = code;
2626
this.koreanName = koreanName;
2727
}
28+
29+
public void updateKoreanName(String koreanName) {
30+
this.koreanName = koreanName;
31+
}
2832
}

src/main/java/com/example/solidconnection/location/region/repository/RegionRepository.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import java.util.Optional;
66
import org.springframework.data.jpa.repository.JpaRepository;
77

8-
public interface RegionRepository extends JpaRepository<Region, Long> {
8+
public interface RegionRepository extends JpaRepository<Region, String> {
99

1010
List<Region> findAllByKoreanNameIn(List<String> koreanNames);
1111

0 commit comments

Comments
 (0)