From 3ce274848d5ce1ff6d1cda4162e65f0ce5d83a5a Mon Sep 17 00:00:00 2001 From: KyungMin Lee Date: Fri, 23 Feb 2024 22:47:28 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[CHORE]=20websocket,=20mongoDB=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=20=EC=B6=94=EA=B0=80=20(#164)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FIX] reissue 오류 수정 * [CHORE] build.gradle Spring Boot MongoDB 추가 --------- Co-authored-by: happyjamy <78072370+happyjamy@users.noreply.github.com> Co-authored-by: JIN-076 <57834671+JIN-076@users.noreply.github.com> --- build.gradle | 4 ++++ .../mogakgo/domain/auth/presentation/AuthController.java | 1 - .../auth/presentation/dto/req/AuthReissueRequest.java | 6 +++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index eff133bc..d4abb293 100644 --- a/build.gradle +++ b/build.gradle @@ -52,6 +52,10 @@ dependencies { implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' // Spring Boot WebFlux implementation 'org.springframework.boot:spring-boot-starter-webflux:3.1.8' + // Spring Boot Data MongoDB + implementation 'org.springframework.boot:spring-boot-starter-data-mongodb:3.1.8' + // Spring Boot WebSocket + implementation 'org.springframework.boot:spring-boot-starter-websocket:3.1.8' // SpringBoot Test testImplementation 'org.springframework.boot:spring-boot-starter-test:3.1.8' diff --git a/src/main/java/io/oeid/mogakgo/domain/auth/presentation/AuthController.java b/src/main/java/io/oeid/mogakgo/domain/auth/presentation/AuthController.java index ed574b0c..244d2768 100644 --- a/src/main/java/io/oeid/mogakgo/domain/auth/presentation/AuthController.java +++ b/src/main/java/io/oeid/mogakgo/domain/auth/presentation/AuthController.java @@ -22,7 +22,6 @@ public class AuthController implements AuthSwagger { @PostMapping("/reissue") public ResponseEntity reissue( @RequestHeader("Authorization") String accessToken, @RequestBody AuthReissueRequest request) { - accessToken = accessToken.substring(7); var accessTokenDto = authService.reissue(accessToken, request.getRefreshToken()); return ResponseEntity.ok(AuthAccessTokenResponse.of(accessTokenDto.getAccessToken(), null)); } diff --git a/src/main/java/io/oeid/mogakgo/domain/auth/presentation/dto/req/AuthReissueRequest.java b/src/main/java/io/oeid/mogakgo/domain/auth/presentation/dto/req/AuthReissueRequest.java index 4161a4b8..c58e7441 100644 --- a/src/main/java/io/oeid/mogakgo/domain/auth/presentation/dto/req/AuthReissueRequest.java +++ b/src/main/java/io/oeid/mogakgo/domain/auth/presentation/dto/req/AuthReissueRequest.java @@ -1,14 +1,14 @@ package io.oeid.mogakgo.domain.auth.presentation.dto.req; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; @Getter -@AllArgsConstructor +@NoArgsConstructor @Schema(description = "토큰 재발급 요청") public class AuthReissueRequest { @Schema(description = "Refresh Token") - private final String refreshToken; + private String refreshToken; } From 50861002a4a94ff2fcb6adf0d932c2d491332d05 Mon Sep 17 00:00:00 2001 From: JIN-076 <57834671+JIN-076@users.noreply.github.com> Date: Sun, 25 Feb 2024 01:17:04 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[REFACT]=20=EC=A0=84=EB=B0=98=EC=A0=81?= =?UTF-8?q?=EC=9D=B8=20API=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81,=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EC=B9=B4=EB=93=9C=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=9C=20=EC=B0=94=EB=9F=AC=EB=B3=B4=EA=B8=B0=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=EC=B7=A8=EC=86=8C=20API=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B0=9C=EB=B0=9C=20(#166)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [REFACT] 탈퇴하지 않은 사용자에 대한 프로필 카드 리스트 조회 쿼리 메서드 구현 * [REFACT] 동네 인증 여부 유효성 검사 로직을 User 도메인으로 이동 * [REFACT] 주어진 좌표, 법정구역코드가 유효한 서비스 지역인지 검증하는 유효성 검사 메서드 리팩토링 * [REFACT] ProfileCard 도메인에 '찔러보기' 요청 증가, 감소 메서드 구현 * [REFACT] User 도메인에 찔러보기 요청 카운트 증가 메서드 구현 * [FEAT] senderId, receiverId를 이용해 ProfileCardLike 조회 메서드 구현 * [FEAT] 서비스 지역명 영어 네이밍 변경 * [FEAT] 회원가입한 사용자에 대한 프로필 카드 생성 요청 DTO 구현 * [FEAT] ProfileCard 관련 Custom Exception 클래스 구현 * [FEAT] ProfileCard에 대해 '찔러보기' 요청 취소 DTO 구현 * [FEAT] ProfileCard 도메인 관련 유효성 검사 로직 추가 및 리팩토링 * [FEAT] ProfileCardLike 도메인 관련 유효성 검사 로직 추가 및 리팩토링 * [FEAT] Project 도메인 관련 매칭 요청이 가능한 상태인지 유효성 검사 로직 구현 * [FEAT] ProjectJoinRequest 도메인 관련 매칭 요청이 가능한 상태인지 유효성 검사 로직 구현 및 리팩토링 * [REFACT] 사용자가 받은 '찔러보기' 요청 수 조회 메서드 네이밍 변경 * [REFACT] 메서드 네이밍 변경 및 불필요한 메서드 제거 * [REFACT] 불필요한 메서드 제거 및 네이밍 변경 * [REFACT] 오타 수정 및 빌더 패턴을 정적 팩토리 메서드로 대체 * [REFACT] 유효성 검사 관련 리팩토링 및 DDD 적용 * [REFACT] 프로필 카드 생성 로직 구현, DDD 적용으로 인한 불필요한 메서드 제거 * [REFACT] 메서드 네이밍 변경 및 DDD를 적용한 유효성 검사 관련 리팩토링 * [REFACT] 회원가입과 동시에 프로필 카드가 생성되도록 프로필 생성 로직 추가 * [FEAT] 400, 404 관련 에러코드 추가 * [FEAT] ProfileCard 관련 Swagger 오류 예시 클래스 작성 * [FEAT] 프로필 카드에 대한 찔러보기 요청 취소 API 구현 * [FEAT] 프로필 카드에 대한 찔러보기 요청 취소 API 관련 Swagger 작성, 에러코드와 에러 예시 작성 * [REFACT] 도메인의 유효성 검사 로직 위치 변경 --- .../template/ProfileCardLikeSwagger.java | 31 ++++++++ .../SwaggerProfileCardErrorExamples.java | 9 +++ .../SwaggerProfileCardLikeErrorExamples.java | 2 + .../error/SwaggerUserErrorExamples.java | 2 + .../domain/cert/application/CertService.java | 17 +---- .../domain/geo/application/GeoService.java | 32 ++++---- .../domain/geo/domain/enums/Region.java | 26 +++---- .../application/ProfileCardLikeService.java | 75 +++++++++++++------ .../application/ProfileCardService.java | 22 +++--- .../dto/req/UserProfileCardReq.java | 29 +++++++ .../profile/domain/entity/ProfileCard.java | 31 ++++++-- .../domain/entity/ProfileCardLike.java | 24 +++++- .../exception/ProfileCardException.java | 11 +++ .../ProfileCardJpaRepository.java | 2 + .../ProfileCardLikeJpaRepository.java | 4 + .../ProfileCardLikeRepositoryCustom.java | 5 +- .../ProfileCardLikeRepositoryCustomImpl.java | 17 +---- .../ProfileCardLikeController.java | 10 +++ .../dto/req/UserProfileLikeCancelAPIReq.java | 33 ++++++++ .../dto/req/UserProfileLikeCreateAPIReq.java | 8 +- .../project/application/ProjectService.java | 3 +- .../domain/project/domain/entity/Project.java | 4 + .../ProjectJoinRequestService.java | 48 ++---------- .../domain/entity/ProjectJoinRequest.java | 27 +++++++ .../ProjectJoinRequestJpaRepository.java | 5 +- .../domain/user/application/UserService.java | 4 + .../oeid/mogakgo/domain/user/domain/User.java | 20 ++++- .../mogakgo/exception/code/ErrorCode400.java | 5 +- .../mogakgo/exception/code/ErrorCode404.java | 1 + 29 files changed, 346 insertions(+), 161 deletions(-) create mode 100644 src/main/java/io/oeid/mogakgo/core/properties/swagger/error/SwaggerProfileCardErrorExamples.java create mode 100644 src/main/java/io/oeid/mogakgo/domain/profile/application/dto/req/UserProfileCardReq.java create mode 100644 src/main/java/io/oeid/mogakgo/domain/profile/exception/ProfileCardException.java create mode 100644 src/main/java/io/oeid/mogakgo/domain/profile/presentation/dto/req/UserProfileLikeCancelAPIReq.java diff --git a/src/main/java/io/oeid/mogakgo/common/swagger/template/ProfileCardLikeSwagger.java b/src/main/java/io/oeid/mogakgo/common/swagger/template/ProfileCardLikeSwagger.java index de11a355..eb46ea6a 100644 --- a/src/main/java/io/oeid/mogakgo/common/swagger/template/ProfileCardLikeSwagger.java +++ b/src/main/java/io/oeid/mogakgo/common/swagger/template/ProfileCardLikeSwagger.java @@ -2,8 +2,10 @@ import io.oeid.mogakgo.common.base.CursorPaginationInfoReq; import io.oeid.mogakgo.common.base.CursorPaginationResult; +import io.oeid.mogakgo.core.properties.swagger.error.SwaggerProfileCardErrorExamples; import io.oeid.mogakgo.core.properties.swagger.error.SwaggerProfileCardLikeErrorExamples; import io.oeid.mogakgo.core.properties.swagger.error.SwaggerUserErrorExamples; +import io.oeid.mogakgo.domain.profile.presentation.dto.req.UserProfileLikeCancelAPIReq; import io.oeid.mogakgo.domain.profile.presentation.dto.req.UserProfileLikeCreateAPIReq; import io.oeid.mogakgo.domain.profile.presentation.dto.res.UserProfileLikeAPIRes; import io.oeid.mogakgo.domain.profile.presentation.dto.res.UserProfileLikeCreateAPIRes; @@ -125,4 +127,33 @@ ResponseEntity> getProfileLike @Parameter(description = "'찔러보기' 요청을 조회하는 사용자 ID", required = true) Long id, @Parameter(hidden = true) CursorPaginationInfoReq pageable ); + + @Operation(summary = "사용자의 '찔러보기' 요청 취소", description = "사용자가 자신이 보낸 '찔러보기' 요청을 취소할 때 사용하는 API") + @ApiResponses(value = { + @ApiResponse(responseCode = "204", description = "'찔러보기' 요청 취소 성공"), + @ApiResponse(responseCode = "400", description = "요청한 데이터가 유효하지 않음", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class), + examples = { + @ExampleObject(name = "E040105", value = SwaggerProfileCardLikeErrorExamples.PROFILE_CARD_LIKE_NOT_EXIST), + @ExampleObject(name = "E040104", value = SwaggerProfileCardErrorExamples.PROFILE_CARD_LIKE_AMOUNT_IS_ZERO), + @ExampleObject(name = "E020107", value = SwaggerUserErrorExamples.USER_AVAILABLE_LIKE_COUNT_IS_ZERO), + @ExampleObject(name = "E020108", value = SwaggerUserErrorExamples.USER_AVAILABLE_LIKE_COUNT_IS_FULL) + } + )), + @ApiResponse(responseCode = "404", description = "요청한 데이터가 존재하지 않음", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class), + examples = { + @ExampleObject(name = "E020301", value = SwaggerUserErrorExamples.USER_NOT_FOUND), + @ExampleObject(name = "E040301", value = SwaggerProfileCardLikeErrorExamples.PROFILE_CARD_NOT_FOUND) + } + )), + }) + ResponseEntity cancel( + @Parameter(hidden = true) Long userId, + UserProfileLikeCancelAPIReq request + ); } diff --git a/src/main/java/io/oeid/mogakgo/core/properties/swagger/error/SwaggerProfileCardErrorExamples.java b/src/main/java/io/oeid/mogakgo/core/properties/swagger/error/SwaggerProfileCardErrorExamples.java new file mode 100644 index 00000000..e2af5a7c --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/core/properties/swagger/error/SwaggerProfileCardErrorExamples.java @@ -0,0 +1,9 @@ +package io.oeid.mogakgo.core.properties.swagger.error; + +public class SwaggerProfileCardErrorExamples { + + public static final String PROFILE_CARD_LIKE_AMOUNT_IS_ZERO = "{\"timestamp\":\"2024-02-17T10:07:31.404Z\",\"statusCode\":400,\"code\":\"E040104\",\"message\":\"해당 프로필 카드에 찔러보기 요청이 존재하지 않으므로 더 이상 차감할 수 없습니다.\"}"; + private SwaggerProfileCardErrorExamples() { + + } +} diff --git a/src/main/java/io/oeid/mogakgo/core/properties/swagger/error/SwaggerProfileCardLikeErrorExamples.java b/src/main/java/io/oeid/mogakgo/core/properties/swagger/error/SwaggerProfileCardLikeErrorExamples.java index d02bc4eb..a5d390e1 100644 --- a/src/main/java/io/oeid/mogakgo/core/properties/swagger/error/SwaggerProfileCardLikeErrorExamples.java +++ b/src/main/java/io/oeid/mogakgo/core/properties/swagger/error/SwaggerProfileCardLikeErrorExamples.java @@ -3,8 +3,10 @@ public class SwaggerProfileCardLikeErrorExamples { public static final String PROFILE_CARD_LIKE_ALREADY_EXIST = "{\"timestamp\":\"2024-02-17T10:07:31.404Z\",\"statusCode\":400,\"code\":\"E040102\",\"message\":\"이미 찔러보기 요청을 전송한 프로필 카드에 찔러보기 요청을 전송할 수 없습니다.\"}"; + public static final String PROFILE_CARD_LIKE_NOT_EXIST = "{\"timestamp\":\"2024-02-17T10:07:31.404Z\",\"statusCode\":400,\"code\":\"E040105\",\"message\":\"해당 프로필 카드에 찔러보기 요청이 존재하지 않아 요청을 취소할 수 없습니다.\"}"; public static final String INVALID_PROFILE_CARD_LIKE_RECEIVER_INFO = "{\"timestamp\":\"2024-02-17T10:07:31.404Z\",\"statusCode\":400,\"code\":\"E040103\",\"message\":\"찔러보기 요청의 사용자가 존재하지 않습니다.\"}"; public static final String PROFILE_CARD_LIKE_FORBIDDEN_OPERATION = "{\"timestamp\":\"2024-02-17T10:07:31.404Z\",\"statusCode\":403,\"code\":\"E040103\",\"message\":\"본인의 프로필 카드 외에 대해 찔러보기 요청 권한이 없습니다.\"}"; + public static final String PROFILE_CARD_NOT_FOUND = "{\"timestamp\":\"2024-02-17T10:07:31.404Z\",\"statusCode\":404,\"code\":\"E040301\",\"message\":\"해당 프로필 카드가 존재하지 않습니다.\"}"; private SwaggerProfileCardLikeErrorExamples() { } diff --git a/src/main/java/io/oeid/mogakgo/core/properties/swagger/error/SwaggerUserErrorExamples.java b/src/main/java/io/oeid/mogakgo/core/properties/swagger/error/SwaggerUserErrorExamples.java index de1c9cbc..9b6b194d 100644 --- a/src/main/java/io/oeid/mogakgo/core/properties/swagger/error/SwaggerUserErrorExamples.java +++ b/src/main/java/io/oeid/mogakgo/core/properties/swagger/error/SwaggerUserErrorExamples.java @@ -5,6 +5,8 @@ public class SwaggerUserErrorExamples { public static final String USER_NOT_FOUND = "{\"timestamp\":\"2024-02-17T10:07:31.404Z\",\"statusCode\":404,\"code\":\"E020301\",\"message\":\"해당 유저가 존재하지 않습니다.\"}"; public static final String USER_WANTED_JOB_DUPLICATE = "{\"timestamp\":\"2024-02-17T10:07:31.404Z\",\"statusCode\":400,\"code\":\"E020105\",\"message\":\"중복된 희망 직무가 있습니다.\"}"; public static final String INVALID_USER_NAME = "{\"timestamp\":\"2024-02-17T10:07:31.404Z\",\"statusCode\":400,\"code\":\"E020103\",\"message\":\"유저 이름은 비어있을 수 없습니다.\"}"; + public static final String USER_AVAILABLE_LIKE_COUNT_IS_ZERO = "{\"timestamp\":\"2024-02-17T10:07:31.404Z\",\"statusCode\":400,\"code\":\"E020107\",\"message\":\"당일 사용 가능한 찔러보기 요청을 모두 소진했습니다.\"}"; + public static final String USER_AVAILABLE_LIKE_COUNT_IS_FULL = "{\"timestamp\":\"2024-02-17T10:07:31.404Z\",\"statusCode\":400,\"code\":\"E020108\",\"message\":\"당일 사용 가능한 찔러보기 최대 요청 횟수를 초과활 수 없습니다.\"}"; private SwaggerUserErrorExamples() { } diff --git a/src/main/java/io/oeid/mogakgo/domain/cert/application/CertService.java b/src/main/java/io/oeid/mogakgo/domain/cert/application/CertService.java index bfabbde1..f53045f1 100644 --- a/src/main/java/io/oeid/mogakgo/domain/cert/application/CertService.java +++ b/src/main/java/io/oeid/mogakgo/domain/cert/application/CertService.java @@ -7,7 +7,6 @@ import io.oeid.mogakgo.domain.geo.domain.enums.Region; import io.oeid.mogakgo.domain.geo.exception.GeoException; import io.oeid.mogakgo.domain.user.application.UserCommonService; -import io.oeid.mogakgo.domain.user.application.UserGeoService; import io.oeid.mogakgo.domain.user.domain.User; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -18,17 +17,15 @@ @RequiredArgsConstructor public class CertService { - private final UserGeoService userGeoService; private final UserCommonService userCommonService; @Transactional public Long certificate(Long tokenUserId, Long userId, int areaCode) { - User tokenUser = validateToken(tokenUserId); - validateCertificator(tokenUser, userId); + User user = validateToken(tokenUserId); + validateCertificator(user, userId); Region region = validateAreaCodeCoverage(areaCode); - if (isPossibleCertification(userId, region)) { - userGeoService.updateUserGeo(userId, region); - } + + user.updateRegion(region); return userId; } @@ -36,12 +33,6 @@ private User validateToken(Long userId) { return userCommonService.getUserById(userId); } - // 사용자가 아직 동네 인증을 하지 않았거나, 새롭게 인증하려는 지역이 이미 인증된 지역과 다를 경우만 동네 인증 처리 - private boolean isPossibleCertification(Long userId, Region region) { - Region userRegionInfo = userGeoService.getUserGeo(userId).getRegion(); - return userRegionInfo == null || userRegionInfo != region; - } - private void validateCertificator(User tokenUser, Long userId) { if (!tokenUser.getId().equals(userId)) { throw new CertException(INVALID_CERT_INFORMATION); diff --git a/src/main/java/io/oeid/mogakgo/domain/geo/application/GeoService.java b/src/main/java/io/oeid/mogakgo/domain/geo/application/GeoService.java index 17713242..032ad77a 100644 --- a/src/main/java/io/oeid/mogakgo/domain/geo/application/GeoService.java +++ b/src/main/java/io/oeid/mogakgo/domain/geo/application/GeoService.java @@ -26,16 +26,22 @@ public class GeoService { public int getUserRegionInfoAboutCoordinates(Long tokenUserId, Double x, Double y) { validateToken(tokenUserId); - validateCoordinates(x, y); - return validateCoordinatesCoverage(x, y); + return getRegionAboutCoordinates(x, y).getAreaCode(); + } + + public Region getRegionAboutCoordinates(Double x, Double y) { + // 해당 법정구역코드가 유효한 서비스 지역인지 검증 + return validateAreaCodeCoverage(getAreaCodeAboutCoordinates(x, y)); } public int getAreaCodeAboutCoordinates(Double x, Double y) { + // 해당 좌표가 유효한 서비스 지역 내 좌표인지 검증 + validateAvailableCoordinates(x, y); AddressDocument document = getAddressInfoAboutAreaCode(x, y); return extractAreaCode(document); } - public AddressDocument getAddressInfoAboutAreaCode(Double x, Double y) { + private AddressDocument getAddressInfoAboutAreaCode(Double x, Double y) { String key = generateKey(kakaoProperties); AddressInfoDto response = kakaoFeignClient.getAreaCodeAboutCoordinates(key, x, y); return response.getDocuments()[0]; @@ -49,26 +55,22 @@ private int extractAreaCode(AddressDocument document) { return Integer.parseInt(document.getCode().substring(0, 5)); } - private int validateCoordinatesCoverage(Double x, Double y) { - int areaCode = getAreaCodeAboutCoordinates(x, y); - validateCodeCoverage(areaCode); - return areaCode; + private void validateAvailableCoordinates(Double x, Double y) { + if (x < 123.0 || x > 132.0 || y < 32.0 || y > 39.0) { + throw new GeoException(INVALID_SERVICE_REGION); + } } - private void validateCodeCoverage(int areaCode) { - if (Region.getByAreaCode(areaCode) == null) { + private Region validateAreaCodeCoverage(int areaCode) { + Region region = Region.getByAreaCode(areaCode); + if (region == null) { throw new GeoException(INVALID_SERVICE_REGION); } + return region; } private User validateToken(Long userId) { return userCommonService.getUserById(userId); } - private void validateCoordinates(Double x, Double y) { - if (x < 123.0 || x > 132.0 || y < 32.0 || y > 39.0) { - throw new GeoException(INVALID_SERVICE_REGION); - } - } - } diff --git a/src/main/java/io/oeid/mogakgo/domain/geo/domain/enums/Region.java b/src/main/java/io/oeid/mogakgo/domain/geo/domain/enums/Region.java index 6cbb4027..c730088e 100644 --- a/src/main/java/io/oeid/mogakgo/domain/geo/domain/enums/Region.java +++ b/src/main/java/io/oeid/mogakgo/domain/geo/domain/enums/Region.java @@ -8,31 +8,31 @@ @RequiredArgsConstructor public enum Region { - JONGRO("서울특별시", "종로구", 11110), + JONGNO("서울특별시", "종로구", 11110), JUNG("서울특별시", "중구", 11140), YONGSAN("서울특별시", "용산구", 11170), SEONGDONG("서울특별시", "성동구", 11200), - KWANGJIN("서울특별시", "광진구", 11215), + GWANGJIN("서울특별시", "광진구", 11215), DONGDAEMUN("서울특별시", "동대문구", 11230), - JUNGRANG("서울특별시", "중랑구", 11260), + JUNGNANG("서울특별시", "중랑구", 11260), SEONGBUK("서울특별시", "성북구", 11290), - KANGBUK("서울특별시", "강북구", 11305), + GANGBUK("서울특별시", "강북구", 11305), DOBONG("서울특별시", "도봉구", 11320), NOWON("서울특별시", "노원구", 11350), EUNPYEONG("서울특별시", "은평구", 11380), SEODAEMUN("서울특별시", "서대문구", 11410), MAPO("서울특별시", "마포구", 11440), - YANGCHUN("서울특별시", "양천구", 11470), - KANGSEO("서울특별시", "강서구", 11500), + YANGCHEON("서울특별시", "양천구", 11470), + GANGSEO("서울특별시", "강서구", 11500), GURO("서울특별시", "구로구", 11530), - GEUMCHUN("서울특별시", "금천구", 11545), + GEUMCHEON("서울특별시", "금천구", 11545), YOUNGDEUNGPO("서울특별시", "영등포구", 11560), DONGJAK("서울특별시", "동작구", 11590), - KWANAK("서울특별시", "관악구", 11620), + GWANAK("서울특별시", "관악구", 11620), SEOCHO("서울특별시", "서초구", 11650), - KANGNAM("서울특별시", "강남구", 11680), + GANGNAM("서울특별시", "강남구", 11680), SONGPA("서울특별시", "송파구", 11710), - KANGDONG("서울특별시", "강동구", 11740), + GANGDONG("서울특별시", "강동구", 11740), BUNDANG("경기도 성남시", "분당구", 41135); private final String depth1; @@ -50,9 +50,9 @@ public static Region getByAreaCode(int areaCode) { public static List getDefaultDensityRank() { return List.of( - JONGRO, JUNG, YONGSAN, SEONGDONG, KWANGJIN, DONGDAEMUN, JUNGRANG, SEONGBUK, KANGBUK, - DOBONG, NOWON, EUNPYEONG, SEODAEMUN, MAPO, YANGCHUN, KANGSEO, GURO, GEUMCHUN, - YOUNGDEUNGPO, DONGJAK, KWANAK, SEOCHO, KANGNAM, SONGPA, KANGDONG + JONGNO, JUNG, YONGSAN, SEONGDONG, GWANGJIN, DONGDAEMUN, JUNGNANG, SEONGBUK, GANGBUK, + DOBONG, NOWON, EUNPYEONG, SEODAEMUN, MAPO, YANGCHEON, GANGSEO, GURO, GEUMCHEON, + YOUNGDEUNGPO, DONGJAK, GWANAK, SEOCHO, GANGNAM, SONGPA, GANGDONG ); } diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/application/ProfileCardLikeService.java b/src/main/java/io/oeid/mogakgo/domain/profile/application/ProfileCardLikeService.java index e8f35ce5..0574ce13 100644 --- a/src/main/java/io/oeid/mogakgo/domain/profile/application/ProfileCardLikeService.java +++ b/src/main/java/io/oeid/mogakgo/domain/profile/application/ProfileCardLikeService.java @@ -1,17 +1,21 @@ package io.oeid.mogakgo.domain.profile.application; import static io.oeid.mogakgo.exception.code.ErrorCode400.PROFILE_CARD_LIKE_ALREADY_EXIST; +import static io.oeid.mogakgo.exception.code.ErrorCode400.PROFILE_CARD_LIKE_NOT_EXIST; import static io.oeid.mogakgo.exception.code.ErrorCode403.PROFILE_CARD_LIKE_FORBIDDEN_OPERATION; +import static io.oeid.mogakgo.exception.code.ErrorCode404.PROFILE_CARD_NOT_FOUND; import io.oeid.mogakgo.common.base.CursorPaginationInfoReq; import io.oeid.mogakgo.common.base.CursorPaginationResult; +import io.oeid.mogakgo.domain.profile.domain.entity.ProfileCard; import io.oeid.mogakgo.domain.profile.domain.entity.ProfileCardLike; import io.oeid.mogakgo.domain.profile.exception.ProfileCardLikeException; +import io.oeid.mogakgo.domain.profile.infrastructure.ProfileCardJpaRepository; import io.oeid.mogakgo.domain.profile.infrastructure.ProfileCardLikeJpaRepository; +import io.oeid.mogakgo.domain.profile.presentation.dto.req.UserProfileLikeCancelAPIReq; import io.oeid.mogakgo.domain.profile.presentation.dto.req.UserProfileLikeCreateAPIReq; import io.oeid.mogakgo.domain.profile.presentation.dto.res.UserProfileLikeInfoAPIRes; import io.oeid.mogakgo.domain.user.application.UserCommonService; -import io.oeid.mogakgo.domain.user.application.UserProfileService; import io.oeid.mogakgo.domain.user.domain.User; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -23,39 +27,60 @@ public class ProfileCardLikeService { private final ProfileCardLikeJpaRepository profileCardLikeRepository; - private final ProfileCardService profileCardService; + private final ProfileCardJpaRepository profileCardRepository; private final UserCommonService userCommonService; - private final UserProfileService userProfileService; @Transactional public Long create(Long userId, UserProfileLikeCreateAPIReq request) { - User tokenUser = validateToken(userId); - validateSendor(tokenUser, request.getSenderId()); - validateReceiver(userId); - validateLikeAlreadyExist(request.getSenderId(), request.getReceiverId()); + User user = validateToken(userId); - User receiver = userCommonService.getUserById(request.getReceiverId()); - ProfileCardLike profileCardLike = request.toEntity(tokenUser, receiver); - profileCardLikeRepository.save(profileCardLike); + ProfileCard profileCard = validateProfileCardExist(request.getReceiverId()); + + if (hasAlreadyExist(userId, request.getReceiverId())) { + throw new ProfileCardLikeException(PROFILE_CARD_LIKE_ALREADY_EXIST); + } - profileCardService.increaseTotalLikeAmount(receiver.getId()); - userProfileService.decreaseAvailableLikeCount(userId); + profileCard.increaseLikeAmount(); + user.decreaseAvailableLikeCount(); + + // 프로필 카드에 '찔러보기' 요청 생성 + ProfileCardLike profileCardLike = request.toEntity(user, profileCard.getUser()); + profileCardLikeRepository.save(profileCardLike); return profileCardLike.getId(); } + @Transactional + public void cancel(Long userId, UserProfileLikeCancelAPIReq request) { + User user = validateToken(userId); + validateSender(user, request.getSenderId()); + + ProfileCard profileCard = validateProfileCardExist(request.getReceiverId()); + + if (!hasAlreadyExist(userId, request.getReceiverId())) { + throw new ProfileCardLikeException(PROFILE_CARD_LIKE_NOT_EXIST); + } + + profileCard.decreaseLikeAmount(); + user.increaseAvailableLikeCount(); + + // 프로필 카드에 '찔러보기' 요청 취소 + ProfileCardLike profileCardLike = getBySenderAndReceiver(userId, request.getReceiverId()); + profileCardLikeRepository.delete(profileCardLike); + } + // 나의 찔러보기 요청 수 조회 public Long getReceivedLikeCountForProfileCard(Long userId, Long id) { User tokenUser = validateToken(userId); - validateSendor(tokenUser, userId); + validateSender(tokenUser, userId); - return profileCardLikeRepository.getLikeCount(id); + return profileCardLikeRepository.getReceivedLikeCount(id); } // 내가 보낸 찔러보기 요청 수 조회 public Long getSentLikeCountForProfileCard(Long userId, Long id) { User tokenUser = validateToken(userId); - validateSendor(tokenUser, userId); + validateSender(tokenUser, userId); return profileCardLikeRepository.getLikeCountByCondition(id, null); } @@ -63,28 +88,32 @@ public Long getSentLikeCountForProfileCard(Long userId, Long id) { public CursorPaginationResult getLikeInfoSenderProfile( Long userId, Long id, CursorPaginationInfoReq pageable) { User tokenUser = validateToken(userId); - validateSendor(tokenUser, userId); + validateSender(tokenUser, userId); return profileCardLikeRepository.getLikeInfoBySender(id, pageable); } + private ProfileCardLike getBySenderAndReceiver(Long senderId, Long receiverId) { + return profileCardLikeRepository.findBySenderAndReceiver(senderId, receiverId) + .orElseThrow(() -> new ProfileCardLikeException(PROFILE_CARD_LIKE_NOT_EXIST)); + } + private User validateToken(Long userId) { return userCommonService.getUserById(userId); } - private void validateSendor(User tokenUser, Long userId) { + private void validateSender(User tokenUser, Long userId) { if (!tokenUser.getId().equals(userId)) { throw new ProfileCardLikeException(PROFILE_CARD_LIKE_FORBIDDEN_OPERATION); } } - private void validateReceiver(Long userId) { - userCommonService.getUserById(userId); + private ProfileCard validateProfileCardExist(Long receiverId) { + return profileCardRepository.findByUserId(receiverId) + .orElseThrow(() -> new ProfileCardLikeException(PROFILE_CARD_NOT_FOUND)); } - private void validateLikeAlreadyExist(Long userId, Long creatorId) { - if (profileCardLikeRepository.findBySenderAndReceiver(userId, creatorId).isPresent()) { - throw new ProfileCardLikeException(PROFILE_CARD_LIKE_ALREADY_EXIST); - } + private boolean hasAlreadyExist(Long userId, Long creatorId) { + return profileCardLikeRepository.findBySenderAndReceiver(userId, creatorId).isPresent(); } } diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/application/ProfileCardService.java b/src/main/java/io/oeid/mogakgo/domain/profile/application/ProfileCardService.java index b8b1c14e..118576c9 100644 --- a/src/main/java/io/oeid/mogakgo/domain/profile/application/ProfileCardService.java +++ b/src/main/java/io/oeid/mogakgo/domain/profile/application/ProfileCardService.java @@ -1,17 +1,16 @@ package io.oeid.mogakgo.domain.profile.application; import static io.oeid.mogakgo.exception.code.ErrorCode400.INVALID_SERVICE_REGION; -import static io.oeid.mogakgo.exception.code.ErrorCode404.USER_NOT_FOUND; import io.oeid.mogakgo.common.base.CursorPaginationInfoReq; import io.oeid.mogakgo.common.base.CursorPaginationResult; import io.oeid.mogakgo.domain.geo.domain.enums.Region; import io.oeid.mogakgo.domain.geo.exception.GeoException; +import io.oeid.mogakgo.domain.profile.application.dto.req.UserProfileCardReq; import io.oeid.mogakgo.domain.profile.domain.entity.ProfileCard; import io.oeid.mogakgo.domain.profile.infrastructure.ProfileCardJpaRepository; import io.oeid.mogakgo.domain.user.application.UserCommonService; import io.oeid.mogakgo.domain.user.domain.User; -import io.oeid.mogakgo.domain.user.exception.UserException; import io.oeid.mogakgo.domain.user.presentation.dto.res.UserPublicApiResponse; import java.util.Collections; import lombok.RequiredArgsConstructor; @@ -26,6 +25,14 @@ public class ProfileCardService { private final ProfileCardJpaRepository profileCardRepository; private final UserCommonService userCommonService; + public Long create(UserProfileCardReq request) { + + ProfileCard profileCard = request.toEntity(request.getUser()); + profileCardRepository.save(profileCard); + + return profileCard.getId(); + } + public CursorPaginationResult getRandomOrderedProfileCardsByRegion( Long userId, Region region, CursorPaginationInfoReq pageable ) { @@ -41,17 +48,6 @@ public CursorPaginationResult getRandomOrderedProfileCard return profiles; } - @Transactional - public void increaseTotalLikeAmount(Long userId) { - var profileCard = getProfileCard(userId); - profileCard.addLike(); - } - - private ProfileCard getProfileCard(Long userId) { - return profileCardRepository.findByUserId(userId) - .orElseThrow(() -> new UserException(USER_NOT_FOUND)); - } - private User validateToken(Long userId) { return userCommonService.getUserById(userId); } diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/application/dto/req/UserProfileCardReq.java b/src/main/java/io/oeid/mogakgo/domain/profile/application/dto/req/UserProfileCardReq.java new file mode 100644 index 00000000..7dfa6ca2 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/profile/application/dto/req/UserProfileCardReq.java @@ -0,0 +1,29 @@ +package io.oeid.mogakgo.domain.profile.application.dto.req; + +import io.oeid.mogakgo.domain.profile.domain.entity.ProfileCard; +import io.oeid.mogakgo.domain.user.domain.User; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; + +@Schema(description = "프로필 카드 생성 요청 DTO") +@Getter +public class UserProfileCardReq { + + @Schema(description = "프로필 카드를 생성할 사용자") + @NotNull + private final User user; + + private UserProfileCardReq(User user) { + this.user = user; + } + + public ProfileCard toEntity(User user) { + return ProfileCard.from(user); + } + + public static UserProfileCardReq of(User user) { + return new UserProfileCardReq(user); + } + +} diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/domain/entity/ProfileCard.java b/src/main/java/io/oeid/mogakgo/domain/profile/domain/entity/ProfileCard.java index 08cc0193..91c5f556 100644 --- a/src/main/java/io/oeid/mogakgo/domain/profile/domain/entity/ProfileCard.java +++ b/src/main/java/io/oeid/mogakgo/domain/profile/domain/entity/ProfileCard.java @@ -1,6 +1,11 @@ package io.oeid.mogakgo.domain.profile.domain.entity; +import static io.oeid.mogakgo.exception.code.ErrorCode400.PROFILE_CARD_LIKE_AMOUNT_IS_ZERO; +import static io.oeid.mogakgo.exception.code.ErrorCode404.USER_NOT_FOUND; + +import io.oeid.mogakgo.domain.profile.exception.ProfileCardException; import io.oeid.mogakgo.domain.user.domain.User; +import io.oeid.mogakgo.domain.user.exception.UserException; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -35,19 +40,31 @@ public class ProfileCard { @Column(name = "total_like_amount", nullable = false) private Long totalLikeAmount; - public void addLike() { - this.totalLikeAmount += 1; - } - @Builder private ProfileCard(User user) { this.user = user; this.totalLikeAmount = 0L; + validateUser(user); + } + + public void validateUser(User user) { + if (!this.user.getId().equals(user.getId())) { + throw new UserException(USER_NOT_FOUND); + } + } + + public void increaseLikeAmount() { + this.totalLikeAmount += 1; + } + + public void decreaseLikeAmount() { + if (this.totalLikeAmount <= 0) { + throw new ProfileCardException(PROFILE_CARD_LIKE_AMOUNT_IS_ZERO); + } + this.totalLikeAmount -= 1; } public static ProfileCard from(User user) { - return ProfileCard.builder() - .user(user) - .build(); + return new ProfileCard(user); } } diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/domain/entity/ProfileCardLike.java b/src/main/java/io/oeid/mogakgo/domain/profile/domain/entity/ProfileCardLike.java index eb4b2d57..26d303db 100644 --- a/src/main/java/io/oeid/mogakgo/domain/profile/domain/entity/ProfileCardLike.java +++ b/src/main/java/io/oeid/mogakgo/domain/profile/domain/entity/ProfileCardLike.java @@ -1,6 +1,11 @@ package io.oeid.mogakgo.domain.profile.domain.entity; +import static io.oeid.mogakgo.exception.code.ErrorCode403.PROFILE_CARD_LIKE_FORBIDDEN_OPERATION; +import static io.oeid.mogakgo.exception.code.ErrorCode404.USER_NOT_FOUND; + +import io.oeid.mogakgo.domain.profile.exception.ProfileCardLikeException; import io.oeid.mogakgo.domain.user.domain.User; +import io.oeid.mogakgo.domain.user.exception.UserException; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EntityListeners; @@ -49,12 +54,23 @@ public class ProfileCardLike { private ProfileCardLike(User sender, User receiver) { this.sender = sender; this.receiver = receiver; + validateSender(sender); + validateReceiver(receiver); + } + + private void validateSender(User tokenUser) { + if (!this.sender.getId().equals(tokenUser.getId())) { + throw new ProfileCardLikeException(PROFILE_CARD_LIKE_FORBIDDEN_OPERATION); + } + } + + private void validateReceiver(User receiver) { + if (!this.receiver.getId().equals(receiver.getId())) { + throw new UserException(USER_NOT_FOUND); + } } public static ProfileCardLike of(User sender, User receiver) { - return ProfileCardLike.builder() - .sender(sender) - .receiver(receiver) - .build(); + return new ProfileCardLike(sender, receiver); } } diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/exception/ProfileCardException.java b/src/main/java/io/oeid/mogakgo/domain/profile/exception/ProfileCardException.java new file mode 100644 index 00000000..a2413f13 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/profile/exception/ProfileCardException.java @@ -0,0 +1,11 @@ +package io.oeid.mogakgo.domain.profile.exception; + +import io.oeid.mogakgo.exception.code.ErrorCode; +import io.oeid.mogakgo.exception.exception_class.CustomException; + +public class ProfileCardException extends CustomException { + + public ProfileCardException(ErrorCode errorCode) { + super(errorCode); + } +} diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardJpaRepository.java b/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardJpaRepository.java index 5136bab0..c064f5fa 100644 --- a/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardJpaRepository.java +++ b/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardJpaRepository.java @@ -3,11 +3,13 @@ import io.oeid.mogakgo.domain.profile.domain.entity.ProfileCard; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; @Repository public interface ProfileCardJpaRepository extends JpaRepository, ProfileCardRepositoryCustom { + @Query("select pf from ProfileCard pf where pf.user.id = :userId and pf.user.deletedAt is null") Optional findByUserId(Long userId); } diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardLikeJpaRepository.java b/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardLikeJpaRepository.java index f4095fb7..ff7e990b 100644 --- a/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardLikeJpaRepository.java +++ b/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardLikeJpaRepository.java @@ -1,9 +1,13 @@ package io.oeid.mogakgo.domain.profile.infrastructure; import io.oeid.mogakgo.domain.profile.domain.entity.ProfileCardLike; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; public interface ProfileCardLikeJpaRepository extends JpaRepository, ProfileCardLikeRepositoryCustom { + @Query("select pcl from ProfileCardLike pcl where pcl.sender.id = :senderId and pcl.receiver.id = :receiverId") + Optional findBySenderAndReceiver(Long senderId, Long receiverId); } diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardLikeRepositoryCustom.java b/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardLikeRepositoryCustom.java index ab5a4498..fe839972 100644 --- a/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardLikeRepositoryCustom.java +++ b/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardLikeRepositoryCustom.java @@ -2,15 +2,12 @@ import io.oeid.mogakgo.common.base.CursorPaginationInfoReq; import io.oeid.mogakgo.common.base.CursorPaginationResult; -import io.oeid.mogakgo.domain.profile.domain.entity.ProfileCardLike; import io.oeid.mogakgo.domain.profile.presentation.dto.res.UserProfileLikeInfoAPIRes; -import java.util.Optional; public interface ProfileCardLikeRepositoryCustom { Long getLikeCountByCondition(Long senderId, Long receiverId); - Long getLikeCount(Long userId); + Long getReceivedLikeCount(Long userId); CursorPaginationResult getLikeInfoBySender( Long senderId, CursorPaginationInfoReq pageable); - Optional findBySenderAndReceiver(Long senderId, Long receiverId); } diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardLikeRepositoryCustomImpl.java b/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardLikeRepositoryCustomImpl.java index 43224d6c..c98d0faf 100644 --- a/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardLikeRepositoryCustomImpl.java +++ b/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardLikeRepositoryCustomImpl.java @@ -12,7 +12,6 @@ import io.oeid.mogakgo.domain.profile.presentation.dto.res.UserProfileLikeInfoAPIRes; import io.oeid.mogakgo.domain.user.domain.QUser; import java.util.List; -import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @@ -31,8 +30,8 @@ public Long getLikeCountByCondition(Long senderId, Long receiverId) { return jpaQueryFactory .select(profileCardLike.count()) .from(profileCardLike) - .innerJoin(profileCardLike.sender, sender) //.on(profileCardLike.sender.id.eq(user.id)) - .innerJoin(profileCardLike.receiver, receiver) //.on(profileCardLike.receiver.id.eq(user.id)) + .innerJoin(profileCardLike.sender, sender) + .innerJoin(profileCardLike.receiver, receiver) .where( senderIdEq(senderId), receiveridEq(receiverId) @@ -41,7 +40,7 @@ public Long getLikeCountByCondition(Long senderId, Long receiverId) { } @Override - public Long getLikeCount(Long userId) { + public Long getReceivedLikeCount(Long userId) { return jpaQueryFactory.select(profileCard.totalLikeAmount) .from(profileCard) .innerJoin(profileCard.user, user) @@ -75,16 +74,6 @@ public CursorPaginationResult getLikeInfoBySender( ); } - @Override - public Optional findBySenderAndReceiver(Long senderId, Long receiverId) { - return Optional.ofNullable(jpaQueryFactory.selectFrom(profileCardLike) - .where( - senderIdEq(senderId), - receiveridEq(receiverId) - ) - .fetchOne()); - } - private BooleanExpression senderIdEq(Long senderId) { return senderId != null ? profileCardLike.sender.id.eq(senderId) : null; } diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/presentation/ProfileCardLikeController.java b/src/main/java/io/oeid/mogakgo/domain/profile/presentation/ProfileCardLikeController.java index 6103753b..6579346b 100644 --- a/src/main/java/io/oeid/mogakgo/domain/profile/presentation/ProfileCardLikeController.java +++ b/src/main/java/io/oeid/mogakgo/domain/profile/presentation/ProfileCardLikeController.java @@ -5,6 +5,7 @@ import io.oeid.mogakgo.common.base.CursorPaginationResult; import io.oeid.mogakgo.common.swagger.template.ProfileCardLikeSwagger; import io.oeid.mogakgo.domain.profile.application.ProfileCardLikeService; +import io.oeid.mogakgo.domain.profile.presentation.dto.req.UserProfileLikeCancelAPIReq; import io.oeid.mogakgo.domain.profile.presentation.dto.req.UserProfileLikeCreateAPIReq; import io.oeid.mogakgo.domain.profile.presentation.dto.res.UserProfileLikeAPIRes; import io.oeid.mogakgo.domain.profile.presentation.dto.res.UserProfileLikeCreateAPIRes; @@ -12,6 +13,7 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; @@ -62,4 +64,12 @@ public ResponseEntity> getProf return ResponseEntity.ok() .body(profileCardLikeService.getLikeInfoSenderProfile(userId, id, pageable)); } + + @DeleteMapping("/like") + public ResponseEntity cancel( + @UserId Long userId, @Valid @RequestBody UserProfileLikeCancelAPIReq request + ) { + profileCardLikeService.cancel(userId, request); + return ResponseEntity.noContent().build(); + } } diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/presentation/dto/req/UserProfileLikeCancelAPIReq.java b/src/main/java/io/oeid/mogakgo/domain/profile/presentation/dto/req/UserProfileLikeCancelAPIReq.java new file mode 100644 index 00000000..b6d57b12 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/profile/presentation/dto/req/UserProfileLikeCancelAPIReq.java @@ -0,0 +1,33 @@ +package io.oeid.mogakgo.domain.profile.presentation.dto.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Getter; + +@Schema(description = "관심 있는 프로필 카드에 '찔러보기' 요청 취소 요청 DTO") +@Getter +public class UserProfileLikeCancelAPIReq { + + @Schema(description = "요청 취소하는 사용자 ID", example = "2", implementation = Long.class) + @NotNull + private final Long senderId; + + @Schema(description = "요청을 취소할 프로필 카드의 사용자 ID", example = "3", implementation = Long.class) + @NotNull + private final Long receiverId; + + @Builder + private UserProfileLikeCancelAPIReq(Long senderId, Long receiverId) { + this.senderId = senderId; + this.receiverId = receiverId; + } + + public static UserProfileLikeCancelAPIReq from(Long senderId, Long receiverId) { + return UserProfileLikeCancelAPIReq.builder() + .senderId(senderId) + .receiverId(receiverId) + .build(); + } + +} diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/presentation/dto/req/UserProfileLikeCreateAPIReq.java b/src/main/java/io/oeid/mogakgo/domain/profile/presentation/dto/req/UserProfileLikeCreateAPIReq.java index 3fe304d7..25ce9c1e 100644 --- a/src/main/java/io/oeid/mogakgo/domain/profile/presentation/dto/req/UserProfileLikeCreateAPIReq.java +++ b/src/main/java/io/oeid/mogakgo/domain/profile/presentation/dto/req/UserProfileLikeCreateAPIReq.java @@ -14,7 +14,7 @@ public class UserProfileLikeCreateAPIReq { @NotNull private final Long senderId; - @Schema(description = "찔러보기 요청으 받는 사용자 ID") + @Schema(description = "찔러보기 요청을 받는 사용자 ID") @NotNull private final Long receiverId; @@ -24,10 +24,6 @@ private UserProfileLikeCreateAPIReq(Long senderId, Long receiverId) { } public ProfileCardLike toEntity(User sender, User receiver) { - return ProfileCardLike.builder() - .sender(sender) - .receiver(receiver) - .build(); + return ProfileCardLike.of(sender, receiver); } - } diff --git a/src/main/java/io/oeid/mogakgo/domain/project/application/ProjectService.java b/src/main/java/io/oeid/mogakgo/domain/project/application/ProjectService.java index 7c4e0e3e..99ff0832 100644 --- a/src/main/java/io/oeid/mogakgo/domain/project/application/ProjectService.java +++ b/src/main/java/io/oeid/mogakgo/domain/project/application/ProjectService.java @@ -186,8 +186,7 @@ private Project getProject(Long projectId) { } private void validateMeetLocation(Double lat, Double lng, Region userRegion) { - Region reqArea = Region.getByAreaCode( - geoService.getAreaCodeAboutCoordinates(lng, lat)); + Region reqArea = geoService.getRegionAboutCoordinates(lng, lat); if (userRegion != reqArea) { throw new ProjectException(NOT_MATCH_MEET_LOCATION); } diff --git a/src/main/java/io/oeid/mogakgo/domain/project/domain/entity/Project.java b/src/main/java/io/oeid/mogakgo/domain/project/domain/entity/Project.java index 51b0bfc2..88ce5cfb 100644 --- a/src/main/java/io/oeid/mogakgo/domain/project/domain/entity/Project.java +++ b/src/main/java/io/oeid/mogakgo/domain/project/domain/entity/Project.java @@ -112,6 +112,10 @@ public void validateCreator(User tokenUser) { } } + public void validateAvailableMatched() { + this.projectStatus.validateAvailableMatched(); + } + private void validateAvailableCancel(User tokenUser) { validateCreator(tokenUser); this.projectStatus.validateAvailableCancel(); diff --git a/src/main/java/io/oeid/mogakgo/domain/project_join_req/application/ProjectJoinRequestService.java b/src/main/java/io/oeid/mogakgo/domain/project_join_req/application/ProjectJoinRequestService.java index 78fa31aa..33c9a83b 100644 --- a/src/main/java/io/oeid/mogakgo/domain/project_join_req/application/ProjectJoinRequestService.java +++ b/src/main/java/io/oeid/mogakgo/domain/project_join_req/application/ProjectJoinRequestService.java @@ -1,23 +1,18 @@ package io.oeid.mogakgo.domain.project_join_req.application; -import static io.oeid.mogakgo.exception.code.ErrorCode400.INVALID_CREATOR_PROJECT_JOIN_REQUEST; import static io.oeid.mogakgo.exception.code.ErrorCode400.INVALID_MATCHING_USER_TO_ACCEPT; -import static io.oeid.mogakgo.exception.code.ErrorCode400.INVALID_PROJECT_JOIN_REQUEST_REGION; import static io.oeid.mogakgo.exception.code.ErrorCode400.INVALID_SENDER_TO_ACCEPT; -import static io.oeid.mogakgo.exception.code.ErrorCode400.PROJECT_JOIN_REQUEST_ALREADY_EXIST; import static io.oeid.mogakgo.exception.code.ErrorCode400.PROJECT_JOIN_REQUEST_SHOULD_BE_ONLY_ONE; import static io.oeid.mogakgo.exception.code.ErrorCode403.PROJECT_JOIN_REQUEST_FORBIDDEN_OPERATION; import static io.oeid.mogakgo.exception.code.ErrorCode404.PROJECT_JOIN_REQUEST_NOT_FOUND; import static io.oeid.mogakgo.exception.code.ErrorCode404.PROJECT_NOT_FOUND; import static io.oeid.mogakgo.exception.code.ErrorCode404.USER_NOT_FOUND; -import io.oeid.mogakgo.exception.code.ErrorCode400; import io.oeid.mogakgo.common.base.CursorPaginationInfoReq; import io.oeid.mogakgo.common.base.CursorPaginationResult; import io.oeid.mogakgo.domain.matching.application.MatchingService; import io.oeid.mogakgo.domain.matching.application.UserMatchingService; import io.oeid.mogakgo.domain.project.domain.entity.Project; -import io.oeid.mogakgo.domain.project.domain.entity.enums.ProjectStatus; import io.oeid.mogakgo.domain.project.exception.ProjectException; import io.oeid.mogakgo.domain.project.infrastructure.ProjectJpaRepository; import io.oeid.mogakgo.domain.project_join_req.application.dto.req.ProjectJoinCreateReq; @@ -29,7 +24,6 @@ import io.oeid.mogakgo.domain.user.domain.User; import io.oeid.mogakgo.domain.user.exception.UserException; import io.oeid.mogakgo.domain.user.infrastructure.UserJpaRepository; -import java.util.Objects; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -50,13 +44,11 @@ public class ProjectJoinRequestService { @Transactional public Long create(Long userId, ProjectJoinCreateReq request) { User tokenUser = validateToken(userId); - validateSender(tokenUser, request.getSenderId()); - Project project = validateProjectExist(request.getProjectId()); - validateProjectStatus(project.getProjectStatus()); // 요청을 보내려는 프로젝트가 대기중이 맞는지 검사 - validateProjectCreator(project, userId); - validateUserCertRegion(project, tokenUser); - validateAlreadyExistRequest(userId, project.getId()); - validateUserAnotherRequestExists(userId); + + Project project = validateProject(request.getProjectId()); + + // 다른 프로젝트에 매칭 요청을 보낸 적이 있는지 검증 (매칭은 한 번에 하나만 가능하므로) + validateAnotherRequestAlreadyExist(userId); // 프로젝트 매칭 요청 생성 ProjectJoinRequest joinRequest = request.toEntity(tokenUser, project); @@ -140,37 +132,13 @@ private void validateSender(User tokenUser, Long userId) { } } - private Project validateProjectExist(Long projectId) { + private Project validateProject(Long projectId) { return projectRepository.findById(projectId) .orElseThrow(() -> new ProjectException(PROJECT_NOT_FOUND)); } - private void validateProjectStatus(ProjectStatus projectStatus) { - if (!projectStatus.equals(ProjectStatus.PENDING)) { - throw new ProjectJoinRequestException(ErrorCode400.INVALID_PROJECT_STATUS_TO_ACCEPT); - } - } - - private void validateProjectCreator(Project project, Long userId) { - if (project.getCreator().getId().equals(userId)) { - throw new ProjectJoinRequestException(INVALID_CREATOR_PROJECT_JOIN_REQUEST); - } - } - - private void validateAlreadyExistRequest(Long userId, Long projectId) { - if (projectJoinRequestRepository.findAlreadyExists(userId, projectId).isPresent()) { - throw new ProjectJoinRequestException(PROJECT_JOIN_REQUEST_ALREADY_EXIST); - } - } - - private void validateUserCertRegion(Project project, User tokenUser) { - if (!Objects.equals(project.getCreatorInfo().getRegion(), tokenUser.getRegion())) { - throw new ProjectJoinRequestException(INVALID_PROJECT_JOIN_REQUEST_REGION); - } - } - - private void validateUserAnotherRequestExists(Long userId) { - if (projectJoinRequestRepository.findAlreadyExistsAnotherJoinReq(userId).isPresent()) { + private void validateAnotherRequestAlreadyExist(Long userId) { + if (projectJoinRequestRepository.findAnotherRequestAlreadyExist(userId).isPresent()) { throw new ProjectJoinRequestException(PROJECT_JOIN_REQUEST_SHOULD_BE_ONLY_ONE); } } diff --git a/src/main/java/io/oeid/mogakgo/domain/project_join_req/domain/entity/ProjectJoinRequest.java b/src/main/java/io/oeid/mogakgo/domain/project_join_req/domain/entity/ProjectJoinRequest.java index 79f06ce1..33c50df1 100644 --- a/src/main/java/io/oeid/mogakgo/domain/project_join_req/domain/entity/ProjectJoinRequest.java +++ b/src/main/java/io/oeid/mogakgo/domain/project_join_req/domain/entity/ProjectJoinRequest.java @@ -1,8 +1,12 @@ package io.oeid.mogakgo.domain.project_join_req.domain.entity; +import static io.oeid.mogakgo.exception.code.ErrorCode400.INVALID_CREATOR_PROJECT_JOIN_REQUEST; +import static io.oeid.mogakgo.exception.code.ErrorCode400.INVALID_PROJECT_JOIN_REQUEST_REGION; import static io.oeid.mogakgo.exception.code.ErrorCode403.PROJECT_JOIN_REQUEST_FORBIDDEN_OPERATION; +import static io.oeid.mogakgo.exception.code.ErrorCode404.PROJECT_NOT_FOUND; import io.oeid.mogakgo.domain.project.domain.entity.Project; +import io.oeid.mogakgo.domain.project.exception.ProjectException; import io.oeid.mogakgo.domain.project_join_req.domain.entity.enums.RequestStatus; import io.oeid.mogakgo.domain.project_join_req.exception.ProjectJoinRequestException; import io.oeid.mogakgo.domain.user.domain.User; @@ -59,6 +63,10 @@ private ProjectJoinRequest(User sender, Project project) { this.sender = sender; this.project = project; this.requestStatus = RequestStatus.PENDING; + validateSender(sender); + validateProject(project); + validateProjectCreator(); + validateUserRegion(); } public static ProjectJoinRequest of(User sender, Project project) { @@ -92,4 +100,23 @@ private void validateSender(User user) { throw new ProjectJoinRequestException(PROJECT_JOIN_REQUEST_FORBIDDEN_OPERATION); } } + + private void validateProjectCreator() { + if (project.getCreator().getId().equals(sender.getId())) { + throw new ProjectJoinRequestException(INVALID_CREATOR_PROJECT_JOIN_REQUEST); + } + } + + private void validateUserRegion() { + if (!project.getCreatorInfo().getRegion().equals(sender.getRegion())) { + throw new ProjectJoinRequestException(INVALID_PROJECT_JOIN_REQUEST_REGION); + } + } + + private void validateProject(Project project) { + if (!this.project.getId().equals(project.getId())) { + throw new ProjectException(PROJECT_NOT_FOUND); + } + this.project.validateAvailableMatched(); + } } diff --git a/src/main/java/io/oeid/mogakgo/domain/project_join_req/infrastructure/ProjectJoinRequestJpaRepository.java b/src/main/java/io/oeid/mogakgo/domain/project_join_req/infrastructure/ProjectJoinRequestJpaRepository.java index 78279e69..8f56c8c1 100644 --- a/src/main/java/io/oeid/mogakgo/domain/project_join_req/infrastructure/ProjectJoinRequestJpaRepository.java +++ b/src/main/java/io/oeid/mogakgo/domain/project_join_req/infrastructure/ProjectJoinRequestJpaRepository.java @@ -17,11 +17,8 @@ public interface ProjectJoinRequestJpaRepository extends JpaRepository findPendingBySenderId(Long senderId); - @Query("select pjr from ProjectJoinRequest pjr where pjr.sender.id = :userId and pjr.project.id = :projectId") - Optional findAlreadyExists(Long userId, Long projectId); - @Query("select pjr from ProjectJoinRequest pjr where pjr.sender.id = :userId and pjr.requestStatus = 'PENDING'") - Optional findAlreadyExistsAnotherJoinReq(Long userId); + Optional findAnotherRequestAlreadyExist(Long userId); @Modifying @Query("update ProjectJoinRequest pjr set pjr.requestStatus = 'REJECTED' " diff --git a/src/main/java/io/oeid/mogakgo/domain/user/application/UserService.java b/src/main/java/io/oeid/mogakgo/domain/user/application/UserService.java index 720d76a5..74c0811a 100644 --- a/src/main/java/io/oeid/mogakgo/domain/user/application/UserService.java +++ b/src/main/java/io/oeid/mogakgo/domain/user/application/UserService.java @@ -1,5 +1,7 @@ package io.oeid.mogakgo.domain.user.application; +import io.oeid.mogakgo.domain.profile.application.ProfileCardService; +import io.oeid.mogakgo.domain.profile.application.dto.req.UserProfileCardReq; import io.oeid.mogakgo.domain.user.application.dto.req.UserSignUpRequest; import io.oeid.mogakgo.domain.user.application.dto.res.UserDevelopLanguageRes; import io.oeid.mogakgo.domain.user.application.dto.res.UserProfileResponse; @@ -28,6 +30,7 @@ public class UserService { private final UserCommonService userCommonService; + private final ProfileCardService profileCardService; private final UserWantedJobTagJpaRepository userWantedJobTagRepository; private final UserDevelopLanguageTagJpaRepository userDevelopLanguageTagRepository; private final UserGithubUtil userGithubUtil; @@ -44,6 +47,7 @@ public UserSignUpResponse userSignUp(UserSignUpRequest userSignUpRequest) { .build()); } user.signUpComplete(); + profileCardService.create(UserProfileCardReq.of(user)); return UserSignUpResponse.from(user); } diff --git a/src/main/java/io/oeid/mogakgo/domain/user/domain/User.java b/src/main/java/io/oeid/mogakgo/domain/user/domain/User.java index b4847918..8bd19056 100644 --- a/src/main/java/io/oeid/mogakgo/domain/user/domain/User.java +++ b/src/main/java/io/oeid/mogakgo/domain/user/domain/User.java @@ -1,5 +1,6 @@ package io.oeid.mogakgo.domain.user.domain; +import static io.oeid.mogakgo.exception.code.ErrorCode400.USER_AVAILABLE_LIKE_AMOUNT_IS_FULL; import static io.oeid.mogakgo.exception.code.ErrorCode400.USER_AVAILABLE_LIKE_COUNT_IS_ZERO; import io.oeid.mogakgo.domain.achievement.domain.Achievement; @@ -41,6 +42,7 @@ public class User { private static final int MAX_TAG_SIZE = 3; + private static final int MAX_AVAILABLE_LIKE_COUNT = 10; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -157,6 +159,13 @@ public void updateUsername(String username) { this.username = username; } + public void increaseAvailableLikeCount() { + if (this.availableLikeCount >= MAX_AVAILABLE_LIKE_COUNT) { + throw new UserException(USER_AVAILABLE_LIKE_AMOUNT_IS_FULL); + } + this.availableLikeCount += 1; + } + public void decreaseAvailableLikeCount() { if (this.availableLikeCount <= 0) { throw new UserException(USER_AVAILABLE_LIKE_COUNT_IS_ZERO); @@ -184,12 +193,19 @@ public void updateRegion(Region region) { if (region == null) { throw new UserException(ErrorCode400.USER_REGION_SHOULD_BE_NOT_EMPTY); } - this.region = region; - this.regionAuthenticationAt = LocalDateTime.now(); + // 사용자가 아직 동네 인증을 하지 않았거나, 새롭게 인증하려는 지역이 이미 인증된 지역과 다를 경우만 동네 인증 처리 + if (validateAvailableRegionUpdate(region)) { + this.region = region; + this.regionAuthenticationAt = LocalDateTime.now(); + } } //TODO : 추후 구현 필요 public void decreaseJandiRate() { return; } + + private boolean validateAvailableRegionUpdate(Region region) { + return this.region == null || !this.region.equals(region); + } } diff --git a/src/main/java/io/oeid/mogakgo/exception/code/ErrorCode400.java b/src/main/java/io/oeid/mogakgo/exception/code/ErrorCode400.java index fb1121be..1b65c942 100644 --- a/src/main/java/io/oeid/mogakgo/exception/code/ErrorCode400.java +++ b/src/main/java/io/oeid/mogakgo/exception/code/ErrorCode400.java @@ -31,11 +31,14 @@ public enum ErrorCode400 implements ErrorCode { USER_REGION_SHOULD_BE_NOT_EMPTY("E020104", "유저 지역은 비어있을 수 없습니다."), USER_WANTED_JOB_DUPLICATE("E020105", "중복된 희망 직무가 있습니다."), USER_DEVELOP_LANGUAGE_DUPLICATE("E020106", "중복된 개발 언어가 있습니다."), - USER_AVAILABLE_LIKE_COUNT_IS_ZERO("E020105", "당일 사용할 수 있는 찔러보기 요청을 모두 소진했습니다."), + USER_AVAILABLE_LIKE_COUNT_IS_ZERO("E020107", "당일 사용할 수 있는 찔러보기 요청을 모두 소진했습니다."), + USER_AVAILABLE_LIKE_AMOUNT_IS_FULL("E020108", "프로필 카드의 찔러보기 요청의 최대 횟수를 넘을 수 없습니다."), USER_ID_NOT_NULL("E020001", "유저 아이디는 필수값입니다."), PROFILE_CARD_LIKE_ALREADY_EXIST("E040102", "이미 찔러보기 요청을 전송한 프로필 카드에 찔러보기 요청을 전송할 수 없습니다."), INVALID_PROFILE_CARD_LIKE_RECEIVER_INFO("E040103", "찔러보기 요청의 사용자가 존재하지 않습니다."), + PROFILE_CARD_LIKE_AMOUNT_IS_ZERO("E040104", "프로필 카드의 찔러보기 요청이 존재하지 않습니다."), + PROFILE_CARD_LIKE_NOT_EXIST("E040105", "해당 프로필 카드에 '찔러보기' 요청이 존재하지 않아 요청을 취소할 수 없습니다."), INVALID_PROJECT_STATUS_TO_ACCEPT("E050101", "프로젝트가 대기중이 아니여서 참여 요청을 수락할 수 없습니다."), INVALID_PROJECT_REQ_STATUS_TO_ACCEPT("E050102", "프로젝트 참여 요청이 대기중이 아니여서 참여 요청을 수락할 수 없습니다."), diff --git a/src/main/java/io/oeid/mogakgo/exception/code/ErrorCode404.java b/src/main/java/io/oeid/mogakgo/exception/code/ErrorCode404.java index 354e25b5..58f9ee41 100644 --- a/src/main/java/io/oeid/mogakgo/exception/code/ErrorCode404.java +++ b/src/main/java/io/oeid/mogakgo/exception/code/ErrorCode404.java @@ -7,6 +7,7 @@ public enum ErrorCode404 implements ErrorCode { USER_NOT_FOUND("E020301", "해당 유저가 존재하지 않습니다."), PROJECT_NOT_FOUND("E030301", "해당 프로젝트가 존재하지 않습니다."), + PROFILE_CARD_NOT_FOUND("E040301", "해당 프로필 카드가 존재하지 않습니다."), NOTIFICATION_FCM_TOKEN_NOT_FOUND("E060301", "해당 유저의 FCM 토큰이 존재하지 않습니다."), PROJECT_JOIN_REQUEST_NOT_FOUND("E050301", "해당 프로젝트 참여 요청이 존재하지 않습니다."), MATCHING_NOT_FOUND("E090301", "해당 매칭이 존재하지 않습니다."),