Skip to content

Commit ff886b7

Browse files
authored
Feature/75 관심기관 등록, 취소 기능 구현 (#85)
* feat: 관심기관 등록 기능 구현 - InterestCenter 엔티티에 누락된 기관, 봉사자 id 필드 추가 - 관심 기관 등록 유스케이스 추가및 서비스 레이어 구현 - 등록, 검증을 위한 영속성 레이어 구현 - 중복 예외 클래스 추가 - 예외 메세지 추가 - 테스트 코드 작성및 검증 완 * feat: 관심기관 취소 기능 구현 - 관심 기관 취소 유스케이스 추가및 서비스 레이어 구현 - 관심기관 취소, 검증을 위한 영속성 레이어 구현 - 예외 메세지 추가 - 테스트 코드 작성및 검증 완료 * feat: 관심기관 API 컨트롤러 구현 - 관심 기관 등록과 취소 엔드포인트 컨트롤러 구현 - ApiResponse 공통 응답 객체 메서드 추가(논의 예정) - 테스트 코드 작성및 검증완료 * feat: 요청 유효성 검증추가 - 관심기관 등록 요청 Dto에 유효성 검증 어노테이션 추가 - 활성화를 위해 컨트롤러에 @Valid 어노테이션 추가 * refactor: 공통 응답 객체 수정및 적용 - 기존 와일드카드를 사용하던 ApiResponse의 ok 메서드를 String 타입으로 리팩토링 - 변경에 따른 컨트롤러 return문 수정 * chore: 요청 Dto swagger scheme 수정 - 봉사자 -> 기관으로 수정 * chore: 컨트롤러 엔드 포인트 수정 - 기존 RequestMapping을 이용한 전역적인 엔드포인트 적용에서 엔드포인트마다 url을 붙여주는 방식으로 수정
1 parent d4d2790 commit ff886b7

19 files changed

+610
-27
lines changed

src/main/java/com/somemore/domains/InterestCenter.java

Lines changed: 0 additions & 18 deletions
This file was deleted.

src/main/java/com/somemore/global/common/response/ApiResponse.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,10 @@ public static <T> ApiResponse<T> ok(int status, T data, String message) {
1515
return new ApiResponse<>(status, message, data);
1616
}
1717

18-
public static ApiResponse<?> ok(String message) {
18+
public static ApiResponse<String> ok(String message) {
1919
return new ApiResponse<>(200, message, "");
2020
}
2121

22-
public static ApiResponse<?> error(int code, String message) {
23-
return new ApiResponse<>(code, message, "");
24-
}
25-
2622
public ApiResponse(int code, String message, T data) {
2723
this.code = code;
2824
this.message = message;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.somemore.global.exception;
2+
3+
public class DuplicateException extends RuntimeException{
4+
5+
public DuplicateException(final String message) {
6+
super(message);
7+
}
8+
}

src/main/java/com/somemore/global/exception/ExceptionMessage.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ public enum ExceptionMessage {
2121
FILE_SIZE_EXCEEDED("파일 크기가 허용된 한도를 초과했습니다."),
2222
EMPTY_FILE("파일이 존재하지 않습니다."),
2323
INSTANTIATION_NOT_ALLOWED("인스턴스화 할 수 없는 클래스 입니다."),
24+
CANNOT_CANCEL_DELETED_INTEREST_CENTER("이미 삭제된 관심 기관은 취소할 수 없습니다."),
25+
DUPLICATE_INTEREST_CENTER("이미 관심 표시한 기관입니다.")
2426
;
2527

2628
private final String message;

src/main/java/com/somemore/global/handler/GlobalExceptionHandler.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
package com.somemore.global.handler;
22

3-
43
import com.somemore.global.exception.BadRequestException;
4+
import com.somemore.global.exception.DuplicateException;
55
import com.somemore.global.exception.ImageUploadException;
6-
import org.springframework.data.crossstore.ChangeSetPersister;
76
import org.springframework.http.HttpStatus;
87
import org.springframework.http.ProblemDetail;
98
import org.springframework.web.bind.annotation.ExceptionHandler;
109
import org.springframework.web.bind.annotation.RestControllerAdvice;
11-
import org.springframework.web.context.request.WebRequest;
1210
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
1311

14-
1512
@RestControllerAdvice
1613
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
1714

@@ -39,4 +36,13 @@ ProblemDetail handleImageUploadException(final ImageUploadException e) {
3936
return problemDetail;
4037
}
4138

39+
@ExceptionHandler(DuplicateException.class)
40+
ProblemDetail handleDuplicateException(final DuplicateException e) {
41+
42+
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, e.getMessage());
43+
problemDetail.setTitle("중복 예외");
44+
45+
return problemDetail;
46+
}
47+
4248
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.somemore.interestcenter.controller;
2+
3+
import com.somemore.global.common.response.ApiResponse;
4+
import com.somemore.interestcenter.dto.request.RegisterInterestCenterRequestDto;
5+
import com.somemore.interestcenter.dto.response.RegisterInterestCenterResponseDto;
6+
import com.somemore.interestcenter.usecase.CancelInterestCenterUseCase;
7+
import com.somemore.interestcenter.usecase.RegisterInterestCenterUseCase;
8+
import io.swagger.v3.oas.annotations.Operation;
9+
import io.swagger.v3.oas.annotations.tags.Tag;
10+
import jakarta.validation.Valid;
11+
import lombok.RequiredArgsConstructor;
12+
import org.springframework.web.bind.annotation.*;
13+
14+
@RequiredArgsConstructor
15+
@RestController
16+
@Tag(name = "Interest Center Command API", description = "관심 기관의 등록과 취소 API를 제공합니다")
17+
public class InterestCenterCommandApiController {
18+
19+
private final RegisterInterestCenterUseCase registerInterestCenterUseCase;
20+
private final CancelInterestCenterUseCase cancelInterestCenterUseCase;
21+
22+
@Operation(summary = "관심기관 등록 API")
23+
@PostMapping("/api/interest-center")
24+
public ApiResponse<RegisterInterestCenterResponseDto> registerInterestCenter(@Valid @RequestBody RegisterInterestCenterRequestDto requestDto) {
25+
26+
RegisterInterestCenterResponseDto responseDto = registerInterestCenterUseCase.registerInterestCenter(requestDto);
27+
28+
return ApiResponse.ok(200, responseDto, "관심 기관 등록 성공");
29+
}
30+
31+
@Operation(summary = "관심기관 취소 API")
32+
@DeleteMapping("/api/interest-center/{interest-center-id}")
33+
public ApiResponse<String> deleteInterestCenter(@PathVariable("interest-center-id") Long interestCenterId) {
34+
35+
cancelInterestCenterUseCase.cancelInterestCenter(interestCenterId);
36+
37+
return ApiResponse.ok("관심 기관 취소 성공");
38+
}
39+
40+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.somemore.interestcenter.domain;
2+
3+
import com.somemore.global.common.BaseEntity;
4+
import jakarta.persistence.*;
5+
import lombok.*;
6+
7+
import java.util.UUID;
8+
9+
import static jakarta.persistence.GenerationType.IDENTITY;
10+
11+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
12+
@Getter
13+
@Entity
14+
@Table(name = "interest_center")
15+
public class InterestCenter extends BaseEntity {
16+
17+
@Id
18+
@GeneratedValue(strategy = IDENTITY)
19+
private Long id;
20+
21+
@Column(name = "volunteer_id", nullable = false)
22+
private UUID volunteerId;
23+
24+
@Column(name = "center_id", nullable = false)
25+
private UUID centerId;
26+
27+
@Builder
28+
private InterestCenter(UUID volunteerId, UUID centerId) {
29+
this.volunteerId = volunteerId;
30+
this.centerId = centerId;
31+
}
32+
33+
public static InterestCenter create(UUID volunteerId, UUID centerId) {
34+
return InterestCenter.builder()
35+
.volunteerId(volunteerId)
36+
.centerId(centerId)
37+
.build();
38+
}
39+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.somemore.interestcenter.dto.request;
2+
3+
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
4+
import com.fasterxml.jackson.databind.annotation.JsonNaming;
5+
import com.somemore.interestcenter.domain.InterestCenter;
6+
import io.swagger.v3.oas.annotations.media.Schema;
7+
import jakarta.validation.constraints.NotNull;
8+
9+
import java.util.UUID;
10+
11+
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
12+
public record RegisterInterestCenterRequestDto(
13+
14+
@Schema(description = "봉사자 ID", example = "123e4567-e89b-12d3-a456-426614174000")
15+
@NotNull(message = "봉사자 ID는 필수값입니다.")
16+
UUID volunteerId,
17+
18+
@Schema(description = "기관 ID", example = "123e4567-e89b-12d3-a456-426614174000")
19+
@NotNull(message = "기관 ID는 필수값입니다.")
20+
UUID centerId
21+
) {
22+
public InterestCenter toEntity(){
23+
return InterestCenter.create(volunteerId, centerId);
24+
}
25+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.somemore.interestcenter.dto.response;
2+
3+
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
4+
import com.fasterxml.jackson.databind.annotation.JsonNaming;
5+
import com.somemore.interestcenter.domain.InterestCenter;
6+
import io.swagger.v3.oas.annotations.media.Schema;
7+
import lombok.Builder;
8+
9+
import java.util.UUID;
10+
11+
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
12+
@Builder
13+
public record RegisterInterestCenterResponseDto(
14+
@Schema(description = "관심 ID", example = "1111")
15+
Long id,
16+
17+
@Schema(description = "봉사자 ID", example = "123e4567-e89b-12d3-a456-426614174000")
18+
UUID volunteerId,
19+
20+
@Schema(description = "센터 ID", example = "123e4567-e89b-12d3-a456-426614174000")
21+
UUID centerId
22+
) {
23+
public static RegisterInterestCenterResponseDto from(InterestCenter interestCenter) {
24+
return RegisterInterestCenterResponseDto.builder()
25+
.id(interestCenter.getId())
26+
.volunteerId(interestCenter.getVolunteerId())
27+
.centerId(interestCenter.getCenterId())
28+
.build();
29+
}
30+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.somemore.interestcenter.repository;
2+
3+
import com.somemore.interestcenter.domain.InterestCenter;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
6+
public interface InterestCenterJpaRepository extends JpaRepository<InterestCenter, Long> {
7+
}

0 commit comments

Comments
 (0)