diff --git a/.github/workflows/CD.yml b/.github/workflows/CD.yml
index 5dee87723..4223d083b 100644
--- a/.github/workflows/CD.yml
+++ b/.github/workflows/CD.yml
@@ -23,15 +23,17 @@ jobs:
NAVER_CLIENT_ID: ${{ secrets.NAVER_CLIENT_ID }}
NAVER_CLIENT_SECRET: ${{ secrets.NAVER_CLIENT_SECRET }}
NAVER_SCOPE: ${{ secrets.NAVER_SCOPE }}
- NAVER_REDIRECT_URL: ${{secrets.NAVER_REDIRECT_URL}}
+ NAVER_REDIRECT_URL: ${{ secrets.NAVER_REDIRECT_URL }}
JWT_SECRET: ${{ secrets.JWT_SECRET }}
- FRONT_URL: ${{secrets.FRONT_URL}}
- BACK_URL: ${{secrets.BACK_URL}}
- BUCKET_NAME: ${{secrets.BUCKET_NAME}}
- BUCKET_REGION: ${{secrets.BUCKET_REGION}}
- IMG_BASE_URL: ${{secrets.BASE_URL}}
- S3_ACCESS_KEY: ${{secrets.S3_ACCESS_KEY}}
- S3_SECRET_KEY: ${{secrets.S3_SECRET_KEY}}
+ FRONT_URL: ${{ secrets.FRONT_URL }}
+ BACK_URL: ${{ secrets.BACK_URL }}
+ BUCKET_NAME: ${{ secrets.BUCKET_NAME }}
+ BUCKET_REGION: ${{ secrets.BUCKET_REGION }}
+ IMG_BASE_URL: ${{ secrets.BASE_URL }}
+ S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }}
+ S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }}
+ DEFAULT_IMG_URL: ${{ secrets.DEFAULT_IMG_URL }}
+ APP_DEVELOP_MODE: ${{ secrets.APP_DEVELOP_MODE }}
steps:
- name: Github Repository 파일 불러오기
diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
index 629520d89..13ad59f60 100644
--- a/.github/workflows/CI.yml
+++ b/.github/workflows/CI.yml
@@ -31,15 +31,17 @@ jobs:
NAVER_CLIENT_ID: ${{ secrets.NAVER_CLIENT_ID }}
NAVER_CLIENT_SECRET: ${{ secrets.NAVER_CLIENT_SECRET }}
NAVER_SCOPE: ${{ secrets.NAVER_SCOPE }}
- NAVER_REDIRECT_URL: ${{secrets.NAVER_REDIRECT_URL}}
+ NAVER_REDIRECT_URL: ${{ secrets.NAVER_REDIRECT_URL }}
JWT_SECRET: ${{ secrets.JWT_SECRET }}
- FRONT_URL: ${{secrets.FRONT_URL}}
- BACK_URL: ${{secrets.BACK_URL}}
- BUCKET_NAME: ${{secrets.BUCKET_NAME}}
- BUCKET_REGION: ${{secrets.BUCKET_REGION}}
- IMG_BASE_URL: ${{secrets.BASE_URL}}
- S3_ACCESS_KEY: ${{secrets.S3_ACCESS_KEY}}
- S3_SECRET_KEY: ${{secrets.S3_SECRET_KEY}}
+ FRONT_URL: ${{ secrets.FRONT_URL }}
+ BACK_URL: ${{ secrets.BACK_URL }}
+ BUCKET_NAME: ${{ secrets.BUCKET_NAME }}
+ BUCKET_REGION: ${{ secrets.BUCKET_REGION }}
+ IMG_BASE_URL: ${{ secrets.BASE_URL }}
+ S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }}
+ S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }}
+ DEFAULT_IMG_URL: ${{ secrets.DEFAULT_IMG_URL }}
+ APP_DEVELOP_MODE: ${{ secrets.APP_DEVELOP_MODE }}
steps:
diff --git a/src/main/java/com/somemore/auth/jwt/filter/JwtAuthFilter.java b/src/main/java/com/somemore/auth/jwt/filter/JwtAuthFilter.java
index 08ced03a6..071cf7677 100644
--- a/src/main/java/com/somemore/auth/jwt/filter/JwtAuthFilter.java
+++ b/src/main/java/com/somemore/auth/jwt/filter/JwtAuthFilter.java
@@ -9,6 +9,8 @@
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
@@ -17,9 +19,6 @@
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
-import java.io.IOException;
-import java.util.List;
-
@RequiredArgsConstructor
@Slf4j
@Component
@@ -29,12 +28,13 @@ public class JwtAuthFilter extends OncePerRequestFilter {
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
- return true; // 개발 중 모든 요청 허용
-// return httpServletRequest.getRequestURI().contains("token");
+ String token = request.getHeader("Authorization");
+ return token == null || token.isEmpty();
}
@Override
- protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
+ FilterChain filterChain) throws ServletException, IOException {
EncodedToken accessToken = getAccessToken(request);
jwtUseCase.processAccessToken(accessToken, response);
@@ -47,15 +47,19 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
private EncodedToken getAccessToken(HttpServletRequest request) {
String accessToken = request.getHeader("Authorization");
- if (accessToken == null || accessToken.isEmpty()) {
+ if (!accessToken.startsWith("Bearer ")) {
throw new JwtException(JwtErrorType.MISSING_TOKEN);
}
+
+ accessToken = accessToken.substring(7);
+
return new EncodedToken(accessToken);
}
- private JwtAuthenticationToken createAuthenticationToken(Claims claims, EncodedToken accessToken) {
+ private JwtAuthenticationToken createAuthenticationToken(Claims claims,
+ EncodedToken accessToken) {
String userId = claims.get("id", String.class);
- UserRole role = claims.get("role", UserRole.class);
+ UserRole role = UserRole.valueOf(claims.get("role", String.class));
return new JwtAuthenticationToken(
userId,
diff --git a/src/main/java/com/somemore/auth/util/DevAccountSetUpConfig.java b/src/main/java/com/somemore/auth/util/DevAccountSetUpConfig.java
new file mode 100644
index 000000000..42e98fb60
--- /dev/null
+++ b/src/main/java/com/somemore/auth/util/DevAccountSetUpConfig.java
@@ -0,0 +1,99 @@
+package com.somemore.auth.util;
+
+import static com.somemore.auth.oauth.OAuthProvider.NAVER;
+
+import com.somemore.auth.jwt.domain.EncodedToken;
+import com.somemore.auth.jwt.domain.TokenType;
+import com.somemore.auth.jwt.domain.UserRole;
+import com.somemore.auth.jwt.generator.JwtGenerator;
+import com.somemore.auth.jwt.refresh.domain.RefreshToken;
+import com.somemore.auth.jwt.refresh.manager.RefreshTokenManager;
+import com.somemore.center.domain.Center;
+import com.somemore.center.repository.CenterJpaRepository;
+import com.somemore.volunteer.domain.Volunteer;
+import com.somemore.volunteer.repository.VolunteerJpaRepository;
+import jakarta.annotation.PostConstruct;
+import jakarta.annotation.PreDestroy;
+import java.util.UUID;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@RequiredArgsConstructor
+@Component
+public class DevAccountSetUpConfig {
+
+ private final VolunteerJpaRepository volunteerRepository;
+ private final CenterJpaRepository centerRepository;
+ private final JwtGenerator jwtGenerator;
+ private final RefreshTokenManager refreshTokenManager;
+
+ private Volunteer volunteer;
+ private Center center;
+
+ @Value("${app.develop.mode}")
+ private boolean developMode;
+
+ @PostConstruct
+ public void generateAccessTokenForDev() {
+ if (!developMode) {
+ return; // 개발 모드에서만 실행
+ }
+
+ volunteer = Volunteer.createDefault(NAVER, "bongdari");
+ center = Center.create(
+ "봉다리 자원봉사센터",
+ "02-1234-5678",
+ "",
+ "봉다리 기관 테스트 계정입니다.",
+ "https://somemore.bongdari.com",
+ "bongdari",
+ "1234"
+ );
+
+ volunteer = volunteerRepository.findByOauthId(volunteer.getOauthId())
+ .orElseGet(() -> volunteerRepository.save(volunteer));
+
+ center = centerRepository.findByName(center.getName())
+ .orElseGet(() -> centerRepository.save(center));
+
+ EncodedToken volunteerToken = saveRefreshTokenAndReturnAccessToken(volunteer.getId(),
+ UserRole.VOLUNTEER);
+ EncodedToken centerToken = saveRefreshTokenAndReturnAccessToken(center.getId(),
+ UserRole.CENTER);
+
+ log.info("테스트용 봉사자 AccessToken: {}", volunteerToken.value());
+ log.info("테스트용 기관 AccessToken: {}", centerToken.value());
+ }
+
+ @PreDestroy
+ public void cleanup() {
+ if (volunteer != null) {
+ refreshTokenManager.removeRefreshToken(volunteer.getId().toString());
+ log.info("테스트용 AccessToken 제거, 봉사자 ID: {}", volunteer.getId());
+ }
+ if (center != null) {
+ refreshTokenManager.removeRefreshToken(center.getId().toString());
+ log.info("테스트용 AccessToken 제거, 기관 ID: {}", center.getId());
+ }
+ }
+
+ private EncodedToken saveRefreshTokenAndReturnAccessToken(UUID id, UserRole role) {
+ EncodedToken accessToken = generateToken(id, role, TokenType.ACCESS);
+ RefreshToken refreshToken = generateRefreshToken(id, role, accessToken);
+ refreshTokenManager.save(refreshToken);
+ return accessToken;
+ }
+
+ private EncodedToken generateToken(UUID id, UserRole role, TokenType tokenType) {
+ return jwtGenerator.generateToken(id.toString(), role.name(), tokenType);
+ }
+
+ private RefreshToken generateRefreshToken(UUID id, UserRole role, EncodedToken accessToken) {
+ return new RefreshToken(id.toString(), accessToken,
+ generateToken(id, role, TokenType.REFRESH));
+ }
+}
+
diff --git a/src/main/java/com/somemore/center/repository/CenterJpaRepository.java b/src/main/java/com/somemore/center/repository/CenterJpaRepository.java
index fde29e33a..c68bb8441 100644
--- a/src/main/java/com/somemore/center/repository/CenterJpaRepository.java
+++ b/src/main/java/com/somemore/center/repository/CenterJpaRepository.java
@@ -9,4 +9,5 @@
public interface CenterJpaRepository extends JpaRepository
{
boolean existsById(UUID id);
Optional findCenterById(UUID id);
+ Optional findByName(String name);
}
diff --git a/src/main/java/com/somemore/community/domain/CommunityComment.java b/src/main/java/com/somemore/community/domain/CommunityComment.java
index 42acddc2d..36cb2c792 100644
--- a/src/main/java/com/somemore/community/domain/CommunityComment.java
+++ b/src/main/java/com/somemore/community/domain/CommunityComment.java
@@ -22,6 +22,9 @@ public class CommunityComment extends BaseEntity {
@Column(name = "id", nullable = false)
private Long id;
+ @Column(name = "community_board_id", nullable = false)
+ private Long communityBoardId;
+
@Column(name = "writer_id", nullable = false, length = 16)
private UUID writerId;
@@ -33,7 +36,8 @@ public class CommunityComment extends BaseEntity {
private Long parentCommentId;
@Builder
- public CommunityComment(UUID writerId, String content, Long parentCommentId) {
+ public CommunityComment(Long communityBoardId, UUID writerId, String content, Long parentCommentId) {
+ this.communityBoardId = communityBoardId;
this.writerId = writerId;
this.content = content;
this.parentCommentId = parentCommentId;
diff --git a/src/main/java/com/somemore/community/dto/request/CommunityCommentCreateRequestDto.java b/src/main/java/com/somemore/community/dto/request/CommunityCommentCreateRequestDto.java
index 4366779a0..2c6a5b104 100644
--- a/src/main/java/com/somemore/community/dto/request/CommunityCommentCreateRequestDto.java
+++ b/src/main/java/com/somemore/community/dto/request/CommunityCommentCreateRequestDto.java
@@ -6,6 +6,7 @@
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.annotation.Nullable;
import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
import lombok.Builder;
import java.util.UUID;
@@ -13,15 +14,19 @@
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
@Builder
public record CommunityCommentCreateRequestDto(
+ @Schema(description = "커뮤니티 게시글 ID", example = "33")
+ @NotNull(message = "게시글 ID는 필수 값입니다.")
+ Long communityBoardId,
@Schema(description = "커뮤니티 댓글 내용", example = "저도 함께 하고 싶습니다.")
@NotBlank(message = "댓글 내용은 필수 값입니다.")
String content,
- @Schema(description = "부모 댓글의 ID", example = "1234", nullable = true)
+ @Schema(description = "부모 댓글 ID", example = "1234", nullable = true)
@Nullable
Long parentCommentId
) {
public CommunityComment toEntity(UUID writerId) {
return CommunityComment.builder()
+ .communityBoardId(communityBoardId)
.writerId(writerId)
.content(content)
.parentCommentId(parentCommentId)
diff --git a/src/main/java/com/somemore/community/repository/board/CommunityBoardJpaRepository.java b/src/main/java/com/somemore/community/repository/board/CommunityBoardJpaRepository.java
index 79b03cd1c..00d96d29e 100644
--- a/src/main/java/com/somemore/community/repository/board/CommunityBoardJpaRepository.java
+++ b/src/main/java/com/somemore/community/repository/board/CommunityBoardJpaRepository.java
@@ -4,4 +4,5 @@
import org.springframework.data.jpa.repository.JpaRepository;
public interface CommunityBoardJpaRepository extends JpaRepository {
+ boolean existsByIdAndDeletedFalse(Long id);
}
diff --git a/src/main/java/com/somemore/community/repository/board/CommunityBoardRepository.java b/src/main/java/com/somemore/community/repository/board/CommunityBoardRepository.java
index 3340735a5..44b34f517 100644
--- a/src/main/java/com/somemore/community/repository/board/CommunityBoardRepository.java
+++ b/src/main/java/com/somemore/community/repository/board/CommunityBoardRepository.java
@@ -12,5 +12,9 @@ public interface CommunityBoardRepository {
Optional findById(Long id);
List getCommunityBoards();
List findByWriterId(UUID writerId);
+ boolean existsById(Long id);
+ default boolean doesNotExistById(Long id) {
+ return !existsById(id);
+ }
void deleteAllInBatch();
}
diff --git a/src/main/java/com/somemore/community/repository/board/CommunityBoardRepositoryImpl.java b/src/main/java/com/somemore/community/repository/board/CommunityBoardRepositoryImpl.java
index ad1d79924..72bed7542 100644
--- a/src/main/java/com/somemore/community/repository/board/CommunityBoardRepositoryImpl.java
+++ b/src/main/java/com/somemore/community/repository/board/CommunityBoardRepositoryImpl.java
@@ -52,6 +52,11 @@ public List findByWriterId(UUID writerId) {
.fetch();
}
+ @Override
+ public boolean existsById(Long id) {
+ return communityBoardJpaRepository.existsByIdAndDeletedFalse(id);
+ }
+
private JPAQuery getCommunityBoardsQuery() {
QCommunityBoard communityBoard = QCommunityBoard.communityBoard;
QVolunteer volunteer = QVolunteer.volunteer;
diff --git a/src/main/java/com/somemore/community/repository/comment/CommunityCommentRepository.java b/src/main/java/com/somemore/community/repository/comment/CommunityCommentRepository.java
index 07722e607..c5db8228b 100644
--- a/src/main/java/com/somemore/community/repository/comment/CommunityCommentRepository.java
+++ b/src/main/java/com/somemore/community/repository/comment/CommunityCommentRepository.java
@@ -8,5 +8,8 @@ public interface CommunityCommentRepository {
CommunityComment save(CommunityComment communityComment);
Optional findById(Long id);
boolean existsById(Long id);
+ default boolean doesNotExistById(Long id) {
+ return !existsById(id);
+ }
void deleteAllInBatch();
}
diff --git a/src/main/java/com/somemore/community/service/comment/CreateCommunityCommentService.java b/src/main/java/com/somemore/community/service/comment/CreateCommunityCommentService.java
index e6da18066..ba7a116db 100644
--- a/src/main/java/com/somemore/community/service/comment/CreateCommunityCommentService.java
+++ b/src/main/java/com/somemore/community/service/comment/CreateCommunityCommentService.java
@@ -2,6 +2,7 @@
import com.somemore.community.domain.CommunityComment;
import com.somemore.community.dto.request.CommunityCommentCreateRequestDto;
+import com.somemore.community.repository.board.CommunityBoardRepository;
import com.somemore.community.repository.comment.CommunityCommentRepository;
import com.somemore.community.usecase.comment.CreateCommunityCommentUseCase;
import com.somemore.global.exception.BadRequestException;
@@ -11,6 +12,7 @@
import java.util.UUID;
+import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_COMMUNITY_BOARD;
import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_COMMUNITY_COMMENT;
@RequiredArgsConstructor
@@ -18,19 +20,30 @@
@Service
public class CreateCommunityCommentService implements CreateCommunityCommentUseCase {
+ private final CommunityBoardRepository communityBoardRepository;
private final CommunityCommentRepository communityCommentRepository;
@Override
public Long createCommunityComment(CommunityCommentCreateRequestDto requestDto, UUID writerId) {
CommunityComment communityComment = requestDto.toEntity(writerId);
- validateParentCommentExists(communityComment.getParentCommentId());
+ validateCommunityBoardExists(communityComment.getCommunityBoardId());
+
+ if (requestDto.parentCommentId() != null) {
+ validateParentCommentExists(communityComment.getParentCommentId());
+ }
return communityCommentRepository.save(communityComment).getId();
}
+ private void validateCommunityBoardExists(Long communityBoardId) {
+ if (communityBoardRepository.doesNotExistById(communityBoardId)) {
+ throw new BadRequestException(NOT_EXISTS_COMMUNITY_BOARD.getMessage());
+ }
+ }
+
private void validateParentCommentExists(Long parentCommentId) {
- if (parentCommentId != null && !communityCommentRepository.existsById(parentCommentId)) {
+ if (communityCommentRepository.doesNotExistById(parentCommentId)) {
throw new BadRequestException(NOT_EXISTS_COMMUNITY_COMMENT.getMessage());
}
}
diff --git a/src/main/java/com/somemore/domains/InterestCenter.java b/src/main/java/com/somemore/domains/InterestCenter.java
deleted file mode 100644
index 4c35d5119..000000000
--- a/src/main/java/com/somemore/domains/InterestCenter.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.somemore.domains;
-
-import jakarta.persistence.*;
-import lombok.Getter;
-import lombok.Setter;
-
-import java.util.UUID;
-
-@Getter
-@Setter
-@Entity
-@Table(name = "interest_center")
-public class InterestCenter {
- @Id
- @GeneratedValue(strategy = GenerationType.UUID)
- private UUID id;
-
-}
\ No newline at end of file
diff --git a/src/main/java/com/somemore/global/common/response/ApiResponse.java b/src/main/java/com/somemore/global/common/response/ApiResponse.java
index ebdd60c3a..6b00a6c07 100644
--- a/src/main/java/com/somemore/global/common/response/ApiResponse.java
+++ b/src/main/java/com/somemore/global/common/response/ApiResponse.java
@@ -15,14 +15,10 @@ public static ApiResponse ok(int status, T data, String message) {
return new ApiResponse<>(status, message, data);
}
- public static ApiResponse> ok(String message) {
+ 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 ApiResponse(int code, String message, T data) {
this.code = code;
this.message = message;
diff --git a/src/main/java/com/somemore/global/configure/SwaggerConfig.java b/src/main/java/com/somemore/global/configure/SwaggerConfig.java
new file mode 100644
index 000000000..0ea82150a
--- /dev/null
+++ b/src/main/java/com/somemore/global/configure/SwaggerConfig.java
@@ -0,0 +1,36 @@
+package com.somemore.global.configure;
+
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.security.SecurityRequirement;
+import io.swagger.v3.oas.models.security.SecurityScheme;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class SwaggerConfig {
+
+ @Bean
+ public OpenAPI customOpenAPI() {
+ SecurityScheme securityScheme = new SecurityScheme()
+ .type(SecurityScheme.Type.HTTP)
+ .scheme("bearer")
+ .bearerFormat("JWT")
+ .description("JWT 토큰을 이용한 인증");
+
+ SecurityRequirement securityRequirement = new SecurityRequirement()
+ .addList("AccessToken");
+
+ return new OpenAPI()
+ .info(new Info()
+ .title("Somemore API")
+ .version("1.0")
+ .description("Somemore swagger-ui 화면입니다.")
+ )
+ .components(new Components()
+ .addSecuritySchemes("AccessToken", securityScheme)
+ )
+ .addSecurityItem(securityRequirement);
+ }
+}
diff --git a/src/main/java/com/somemore/global/exception/DuplicateException.java b/src/main/java/com/somemore/global/exception/DuplicateException.java
new file mode 100644
index 000000000..0a752c189
--- /dev/null
+++ b/src/main/java/com/somemore/global/exception/DuplicateException.java
@@ -0,0 +1,8 @@
+package com.somemore.global.exception;
+
+public class DuplicateException extends RuntimeException{
+
+ public DuplicateException(final String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/com/somemore/global/exception/ExceptionMessage.java b/src/main/java/com/somemore/global/exception/ExceptionMessage.java
index 43a1be574..1aa5d8fdd 100644
--- a/src/main/java/com/somemore/global/exception/ExceptionMessage.java
+++ b/src/main/java/com/somemore/global/exception/ExceptionMessage.java
@@ -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;
diff --git a/src/main/java/com/somemore/global/handler/GlobalExceptionHandler.java b/src/main/java/com/somemore/global/handler/GlobalExceptionHandler.java
index 3f6cc0736..48fb90dfc 100644
--- a/src/main/java/com/somemore/global/handler/GlobalExceptionHandler.java
+++ b/src/main/java/com/somemore/global/handler/GlobalExceptionHandler.java
@@ -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 {
@@ -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;
+ }
+
}
diff --git a/src/main/java/com/somemore/interestcenter/controller/InterestCenterCommandApiController.java b/src/main/java/com/somemore/interestcenter/controller/InterestCenterCommandApiController.java
new file mode 100644
index 000000000..bcd3df349
--- /dev/null
+++ b/src/main/java/com/somemore/interestcenter/controller/InterestCenterCommandApiController.java
@@ -0,0 +1,40 @@
+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
+@Tag(name = "Interest Center Command API", description = "관심 기관의 등록과 취소 API를 제공합니다")
+public class InterestCenterCommandApiController {
+
+ private final RegisterInterestCenterUseCase registerInterestCenterUseCase;
+ private final CancelInterestCenterUseCase cancelInterestCenterUseCase;
+
+ @Operation(summary = "관심기관 등록 API")
+ @PostMapping("/api/interest-center")
+ public ApiResponse registerInterestCenter(@Valid @RequestBody RegisterInterestCenterRequestDto requestDto) {
+
+ RegisterInterestCenterResponseDto responseDto = registerInterestCenterUseCase.registerInterestCenter(requestDto);
+
+ return ApiResponse.ok(200, responseDto, "관심 기관 등록 성공");
+ }
+
+ @Operation(summary = "관심기관 취소 API")
+ @DeleteMapping("/api/interest-center/{interest-center-id}")
+ public ApiResponse deleteInterestCenter(@PathVariable("interest-center-id") Long interestCenterId) {
+
+ cancelInterestCenterUseCase.cancelInterestCenter(interestCenterId);
+
+ return ApiResponse.ok("관심 기관 취소 성공");
+ }
+
+}
diff --git a/src/main/java/com/somemore/interestcenter/domain/InterestCenter.java b/src/main/java/com/somemore/interestcenter/domain/InterestCenter.java
new file mode 100644
index 000000000..df1c045c7
--- /dev/null
+++ b/src/main/java/com/somemore/interestcenter/domain/InterestCenter.java
@@ -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();
+ }
+}
diff --git a/src/main/java/com/somemore/interestcenter/dto/request/RegisterInterestCenterRequestDto.java b/src/main/java/com/somemore/interestcenter/dto/request/RegisterInterestCenterRequestDto.java
new file mode 100644
index 000000000..beb917c84
--- /dev/null
+++ b/src/main/java/com/somemore/interestcenter/dto/request/RegisterInterestCenterRequestDto.java
@@ -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는 필수값입니다.")
+ UUID centerId
+) {
+ public InterestCenter toEntity(){
+ return InterestCenter.create(volunteerId, centerId);
+ }
+}
diff --git a/src/main/java/com/somemore/interestcenter/dto/response/RegisterInterestCenterResponseDto.java b/src/main/java/com/somemore/interestcenter/dto/response/RegisterInterestCenterResponseDto.java
new file mode 100644
index 000000000..4a20fa957
--- /dev/null
+++ b/src/main/java/com/somemore/interestcenter/dto/response/RegisterInterestCenterResponseDto.java
@@ -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();
+ }
+}
diff --git a/src/main/java/com/somemore/interestcenter/repository/InterestCenterJpaRepository.java b/src/main/java/com/somemore/interestcenter/repository/InterestCenterJpaRepository.java
new file mode 100644
index 000000000..34b9a37e1
--- /dev/null
+++ b/src/main/java/com/somemore/interestcenter/repository/InterestCenterJpaRepository.java
@@ -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 {
+}
diff --git a/src/main/java/com/somemore/interestcenter/repository/InterestCenterRepository.java b/src/main/java/com/somemore/interestcenter/repository/InterestCenterRepository.java
new file mode 100644
index 000000000..14b07b2d2
--- /dev/null
+++ b/src/main/java/com/somemore/interestcenter/repository/InterestCenterRepository.java
@@ -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 findById(Long id);
+ Optional findInterestCenterResponseById(Long id);
+ boolean existsByVolunteerIdAndCenterId(UUID volunteerId, UUID centerId);
+}
diff --git a/src/main/java/com/somemore/interestcenter/repository/InterestCenterRepositoryImpl.java b/src/main/java/com/somemore/interestcenter/repository/InterestCenterRepositoryImpl.java
new file mode 100644
index 000000000..5667967b8
--- /dev/null
+++ b/src/main/java/com/somemore/interestcenter/repository/InterestCenterRepositoryImpl.java
@@ -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 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 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;
+ }
+
+}
diff --git a/src/main/java/com/somemore/interestcenter/service/CancelInterestCenterService.java b/src/main/java/com/somemore/interestcenter/service/CancelInterestCenterService.java
new file mode 100644
index 000000000..a3c72145c
--- /dev/null
+++ b/src/main/java/com/somemore/interestcenter/service/CancelInterestCenterService.java
@@ -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);
+ }
+
+}
diff --git a/src/main/java/com/somemore/interestcenter/service/RegisterInterestCenterService.java b/src/main/java/com/somemore/interestcenter/service/RegisterInterestCenterService.java
new file mode 100644
index 000000000..c9fb8ce3d
--- /dev/null
+++ b/src/main/java/com/somemore/interestcenter/service/RegisterInterestCenterService.java
@@ -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);
+ }
+
+}
diff --git a/src/main/java/com/somemore/interestcenter/usecase/CancelInterestCenterUseCase.java b/src/main/java/com/somemore/interestcenter/usecase/CancelInterestCenterUseCase.java
new file mode 100644
index 000000000..53515a1cd
--- /dev/null
+++ b/src/main/java/com/somemore/interestcenter/usecase/CancelInterestCenterUseCase.java
@@ -0,0 +1,5 @@
+package com.somemore.interestcenter.usecase;
+
+public interface CancelInterestCenterUseCase {
+ void cancelInterestCenter(Long interestCenterId);
+}
diff --git a/src/main/java/com/somemore/interestcenter/usecase/RegisterInterestCenterUseCase.java b/src/main/java/com/somemore/interestcenter/usecase/RegisterInterestCenterUseCase.java
new file mode 100644
index 000000000..f6ce68e7a
--- /dev/null
+++ b/src/main/java/com/somemore/interestcenter/usecase/RegisterInterestCenterUseCase.java
@@ -0,0 +1,8 @@
+package com.somemore.interestcenter.usecase;
+
+import com.somemore.interestcenter.dto.request.RegisterInterestCenterRequestDto;
+import com.somemore.interestcenter.dto.response.RegisterInterestCenterResponseDto;
+
+public interface RegisterInterestCenterUseCase {
+ RegisterInterestCenterResponseDto registerInterestCenter(RegisterInterestCenterRequestDto requestDto);
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 6621c5d18..cdbad3892 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -1,6 +1,8 @@
app:
front-url: ${FRONT_URL}
back-url: ${BACK_URL}
+ develop:
+ mode: ${APP_DEVELOP_MODE}
# AWS S3
cloud:
diff --git a/src/test/java/com/somemore/community/repository/CommunityBoardRepositoryTest.java b/src/test/java/com/somemore/community/repository/CommunityBoardRepositoryTest.java
index 2f6fdbd52..acb29075f 100644
--- a/src/test/java/com/somemore/community/repository/CommunityBoardRepositoryTest.java
+++ b/src/test/java/com/somemore/community/repository/CommunityBoardRepositoryTest.java
@@ -158,4 +158,27 @@ void getCommunityBoardsByWriterId() {
assertThat(communityBoards.getLast().writerNickname()).isEqualTo(volunteer.getNickname());
assertThat(communityBoards.getLast().communityBoard().getCreatedAt()).isEqualTo(communityBoard1.getCreatedAt());
}
+
+ @DisplayName("게시글 id로 게시글이 존재하는지 확인할 수 있다.")
+ @Test
+ void existsById() {
+
+ //given
+ UUID writerId = UUID.randomUUID();
+
+ CommunityBoard communityBoard = CommunityBoard.builder()
+ .title("테스트 커뮤니티 게시글 제목")
+ .content("테스트 커뮤니티 게시글 내용")
+ .imgUrl("http://community.example.com/123")
+ .writerId(writerId)
+ .build();
+
+ CommunityBoard savedComment = communityBoardRepository.save(communityBoard);
+
+ //when
+ boolean isExist = communityBoardRepository.existsById(savedComment.getId());
+
+ //then
+ assertThat(isExist).isTrue();
+ }
}
diff --git a/src/test/java/com/somemore/community/repository/CommunityCommentRepositoryTest.java b/src/test/java/com/somemore/community/repository/CommunityCommentRepositoryTest.java
index e3ba059d5..8903033ce 100644
--- a/src/test/java/com/somemore/community/repository/CommunityCommentRepositoryTest.java
+++ b/src/test/java/com/somemore/community/repository/CommunityCommentRepositoryTest.java
@@ -1,8 +1,11 @@
package com.somemore.community.repository;
import com.somemore.IntegrationTestSupport;
+import com.somemore.community.domain.CommunityBoard;
import com.somemore.community.domain.CommunityComment;
+import com.somemore.community.repository.board.CommunityBoardRepository;
import com.somemore.community.repository.comment.CommunityCommentRepository;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
@@ -15,25 +18,47 @@
@Transactional
class CommunityCommentRepositoryTest extends IntegrationTestSupport {
+
@Autowired
CommunityCommentRepository communityCommentRepository;
+ @Autowired
+ CommunityBoardRepository communityBoardRepository;
+
+ private Long boardId;
+ private UUID writerId;
+ private CommunityComment savedComment;
+
+ @BeforeEach
+ void setUp() {
+ CommunityBoard communityBoard = CommunityBoard.builder()
+ .title("테스트 커뮤니티 게시글 제목")
+ .content("테스트 커뮤니티 게시글 내용")
+ .imgUrl("http://community.example.com/123")
+ .writerId(UUID.randomUUID())
+ .build();
- @DisplayName("커뮤니티 게시글에 댓글을 생성할 수 있다. (Repository)")
- @Test
- void createCommunityComment() {
+ communityBoardRepository.save(communityBoard);
- //given
- UUID writerId = UUID.randomUUID();
+ boardId = communityBoard.getId();
+
+ writerId = UUID.randomUUID();
CommunityComment communityComment = CommunityComment.builder()
+ .communityBoardId(boardId)
.writerId(writerId)
.content("커뮤니티 댓글 테스트 내용")
.parentCommentId(null)
.build();
- //when
- CommunityComment savedComment = communityCommentRepository.save(communityComment);
+ savedComment = communityCommentRepository.save(communityComment);
+ }
+
+ @DisplayName("커뮤니티 게시글에 댓글을 생성할 수 있다. (Repository)")
+ @Test
+ void createCommunityComment() {
+ //given
+ //when
//then
assertThat(savedComment.getWriterId()).isEqualTo(writerId);
assertThat(savedComment.getContent()).isEqualTo("커뮤니티 댓글 테스트 내용");
@@ -45,21 +70,20 @@ void createCommunityComment() {
void createCommunityCommentReply() {
//given
- UUID writerId = UUID.randomUUID();
-
- CommunityComment communityComment = CommunityComment.builder()
+ CommunityComment communityCommentReply = CommunityComment.builder()
+ .communityBoardId(boardId)
.writerId(writerId)
.content("커뮤니티 댓글 테스트 내용")
.parentCommentId(1L)
.build();
//when
- CommunityComment savedComment = communityCommentRepository.save(communityComment);
+ CommunityComment savedCommentReply = communityCommentRepository.save(communityCommentReply);
//then
- assertThat(savedComment.getWriterId()).isEqualTo(writerId);
- assertThat(savedComment.getContent()).isEqualTo("커뮤니티 댓글 테스트 내용");
- assertThat(savedComment.getParentCommentId()).isEqualTo(1L);
+ assertThat(savedCommentReply.getWriterId()).isEqualTo(writerId);
+ assertThat(savedCommentReply.getContent()).isEqualTo("커뮤니티 댓글 테스트 내용");
+ assertThat(savedCommentReply.getParentCommentId()).isEqualTo(1L);
}
@DisplayName("댓글을 id로 조회할 수 있다. (Repository)")
@@ -67,16 +91,6 @@ void createCommunityCommentReply() {
void findCommunityCommentById() {
//given
- UUID writerId = UUID.randomUUID();
-
- CommunityComment communityComment = CommunityComment.builder()
- .writerId(writerId)
- .content("커뮤니티 댓글 테스트 내용")
- .parentCommentId(null)
- .build();
-
- CommunityComment savedComment = communityCommentRepository.save(communityComment);
-
//when
Optional comment = communityCommentRepository.findById(savedComment.getId());
@@ -92,16 +106,6 @@ void findCommunityCommentById() {
void existsById() {
//given
- UUID writerId = UUID.randomUUID();
-
- CommunityComment communityComment = CommunityComment.builder()
- .writerId(writerId)
- .content("커뮤니티 댓글 테스트 내용")
- .parentCommentId(null)
- .build();
-
- CommunityComment savedComment = communityCommentRepository.save(communityComment);
-
//when
boolean isExist = communityCommentRepository.existsById(savedComment.getId());
diff --git a/src/test/java/com/somemore/community/service/comment/CreateCommunityCommentServiceTest.java b/src/test/java/com/somemore/community/service/comment/CreateCommunityCommentServiceTest.java
index bc955770c..b65a2c2c5 100644
--- a/src/test/java/com/somemore/community/service/comment/CreateCommunityCommentServiceTest.java
+++ b/src/test/java/com/somemore/community/service/comment/CreateCommunityCommentServiceTest.java
@@ -1,13 +1,16 @@
package com.somemore.community.service.comment;
import com.somemore.IntegrationTestSupport;
+import com.somemore.community.domain.CommunityBoard;
import com.somemore.community.domain.CommunityComment;
+import com.somemore.community.dto.request.CommunityBoardCreateRequestDto;
import com.somemore.community.dto.request.CommunityCommentCreateRequestDto;
+import com.somemore.community.repository.board.CommunityBoardRepository;
import com.somemore.community.repository.comment.CommunityCommentRepository;
import com.somemore.global.exception.BadRequestException;
-import com.somemore.global.exception.ExceptionMessage;
import org.assertj.core.api.ThrowableAssert;
import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
@@ -15,6 +18,8 @@
import java.util.Optional;
import java.util.UUID;
+import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_COMMUNITY_BOARD;
+import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_COMMUNITY_COMMENT;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -24,6 +29,24 @@ class CreateCommunityCommentServiceTest extends IntegrationTestSupport {
private CreateCommunityCommentService createCommunityCommentService;
@Autowired
private CommunityCommentRepository communityCommentRepository;
+ @Autowired
+ private CommunityBoardRepository communityBoardRepository;
+
+ private UUID writerId;
+ private Long boardId;
+
+ @BeforeEach
+ void setUp() {
+ CommunityBoardCreateRequestDto boardDto = CommunityBoardCreateRequestDto.builder()
+ .title("커뮤니티 테스트 제목")
+ .content("커뮤니티 테스트 내용")
+ .build();
+
+ writerId = UUID.randomUUID();
+
+ CommunityBoard communityBoard = communityBoardRepository.save(boardDto.toEntity(writerId, "https://test.image/123"));
+ boardId = communityBoard.getId();
+ }
@AfterEach
void tearDown() {
@@ -36,12 +59,11 @@ void createCommunityCommentWithDto() {
//given
CommunityCommentCreateRequestDto dto = CommunityCommentCreateRequestDto.builder()
+ .communityBoardId(boardId)
.content("커뮤니티 댓글 테스트 내용")
.parentCommentId(null)
.build();
- UUID writerId = UUID.randomUUID();
-
//when
Long commentId = createCommunityCommentService.createCommunityComment(dto, writerId);
@@ -61,14 +83,15 @@ void createCommunityCommentRelyWithDto() {
//given
CommunityCommentCreateRequestDto commentDto = CommunityCommentCreateRequestDto.builder()
+ .communityBoardId(boardId)
.content("커뮤니티 댓글 테스트 내용")
.parentCommentId(null)
.build();
- UUID writerId = UUID.randomUUID();
Long commentId = createCommunityCommentService.createCommunityComment(commentDto, writerId);
CommunityCommentCreateRequestDto replyDto = CommunityCommentCreateRequestDto.builder()
+ .communityBoardId(boardId)
.content("커뮤니티 대댓글 테스트 내용")
.parentCommentId(commentId)
.build();
@@ -86,12 +109,13 @@ void createCommunityCommentRelyWithDto() {
assertThat(communityCommentReply.get().getParentCommentId()).isEqualTo(commentId);
}
- @DisplayName("삭제된 댓글에 대댓글을 등록할 때 예외를 던진다.")
+ @DisplayName("삭제된 댓글이나 존재하지 않는 댓글에 대댓글을 등록할 때 예외를 던진다.")
@Test
void createCommunityCommentReplyWithDeletedParentId() {
//given
CommunityCommentCreateRequestDto replyDto = CommunityCommentCreateRequestDto.builder()
+ .communityBoardId(boardId)
.content("커뮤니티 대댓글 테스트 내용")
.parentCommentId(2L)
.build();
@@ -102,6 +126,28 @@ void createCommunityCommentReplyWithDeletedParentId() {
//then
assertThatExceptionOfType(BadRequestException.class)
.isThrownBy(callable)
- .withMessage(ExceptionMessage.NOT_EXISTS_COMMUNITY_COMMENT.getMessage());
+ .withMessage(NOT_EXISTS_COMMUNITY_COMMENT.getMessage());
+ }
+
+ @DisplayName("삭제된 게시글에 댓글을 등록할 때 예외를 던진다.")
+ @Test
+ void createCommunityCommentWithDeletedBoardId() {
+
+ //given
+ CommunityCommentCreateRequestDto commentDto = CommunityCommentCreateRequestDto.builder()
+ .communityBoardId(boardId)
+ .content("커뮤니티 댓글 테스트 내용")
+ .parentCommentId(null)
+ .build();
+
+ communityBoardRepository.deleteAllInBatch();
+
+ //when
+ ThrowableAssert.ThrowingCallable callable = () -> createCommunityCommentService.createCommunityComment(commentDto, UUID.randomUUID());
+
+ //then
+ assertThatExceptionOfType(BadRequestException.class)
+ .isThrownBy(callable)
+ .withMessage(NOT_EXISTS_COMMUNITY_BOARD.getMessage());
}
}
diff --git a/src/test/java/com/somemore/community/service/comment/DeleteCommunityCommentServiceTest.java b/src/test/java/com/somemore/community/service/comment/DeleteCommunityCommentServiceTest.java
index 0b5df5d2c..2e6db5020 100644
--- a/src/test/java/com/somemore/community/service/comment/DeleteCommunityCommentServiceTest.java
+++ b/src/test/java/com/somemore/community/service/comment/DeleteCommunityCommentServiceTest.java
@@ -1,11 +1,13 @@
package com.somemore.community.service.comment;
import com.somemore.IntegrationTestSupport;
+import com.somemore.community.domain.CommunityBoard;
+import com.somemore.community.domain.CommunityComment;
+import com.somemore.community.dto.request.CommunityBoardCreateRequestDto;
import com.somemore.community.dto.request.CommunityCommentCreateRequestDto;
+import com.somemore.community.repository.board.CommunityBoardRepository;
import com.somemore.community.repository.comment.CommunityCommentRepository;
-import com.somemore.community.usecase.comment.CreateCommunityCommentUseCase;
import com.somemore.global.exception.BadRequestException;
-import com.somemore.global.exception.ExceptionMessage;
import org.assertj.core.api.ThrowableAssert;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@@ -15,6 +17,8 @@
import java.util.UUID;
+import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_COMMUNITY_COMMENT;
+import static com.somemore.global.exception.ExceptionMessage.UNAUTHORIZED_COMMUNITY_COMMENT;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -23,24 +27,33 @@ class DeleteCommunityCommentServiceTest extends IntegrationTestSupport {
@Autowired
private DeleteCommunityCommentService deleteCommunityCommentService;
@Autowired
- private CreateCommunityCommentUseCase createCommunityCommentUseCase;
- @Autowired
private CommunityCommentRepository communityCommentRepository;
+ @Autowired
+ private CommunityBoardRepository communityBoardRepository;
private UUID writerId;
private Long commentId;
@BeforeEach
void setUp() {
+ CommunityBoardCreateRequestDto boardDto = CommunityBoardCreateRequestDto.builder()
+ .title("커뮤니티 테스트 제목")
+ .content("커뮤니티 테스트 내용")
+ .build();
+
+ writerId = UUID.randomUUID();
+
+ CommunityBoard communityBoard = communityBoardRepository.save(boardDto.toEntity(writerId, "https://test.image/123"));
+
CommunityCommentCreateRequestDto dto = CommunityCommentCreateRequestDto.builder()
+ .communityBoardId(communityBoard.getId())
.content("커뮤니티 댓글 테스트 내용")
.parentCommentId(null)
.build();
- writerId = UUID.randomUUID();
+ CommunityComment communityComment = communityCommentRepository.save(dto.toEntity(writerId));
- commentId = createCommunityCommentUseCase.createCommunityComment(dto, writerId);
- }
+ commentId = communityComment.getId();}
@AfterEach
void tearDown() {
@@ -50,6 +63,7 @@ void tearDown() {
@DisplayName("댓글 id로 댓글을 삭제한다.")
@Test
void deleteCommunityCommentWithId() {
+
//given
//when
deleteCommunityCommentService.deleteCommunityComment(writerId, commentId);
@@ -61,6 +75,7 @@ void deleteCommunityCommentWithId() {
@DisplayName("삭제된 댓글의 id로 댓글을 삭제할 때 예외를 던진다.")
@Test
void deleteCommunityCommentWithDeletedId() {
+
//given
deleteCommunityCommentService.deleteCommunityComment(writerId, commentId);
@@ -70,12 +85,13 @@ void deleteCommunityCommentWithDeletedId() {
//then
assertThatExceptionOfType(BadRequestException.class)
.isThrownBy(callable)
- .withMessage(ExceptionMessage.NOT_EXISTS_COMMUNITY_COMMENT.getMessage());
+ .withMessage(NOT_EXISTS_COMMUNITY_COMMENT.getMessage());
}
@DisplayName("작성자가 아닌 id로 댓글을 삭제하고자 할 때 예외를 던진다.")
@Test
void deleteCommunityCommentWithNotWriterId() {
+
//given
//when
ThrowableAssert.ThrowingCallable callable = () -> deleteCommunityCommentService.deleteCommunityComment(UUID.randomUUID(), commentId);
@@ -83,6 +99,6 @@ void deleteCommunityCommentWithNotWriterId() {
//then
assertThatExceptionOfType(BadRequestException.class)
.isThrownBy(callable)
- .withMessage(ExceptionMessage.UNAUTHORIZED_COMMUNITY_COMMENT.getMessage());
+ .withMessage(UNAUTHORIZED_COMMUNITY_COMMENT.getMessage());
}
}
diff --git a/src/test/java/com/somemore/interestcenter/controller/InterestCenterCommandApiControllerTest.java b/src/test/java/com/somemore/interestcenter/controller/InterestCenterCommandApiControllerTest.java
new file mode 100644
index 000000000..a49476b02
--- /dev/null
+++ b/src/test/java/com/somemore/interestcenter/controller/InterestCenterCommandApiControllerTest.java
@@ -0,0 +1,68 @@
+package com.somemore.interestcenter.controller;
+
+import com.somemore.ControllerTestSupport;
+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 org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.http.MediaType;
+import org.springframework.boot.test.mock.mockito.MockBean;
+
+import java.util.UUID;
+
+import static org.mockito.BDDMockito.given;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+class InterestCenterCommandApiControllerTest extends ControllerTestSupport {
+
+ @MockBean
+ private RegisterInterestCenterUseCase registerInterestCenterUseCase;
+
+ @MockBean
+ private CancelInterestCenterUseCase cancelInterestCenterUseCase;
+
+ private RegisterInterestCenterRequestDto requestDto;
+
+ @BeforeEach
+ void setUp() {
+ requestDto = new RegisterInterestCenterRequestDto(
+ UUID.fromString("123e4567-e89b-12d3-a456-426614174000"),
+ UUID.fromString("123e4567-e89b-12d3-a456-426614174000")
+ );
+ }
+
+ @Test
+ void registerInterestCenter_ShouldReturnSuccess() throws Exception {
+ // given
+ RegisterInterestCenterResponseDto responseDto = new RegisterInterestCenterResponseDto(1L, requestDto.volunteerId(), requestDto.centerId());
+ given(registerInterestCenterUseCase.registerInterestCenter(any(RegisterInterestCenterRequestDto.class)))
+ .willReturn(responseDto);
+
+ // when & then
+ mockMvc.perform(post("/api/interest-center")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(requestDto)))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.message").value("관심 기관 등록 성공"))
+ .andExpect(jsonPath("$.data.id").value(responseDto.id()))
+ .andExpect(jsonPath("$.data.volunteer_id").value(responseDto.volunteerId().toString()))
+ .andExpect(jsonPath("$.data.center_id").value(responseDto.centerId().toString()));
+ }
+
+ @Test
+ void deleteInterestCenter_ShouldReturnSuccess() throws Exception {
+ // given
+ Long interestCenterId = 1L;
+ doNothing().when(cancelInterestCenterUseCase).cancelInterestCenter(interestCenterId);
+
+ // when & then
+ mockMvc.perform(delete("/api/interest-center/{interest-center-id}", interestCenterId))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.message").value("관심 기관 취소 성공"));
+ }
+}
diff --git a/src/test/java/com/somemore/interestcenter/service/CancelInterestCenterServiceTest.java b/src/test/java/com/somemore/interestcenter/service/CancelInterestCenterServiceTest.java
new file mode 100644
index 000000000..e89946ffc
--- /dev/null
+++ b/src/test/java/com/somemore/interestcenter/service/CancelInterestCenterServiceTest.java
@@ -0,0 +1,98 @@
+package com.somemore.interestcenter.service;
+
+import com.somemore.IntegrationTestSupport;
+import com.somemore.center.domain.Center;
+import com.somemore.center.repository.CenterRepository;
+import com.somemore.global.exception.BadRequestException;
+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.CancelInterestCenterUseCase;
+import com.somemore.interestcenter.usecase.RegisterInterestCenterUseCase;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Optional;
+import java.util.UUID;
+
+import static com.somemore.global.exception.ExceptionMessage.CANNOT_CANCEL_DELETED_INTEREST_CENTER;
+import static org.junit.jupiter.api.Assertions.*;
+
+@Transactional
+class CancelInterestCenterServiceTest extends IntegrationTestSupport {
+
+ @Autowired
+ private CancelInterestCenterUseCase cancelInterestCenterUseCase;
+
+ @Autowired
+ private RegisterInterestCenterUseCase registerInterestCenterUseCase;
+
+ @Autowired
+ private InterestCenterRepository interestCenterRepository;
+
+ @Autowired
+ private CenterRepository centerRepository;
+
+ @DisplayName("봉사자는 기관에 대한 관심 표시를 취소할 수 있다.")
+ @Test
+ void CancelInterestCenter() {
+ //given
+ Center center = createCenter();
+ UUID volunteerId = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
+ UUID centerId = center.getId();
+ RegisterInterestCenterRequestDto requestDto = new RegisterInterestCenterRequestDto(volunteerId, centerId);
+
+ RegisterInterestCenterResponseDto responseDto = registerInterestCenterUseCase.registerInterestCenter(requestDto);
+
+ InterestCenter savedInterestCenter = interestCenterRepository.findById(responseDto.id())
+ .orElseThrow(() -> new IllegalStateException("등록된 관심 기관이 없습니다."));
+ assertEquals(savedInterestCenter.getId(), responseDto.id());
+
+ //when
+ cancelInterestCenterUseCase.cancelInterestCenter(responseDto.id());
+
+ //then
+ Optional deletedInterestCenterOptional = interestCenterRepository.findById(responseDto.id());
+ assertTrue(deletedInterestCenterOptional.isEmpty());
+
+ }
+
+ @DisplayName("이미 삭제된 관심 기관을 다시 취소하려 하면 예외가 발생한다.")
+ @Test
+ void cancelInterestCenter_AlreadyDeleted_ShouldThrowException() {
+ //given
+ Center center = createCenter();
+ UUID volunteerId = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
+ UUID centerId = center.getId();
+ RegisterInterestCenterRequestDto requestDto = new RegisterInterestCenterRequestDto(volunteerId, centerId);
+ RegisterInterestCenterResponseDto responseDto = registerInterestCenterUseCase.registerInterestCenter(requestDto);
+ cancelInterestCenterUseCase.cancelInterestCenter(responseDto.id());
+
+ //when, then
+ long interestCenterId = responseDto.id();
+ assertThrows(BadRequestException.class,
+ () -> cancelInterestCenterUseCase.cancelInterestCenter(interestCenterId),
+ CANNOT_CANCEL_DELETED_INTEREST_CENTER.getMessage()
+ );
+ }
+
+ private Center createCenter() {
+ Center center = Center.create(
+ "기본 기관 이름",
+ "010-1234-5678",
+ "http://example.com/image.jpg",
+ "기관 소개 내용",
+ "http://example.com",
+ "account123",
+ "password123"
+ );
+
+ centerRepository.save(center);
+
+ return center;
+ }
+
+}
diff --git a/src/test/java/com/somemore/interestcenter/service/RegisterInterestCenterServiceTest.java b/src/test/java/com/somemore/interestcenter/service/RegisterInterestCenterServiceTest.java
new file mode 100644
index 000000000..a7e7bbeee
--- /dev/null
+++ b/src/test/java/com/somemore/interestcenter/service/RegisterInterestCenterServiceTest.java
@@ -0,0 +1,109 @@
+package com.somemore.interestcenter.service;
+
+import com.somemore.IntegrationTestSupport;
+import com.somemore.center.domain.Center;
+import com.somemore.center.repository.CenterRepository;
+import com.somemore.global.exception.BadRequestException;
+import com.somemore.global.exception.DuplicateException;
+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 org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Optional;
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@Transactional
+class RegisterInterestCenterServiceTest extends IntegrationTestSupport {
+
+ @Autowired
+ private RegisterInterestCenterUseCase registerInterestCenter;
+
+ @Autowired
+ private InterestCenterRepository interestCenterRepository;
+
+ @Autowired
+ private CenterRepository centerRepository;
+
+ @DisplayName("봉사자는 관심 기관을 등록할 수 있다.")
+ @Test
+ void RegisterInterestCenter() {
+ //given
+ Center center = createCenter();
+ UUID volunteerId = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
+ UUID centerId = center.getId();
+ RegisterInterestCenterRequestDto requestDto = new RegisterInterestCenterRequestDto(volunteerId, centerId);
+
+ //when
+ RegisterInterestCenterResponseDto responseDto = registerInterestCenter.registerInterestCenter(requestDto);
+
+ //then
+ Optional result = interestCenterRepository.findInterestCenterResponseById(responseDto.id());
+ assertTrue(result.isPresent());
+ assertEquals(responseDto.id(), result.get().id());
+ assertEquals(volunteerId, result.get().volunteerId());
+ assertEquals(centerId, result.get().centerId());
+ }
+
+ @DisplayName("이미 관심 표시한 기관에 관심 표시를 시도하면 예외를 던져준다.")
+ @Test
+ void registerInterestCenter_WithDuplicateCenterId_ShouldThrowException() {
+ // given
+ Center center = createCenter();
+ UUID volunteerId = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
+ UUID centerId = center.getId();
+ RegisterInterestCenterRequestDto requestDto = new RegisterInterestCenterRequestDto(volunteerId, centerId);
+
+ registerInterestCenter.registerInterestCenter(requestDto);
+
+ // when
+ DuplicateException exception = assertThrows(
+ DuplicateException.class,
+ () -> registerInterestCenter.registerInterestCenter(requestDto)
+ );
+
+ // then
+ assertEquals("이미 관심 표시한 기관입니다.", exception.getMessage());
+ }
+
+
+ @DisplayName("존재하지 않는 기관 Id로 관심 기관 등록 시 예외가 발생한다.")
+ @Test
+ void registerInterestCenter_WithInvalidCenterId_ShouldThrowException() {
+ // given
+ UUID volunteerId = UUID.fromString("123e4567-e89b-12d3-a456-426614174000");
+ UUID invalidCenterId = UUID.fromString("123e4567-e89b-12d3-a456-426614174001");
+ RegisterInterestCenterRequestDto requestDto = new RegisterInterestCenterRequestDto(volunteerId, invalidCenterId);
+
+ // when
+ BadRequestException exception = assertThrows(BadRequestException.class, () -> {
+ registerInterestCenter.registerInterestCenter(requestDto);
+ });
+
+ //then
+ assertEquals("존재하지 않는 기관 입니다.", exception.getMessage());
+ }
+
+ private Center createCenter() {
+ Center center = Center.create(
+ "기본 기관 이름",
+ "010-1234-5678",
+ "http://example.com/image.jpg",
+ "기관 소개 내용",
+ "http://example.com",
+ "account123",
+ "password123"
+ );
+
+ centerRepository.save(center);
+
+ return center;
+ }
+
+}
diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml
index 50d9922c6..d0d564d75 100644
--- a/src/test/resources/application-test.yml
+++ b/src/test/resources/application-test.yml
@@ -1,6 +1,8 @@
app:
front-url: "http://localhost:3000"
back-url: "http://localhost:8080"
+ develop:
+ mode: false
spring:
config: