Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
18 changes: 0 additions & 18 deletions src/main/java/com/somemore/domains/InterestCenter.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ public static ApiResponse<?> ok(String message) {
return new ApiResponse<>(200, message, "");
}

public static ApiResponse<?> error(int code, String message) {
return new ApiResponse<>(code, message, "");
public static ApiResponse<String> okWithoutData(String message) {
return new ApiResponse<>(200, message, "");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 이거 만들려고 했는데 ㅎㅎ. 감사히 사용하겠습니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

최신 커밋에서는 이름이 다시 ok로 변경되었습니다!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

더 좋습니다.

}

public ApiResponse(int code, String message, T data) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.somemore.global.exception;

public class DuplicateException extends RuntimeException{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BadReq로 모든 것을 처리하는 게 아니였나요...?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

중복 검사가 계속 생길거 같은데 전부 BadReq로 하면 예외가 명확하지 못한 느낌이 들어서 추가해주었습니다
전부 BadReq로 처리하기로 한 거 였나요? BadReq를 포괄적으로 사용할 수 있다 라는 느낌으로 이해하고 있었어요

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BadReq에서 구분된다면, Exception 메시지들도 따로 관리하는 것이 명확하다고 생각했고 그 반대도 같은 의미로 받아들이고 있었습니다. 저는 오히려 서진님의 구분된 Exception이 더 명확하다고 생각합니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exception 메시지들도 따로 관리하는 것이 혹시 도메인 별로 메시지들을 관리하는게 맞나요?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Exception이 BadReq 뿐만 아니라 여러 개로 나뉨
  2. Exception 별로 예외 메시지 클래스가 생성 -> 서로 다른 ENUM 예외 메시지 클래스
  3. ENUM 클래스 -> 도메인 별로 메시지를 관리할 수 있도록 템플릿처럼 관리 (%s ~~~~~ 입니다, 도메인)
  4. %s에 들어갈 도메인 ENUM 클래스...
  5. 3에서 만든 클래스 -> (%s%s, 유저, 닉네임), (%s%s, 기관, 전화번호?)

이거 같은데 우선 진행해도 무방하다고 생각합니다.


public DuplicateException(final String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ public enum ExceptionMessage {
FILE_SIZE_EXCEEDED("파일 크기가 허용된 한도를 초과했습니다."),
EMPTY_FILE("파일이 존재하지 않습니다."),
INSTANTIATION_NOT_ALLOWED("인스턴스화 할 수 없는 클래스 입니다."),
CANNOT_CANCEL_DELETED_INTEREST_CENTER("이미 삭제된 관심 기관은 취소할 수 없습니다."),
DUPLICATE_INTEREST_CENTER("이미 관심 표시한 기관입니다.")
;

private final String message;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
package com.somemore.global.handler;


import com.somemore.global.exception.BadRequestException;
import com.somemore.global.exception.DuplicateException;
import com.somemore.global.exception.ImageUploadException;
import org.springframework.data.crossstore.ChangeSetPersister;
import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;


@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

Expand Down Expand Up @@ -39,4 +36,13 @@ ProblemDetail handleImageUploadException(final ImageUploadException e) {
return problemDetail;
}

@ExceptionHandler(DuplicateException.class)
ProblemDetail handleDuplicateException(final DuplicateException e) {

ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, e.getMessage());
problemDetail.setTitle("중복 예외");

return problemDetail;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.somemore.interestcenter.controller;

import com.somemore.global.common.response.ApiResponse;
import com.somemore.interestcenter.dto.request.RegisterInterestCenterRequestDto;
import com.somemore.interestcenter.dto.response.RegisterInterestCenterResponseDto;
import com.somemore.interestcenter.usecase.CancelInterestCenterUseCase;
import com.somemore.interestcenter.usecase.RegisterInterestCenterUseCase;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

@RequiredArgsConstructor
@RestController
@RequestMapping("/api/interest-center")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 나중에 s를 뒤에 붙이실 때 어떻게 하실건가요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

생각하지 못했습니다 차라리 컨트롤러에 적어주는게 나을까요?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

범수님은 컨트롤러 엔드포인트 하나하나마다 추가해주셨습니다. 개발 시작 전에도 범수 님이 이 문제에 대해서 짚어주셔서 인지하고 있었습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

말씀해주신 방법이 더 깔끔한 것 같아요 저도 엔드포인트마다 추가 해주는걸로 수정하겠습니다

@Tag(name = "Interest Center Command API", description = "관심 기관의 등록과 취소 API를 제공합니다")
public class InterestCenterCommandApiController {

private final RegisterInterestCenterUseCase registerInterestCenterUseCase;
private final CancelInterestCenterUseCase cancelInterestCenterUseCase;

@Operation(summary = "관심기관 등록 API")
@PostMapping()
public ApiResponse<RegisterInterestCenterResponseDto> registerInterestCenter(@Valid @RequestBody RegisterInterestCenterRequestDto requestDto) {

RegisterInterestCenterResponseDto responseDto = registerInterestCenterUseCase.registerInterestCenter(requestDto);

return ApiResponse.ok(200, responseDto, "관심 기관 등록 성공");
}

@Operation(summary = "관심기관 취소 API")
@DeleteMapping("/{interest-center-id}")
public ApiResponse<String> deleteInterestCenter(@PathVariable("interest-center-id") Long interestCenterId) {

cancelInterestCenterUseCase.cancelInterestCenter(interestCenterId);

return ApiResponse.okWithoutData("관심 기관 취소 성공");
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.somemore.interestcenter.domain;

import com.somemore.global.common.BaseEntity;
import jakarta.persistence.*;
import lombok.*;

import java.util.UUID;

import static jakarta.persistence.GenerationType.IDENTITY;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Entity
@Table(name = "interest_center")
public class InterestCenter extends BaseEntity {

@Id
@GeneratedValue(strategy = IDENTITY)
private Long id;

@Column(name = "volunteer_id", nullable = false)
private UUID volunteerId;

@Column(name = "center_id", nullable = false)
private UUID centerId;

@Builder
private InterestCenter(UUID volunteerId, UUID centerId) {
this.volunteerId = volunteerId;
this.centerId = centerId;
}

public static InterestCenter create(UUID volunteerId, UUID centerId) {
return InterestCenter.builder()
.volunteerId(volunteerId)
.centerId(centerId)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.somemore.interestcenter.dto.request;

import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.somemore.interestcenter.domain.InterestCenter;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;

import java.util.UUID;

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public record RegisterInterestCenterRequestDto(

@Schema(description = "봉사자 ID", example = "123e4567-e89b-12d3-a456-426614174000")
@NotNull(message = "봉사자 ID는 필수값입니다.")
UUID volunteerId,

@Schema(description = "봉사자 ID", example = "123e4567-e89b-12d3-a456-426614174000")
@NotNull(message = "기관 ID는 필수값입니다.")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

봉사자 ID -> 기관 ID 오타인것 같아요.

그리고 UUID volunteerId은 로그인시 시큐리티에서 컨트롤러로 가져오는 값이지 않나요??
@Athunci~~ 어노테이션으로요

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이런 실수를... 자세히 봐주셔서 감사합니다

UUID centerId
) {
public InterestCenter toEntity(){
return InterestCenter.create(volunteerId, centerId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.somemore.interestcenter.dto.response;

import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.somemore.interestcenter.domain.InterestCenter;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;

import java.util.UUID;

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
@Builder
public record RegisterInterestCenterResponseDto(
@Schema(description = "관심 ID", example = "1111")
Long id,

@Schema(description = "봉사자 ID", example = "123e4567-e89b-12d3-a456-426614174000")
UUID volunteerId,

@Schema(description = "센터 ID", example = "123e4567-e89b-12d3-a456-426614174000")
UUID centerId
) {
public static RegisterInterestCenterResponseDto from(InterestCenter interestCenter) {
return RegisterInterestCenterResponseDto.builder()
.id(interestCenter.getId())
.volunteerId(interestCenter.getVolunteerId())
.centerId(interestCenter.getCenterId())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.somemore.interestcenter.repository;

import com.somemore.interestcenter.domain.InterestCenter;
import org.springframework.data.jpa.repository.JpaRepository;

public interface InterestCenterJpaRepository extends JpaRepository<InterestCenter, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.somemore.interestcenter.repository;

import com.somemore.interestcenter.domain.InterestCenter;
import com.somemore.interestcenter.dto.response.RegisterInterestCenterResponseDto;

import java.util.Optional;
import java.util.UUID;

public interface InterestCenterRepository {
InterestCenter save(InterestCenter interestCenter);
Optional<InterestCenter> findById(Long id);
Optional<RegisterInterestCenterResponseDto> findInterestCenterResponseById(Long id);
boolean existsByVolunteerIdAndCenterId(UUID volunteerId, UUID centerId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package com.somemore.interestcenter.repository;

import com.querydsl.core.types.Projections;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.somemore.interestcenter.domain.InterestCenter;
import com.somemore.interestcenter.domain.QInterestCenter;
import com.somemore.interestcenter.dto.response.RegisterInterestCenterResponseDto;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

import java.util.Optional;
import java.util.UUID;

@RequiredArgsConstructor
@Repository
public class InterestCenterRepositoryImpl implements InterestCenterRepository {

private final JPAQueryFactory queryFactory;
private final InterestCenterJpaRepository interestCenterJpaRepository;

@Override
public InterestCenter save(InterestCenter interestCenter) {
return interestCenterJpaRepository.save(interestCenter);
}

@Override
public Optional<InterestCenter> findById(Long id) {
QInterestCenter interestCenter = QInterestCenter.interestCenter;

InterestCenter result = queryFactory
.selectFrom(interestCenter)
.where(
interestCenter.id.eq(id)
.and(interestCenter.deleted.eq(false))
)
.fetchOne();

return Optional.ofNullable(result);
}

@Override
public Optional<RegisterInterestCenterResponseDto> findInterestCenterResponseById(Long id) {
QInterestCenter interestCenter = QInterestCenter.interestCenter;

RegisterInterestCenterResponseDto result = queryFactory
.select(
Projections.constructor(
RegisterInterestCenterResponseDto.class,
interestCenter.id,
interestCenter.volunteerId,
interestCenter.centerId
)
)
.from(interestCenter)
.where(
interestCenter.id.eq(id)
.and(interestCenter.deleted.eq(false))
)
.fetchOne();

return Optional.ofNullable(result);
}

@Override
public boolean existsByVolunteerIdAndCenterId(UUID volunteerId, UUID centerId) {
QInterestCenter interestCenter = QInterestCenter.interestCenter;

Integer result = queryFactory
.selectOne()
.from(interestCenter)
.where(
interestCenter.volunteerId.eq(volunteerId)
.and(interestCenter.centerId.eq(centerId))
.and(interestCenter.deleted.eq(false))
)
.fetchFirst();

return result != null;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.somemore.interestcenter.service;

import com.somemore.global.exception.BadRequestException;
import com.somemore.interestcenter.domain.InterestCenter;
import com.somemore.interestcenter.repository.InterestCenterRepository;
import com.somemore.interestcenter.usecase.CancelInterestCenterUseCase;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import static com.somemore.global.exception.ExceptionMessage.CANNOT_CANCEL_DELETED_INTEREST_CENTER;

@RequiredArgsConstructor
@Service
public class CancelInterestCenterService implements CancelInterestCenterUseCase {

private final InterestCenterRepository interestCenterRepository;

@Override
public void cancelInterestCenter(Long interestCenterId) {
InterestCenter interestCenter = interestCenterRepository.findById(interestCenterId)
.orElseThrow(() -> new BadRequestException(CANNOT_CANCEL_DELETED_INTEREST_CENTER.getMessage()));

interestCenter.markAsDeleted();

interestCenterRepository.save(interestCenter);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.somemore.interestcenter.service;

import com.somemore.center.usecase.query.CenterQueryUseCase;
import com.somemore.global.exception.DuplicateException;
import com.somemore.interestcenter.domain.InterestCenter;
import com.somemore.interestcenter.dto.request.RegisterInterestCenterRequestDto;
import com.somemore.interestcenter.dto.response.RegisterInterestCenterResponseDto;
import com.somemore.interestcenter.repository.InterestCenterRepository;
import com.somemore.interestcenter.usecase.RegisterInterestCenterUseCase;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import static com.somemore.global.exception.ExceptionMessage.DUPLICATE_INTEREST_CENTER;

@RequiredArgsConstructor
@Service
public class RegisterInterestCenterService implements RegisterInterestCenterUseCase {

private final InterestCenterRepository repository;
private final CenterQueryUseCase centerQueryUseCase;

@Override
public RegisterInterestCenterResponseDto registerInterestCenter(RegisterInterestCenterRequestDto requestDto) {

centerQueryUseCase.validateCenterExists(requestDto.centerId());

boolean isDuplicate = repository.existsByVolunteerIdAndCenterId(requestDto.volunteerId(), requestDto.centerId());
if(isDuplicate){
throw new DuplicateException(DUPLICATE_INTEREST_CENTER.getMessage());
}

InterestCenter interestCenter = repository.save(requestDto.toEntity());

return RegisterInterestCenterResponseDto.from(interestCenter);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.somemore.interestcenter.usecase;

public interface CancelInterestCenterUseCase {
void cancelInterestCenter(Long interestCenterId);
}
Loading