From d8cc31d20c7728baf43da21ca10e9ba11c0b1e90 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Sat, 9 Mar 2024 17:16:19 +0900 Subject: [PATCH 01/51] =?UTF-8?q?feat:=20=ED=99=94=EC=82=B4=20=EB=B3=B4?= =?UTF-8?q?=EB=82=B4=EA=B8=B0=20=EC=9A=94=EC=B2=AD=20DTO=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 보낼 화살 수를 담은 요청 DTO 정의 --- .../be/arrow/dto/request/ArrowSendRequest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 be/src/main/java/yeonba/be/arrow/dto/request/ArrowSendRequest.java diff --git a/be/src/main/java/yeonba/be/arrow/dto/request/ArrowSendRequest.java b/be/src/main/java/yeonba/be/arrow/dto/request/ArrowSendRequest.java new file mode 100644 index 00000000..8e3616e9 --- /dev/null +++ b/be/src/main/java/yeonba/be/arrow/dto/request/ArrowSendRequest.java @@ -0,0 +1,18 @@ +package yeonba.be.arrow.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Positive; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class ArrowSendRequest { + + @Schema( + type = "number", + description = "보낼 화살 수", + example = "10") + @Positive(message = "화살은 1개 이상부터 보낼 수 있습니다.") + private int arrows; +} From 9b490938f7a9ebb22dea55136d6891231fc39596 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Sat, 9 Mar 2024 17:18:29 +0900 Subject: [PATCH 02/51] =?UTF-8?q?feat:=20=ED=99=95=EC=82=B4=20=EC=86=A1?= =?UTF-8?q?=EC=88=98=EC=8B=A0=20=EB=82=B4=EC=97=AD=20repository,=20?= =?UTF-8?q?=EB=82=B4=EC=97=AD=20=ED=99=95=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 화살 송수신 내역 repository 정의 - 보낸 사용자, 받은 사용자를 통해 내역 존재 여부를 확인하는 로직 구현 --- .../arrow/repository/ArrowTransactionRepository.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 be/src/main/java/yeonba/be/arrow/repository/ArrowTransactionRepository.java diff --git a/be/src/main/java/yeonba/be/arrow/repository/ArrowTransactionRepository.java b/be/src/main/java/yeonba/be/arrow/repository/ArrowTransactionRepository.java new file mode 100644 index 00000000..9dbd2c18 --- /dev/null +++ b/be/src/main/java/yeonba/be/arrow/repository/ArrowTransactionRepository.java @@ -0,0 +1,12 @@ +package yeonba.be.arrow.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import yeonba.be.arrow.entity.ArrowTransaction; +import yeonba.be.user.entity.User; + +@Repository +public interface ArrowTransactionRepository extends JpaRepository { + + boolean existsBySentUserAndReceivedUser(User sentUser, User receivedUser); +} From e953b8531b3eaeb55d1946a0f4ecaba75edd787b Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Sat, 9 Mar 2024 17:23:08 +0900 Subject: [PATCH 03/51] =?UTF-8?q?feat:=20=ED=99=94=EC=82=B4=20=EC=86=A1?= =?UTF-8?q?=EC=88=98=EC=8B=A0=20=EB=82=B4=EC=97=AD=20=EC=A1=B4=EC=9E=AC=20?= =?UTF-8?q?=EC=97=AC=EB=B6=80=20=ED=99=95=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 보낸 사용자, 받은 사용자를 통해 송수신 내역 존재 여부를 확인하는 로직 구현 --- .../yeonba/be/arrow/repository/ArrowQuery.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 be/src/main/java/yeonba/be/arrow/repository/ArrowQuery.java diff --git a/be/src/main/java/yeonba/be/arrow/repository/ArrowQuery.java b/be/src/main/java/yeonba/be/arrow/repository/ArrowQuery.java new file mode 100644 index 00000000..7f67cc48 --- /dev/null +++ b/be/src/main/java/yeonba/be/arrow/repository/ArrowQuery.java @@ -0,0 +1,18 @@ +package yeonba.be.arrow.repository; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import yeonba.be.user.entity.User; + +@Component +@RequiredArgsConstructor +public class ArrowQuery { + + private final ArrowTransactionRepository arrowTransactionRepository; + + public boolean existsBySentUserAndReceivedUser(User sentUser, User receivedUser) { + + return arrowTransactionRepository + .existsBySentUserAndReceivedUser(sentUser, receivedUser); + } +} From 7a7b7c36928590672539dde4d38369a2a4128a87 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Sat, 9 Mar 2024 17:23:36 +0900 Subject: [PATCH 04/51] =?UTF-8?q?feat:=20=ED=99=94=EC=82=B4=20=EC=86=A1?= =?UTF-8?q?=EC=88=98=EC=8B=A0=20=EB=82=B4=EC=97=AD=20=EC=A0=80=EC=9E=A5=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../be/arrow/repository/ArrowCommand.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 be/src/main/java/yeonba/be/arrow/repository/ArrowCommand.java diff --git a/be/src/main/java/yeonba/be/arrow/repository/ArrowCommand.java b/be/src/main/java/yeonba/be/arrow/repository/ArrowCommand.java new file mode 100644 index 00000000..2b4f04a3 --- /dev/null +++ b/be/src/main/java/yeonba/be/arrow/repository/ArrowCommand.java @@ -0,0 +1,18 @@ +package yeonba.be.arrow.repository; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import yeonba.be.arrow.entity.ArrowTransaction; + +@Component +@RequiredArgsConstructor +public class ArrowCommand { + + private final ArrowTransactionRepository arrowTransactionRepository; + + public ArrowTransaction save(ArrowTransaction arrowTransaction){ + + return arrowTransactionRepository.save(arrowTransaction); + } + +} From d781de37c3c4fea5d05a1ce7ac2e71beb2de7976 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Sat, 9 Mar 2024 17:25:05 +0900 Subject: [PATCH 05/51] =?UTF-8?q?feat:=20=ED=99=94=EC=82=B4=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=98=88=EC=99=B8=20enum=20=EC=A0=95=EC=9D=98(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 다음 상황들에 대한 예외 enum을 정의하였다. - 자기 자신에게 화살을 보내는 경우 - 이미 화살을 보낸 사용자인 경우 - 가진 화살 수가 부족해 화살을 보낼 수 없는 경우 --- .../java/yeonba/be/exception/ExceptionType.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/be/src/main/java/yeonba/be/exception/ExceptionType.java b/be/src/main/java/yeonba/be/exception/ExceptionType.java index 6af501e6..5184a778 100644 --- a/be/src/main/java/yeonba/be/exception/ExceptionType.java +++ b/be/src/main/java/yeonba/be/exception/ExceptionType.java @@ -16,7 +16,20 @@ public enum ExceptionType { USER_NOT_FOUND( HttpStatus.BAD_REQUEST, - "해당 사용자가 존재하지 않습니다."); + "해당 사용자가 존재하지 않습니다."), + + // arrow + CAN_NOT_SEND_ARROW_SELF( + HttpStatus.BAD_REQUEST, + "자기 자신에게 화살을 보낼 수 없습니다."), + + ALREADY_SENT_ARROW_USER( + HttpStatus.BAD_REQUEST, + "이미 화살을 보낸 사용자입니다."), + + NOT_ENOUGH_ARROW_TO_SEND( + HttpStatus.BAD_REQUEST, + "가진 화살 수가 부족해 화살을 보낼 수 없습니다."); private final HttpStatus httpStatus; private final String reason; From 8aad1e4c7b65bd8bae676928edc13578fe7ce7e7 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Sat, 9 Mar 2024 17:27:00 +0900 Subject: [PATCH 06/51] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=ED=99=94=EC=82=B4=20=EA=B0=90=EC=86=8C=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 감소하려는 화살 수가 사용자가 보유한 화살 수보다 클 경우 예외 발생 --- be/src/main/java/yeonba/be/user/entity/User.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/be/src/main/java/yeonba/be/user/entity/User.java b/be/src/main/java/yeonba/be/user/entity/User.java index 10e3288d..0dbbf53a 100644 --- a/be/src/main/java/yeonba/be/user/entity/User.java +++ b/be/src/main/java/yeonba/be/user/entity/User.java @@ -15,6 +15,8 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; +import yeonba.be.exception.ExceptionType; +import yeonba.be.exception.GeneralException; @Table(name = "users") @Getter @@ -125,4 +127,12 @@ public void addArrow(int arrow) { this.arrow += arrow; } + + public void minusArrow(int arrow) { + if (this.arrow < arrow) { + throw new GeneralException(ExceptionType.NOT_ENOUGH_ARROW_TO_SEND); + } + + this.arrow -= arrow; + } } From 7dd07aa33e64a3cdf4c3b407bbe8128db154d98e Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Sat, 9 Mar 2024 17:28:34 +0900 Subject: [PATCH 07/51] =?UTF-8?q?refactor:=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=20=ED=95=84=EB=93=9C,=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20?= =?UTF-8?q?=EB=B6=88=EA=B0=80=20DDL=20=EC=86=8D=EC=84=B1=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- be/src/main/java/yeonba/be/arrow/entity/ArrowTransaction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/be/src/main/java/yeonba/be/arrow/entity/ArrowTransaction.java b/be/src/main/java/yeonba/be/arrow/entity/ArrowTransaction.java index aa44810b..5ca5fda9 100644 --- a/be/src/main/java/yeonba/be/arrow/entity/ArrowTransaction.java +++ b/be/src/main/java/yeonba/be/arrow/entity/ArrowTransaction.java @@ -39,7 +39,7 @@ public class ArrowTransaction { private int arrows; @CreatedDate - @Column(nullable = false, updatable = false) + @Column(nullable = false) private LocalDateTime createdAt; public ArrowTransaction( From 3a6137d9c51c1ebba4b5847003158f9fce20948b Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Sat, 9 Mar 2024 17:32:18 +0900 Subject: [PATCH 08/51] =?UTF-8?q?feat:=20=ED=99=94=EC=82=B4=20=EB=B3=B4?= =?UTF-8?q?=EB=82=B4=EA=B8=B0=20=EB=B9=84=EC=A6=88=EB=8B=88=EC=8A=A4=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 화살 보내기 비즈니스 로직은 다음 과정을 거친다. 1. 자기 자신에게 화살을 보내는 상황 검증 2. 화살을 보낸 사용자에게 또 보내는 상황 검증 3. 화살 내역 저장 4. 보내는 사용자 화살 감소, 화살이 부족할 경우 예외 발생(과정 3 롤백) 5. 받는 사용자 화살 증가 --- .../yeonba/be/arrow/service/ArrowService.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/be/src/main/java/yeonba/be/arrow/service/ArrowService.java b/be/src/main/java/yeonba/be/arrow/service/ArrowService.java index 40220e8a..3a962bd0 100644 --- a/be/src/main/java/yeonba/be/arrow/service/ArrowService.java +++ b/be/src/main/java/yeonba/be/arrow/service/ArrowService.java @@ -5,8 +5,13 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import yeonba.be.arrow.dto.request.ArrowSendRequest; import yeonba.be.arrow.entity.ArrowTransaction; +import yeonba.be.arrow.repository.ArrowCommand; +import yeonba.be.arrow.repository.ArrowQuery; import yeonba.be.arrow.repository.ArrowTransactionCommandRepository; +import yeonba.be.exception.ExceptionType; +import yeonba.be.exception.GeneralException; import yeonba.be.user.entity.User; import yeonba.be.user.service.UserService; @@ -17,6 +22,8 @@ public class ArrowService { private final int DAILY_CHECK_ARROW_COUNT = 10; private final UserService userService; private final ArrowTransactionCommandRepository arrowTransactionCommandRepository; + private final ArrowQuery arrowQuery; + private final ArrowCommand arrowCommand; /* 출석 체크는 다음 과정을 거쳐 이뤄진다. @@ -46,10 +53,54 @@ public void dailyCheck(long userId) { dailyCheckUser.addArrow(DAILY_CHECK_ARROW_COUNT); } + /* + 화살 보내기 비즈니스 로직은 다음 과정을 거친다. + 1. 자기 자신에게 화살을 보내는 상황 검증 + 2. 화살을 보낸 사용자에게 또 보내는 상황 검증 + 3. 화살 내역 저장 + 4. 보내는 사용자 화살 감소, 화살이 부족할 경우 예외 발생 + 5. 받는 사용자 화살 증가 + + */ + @Transactional + public void sendArrow( + long senderId, + long recipientId, + ArrowSendRequest request) { + + if (senderId == recipientId) { + throw new GeneralException(ExceptionType.CAN_NOT_SEND_ARROW_SELF); + } + + User sender = userService.findById(senderId); + User recipient = userService.findById(recipientId); + + if (isAlreadySentArrowUser(sender, recipient)) { + throw new GeneralException(ExceptionType.ALREADY_SENT_ARROW_USER); + } + + int arrows = request.getArrows(); + ArrowTransaction arrowTransaction = new ArrowTransaction( + sender, + recipient, + arrows); + arrowCommand.save(arrowTransaction); + + sender.minusArrow(arrows); + recipient.addArrow(arrows); + } + private boolean isAlreadyCheckedUser(User user, LocalDate dailyCheckDate) { return user.getLastAccessedAt() .toLocalDate() .equals(dailyCheckDate); } + + private boolean isAlreadySentArrowUser(User sender, User recipient) { + + return arrowQuery + .existsBySentUserAndReceivedUser(sender, recipient); + } + } From 9a4cae04739eeeaef84c58ccd94614c5edb4a122 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Sat, 9 Mar 2024 17:33:09 +0900 Subject: [PATCH 09/51] =?UTF-8?q?feat:=20=ED=99=94=EC=82=B4=20=EB=B3=B4?= =?UTF-8?q?=EB=82=B4=EA=B8=B0=20API=20=EA=B5=AC=ED=98=84(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../be/arrow/controller/ArrowController.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/be/src/main/java/yeonba/be/arrow/controller/ArrowController.java b/be/src/main/java/yeonba/be/arrow/controller/ArrowController.java index e09fcf72..0f2ef38e 100644 --- a/be/src/main/java/yeonba/be/arrow/controller/ArrowController.java +++ b/be/src/main/java/yeonba/be/arrow/controller/ArrowController.java @@ -10,8 +10,10 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestAttribute; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import yeonba.be.arrow.dto.UserArrowsResponse; +import yeonba.be.arrow.dto.request.ArrowSendRequest; import yeonba.be.arrow.service.ArrowService; import yeonba.be.util.CustomResponse; @@ -51,18 +53,19 @@ public ResponseEntity> dailyCheck( .body(new CustomResponse<>()); } - @Operation( - summary = "화살 보내기", - description = "다른 사용자에게 화살을 보낼 수 있습니다." - ) - @ApiResponse( - responseCode = "204", - description = "화살 전송 정상 처리" - ) + @Operation(summary = "화살 보내기", description = "다른 사용자에게 화살을 보낼 수 있습니다.") + @ApiResponse(responseCode = "202", description = "화살 전송 정상 처리") @PostMapping("/users/{userId}/arrow") public ResponseEntity> sendArrow( + @RequestAttribute("userId") long senderId, @Parameter(description = "화살 받는 사용자 ID", example = "1") - @PathVariable long userId) { + @PathVariable("userId") long recipientId, + @RequestBody ArrowSendRequest request) { + + arrowService.sendArrow( + senderId, + recipientId, + request); return ResponseEntity .accepted() From 52995c4edb1140aee28cb4469dffc75695d05bd2 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Sat, 9 Mar 2024 17:36:21 +0900 Subject: [PATCH 10/51] =?UTF-8?q?refactor:=20=ED=99=94=EC=82=B4=20command?= =?UTF-8?q?=20repository=20=EC=82=AD=EC=A0=9C(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - query, command를 service, repository를 중개하는 별도의 계층을 통해 구분 - 이에 따라 기존 command repository 삭제 - 기존 command repository 사용 위치, 화살 command로 대체 --- .../repository/ArrowTransactionCommandRepository.java | 10 ---------- .../java/yeonba/be/arrow/service/ArrowService.java | 4 +--- 2 files changed, 1 insertion(+), 13 deletions(-) delete mode 100644 be/src/main/java/yeonba/be/arrow/repository/ArrowTransactionCommandRepository.java diff --git a/be/src/main/java/yeonba/be/arrow/repository/ArrowTransactionCommandRepository.java b/be/src/main/java/yeonba/be/arrow/repository/ArrowTransactionCommandRepository.java deleted file mode 100644 index c90715a3..00000000 --- a/be/src/main/java/yeonba/be/arrow/repository/ArrowTransactionCommandRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package yeonba.be.arrow.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; -import yeonba.be.arrow.entity.ArrowTransaction; - -@Repository -public interface ArrowTransactionCommandRepository extends JpaRepository { - -} diff --git a/be/src/main/java/yeonba/be/arrow/service/ArrowService.java b/be/src/main/java/yeonba/be/arrow/service/ArrowService.java index 3a962bd0..0c30f178 100644 --- a/be/src/main/java/yeonba/be/arrow/service/ArrowService.java +++ b/be/src/main/java/yeonba/be/arrow/service/ArrowService.java @@ -9,7 +9,6 @@ import yeonba.be.arrow.entity.ArrowTransaction; import yeonba.be.arrow.repository.ArrowCommand; import yeonba.be.arrow.repository.ArrowQuery; -import yeonba.be.arrow.repository.ArrowTransactionCommandRepository; import yeonba.be.exception.ExceptionType; import yeonba.be.exception.GeneralException; import yeonba.be.user.entity.User; @@ -21,7 +20,6 @@ public class ArrowService { private final int DAILY_CHECK_ARROW_COUNT = 10; private final UserService userService; - private final ArrowTransactionCommandRepository arrowTransactionCommandRepository; private final ArrowQuery arrowQuery; private final ArrowCommand arrowCommand; @@ -47,7 +45,7 @@ public void dailyCheck(long userId) { dailyCheckUser, DAILY_CHECK_ARROW_COUNT ); - arrowTransactionCommandRepository.save(arrowTransaction); + arrowCommand.save(arrowTransaction); dailyCheckUser.updateLastAccessedAt(dailyCheckedAt); dailyCheckUser.addArrow(DAILY_CHECK_ARROW_COUNT); From a2c164d32a4e586df415a65db3ef00b5d592784f Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Sat, 9 Mar 2024 20:44:15 +0900 Subject: [PATCH 11/51] =?UTF-8?q?refactor:=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=A0=95=EB=A0=AC=20=EC=88=98=EC=A0=95(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- be/src/main/java/yeonba/be/arrow/service/ArrowService.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/be/src/main/java/yeonba/be/arrow/service/ArrowService.java b/be/src/main/java/yeonba/be/arrow/service/ArrowService.java index 0c30f178..83e55d39 100644 --- a/be/src/main/java/yeonba/be/arrow/service/ArrowService.java +++ b/be/src/main/java/yeonba/be/arrow/service/ArrowService.java @@ -97,8 +97,7 @@ private boolean isAlreadyCheckedUser(User user, LocalDate dailyCheckDate) { private boolean isAlreadySentArrowUser(User sender, User recipient) { - return arrowQuery - .existsBySentUserAndReceivedUser(sender, recipient); + return arrowQuery.existsBySentUserAndReceivedUser(sender, recipient); } } From 6c7ff1de0a2d4ac746ee49c0ea95d3f2c583ade0 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Mon, 11 Mar 2024 19:50:23 +0900 Subject: [PATCH 12/51] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=20=ED=99=94=EC=82=B4=20=EC=A6=9D=EA=B0=90=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=9D=B4=EB=A6=84=20=EC=88=98=EC=A0=95(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 서로 대치되는 작업을 수행하는 상황을 잘 표현할 수 있도록 메서드 이름 수정 --- be/src/main/java/yeonba/be/user/entity/User.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/be/src/main/java/yeonba/be/user/entity/User.java b/be/src/main/java/yeonba/be/user/entity/User.java index 7439b44f..f172d9bd 100644 --- a/be/src/main/java/yeonba/be/user/entity/User.java +++ b/be/src/main/java/yeonba/be/user/entity/User.java @@ -146,8 +146,17 @@ public void updateLastAccessedAt(LocalDateTime accessedAt) { this.lastAccessedAt = accessedAt; } - public void addArrow(int arrow) { + public void plusArrow(int arrow) { this.arrow += arrow; } + + public void minusArrow(int arrow) { + + if (this.arrow < arrow) { + throw new GeneralException(ArrowException.NOT_ENOUGH_ARROW_TO_SEND); + } + + this.arrow -= arrow; + } } From 01f31382f573b52c3047e77c7ecc4c41094df8c0 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Mon, 11 Mar 2024 19:51:39 +0900 Subject: [PATCH 13/51] =?UTF-8?q?feat:=20=ED=99=94=EC=82=B4=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=98=88=EC=99=B8=20enum=20=EC=B6=94=EA=B0=80(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 이미 화살을 보낸 사용자 예외 - 화살이 부족하여 화살을 보낼 수 없는 예외 --- .../main/java/yeonba/be/exception/ArrowException.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/be/src/main/java/yeonba/be/exception/ArrowException.java b/be/src/main/java/yeonba/be/exception/ArrowException.java index 4d6c31db..7216212d 100644 --- a/be/src/main/java/yeonba/be/exception/ArrowException.java +++ b/be/src/main/java/yeonba/be/exception/ArrowException.java @@ -6,7 +6,15 @@ public enum ArrowException implements BaseException { ALREADY_CHECKED_USER( HttpStatus.BAD_REQUEST, - "이미 출석 체크한 사용자입니다."); + "이미 출석 체크한 사용자입니다."), + + ALREADY_SENT_ARROW_USER( + HttpStatus.BAD_REQUEST, + "이미 화살을 보낸 사용자입니다."), + + NOT_ENOUGH_ARROW_TO_SEND( + HttpStatus.BAD_REQUEST, + "화살이 부족하여 화살을 보낼 수 없습니다."); private final HttpStatus httpStatus; private final String reason; From 95c99d827b1617897cef1966a7e365bdfa6c87c4 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Mon, 11 Mar 2024 19:52:19 +0900 Subject: [PATCH 14/51] =?UTF-8?q?refactor:=20=ED=99=94=EC=82=B4=20?= =?UTF-8?q?=EB=82=B4=EC=97=AD=20=EC=A1=B4=EC=9E=AC=20=EC=97=AC=EB=B6=80=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EC=88=98=EC=A0=95(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 가독성 향상을 위해 간단한 이름을 수정 --- be/src/main/java/yeonba/be/arrow/repository/ArrowQuery.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/be/src/main/java/yeonba/be/arrow/repository/ArrowQuery.java b/be/src/main/java/yeonba/be/arrow/repository/ArrowQuery.java index 7f67cc48..14b5a1e9 100644 --- a/be/src/main/java/yeonba/be/arrow/repository/ArrowQuery.java +++ b/be/src/main/java/yeonba/be/arrow/repository/ArrowQuery.java @@ -10,7 +10,7 @@ public class ArrowQuery { private final ArrowTransactionRepository arrowTransactionRepository; - public boolean existsBySentUserAndReceivedUser(User sentUser, User receivedUser) { + public boolean isArrowTransactionExist(User sentUser, User receivedUser) { return arrowTransactionRepository .existsBySentUserAndReceivedUser(sentUser, receivedUser); From 672f7bb80b94464dddb262d64fd65e1b734e64c8 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Mon, 11 Mar 2024 19:53:26 +0900 Subject: [PATCH 15/51] =?UTF-8?q?refactor:=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EB=B0=B0=EC=B9=98=20=EC=88=98=EC=A0=95(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dev 브랜치 작업 내역 병합에 따른 코드 배치 수정 --- .../yeonba/be/arrow/service/ArrowService.java | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/be/src/main/java/yeonba/be/arrow/service/ArrowService.java b/be/src/main/java/yeonba/be/arrow/service/ArrowService.java index 4d89c075..4097ae2a 100644 --- a/be/src/main/java/yeonba/be/arrow/service/ArrowService.java +++ b/be/src/main/java/yeonba/be/arrow/service/ArrowService.java @@ -5,8 +5,12 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import yeonba.be.arrow.dto.UserArrowsResponse; +import yeonba.be.arrow.dto.request.ArrowSendRequest; import yeonba.be.arrow.entity.ArrowTransaction; import yeonba.be.arrow.repository.ArrowCommand; +import yeonba.be.arrow.repository.ArrowQuery; +import yeonba.be.exception.ArrowException; +import yeonba.be.exception.GeneralException; import yeonba.be.user.entity.User; import yeonba.be.user.repository.UserQuery; @@ -17,6 +21,7 @@ public class ArrowService { private final int DAILY_CHECK_ARROW_COUNT = 10; private final UserQuery userQuery; private final ArrowCommand arrowCommand; + private final ArrowQuery arrowQuery; /* 출석 체크는 다음 과정을 거쳐 이뤄진다. @@ -40,7 +45,7 @@ public void dailyCheck(long userId) { arrowCommand.save(arrowTransaction); dailyCheckUser.updateLastAccessedAt(dailyCheckedAt); - dailyCheckUser.addArrow(DAILY_CHECK_ARROW_COUNT); + dailyCheckUser.plusArrow(DAILY_CHECK_ARROW_COUNT); } @Transactional(readOnly = true) @@ -50,4 +55,39 @@ public UserArrowsResponse getUserArrows(long userId) { return new UserArrowsResponse(user.getArrow()); } + + /* + 화살 보내기 비즈니스 로직은 다음 과정을 거친다. + 1. 자기 자신에게 화살을 보내는 상황 검증 + 2. 화살을 보낸 사용자에게 또 보내는 상황 검증 + 3. 화살 내역 저장 + 4. 보내는 사용자 화살 감소, 화살이 부족할 경우 예외 발생 + 5. 받는 사용자 화살 증가 + + */ + @Transactional + public void sendArrow( + long senderId, + long recipientId, + ArrowSendRequest request) { + + User sender = userQuery.findById(senderId); + User receiver = userQuery.findById(recipientId); + + sender.validateNotSameUser(receiver); + + if (arrowQuery.isArrowTransactionExist(sender, receiver)) { + throw new GeneralException(ArrowException.ALREADY_SENT_ARROW_USER); + } + + int arrows = request.getArrows(); + ArrowTransaction arrowTransaction = new ArrowTransaction( + sender, + receiver, + arrows); + arrowCommand.save(arrowTransaction); + + sender.minusArrow(arrows); + receiver.plusArrow(arrows); + } } From f25c26f26b9f41b83ced7286a154cab6c0f9c521 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Mon, 11 Mar 2024 19:54:57 +0900 Subject: [PATCH 16/51] =?UTF-8?q?refactor:=20=ED=99=94=EC=82=B4=20?= =?UTF-8?q?=EB=B3=B4=EB=82=B4=EA=B8=B0=20API=20=ED=8C=8C=EB=9D=BC=EB=AF=B8?= =?UTF-8?q?=ED=84=B0=20=EC=9D=B4=EB=A6=84=20=EC=88=98=EC=A0=95(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 좀 더 명확하게 대치되는 의미를 나타낼 수 있도록 화살을 받는 사용자 ID 파라미터 이름을 receiverId로 수정 --- .../main/java/yeonba/be/arrow/controller/ArrowController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/be/src/main/java/yeonba/be/arrow/controller/ArrowController.java b/be/src/main/java/yeonba/be/arrow/controller/ArrowController.java index 5ba8b3db..c1df75ce 100644 --- a/be/src/main/java/yeonba/be/arrow/controller/ArrowController.java +++ b/be/src/main/java/yeonba/be/arrow/controller/ArrowController.java @@ -56,12 +56,12 @@ public ResponseEntity> dailyCheck( public ResponseEntity> sendArrow( @RequestAttribute("userId") long senderId, @Parameter(description = "화살 받는 사용자 ID", example = "1") - @PathVariable("userId") long recipientId, + @PathVariable("userId") long receiverId, @RequestBody ArrowSendRequest request) { arrowService.sendArrow( senderId, - recipientId, + receiverId, request); return ResponseEntity From 4d26a1568ae74ce694a2f7efe294e29b56714e28 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Mon, 11 Mar 2024 20:18:21 +0900 Subject: [PATCH 17/51] =?UTF-8?q?refactor:=20=EC=BB=A8=EB=B2=A4=EC=85=98?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EC=BD=94=EB=93=9C=20=EB=B0=B0?= =?UTF-8?q?=EC=B9=98=20=EC=88=98=EC=A0=95(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- be/src/main/java/yeonba/be/exception/ArrowException.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/be/src/main/java/yeonba/be/exception/ArrowException.java b/be/src/main/java/yeonba/be/exception/ArrowException.java index 7216212d..3d21cd72 100644 --- a/be/src/main/java/yeonba/be/exception/ArrowException.java +++ b/be/src/main/java/yeonba/be/exception/ArrowException.java @@ -26,11 +26,13 @@ public enum ArrowException implements BaseException { @Override public HttpStatus getHttpStatus() { + return httpStatus; } @Override public String getReason() { + return reason; } } From 7efedf975f600574176389c88f2b110743efaea1 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Mon, 11 Mar 2024 21:41:48 +0900 Subject: [PATCH 18/51] =?UTF-8?q?feat:=20=EC=9D=B4=EB=A6=84=20=EC=B9=BC?= =?UTF-8?q?=EB=9F=BC,=20=EC=83=9D=EC=84=B1=EC=9E=90=20=EC=B6=94=EA=B0=80(#?= =?UTF-8?q?27)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 이름 칼럼 추가 - 테스트시 활용 가능한 생성자 추가 --- be/src/main/java/yeonba/be/user/entity/Animal.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/be/src/main/java/yeonba/be/user/entity/Animal.java b/be/src/main/java/yeonba/be/user/entity/Animal.java index 5c20a279..a1c01b87 100644 --- a/be/src/main/java/yeonba/be/user/entity/Animal.java +++ b/be/src/main/java/yeonba/be/user/entity/Animal.java @@ -1,25 +1,29 @@ package yeonba.be.user.entity; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; -import lombok.AllArgsConstructor; +import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @Table(name = "animals") @Getter @Entity -@NoArgsConstructor -@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class Animal { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Column(nullable = false) + private String name; - + public Animal(String name) { + this.name = name; + } } From d77dae4b11db5e2e07b38ee47e63deb3672f76e6 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Mon, 11 Mar 2024 22:01:27 +0900 Subject: [PATCH 19/51] =?UTF-8?q?feat:=20=EC=83=81=EB=8C=80=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=9D=91=EB=8B=B5=20DTO=20=EC=88=98?= =?UTF-8?q?=EC=A0=95(#27)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 래퍼 타입 필드들 기본 타입으로 변경 - 프로필 사진 URL 리스트 필드 추가 - 사용자가 가진 총 화살 수 필드 추가 - 음주 성향, 흡연 성향 필드 추가 - 코드 정렬 --- .../dto/response/UserProfileResponse.java | 56 ++++++++++++------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/be/src/main/java/yeonba/be/user/dto/response/UserProfileResponse.java b/be/src/main/java/yeonba/be/user/dto/response/UserProfileResponse.java index 2f4837a9..43e04524 100644 --- a/be/src/main/java/yeonba/be/user/dto/response/UserProfileResponse.java +++ b/be/src/main/java/yeonba/be/user/dto/response/UserProfileResponse.java @@ -1,6 +1,7 @@ package yeonba.be.user.dto.response; import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; import lombok.AllArgsConstructor; import lombok.Getter; @@ -8,59 +9,74 @@ @AllArgsConstructor public class UserProfileResponse { + @Schema( + type = "array", + description = "프로필 사진 URL들") + private List profilePhotosUrls; + @Schema( type = "string", description = "닉네임", - example = "존잘남" - ) + example = "존잘남") private String nickname; + @Schema( + type = "number", + description = "사용자가 가진 총 화살 수", + example = "10") + private int arrows; + @Schema( type = "number", description = "나이", - example = "23" - ) - private Integer age; + example = "23") + private int age; @Schema( type = "number", description = "키", - example = "177" - ) - private Integer height; + example = "177") + private int height; @Schema( type = "string", description = "활동 지역", - example = "서울시 강남구" - ) + example = "서울") private String activityArea; @Schema( type = "number", description = "사진 싱크로율", - example = "80" - ) - private Integer photoSyncRate; + example = "80") + private double photoSyncRate; + + @Schema( + type = "string", + description = "음주 성향", + example = "가끔") + private String drinkingHabit; + + @Schema( + type = "string", + description = "흡연 성향", + example = "자주") + private String smokingHabit; @Schema( type = "string", description = "음역대", - example = "저음" - ) + example = "저음") private String vocalRange; @Schema( type = "string", description = "닮은 동물상", - example = "여우상" - ) + example = "여우상") private String lookAlikeAnimalName; @Schema( type = "boolean", description = "이전 화살 전송 여부", - example = "false" - ) - private Boolean isAlreadySentArrow; + example = "false") + private boolean isAlreadySentArrow; } From d0bbec93c49de88f6dd4507a7c85a44e62fb4fae Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Mon, 11 Mar 2024 22:05:54 +0900 Subject: [PATCH 20/51] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=97=94=ED=8B=B0=ED=8B=B0=20=EC=88=98=EC=A0=95(#27)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 레퍼런스 타입 not null 필드들, Column 어노테이션 사용 명시 - salt 필드 추가 - 생성 일시, 최종 수정 일시 필드 추가 - JpaAuditing 활용, 생성/최종 수정 일시 필드들 엔티티 저장시 자동 설정 - 새로운 생성자 구성 --- .../main/java/yeonba/be/user/entity/User.java | 59 +++++++++++++++++-- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/be/src/main/java/yeonba/be/user/entity/User.java b/be/src/main/java/yeonba/be/user/entity/User.java index f172d9bd..c1617a39 100644 --- a/be/src/main/java/yeonba/be/user/entity/User.java +++ b/be/src/main/java/yeonba/be/user/entity/User.java @@ -1,6 +1,8 @@ package yeonba.be.user.entity; +import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; @@ -12,18 +14,21 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; -import lombok.AllArgsConstructor; +import lombok.AccessLevel; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; import yeonba.be.exception.ArrowException; import yeonba.be.exception.GeneralException; @Table(name = "users") @Getter @Entity -@NoArgsConstructor -@AllArgsConstructor +@EntityListeners(value = AuditingEntityListener.class) +@NoArgsConstructor(access = AccessLevel.PROTECTED) @EqualsAndHashCode(of = "id") public class User { @@ -31,20 +36,46 @@ public class User { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private boolean gender; + + @Column(nullable = false) private String name; + + @Column(nullable = false) private String nickname; + + @Column(nullable = false) private LocalDate birth; + private int age; private int height; + + @Column(nullable = false) private String email; + + @Column(nullable = false) private String encryptedPassword; + + @Column(nullable = false) + private String salt; + + @Column(nullable = false) private String phoneNumber; private int arrow; private double photoSyncRate; private boolean inactiveStatus; + + @Column(nullable = false) private String bodyType; + + @Column(nullable = false) private String job; + + @Column(nullable = false) private String drinkingHabit; + + @Column(nullable = false) private String smokingHabit; + + @Column(nullable = false) private String mbti; @ManyToOne @@ -63,15 +94,25 @@ public class User { List profilePhotos; private LocalDateTime lastAccessedAt; + + @CreatedDate + private LocalDateTime createdAt; + + @LastModifiedDate + private LocalDateTime updatedAt; + private LocalDateTime deletedAt; public User( + boolean gender, String name, String nickname, LocalDate birth, + int age, int height, String email, String encryptedPassword, + String salt, String phoneNumber, int arrow, double photoSyncRate, @@ -80,14 +121,20 @@ public User( String drinkingHabit, String smokingHabit, String mbti, + VocalRange vocalRange, + Animal animal, + Area area, + List profilePhotos, LocalDateTime lastAccessedAt) { - + this.gender = gender; this.name = name; this.nickname = nickname; this.birth = birth; + this.age = age; this.height = height; this.email = email; this.encryptedPassword = encryptedPassword; + this.salt = salt; this.phoneNumber = phoneNumber; this.arrow = arrow; this.photoSyncRate = photoSyncRate; @@ -97,6 +144,10 @@ public User( this.drinkingHabit = drinkingHabit; this.smokingHabit = smokingHabit; this.mbti = mbti; + this.vocalRange = vocalRange; + this.animal = animal; + this.area = area; + this.profilePhotos = profilePhotos; this.lastAccessedAt = lastAccessedAt; } From fa4204d56705542c5242d98223d754444df3fd38 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Mon, 11 Mar 2024 22:08:47 +0900 Subject: [PATCH 21/51] =?UTF-8?q?feat:=20=EC=83=81=EB=8C=80=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=A1=B0=ED=9A=8C=20=EB=B9=84=EC=A6=88?= =?UTF-8?q?=EB=8B=88=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80(#2?= =?UTF-8?q?7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 프로필을 조회하는 사용자 ID의 경우 상대방이 화살을 보넀던 사용자인지 확인하기 위해 파라미터로 받는다. --- .../yeonba/be/user/service/UserService.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/be/src/main/java/yeonba/be/user/service/UserService.java b/be/src/main/java/yeonba/be/user/service/UserService.java index 38ad63a0..e4553c0a 100644 --- a/be/src/main/java/yeonba/be/user/service/UserService.java +++ b/be/src/main/java/yeonba/be/user/service/UserService.java @@ -1,12 +1,49 @@ package yeonba.be.user.service; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import yeonba.be.arrow.repository.ArrowQuery; +import yeonba.be.user.dto.response.UserProfileResponse; +import yeonba.be.user.entity.ProfilePhoto; +import yeonba.be.user.entity.User; import yeonba.be.user.repository.UserQuery; @Service @RequiredArgsConstructor public class UserService { + + private final ArrowQuery arrowQuery; private final UserQuery userQuery; + + + @Transactional(readOnly = true) + public UserProfileResponse getTargetUserProfile(long userId, long targetUserId) { + + User user = userQuery.findById(userId); + User targetUser = userQuery.findById(targetUserId); + + List profilePhotosUrls = targetUser + .getProfilePhotos().stream() + .map(ProfilePhoto::getPhotoUrl) + .toList(); + + boolean isAlreadySentArrow = arrowQuery.isArrowTransactionExist(user, targetUser); + + return new UserProfileResponse( + profilePhotosUrls, + targetUser.getNickname(), + targetUser.getArrow(), + targetUser.getAge(), + targetUser.getHeight(), + targetUser.getArea().getName(), + targetUser.getPhotoSyncRate(), + targetUser.getDrinkingHabit(), + targetUser.getSmokingHabit(), + targetUser.getVocalRange().getClassification(), + targetUser.getAnimal().getName(), + isAlreadySentArrow); + } } From 87ec03ebf9a10de17f8ca5a53310074329769cd2 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Mon, 11 Mar 2024 22:10:38 +0900 Subject: [PATCH 22/51] =?UTF-8?q?feat:=20=EC=83=81=EB=8C=80=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20API=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=A0=95=EB=A0=AC(#27)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../be/user/controller/UserController.java | 30 ++++++------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/be/src/main/java/yeonba/be/user/controller/UserController.java b/be/src/main/java/yeonba/be/user/controller/UserController.java index 31912e30..3ff6f160 100644 --- a/be/src/main/java/yeonba/be/user/controller/UserController.java +++ b/be/src/main/java/yeonba/be/user/controller/UserController.java @@ -19,6 +19,7 @@ import yeonba.be.user.dto.request.UserReportRequest; import yeonba.be.user.dto.response.UserProfileResponse; import yeonba.be.user.dto.response.UserQueryPageResponse; +import yeonba.be.user.service.UserService; import yeonba.be.util.CustomResponse; @Tag(name = "User", description = "사용자 API") @@ -26,6 +27,7 @@ @RequiredArgsConstructor public class UserController { + private final UserService userService; private final ReportService reportService; @Operation( @@ -46,33 +48,19 @@ public ResponseEntity> users( } - @Operation( - summary = "다른 사용자 프로필 조회", - description = "다른 사용자의 프로필을 조회할 수 있습니다." - ) - @ApiResponse( - responseCode = "200", - description = "사용자 프로필 정상 조회" - ) + @Operation(summary = "다른 사용자 프로필 조회", description = "다른 사용자의 프로필을 조회할 수 있습니다.") + @ApiResponse(responseCode = "200", description = "사용자 프로필 정상 조회") @GetMapping("/users/{userId}") public ResponseEntity> profile( + @RequestAttribute("userId") long userId, @Parameter(description = "조회대상 사용자 ID", example = "1") - @PathVariable long userId) { + @PathVariable("userId") long targetUserId) { + + UserProfileResponse response = userService.getTargetUserProfile(userId, targetUserId); return ResponseEntity .ok() - .body(new CustomResponse<>( - new UserProfileResponse( - "존잘남", - 23, - 177, - "서울시 강남구", - 80, - "저음", - "여우상", - false - ) - )); + .body(new CustomResponse<>(response)); } @Operation( From e6f7eb1b129d06e1924781c39ebb462d57b42df0 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Wed, 13 Mar 2024 15:43:31 +0900 Subject: [PATCH 23/51] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=97=94=ED=8B=B0=ED=8B=B0=20=EC=88=98=EC=A0=95(#27)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 요구사항 변경에 따라 음주 성향, 흡연 성향 필드 삭제 - 성별 정보 제공 메서드 추가 --- .../main/java/yeonba/be/user/entity/User.java | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/be/src/main/java/yeonba/be/user/entity/User.java b/be/src/main/java/yeonba/be/user/entity/User.java index c1617a39..ace44b5a 100644 --- a/be/src/main/java/yeonba/be/user/entity/User.java +++ b/be/src/main/java/yeonba/be/user/entity/User.java @@ -69,12 +69,6 @@ public class User { @Column(nullable = false) private String job; - @Column(nullable = false) - private String drinkingHabit; - - @Column(nullable = false) - private String smokingHabit; - @Column(nullable = false) private String mbti; @@ -118,14 +112,11 @@ public User( double photoSyncRate, String bodyType, String job, - String drinkingHabit, - String smokingHabit, String mbti, VocalRange vocalRange, Animal animal, Area area, - List profilePhotos, - LocalDateTime lastAccessedAt) { + List profilePhotos) { this.gender = gender; this.name = name; this.nickname = nickname; @@ -141,14 +132,11 @@ public User( this.inactiveStatus = true; this.bodyType = bodyType; this.job = job; - this.drinkingHabit = drinkingHabit; - this.smokingHabit = smokingHabit; this.mbti = mbti; this.vocalRange = vocalRange; this.animal = animal; this.area = area; this.profilePhotos = profilePhotos; - this.lastAccessedAt = lastAccessedAt; } public void validateSameUser(User user) { @@ -210,4 +198,12 @@ public void minusArrow(int arrow) { this.arrow -= arrow; } + + public String getGender() { + if (this.gender) { + return "남"; + } + + return "여"; + } } From 4aba46d9296d8c74fddde4666e5cb82de328715f Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Wed, 13 Mar 2024 15:44:52 +0900 Subject: [PATCH 24/51] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EC=9D=91=EB=8B=B5=20DTO=20?= =?UTF-8?q?=EC=88=98=EC=A0=95(#27)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 음주 성향, 흡연 성향 필드 삭제 - 성별 필드 추가 --- .../user/dto/response/UserProfileResponse.java | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/be/src/main/java/yeonba/be/user/dto/response/UserProfileResponse.java b/be/src/main/java/yeonba/be/user/dto/response/UserProfileResponse.java index 43e04524..246b9a05 100644 --- a/be/src/main/java/yeonba/be/user/dto/response/UserProfileResponse.java +++ b/be/src/main/java/yeonba/be/user/dto/response/UserProfileResponse.java @@ -14,6 +14,12 @@ public class UserProfileResponse { description = "프로필 사진 URL들") private List profilePhotosUrls; + @Schema( + type = "string", + description = "성별", + example = "남") + private String gender; + @Schema( type = "string", description = "닉네임", @@ -50,18 +56,6 @@ public class UserProfileResponse { example = "80") private double photoSyncRate; - @Schema( - type = "string", - description = "음주 성향", - example = "가끔") - private String drinkingHabit; - - @Schema( - type = "string", - description = "흡연 성향", - example = "자주") - private String smokingHabit; - @Schema( type = "string", description = "음역대", From 6cc2cb659ff44e296752dbc7f6e023022de0bc16 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Wed, 13 Mar 2024 15:47:24 +0900 Subject: [PATCH 25/51] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EC=83=81=EC=84=B8=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20DTO=20=EC=88=98=EC=A0=95(#27)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 음주 성향, 흡연 성향 필드 삭제 - 생성자 성별 필드 설정 로직 수정 - 코드 정렬 --- .../response/UserProfileDetailResponse.java | 61 +++++-------------- 1 file changed, 15 insertions(+), 46 deletions(-) diff --git a/be/src/main/java/yeonba/be/mypage/dto/response/UserProfileDetailResponse.java b/be/src/main/java/yeonba/be/mypage/dto/response/UserProfileDetailResponse.java index 31bcd220..08c30eae 100644 --- a/be/src/main/java/yeonba/be/mypage/dto/response/UserProfileDetailResponse.java +++ b/be/src/main/java/yeonba/be/mypage/dto/response/UserProfileDetailResponse.java @@ -2,9 +2,7 @@ import com.fasterxml.jackson.annotation.JsonFormat; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.Size; import java.time.LocalDate; -import java.time.LocalDateTime; import java.util.List; import lombok.AllArgsConstructor; import lombok.Getter; @@ -17,101 +15,74 @@ public class UserProfileDetailResponse { @Schema( type = "array", - description = "프로필 이미지" - ) - @Size(min = 3, max = 3) + description = "프로필 이미지") private List profilePhotoUrls; @Schema( type = "string", description = "성별", - example = "남" - ) + example = "남") private String gender; @Schema( type = "string", description = "이름", - example = "안민재" - ) + example = "안민재") private String name; @Schema( type = "string", description = "생년월일", - example = "1998-01-01" - ) + example = "1998-01-01") @JsonFormat(pattern = "yyyy-MM-dd") private LocalDate birth; @Schema( type = "number", description = "키", - example = "181" - ) + example = "181") private int height; @Schema( type = "string", description = "이메일", - example = "mj3242@naver.com" - ) + example = "mj3242@naver.com") private String email; @Schema( type = "string", description = "전화번호", - example = "01011112222" - ) + example = "01011112222") private String phoneNumber; @Schema( type = "string", description = "별명", - example = "존잘남" - ) + example = "존잘남") private String nickname; @Schema( type = "number", description = "사진 싱크로율", - example = "80" - ) - private int photoSyncRate; + example = "80") + private double photoSyncRate; @Schema( type = "string", description = "체형", - example = "메른 체형" - ) + example = "메른 체형") private String bodyType; @Schema( type = "string", description = "직업", - example = "학생" - ) + example = "학생") private String job; - @Schema( - type = "string", - description = "음주 성향", - example = "자주" - ) - private String drinkingHabit; - - @Schema( - type = "string", - description = "흡연 성향", - example = "가끔" - ) - private String smokingHabit; - @Schema( type = "string", description = "MBTI", - example = "ISTP" - ) + example = "ISTP") private String mbti; public UserProfileDetailResponse(User user) { @@ -119,18 +90,16 @@ public UserProfileDetailResponse(User user) { this.profilePhotoUrls = user.getProfilePhotos().stream() .map(ProfilePhoto::getPhotoUrl) .toList(); - this.gender = user.isGender() ? "남" : "여"; + this.gender = user.getGender(); this.name = user.getName(); this.birth = user.getBirth(); this.height = user.getHeight(); this.email = user.getEmail(); this.phoneNumber = user.getPhoneNumber(); this.nickname = user.getNickname(); - this.photoSyncRate = (int) (user.getPhotoSyncRate()); + this.photoSyncRate = user.getPhotoSyncRate(); this.bodyType = user.getBodyType(); this.job = user.getJob(); - this.drinkingHabit = user.getDrinkingHabit(); - this.smokingHabit = user.getSmokingHabit(); this.mbti = user.getMbti(); } } From 1d7521839d017e1c874d62678f577441f07dbbb7 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Wed, 13 Mar 2024 15:49:26 +0900 Subject: [PATCH 26/51] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EC=A1=B0=ED=9A=8C=20=EB=B9=84?= =?UTF-8?q?=EC=A6=88=EB=8B=88=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95(#27)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 음주 성향, 흡연 성향 삭제 - 성별 정보 응답에 포함토록 수정 - 코드 정렬 --- be/src/main/java/yeonba/be/user/service/UserService.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/be/src/main/java/yeonba/be/user/service/UserService.java b/be/src/main/java/yeonba/be/user/service/UserService.java index e4553c0a..6b1c2956 100644 --- a/be/src/main/java/yeonba/be/user/service/UserService.java +++ b/be/src/main/java/yeonba/be/user/service/UserService.java @@ -14,11 +14,9 @@ @RequiredArgsConstructor public class UserService { - private final ArrowQuery arrowQuery; private final UserQuery userQuery; - @Transactional(readOnly = true) public UserProfileResponse getTargetUserProfile(long userId, long targetUserId) { @@ -34,14 +32,13 @@ public UserProfileResponse getTargetUserProfile(long userId, long targetUserId) return new UserProfileResponse( profilePhotosUrls, + targetUser.getGender(), targetUser.getNickname(), targetUser.getArrow(), targetUser.getAge(), targetUser.getHeight(), targetUser.getArea().getName(), targetUser.getPhotoSyncRate(), - targetUser.getDrinkingHabit(), - targetUser.getSmokingHabit(), targetUser.getVocalRange().getClassification(), targetUser.getAnimal().getName(), isAlreadySentArrow); From 4e54bfda7a8bc535122139819667c1788ed260f1 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Wed, 13 Mar 2024 19:03:52 +0900 Subject: [PATCH 27/51] =?UTF-8?q?chore:=20spring=20mail=20=EC=A2=85?= =?UTF-8?q?=EC=86=8D=EC=84=B1=20=EC=B6=94=EA=B0=80(#35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- be/build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/be/build.gradle b/be/build.gradle index 44da8a87..5f48c408 100644 --- a/be/build.gradle +++ b/be/build.gradle @@ -37,6 +37,9 @@ dependencies { // aws s3 implementation 'software.amazon.awssdk:aws-sdk-java:2.16.83' implementation 'software.amazon.awssdk:s3:2.16.83' + + // spring mail + implementation 'org.springframework.boot:spring-boot-starter-mail:3.2.2' } tasks.named('bootBuildImage') { From ba20da8b7514816e922869ac73c458d39156eeb1 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Wed, 13 Mar 2024 19:06:48 +0900 Subject: [PATCH 28/51] =?UTF-8?q?chore:=20Gmail=20SMTP=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80(#35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- be/src/main/resources/application.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/be/src/main/resources/application.yml b/be/src/main/resources/application.yml index f2ba3c03..91b663c1 100644 --- a/be/src/main/resources/application.yml +++ b/be/src/main/resources/application.yml @@ -11,3 +11,8 @@ spring: hibernate: show_sql: true format_sql: true + mail: + host: smtp.gmail.com + port: 587 + username: ${GOOGLE_SMTP_USERNAME} + password: ${GOOGLE_SMTP_PASSWORD} From 0d4b90f881ee029a4fafcaaa186c3b161723b11b Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Wed, 13 Mar 2024 19:07:26 +0900 Subject: [PATCH 29/51] =?UTF-8?q?feat:=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EA=B8=B0=EB=B0=98=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80(#35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- be/src/main/java/yeonba/be/user/repository/UserRepository.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/be/src/main/java/yeonba/be/user/repository/UserRepository.java b/be/src/main/java/yeonba/be/user/repository/UserRepository.java index cb9c5ddb..41aada43 100644 --- a/be/src/main/java/yeonba/be/user/repository/UserRepository.java +++ b/be/src/main/java/yeonba/be/user/repository/UserRepository.java @@ -1,5 +1,6 @@ package yeonba.be.user.repository; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import yeonba.be.user.entity.User; @@ -7,4 +8,5 @@ @Repository public interface UserRepository extends JpaRepository { + Optional findByEmail(String email); } From 78ac728d4391fd0bd34596f13ae315728a6db787 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Wed, 13 Mar 2024 19:07:57 +0900 Subject: [PATCH 30/51] =?UTF-8?q?feat:=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EA=B8=B0=EB=B0=98=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80(#35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- be/src/main/java/yeonba/be/user/repository/UserQuery.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/be/src/main/java/yeonba/be/user/repository/UserQuery.java b/be/src/main/java/yeonba/be/user/repository/UserQuery.java index 9d32a736..9a47f7c5 100644 --- a/be/src/main/java/yeonba/be/user/repository/UserQuery.java +++ b/be/src/main/java/yeonba/be/user/repository/UserQuery.java @@ -17,4 +17,10 @@ public User findById(long userId) { return userRepository.findById(userId) .orElseThrow(() -> new GeneralException(UserException.USER_NOT_FOUND)); } + + public User findByEmail(String email) { + + return userRepository.findByEmail(email) + .orElseThrow(() -> new GeneralException(UserException.USER_NOT_FOUND)); + } } From 5bd4d1fa860f67af2a1be0aa4d71e1978149d35a Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Wed, 13 Mar 2024 19:09:04 +0900 Subject: [PATCH 31/51] =?UTF-8?q?feat:=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=20=EC=A0=95=EA=B7=9C=EC=8B=9D=20enum=20?= =?UTF-8?q?=EC=A0=95=EC=9D=98(#35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 서비스에서 사용하는 정규식들을 모아놓는 enum 클래스 정의 - 이메일, 비밀번호 정규식 정의 --- .../main/java/yeonba/be/util/ServiceRegex.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 be/src/main/java/yeonba/be/util/ServiceRegex.java diff --git a/be/src/main/java/yeonba/be/util/ServiceRegex.java b/be/src/main/java/yeonba/be/util/ServiceRegex.java new file mode 100644 index 00000000..d7531dac --- /dev/null +++ b/be/src/main/java/yeonba/be/util/ServiceRegex.java @@ -0,0 +1,18 @@ +package yeonba.be.util; + +public enum ServiceRegex { + + EMAIL("[a-zA-Z0-9_!#$%&’*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$"), + PASSWORD("^(?=.*[a-zA-Z])(?=.*\\d)(?=.*[~#@!]).{8,20}$"); + + private final String pattern; + + ServiceRegex(String pattern) { + this.pattern = pattern; + } + + public String getPattern() { + + return pattern; + } +} From bdc82596f8db54560b25fbb08f0c826416c7e848 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Wed, 13 Mar 2024 19:12:05 +0900 Subject: [PATCH 32/51] =?UTF-8?q?feat:=20=EC=9E=84=EC=8B=9C=20=EB=B9=84?= =?UTF-8?q?=EB=B0=80=EB=B2=88=ED=98=B8=20=EB=B0=9C=EA=B8=89=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84(#35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 비밀번호 정규식을 충족하는 임의의 임시 비밀번호를 발급 - Random보다 생성하는 난수를 예측하기 어려워 보안적으로 뛰어난 SecureRandom 사용 --- .../be/util/TemporaryPasswordGenerator.java | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 be/src/main/java/yeonba/be/util/TemporaryPasswordGenerator.java diff --git a/be/src/main/java/yeonba/be/util/TemporaryPasswordGenerator.java b/be/src/main/java/yeonba/be/util/TemporaryPasswordGenerator.java new file mode 100644 index 00000000..81c424a8 --- /dev/null +++ b/be/src/main/java/yeonba/be/util/TemporaryPasswordGenerator.java @@ -0,0 +1,58 @@ +package yeonba.be.util; + +import java.security.SecureRandom; +import java.util.Random; + +public class TemporaryPasswordGenerator { + + private static final int MIN_LENGTH = 8; + private static final int MAX_LENGTH = 20; + + private static final String LOWER = "abcdefghijklmnopqrstuvwxyz"; + private static final String UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + private static final String DIGITS = "0123456789"; + private static final String SPECIAL = "~#@!"; + + public static String generatePassword(){ + String pattern = ServiceRegex.PASSWORD.getPattern(); + SecureRandom random = new SecureRandom(); + StringBuilder sb = new StringBuilder(); + + int length = MIN_LENGTH + random.nextInt(MAX_LENGTH - MIN_LENGTH+1); + + while(true){ + sb.setLength(0); + for(int i=0; i Date: Wed, 13 Mar 2024 19:14:36 +0900 Subject: [PATCH 33/51] =?UTF-8?q?feat:=20=EB=A9=94=EC=9D=BC=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=84=A4=EC=A0=95=20=EC=A0=95=EC=9D=98(#35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - spring mail에서 이메일 전송을 위해 제공하는 JavaMailSender 빈 등록 - 필요한 설정 값들은 application.yml에 정의된 값들을 끌어와 사용 - 빈 등록 로직에서 구글 SMTP 서버를 통해 메일을 전송하기 위한 설정 코드 추가 --- .../java/yeonba/be/config/MailConfig.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 be/src/main/java/yeonba/be/config/MailConfig.java diff --git a/be/src/main/java/yeonba/be/config/MailConfig.java b/be/src/main/java/yeonba/be/config/MailConfig.java new file mode 100644 index 00000000..089bb3d8 --- /dev/null +++ b/be/src/main/java/yeonba/be/config/MailConfig.java @@ -0,0 +1,40 @@ +package yeonba.be.config; + +import java.util.Properties; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.JavaMailSenderImpl; + +@Configuration +public class MailConfig { + + @Value("${spring.mail.host}") + private String serverHost; + + @Value("${spring.mail.port}") + private int serverPort; + + @Value("${spring.mail.username}") + private String username; + + @Value("${spring.mail.password}") + private String password; + + @Bean + public JavaMailSender javaMailSender() { + JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); + mailSender.setHost(serverHost); + mailSender.setPort(serverPort); + mailSender.setUsername(username); + mailSender.setPassword(password); + + Properties properties = mailSender.getJavaMailProperties(); + properties.put("mail.transport.protocol", "smtp"); + properties.put("mail.smtp.auth", "true"); + properties.put("mail.smtp.starttls.enable", "true"); + + return mailSender; + } +} From 811198dffc35b13035ab8df3962aa1d4b401dd6b Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Wed, 13 Mar 2024 19:16:40 +0900 Subject: [PATCH 34/51] =?UTF-8?q?feat:=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EC=A0=84=EC=86=A1=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80(#3?= =?UTF-8?q?5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 단순 텍스트 이메일 전송 기능 구현 - 발신자는 application.yml에 정의해논 구글 SMTP 사용자 이름 값으로 설정 --- .../java/yeonba/be/util/EmailService.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 be/src/main/java/yeonba/be/util/EmailService.java diff --git a/be/src/main/java/yeonba/be/util/EmailService.java b/be/src/main/java/yeonba/be/util/EmailService.java new file mode 100644 index 00000000..3f64c57d --- /dev/null +++ b/be/src/main/java/yeonba/be/util/EmailService.java @@ -0,0 +1,30 @@ +package yeonba.be.util; + +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class EmailService { + + @Value("{spring.mail.username}") + private String serviceEmail; + + private final JavaMailSender mailSender; + + public void sendMail( + String to, + String subject, + String text){ + SimpleMailMessage mailMessage = new SimpleMailMessage(); + mailMessage.setFrom(serviceEmail); + mailMessage.setTo(to); + mailMessage.setSubject(subject); + mailMessage.setText(text); + + mailSender.send(mailMessage); + } +} From 25703a778cea363faa9873cd7b80a6a7879649f0 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Wed, 13 Mar 2024 19:19:23 +0900 Subject: [PATCH 35/51] =?UTF-8?q?feat:=20=EC=9E=84=EC=8B=9C=20=EB=B9=84?= =?UTF-8?q?=EB=B0=80=EB=B2=88=ED=98=B8=20=EB=B0=9C=EA=B8=89=20=EB=B9=84?= =?UTF-8?q?=EC=A6=88=EB=8B=88=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80(#35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 임시 비밀번호는 다음 과정을 거친다. 1. 요청 이메일 기반 사용자 조회 2. 임시 비밀번호 생성 3. 사용자 비밀번호, 임시 비밀번호로 변경 4. 임시 비밀번호 발급 메일 전송 임시 비밀번호를 암호화하는 로직은 추후 구현 예정 --- .../yeonba/be/login/service/LoginService.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 be/src/main/java/yeonba/be/login/service/LoginService.java diff --git a/be/src/main/java/yeonba/be/login/service/LoginService.java b/be/src/main/java/yeonba/be/login/service/LoginService.java new file mode 100644 index 00000000..f12df5e2 --- /dev/null +++ b/be/src/main/java/yeonba/be/login/service/LoginService.java @@ -0,0 +1,42 @@ +package yeonba.be.login.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import yeonba.be.login.dto.request.UserPasswordInquiryRequest; +import yeonba.be.user.entity.User; +import yeonba.be.user.repository.UserQuery; +import yeonba.be.util.EmailService; +import yeonba.be.util.TemporaryPasswordGenerator; + +@Service +@RequiredArgsConstructor +public class LoginService { + + private final String TEMPORARY_PASSWORD_EMAIL_SUBJECT = "연바(연애는 바로 지금) 임시비밀번호 발급"; + private final String TEMPORARY_PASSWORD_EMAIL_TEXT = "임시비밀번호 : %s"; + private final UserQuery userQuery; + private final EmailService emailService; + + /* + 임시 비밀번호는 다음 과정을 거친다. + 1. 요청 이메일 기반 사용자 조회 + 2. 임시 비밀번호 생성 + 3. 사용자 비밀번호, 임시 비밀번호로 변경 + 4. 임시 비밀번호 발급 메일 전송 + */ + + // TODO : 비밀번호 암호화 로직 추가 + public void sendTemporaryPasswordMail(UserPasswordInquiryRequest request) { + + String email = request.getEmail(); + User user = userQuery.findByEmail(email); + + String temporaryPassword = TemporaryPasswordGenerator.generatePassword(); + + String encryptedPassword = temporaryPassword; + user.changePassword(encryptedPassword); + + String text = String.format(TEMPORARY_PASSWORD_EMAIL_TEXT, temporaryPassword); + emailService.sendMail(email, TEMPORARY_PASSWORD_EMAIL_SUBJECT, text); + } +} From a798b4cbbf88cadc03609bd82e301556345ba0de Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Wed, 13 Mar 2024 19:24:31 +0900 Subject: [PATCH 36/51] =?UTF-8?q?feat:=20=EC=9E=84=EC=8B=9C=20=EB=B9=84?= =?UTF-8?q?=EB=B0=83=EB=B2=88=ED=98=B8=20=EB=B0=9C=EA=B8=89=20=EB=B9=84?= =?UTF-8?q?=EC=A6=88=EB=8B=88=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=ED=8A=B8?= =?UTF-8?q?=EB=9E=9C=EC=9E=AD=EC=85=98=20=EC=A0=95=EC=9D=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80(#35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- be/src/main/java/yeonba/be/login/service/LoginService.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/be/src/main/java/yeonba/be/login/service/LoginService.java b/be/src/main/java/yeonba/be/login/service/LoginService.java index f12df5e2..373c9ecc 100644 --- a/be/src/main/java/yeonba/be/login/service/LoginService.java +++ b/be/src/main/java/yeonba/be/login/service/LoginService.java @@ -2,6 +2,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import yeonba.be.login.dto.request.UserPasswordInquiryRequest; import yeonba.be.user.entity.User; import yeonba.be.user.repository.UserQuery; @@ -26,6 +27,8 @@ public class LoginService { */ // TODO : 비밀번호 암호화 로직 추가 + + @Transactional public void sendTemporaryPasswordMail(UserPasswordInquiryRequest request) { String email = request.getEmail(); From 1bfde17642da344c2cd88a1b2830a2168218544a Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Wed, 13 Mar 2024 19:25:58 +0900 Subject: [PATCH 37/51] =?UTF-8?q?feat:=20=EC=9E=84=EC=8B=9C=20=EB=B9=84?= =?UTF-8?q?=EB=B0=80=EB=B2=88=ED=98=B8=20=EB=B0=9C=EA=B8=89=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20DTO=20=EC=88=98=EC=A0=95(#35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 불필요한 기존 생성자 삭제, 기본 생성자 추가 - 비밀번호 형식 제약 조건 추가 - 코드 정렬 --- .../dto/request/UserPasswordInquiryRequest.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/be/src/main/java/yeonba/be/login/dto/request/UserPasswordInquiryRequest.java b/be/src/main/java/yeonba/be/login/dto/request/UserPasswordInquiryRequest.java index 14caf0db..02036027 100644 --- a/be/src/main/java/yeonba/be/login/dto/request/UserPasswordInquiryRequest.java +++ b/be/src/main/java/yeonba/be/login/dto/request/UserPasswordInquiryRequest.java @@ -1,17 +1,19 @@ package yeonba.be.login.dto.request; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.AllArgsConstructor; +import jakarta.validation.constraints.Pattern; import lombok.Getter; +import lombok.NoArgsConstructor; @Getter -@AllArgsConstructor +@NoArgsConstructor public class UserPasswordInquiryRequest { - @Schema( - type = "string", + @Schema(type = "string", description = "임시 비밀번호를 받을 이메일", - example = "mj3242@naver.com" - ) + example = "mj3242@naver.com") + @Pattern( + regexp = "[a-zA-Z0-9_!#$%&’*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$", + message = "유효하지 않은 이메일 형식입니다.") private String email; } From 89c4db54873ee71ae8775f3356d8d47bdc31ca8d Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Wed, 13 Mar 2024 19:27:14 +0900 Subject: [PATCH 38/51] =?UTF-8?q?feat:=20=EC=9E=84=EC=8B=9C=20=EB=B9=84?= =?UTF-8?q?=EB=B0=80=EB=B2=88=ED=98=B8=20=EB=B0=9C=EA=B8=89=20API=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84(#35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 임시 비밀번호 발급 API 구현 - 명세 속성 수정 - 코드 정렬 --- .../be/login/controller/LoginController.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/be/src/main/java/yeonba/be/login/controller/LoginController.java b/be/src/main/java/yeonba/be/login/controller/LoginController.java index a722d5a7..f74207a0 100644 --- a/be/src/main/java/yeonba/be/login/controller/LoginController.java +++ b/be/src/main/java/yeonba/be/login/controller/LoginController.java @@ -3,6 +3,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -17,12 +18,16 @@ import yeonba.be.login.dto.response.UserJoinResponse; import yeonba.be.login.dto.response.UserLoginResponse; import yeonba.be.login.dto.response.UserRefreshTokenResponse; +import yeonba.be.login.service.LoginService; import yeonba.be.util.CustomResponse; @Tag(name = "Login", description = "로그인 관련 API") @RestController +@RequiredArgsConstructor public class LoginController { + private final LoginService loginService; + @Operation(summary = "회원가입", description = "회원가입을 할 수 있습니다.") @PostMapping("/users/join") public ResponseEntity> join( @@ -69,18 +74,14 @@ public ResponseEntity> idInquiry( .body(new CustomResponse<>(new UserIdInquiryResponse("mj3242@naver.com"))); } - @Operation( - summary = "비밀번호 찾기", - description = "이메일로 임시 비밀번호를 발급받을 수 있습니다." - ) - @ApiResponse( - responseCode = "204", - description = "임시 비밀번호 발급(비밀번호 찾기) 정상 처리" - ) + @Operation(summary = "비밀번호 찾기", description = "이메일로 임시 비밀번호를 발급받을 수 있습니다.") + @ApiResponse(responseCode = "202", description = "임시 비밀번호 발급(비밀번호 찾기) 정상 처리") @PostMapping("/users/help/pw-inquiry") public ResponseEntity> passwordInquiry( @RequestBody UserPasswordInquiryRequest request) { + loginService.sendTemporaryPasswordMail(request); + return ResponseEntity .accepted() .body(new CustomResponse<>()); From 50b998261efac075d978d3ba05ff0201d0b67ed8 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Thu, 14 Mar 2024 21:20:12 +0900 Subject: [PATCH 39/51] =?UTF-8?q?refactor:=20=EB=B3=91=ED=95=A9=EC=97=90?= =?UTF-8?q?=20=EB=94=B0=EB=A5=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=AC=EB=B0=B0?= =?UTF-8?q?=EC=B9=98(#35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dev 브랜치 작업 내역 병합에 따른 코드 재배치 --- be/src/main/java/yeonba/be/user/entity/User.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/be/src/main/java/yeonba/be/user/entity/User.java b/be/src/main/java/yeonba/be/user/entity/User.java index ace44b5a..22ed5825 100644 --- a/be/src/main/java/yeonba/be/user/entity/User.java +++ b/be/src/main/java/yeonba/be/user/entity/User.java @@ -206,4 +206,11 @@ public String getGender() { return "여"; } + + public List getProfilePhotoUrls() { + + return this.profilePhotos.stream() + .map(ProfilePhoto::getPhotoUrl) + .toList(); + } } From 45adb7a64e0a157a99df73e3cde0ba2a68c5c152 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Thu, 14 Mar 2024 21:24:49 +0900 Subject: [PATCH 40/51] =?UTF-8?q?refactor:=20=EB=B3=91=ED=95=A9=EC=97=90?= =?UTF-8?q?=20=EB=94=B0=EB=A5=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=AC=EB=B0=B0?= =?UTF-8?q?=EC=B9=98(#35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dev 브랜치 작업 내역 병합에 따른 코드 재배치 --- .../be/user/controller/UserController.java | 32 ++++++------------- .../yeonba/be/user/service/UserService.java | 9 +----- 2 files changed, 11 insertions(+), 30 deletions(-) diff --git a/be/src/main/java/yeonba/be/user/controller/UserController.java b/be/src/main/java/yeonba/be/user/controller/UserController.java index 02e582a3..d91ca4b6 100644 --- a/be/src/main/java/yeonba/be/user/controller/UserController.java +++ b/be/src/main/java/yeonba/be/user/controller/UserController.java @@ -21,6 +21,7 @@ import yeonba.be.user.dto.response.UserQueryPageResponse; import yeonba.be.user.service.BlockService; import yeonba.be.user.service.FavoriteService; +import yeonba.be.user.service.UserService; import yeonba.be.util.CustomResponse; @Tag(name = "User", description = "사용자 API") @@ -29,8 +30,9 @@ public class UserController { private final BlockService blockService; - private final ReportService reportService; private final FavoriteService favoriteService; + private final ReportService reportService; + private final UserService userService; @Operation( summary = "이성(다른 사용자) 목록 조회", @@ -50,33 +52,19 @@ public ResponseEntity> users( } - @Operation( - summary = "다른 사용자 프로필 조회", - description = "다른 사용자의 프로필을 조회할 수 있습니다." - ) - @ApiResponse( - responseCode = "200", - description = "사용자 프로필 정상 조회" - ) + @Operation(summary = "다른 사용자 프로필 조회", description = "다른 사용자의 프로필을 조회할 수 있습니다.") + @ApiResponse(responseCode = "200", description = "사용자 프로필 정상 조회") @GetMapping("/users/{userId}") public ResponseEntity> profile( + @RequestAttribute("userId") long userId, @Parameter(description = "조회대상 사용자 ID", example = "1") - @PathVariable long userId) { + @PathVariable("userId") long targetUserId) { + + UserProfileResponse response = userService.getTargetUserProfile(userId, targetUserId); return ResponseEntity .ok() - .body(new CustomResponse<>( - new UserProfileResponse( - "존잘남", - 23, - 177, - "서울시 강남구", - 80, - "저음", - "여우상", - false - ) - )); + .body(new CustomResponse<>(response)); } @Operation(summary = "즐겨찾기 등록", description = "다른 사용자를 자신의 즐겨찾기에 등록할 수 있습니다.") diff --git a/be/src/main/java/yeonba/be/user/service/UserService.java b/be/src/main/java/yeonba/be/user/service/UserService.java index 6b1c2956..b14aa08b 100644 --- a/be/src/main/java/yeonba/be/user/service/UserService.java +++ b/be/src/main/java/yeonba/be/user/service/UserService.java @@ -1,12 +1,10 @@ package yeonba.be.user.service; -import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import yeonba.be.arrow.repository.ArrowQuery; import yeonba.be.user.dto.response.UserProfileResponse; -import yeonba.be.user.entity.ProfilePhoto; import yeonba.be.user.entity.User; import yeonba.be.user.repository.UserQuery; @@ -23,15 +21,10 @@ public UserProfileResponse getTargetUserProfile(long userId, long targetUserId) User user = userQuery.findById(userId); User targetUser = userQuery.findById(targetUserId); - List profilePhotosUrls = targetUser - .getProfilePhotos().stream() - .map(ProfilePhoto::getPhotoUrl) - .toList(); - boolean isAlreadySentArrow = arrowQuery.isArrowTransactionExist(user, targetUser); return new UserProfileResponse( - profilePhotosUrls, + targetUser.getProfilePhotoUrls(), targetUser.getGender(), targetUser.getNickname(), targetUser.getArrow(), From d50b7312e5310cbf89c015908329f179d62e1cea Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Fri, 15 Mar 2024 15:07:49 +0900 Subject: [PATCH 41/51] =?UTF-8?q?feat:=20=EC=9E=84=EC=8B=9C=20=EB=B9=84?= =?UTF-8?q?=EB=B0=80=EB=B2=88=ED=98=B8=20=EC=83=9D=EC=84=B1=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=B3=80=EA=B2=BD(#35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 임시 비밀번호는 다음 과정을 거쳐 생성된다. 1. 각 카테고리(영어 대소문자, 숫자, 특수문자)에서 한 글자씩 랜덤 선택 2. 모든 가능한 문자를 포함하는 문자열 형성 3. 앞서 생성한 문자열에서 나머지 길이를 채울 문자 랜덤 선택 4. 선택된 문자들 섞기 5. 최종 임시 비밀번호 생성 - 랜덤한 문자를 선택하는 부분은 spring boot에서 기본 제공되는 RandomStringUtils를 활용 - 문자열을 섞는 부분에서 shuffle을 위해 가변 컬렉션을 생성하려 새로운 List 할당 --- .../be/util/TemporaryPasswordGenerator.java | 86 ++++++++----------- 1 file changed, 38 insertions(+), 48 deletions(-) diff --git a/be/src/main/java/yeonba/be/util/TemporaryPasswordGenerator.java b/be/src/main/java/yeonba/be/util/TemporaryPasswordGenerator.java index 81c424a8..eb9a4d2e 100644 --- a/be/src/main/java/yeonba/be/util/TemporaryPasswordGenerator.java +++ b/be/src/main/java/yeonba/be/util/TemporaryPasswordGenerator.java @@ -1,58 +1,48 @@ package yeonba.be.util; -import java.security.SecureRandom; -import java.util.Random; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.commons.lang3.RandomStringUtils; public class TemporaryPasswordGenerator { private static final int MIN_LENGTH = 8; - private static final int MAX_LENGTH = 20; - private static final String LOWER = "abcdefghijklmnopqrstuvwxyz"; - private static final String UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static final String DIGITS = "0123456789"; - private static final String SPECIAL = "~#@!"; - - public static String generatePassword(){ - String pattern = ServiceRegex.PASSWORD.getPattern(); - SecureRandom random = new SecureRandom(); - StringBuilder sb = new StringBuilder(); - - int length = MIN_LENGTH + random.nextInt(MAX_LENGTH - MIN_LENGTH+1); - - while(true){ - sb.setLength(0); - for(int i=0; i passwordChars = new ArrayList<>( + password.chars() + .mapToObj(c -> (char) c) + .toList()); + Collections.shuffle(passwordChars); + + // 최종 임시 비밀번호 생성 및 반환 + + return passwordChars.stream() + .map(String::valueOf) + .collect(Collectors.joining()); } } From d514a96292cdb316f7fdb33eb64b2bdc01e5b4b7 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Fri, 15 Mar 2024 15:12:27 +0900 Subject: [PATCH 42/51] =?UTF-8?q?chore:=20application.yml=20=EB=A9=94?= =?UTF-8?q?=EC=9D=BC=20=EA=B4=80=EB=A0=A8=20=EC=86=8D=EC=84=B1=20=EC=88=98?= =?UTF-8?q?=EC=A0=95(#35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 메일 서비스 제공 호스트와 포트 번호도 application.properties에서 관리하는 형태로 수정 --- be/src/main/resources/application.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/be/src/main/resources/application.yml b/be/src/main/resources/application.yml index 91b663c1..9d3d6b82 100644 --- a/be/src/main/resources/application.yml +++ b/be/src/main/resources/application.yml @@ -12,7 +12,7 @@ spring: show_sql: true format_sql: true mail: - host: smtp.gmail.com - port: 587 + host: ${GOOGLE_SMTP_HOST} + port: ${GOOGLE_SMTP_PORT} username: ${GOOGLE_SMTP_USERNAME} password: ${GOOGLE_SMTP_PASSWORD} From 78b6f1db4b3653105059ddcb8359bbadec2d9fec Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Sun, 17 Mar 2024 16:03:18 +0900 Subject: [PATCH 43/51] =?UTF-8?q?feat:=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=B0=BE=EA=B8=B0=20API,=20=EC=9D=B4=EB=A9=94?= =?UTF-8?q?=EC=9D=BC=20=ED=98=95=EC=8B=9D=20=EA=B2=80=EC=A6=9D=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80(#35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/yeonba/be/login/controller/LoginController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/be/src/main/java/yeonba/be/login/controller/LoginController.java b/be/src/main/java/yeonba/be/login/controller/LoginController.java index f74207a0..0f6a1aec 100644 --- a/be/src/main/java/yeonba/be/login/controller/LoginController.java +++ b/be/src/main/java/yeonba/be/login/controller/LoginController.java @@ -3,6 +3,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; @@ -78,7 +79,7 @@ public ResponseEntity> idInquiry( @ApiResponse(responseCode = "202", description = "임시 비밀번호 발급(비밀번호 찾기) 정상 처리") @PostMapping("/users/help/pw-inquiry") public ResponseEntity> passwordInquiry( - @RequestBody UserPasswordInquiryRequest request) { + @RequestBody @Valid UserPasswordInquiryRequest request) { loginService.sendTemporaryPasswordMail(request); From caeea543c3ff29e581982e954188fce2b9424004 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Mon, 18 Mar 2024 15:34:29 +0900 Subject: [PATCH 44/51] =?UTF-8?q?refactor:=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=20=EC=88=98=EC=A0=95(#35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 어플리케이션 전역에서 검증을 위해 사용되는 정규식들을 모아놓는 용도를 잘 표현하기 위해 클래스명 수정 --- .../be/util/{ServiceRegex.java => GlobalValidationRegex.java} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename be/src/main/java/yeonba/be/util/{ServiceRegex.java => GlobalValidationRegex.java} (77%) diff --git a/be/src/main/java/yeonba/be/util/ServiceRegex.java b/be/src/main/java/yeonba/be/util/GlobalValidationRegex.java similarity index 77% rename from be/src/main/java/yeonba/be/util/ServiceRegex.java rename to be/src/main/java/yeonba/be/util/GlobalValidationRegex.java index d7531dac..ece19604 100644 --- a/be/src/main/java/yeonba/be/util/ServiceRegex.java +++ b/be/src/main/java/yeonba/be/util/GlobalValidationRegex.java @@ -1,13 +1,13 @@ package yeonba.be.util; -public enum ServiceRegex { +public enum GlobalValidationRegex { EMAIL("[a-zA-Z0-9_!#$%&’*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$"), PASSWORD("^(?=.*[a-zA-Z])(?=.*\\d)(?=.*[~#@!]).{8,20}$"); private final String pattern; - ServiceRegex(String pattern) { + GlobalValidationRegex(String pattern) { this.pattern = pattern; } From 0905fdb6ae7afc96a2b11b3092c1d0d5815d4a03 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Mon, 18 Mar 2024 15:35:13 +0900 Subject: [PATCH 45/51] =?UTF-8?q?chore:=20application.yml=20=EB=82=B4?= =?UTF-8?q?=EC=9A=A9=20=EC=A0=95=EB=A0=AC(#35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- be/src/main/resources/application.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/be/src/main/resources/application.yml b/be/src/main/resources/application.yml index 9d3d6b82..c8411266 100644 --- a/be/src/main/resources/application.yml +++ b/be/src/main/resources/application.yml @@ -1,18 +1,21 @@ spring: config: import: "classpath:application.properties" + datasource: url: ${DATABASE_URL} username: ${DATABASE_USERNAME} password: ${DATABASE_PASSWORD} driver-class-name: com.mysql.cj.jdbc.Driver + jpa: properties: hibernate: show_sql: true format_sql: true + mail: host: ${GOOGLE_SMTP_HOST} port: ${GOOGLE_SMTP_PORT} username: ${GOOGLE_SMTP_USERNAME} - password: ${GOOGLE_SMTP_PASSWORD} + password: ${GOOGLE_SMTP_PASSWORD} \ No newline at end of file From bb589e00b397e1de1a6f67f16b9ed122422daad3 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Mon, 18 Mar 2024 15:39:24 +0900 Subject: [PATCH 46/51] =?UTF-8?q?refactor:=20=EC=99=B8=EB=B6=80=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=ED=95=84=EB=93=9C=20=EC=A3=BC=EC=9E=85=20?= =?UTF-8?q?=ED=98=95=ED=83=9C=20=EC=88=98=EC=A0=95(#35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 사용자 지정 속성을 사용한다는 맥락을 잘 나타낼 수 있도록 수정 --- be/src/main/java/yeonba/be/config/MailConfig.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/be/src/main/java/yeonba/be/config/MailConfig.java b/be/src/main/java/yeonba/be/config/MailConfig.java index 089bb3d8..53aebf03 100644 --- a/be/src/main/java/yeonba/be/config/MailConfig.java +++ b/be/src/main/java/yeonba/be/config/MailConfig.java @@ -10,16 +10,16 @@ @Configuration public class MailConfig { - @Value("${spring.mail.host}") + @Value("${GOOGLE_SMTP_HOST}") private String serverHost; - @Value("${spring.mail.port}") + @Value("${GOOGLE_SMTP_PORT}") private int serverPort; - @Value("${spring.mail.username}") + @Value("${GOOGLE_SMTP_USERNAME}") private String username; - @Value("${spring.mail.password}") + @Value("${GOOGLE_SMTP_PASSWORD}") private String password; @Bean From 870809598e66dde1631b96508c5cf812bfadb759 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Mon, 18 Mar 2024 15:39:53 +0900 Subject: [PATCH 47/51] =?UTF-8?q?refactor:=20=EC=99=B8=EB=B6=80=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=ED=95=84=EB=93=9C=20=EC=A3=BC=EC=9E=85=20?= =?UTF-8?q?=ED=98=95=ED=83=9C=20=EC=88=98=EC=A0=95(#35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- be/src/main/java/yeonba/be/util/EmailService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/be/src/main/java/yeonba/be/util/EmailService.java b/be/src/main/java/yeonba/be/util/EmailService.java index 3f64c57d..d8706038 100644 --- a/be/src/main/java/yeonba/be/util/EmailService.java +++ b/be/src/main/java/yeonba/be/util/EmailService.java @@ -10,7 +10,7 @@ @RequiredArgsConstructor public class EmailService { - @Value("{spring.mail.username}") + @Value("${GOOGLE_SMTP_USERNAME}") private String serviceEmail; private final JavaMailSender mailSender; @@ -18,7 +18,7 @@ public class EmailService { public void sendMail( String to, String subject, - String text){ + String text) { SimpleMailMessage mailMessage = new SimpleMailMessage(); mailMessage.setFrom(serviceEmail); mailMessage.setTo(to); From 2d78b005ff5c1229d963958aac15c06967a501f4 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Mon, 18 Mar 2024 15:40:53 +0900 Subject: [PATCH 48/51] =?UTF-8?q?refactor:=20=ED=8C=8C=EB=9D=BC=EB=AF=B8?= =?UTF-8?q?=ED=84=B0=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=88=9C=EC=84=9C=20=EC=88=98=EC=A0=95(#35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 요청 바디에서 읽어온 데이터를 검증한다는 흐름에서 @Valid 어노테이션이 @RequestBody보다 먼저 위치하도록 코드 수정 --- .../main/java/yeonba/be/login/controller/LoginController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/be/src/main/java/yeonba/be/login/controller/LoginController.java b/be/src/main/java/yeonba/be/login/controller/LoginController.java index 0f6a1aec..e9f4c69c 100644 --- a/be/src/main/java/yeonba/be/login/controller/LoginController.java +++ b/be/src/main/java/yeonba/be/login/controller/LoginController.java @@ -79,7 +79,7 @@ public ResponseEntity> idInquiry( @ApiResponse(responseCode = "202", description = "임시 비밀번호 발급(비밀번호 찾기) 정상 처리") @PostMapping("/users/help/pw-inquiry") public ResponseEntity> passwordInquiry( - @RequestBody @Valid UserPasswordInquiryRequest request) { + @Valid @RequestBody UserPasswordInquiryRequest request) { loginService.sendTemporaryPasswordMail(request); From 947115a2d105d73fa53642e8cbaf40c7b8f6e1cf Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Thu, 21 Mar 2024 22:01:47 +0900 Subject: [PATCH 49/51] =?UTF-8?q?refactor:=20=EC=9E=98=EB=AA=BB=EB=90=9C?= =?UTF-8?q?=20=EB=93=A4=EC=97=AC=EC=93=B0=EA=B8=B0=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=A0=95?= =?UTF-8?q?=EB=A0=AC(#8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/yeonba/be/user/entity/User.java | 364 +++++++++--------- 1 file changed, 182 insertions(+), 182 deletions(-) diff --git a/be/src/main/java/yeonba/be/user/entity/User.java b/be/src/main/java/yeonba/be/user/entity/User.java index 0591b923..11b7e6fb 100644 --- a/be/src/main/java/yeonba/be/user/entity/User.java +++ b/be/src/main/java/yeonba/be/user/entity/User.java @@ -32,186 +32,186 @@ @EqualsAndHashCode(of = "id") public class User { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - private boolean gender; - - @Column(nullable = false) - private String name; - - @Column(nullable = false) - private String nickname; - - @Column(nullable = false) - private LocalDate birth; - private int age; - private int height; - - @Column(nullable = false) - private String email; - - @Column(nullable = false) - private String encryptedPassword; - - @Column(nullable = false) - private String salt; - - @Column(nullable = false) - private String phoneNumber; - private int arrow; - private int photoSyncRate; - private boolean inactiveStatus; - - @Column(nullable = false) - private String bodyType; - - @Column(nullable = false) - private String job; - - @Column(nullable = false) - private String mbti; - - @ManyToOne - @JoinColumn(name = "vocal_range_id") - private VocalRange vocalRange; - - @ManyToOne - @JoinColumn(name = "animal_id") - private Animal animal; - - @ManyToOne - @JoinColumn(name = "area_id") - private Area area; - - @OneToMany(mappedBy = "user", fetch = FetchType.EAGER) - List profilePhotos; - - private LocalDateTime lastAccessedAt; - - @CreatedDate - private LocalDateTime createdAt; - - @LastModifiedDate - private LocalDateTime updatedAt; - - private LocalDateTime deletedAt; - - public User( - boolean gender, - String name, - String nickname, - LocalDate birth, - int age, - int height, - String email, - String encryptedPassword, - String salt, - String phoneNumber, - int arrow, - int photoSyncRate, - String bodyType, - String job, - String mbti, - VocalRange vocalRange, - Animal animal, - Area area, - List profilePhotos) { - this.gender = gender; - this.name = name; - this.nickname = nickname; - this.birth = birth; - this.age = age; - this.height = height; - this.email = email; - this.encryptedPassword = encryptedPassword; - this.salt = salt; - this.phoneNumber = phoneNumber; - this.arrow = arrow; - this.photoSyncRate = photoSyncRate; - this.inactiveStatus = true; - this.bodyType = bodyType; - this.job = job; - this.mbti = mbti; - this.vocalRange = vocalRange; - this.animal = animal; - this.area = area; - this.profilePhotos = profilePhotos; - } - - public void validateSameUser(User user) { - - if (!this.equals(user)) { - throw new IllegalArgumentException("동일한 사용자가 아닙니다."); - } - } - - public void validateNotSameUser(User user) { - - if (this.equals(user)) { - throw new IllegalArgumentException("동일한 사용자입니다."); - } - } - - public void changePassword(String encryptedNewPassword) { - - this.encryptedPassword = encryptedNewPassword; - } - - /** - * 삭제된 사용자인지 검증 - */ - public void validateDeletedUser(LocalDateTime now) { - - if (this.deletedAt.isAfter(now)) { - throw new IllegalArgumentException("삭제된 사용자입니다."); - } - } - - public void validateDailyCheck(LocalDate dailyCheckDay) { - - if (this.lastAccessedAt.isAfter(dailyCheckDay.atStartOfDay())) { - throw new GeneralException(ArrowException.ALREADY_CHECKED_USER); - } - } - - public String getRepresentativeProfilePhoto() { - - return this.profilePhotos.get(0).getPhotoUrl(); - } - - public void updateLastAccessedAt(LocalDateTime accessedAt) { - - this.lastAccessedAt = accessedAt; - } - - public void plusArrow(int arrow) { - - this.arrow += arrow; - } - - public void minusArrow(int arrow) { - - if (this.arrow < arrow) { - throw new GeneralException(ArrowException.NOT_ENOUGH_ARROW_TO_SEND); - } - - this.arrow -= arrow; - } - - public String getGender() { - if (this.gender) { - - return "남"; - } - - return "여"; - } - - public List getProfilePhotoUrls() { - - return this.profilePhotos.stream() - .map(ProfilePhoto::getPhotoUrl) - .toList(); - } + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private boolean gender; + + @Column(nullable = false) + private String name; + + @Column(nullable = false) + private String nickname; + + @Column(nullable = false) + private LocalDate birth; + private int age; + private int height; + + @Column(nullable = false) + private String email; + + @Column(nullable = false) + private String encryptedPassword; + + @Column(nullable = false) + private String salt; + + @Column(nullable = false) + private String phoneNumber; + private int arrow; + private int photoSyncRate; + private boolean inactive; + + @Column(nullable = false) + private String bodyType; + + @Column(nullable = false) + private String job; + + @Column(nullable = false) + private String mbti; + + @ManyToOne + @JoinColumn(name = "vocal_range_id") + private VocalRange vocalRange; + + @ManyToOne + @JoinColumn(name = "animal_id") + private Animal animal; + + @ManyToOne + @JoinColumn(name = "area_id") + private Area area; + + @OneToMany(mappedBy = "user", fetch = FetchType.EAGER) + List profilePhotos; + + private LocalDateTime lastAccessedAt; + + @CreatedDate + private LocalDateTime createdAt; + + @LastModifiedDate + private LocalDateTime updatedAt; + + private LocalDateTime deletedAt; + + public User( + boolean gender, + String name, + String nickname, + LocalDate birth, + int age, + int height, + String email, + String encryptedPassword, + String salt, + String phoneNumber, + int arrow, + int photoSyncRate, + String bodyType, + String job, + String mbti, + VocalRange vocalRange, + Animal animal, + Area area, + List profilePhotos) { + this.gender = gender; + this.name = name; + this.nickname = nickname; + this.birth = birth; + this.age = age; + this.height = height; + this.email = email; + this.encryptedPassword = encryptedPassword; + this.salt = salt; + this.phoneNumber = phoneNumber; + this.arrow = arrow; + this.photoSyncRate = photoSyncRate; + this.inactive = true; + this.bodyType = bodyType; + this.job = job; + this.mbti = mbti; + this.vocalRange = vocalRange; + this.animal = animal; + this.area = area; + this.profilePhotos = profilePhotos; + } + + public void validateSameUser(User user) { + + if (!this.equals(user)) { + throw new IllegalArgumentException("동일한 사용자가 아닙니다."); + } + } + + public void validateNotSameUser(User user) { + + if (this.equals(user)) { + throw new IllegalArgumentException("동일한 사용자입니다."); + } + } + + public void changePassword(String encryptedNewPassword) { + + this.encryptedPassword = encryptedNewPassword; + } + + /** + * 삭제된 사용자인지 검증 + */ + public void validateDeletedUser(LocalDateTime now) { + + if (this.deletedAt.isAfter(now)) { + throw new IllegalArgumentException("삭제된 사용자입니다."); + } + } + + public void validateDailyCheck(LocalDate dailyCheckDay) { + + if (this.lastAccessedAt.isAfter(dailyCheckDay.atStartOfDay())) { + throw new GeneralException(ArrowException.ALREADY_CHECKED_USER); + } + } + + public String getRepresentativeProfilePhoto() { + + return this.profilePhotos.get(0).getPhotoUrl(); + } + + public void updateLastAccessedAt(LocalDateTime accessedAt) { + + this.lastAccessedAt = accessedAt; + } + + public void plusArrow(int arrow) { + + this.arrow += arrow; + } + + public void minusArrow(int arrow) { + + if (this.arrow < arrow) { + throw new GeneralException(ArrowException.NOT_ENOUGH_ARROW_TO_SEND); + } + + this.arrow -= arrow; + } + + public String getGender() { + if (this.gender) { + + return "남"; + } + + return "여"; + } + + public List getProfilePhotoUrls() { + + return this.profilePhotos.stream() + .map(ProfilePhoto::getPhotoUrl) + .toList(); + } } From be5153e465a286f76d347adcc1c62f4940ea7c73 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Thu, 21 Mar 2024 22:07:26 +0900 Subject: [PATCH 50/51] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20spring-mail=20=EA=B4=80=EB=A0=A8=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=82=AD=EC=A0=9C(#8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- be/src/main/resources/application.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/be/src/main/resources/application.yml b/be/src/main/resources/application.yml index c8411266..7ce0b78e 100644 --- a/be/src/main/resources/application.yml +++ b/be/src/main/resources/application.yml @@ -12,10 +12,4 @@ spring: properties: hibernate: show_sql: true - format_sql: true - - mail: - host: ${GOOGLE_SMTP_HOST} - port: ${GOOGLE_SMTP_PORT} - username: ${GOOGLE_SMTP_USERNAME} - password: ${GOOGLE_SMTP_PASSWORD} \ No newline at end of file + format_sql: true \ No newline at end of file From 7ea89d04123073c2b332e5aa8e47ed3dc2571791 Mon Sep 17 00:00:00 2001 From: Minjae-An Date: Sat, 23 Mar 2024 15:03:32 +0900 Subject: [PATCH 51/51] =?UTF-8?q?refactor:=20=EC=9E=98=EB=AA=BB=EB=90=9C?= =?UTF-8?q?=20=EB=93=A4=EC=97=AC=EC=93=B0=EA=B8=B0=20=EC=88=98=EC=A0=95,?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=A0=95=EB=A0=AC(#35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- be/build.gradle | 56 +++--- .../java/yeonba/be/config/MailConfig.java | 42 ++-- .../be/login/controller/LoginController.java | 190 +++++++++--------- .../request/UserPasswordInquiryRequest.java | 14 +- .../yeonba/be/login/service/LoginService.java | 30 +-- .../yeonba/be/user/repository/UserQuery.java | 18 +- .../be/user/repository/UserRepository.java | 2 +- .../java/yeonba/be/util/EmailService.java | 28 +-- .../yeonba/be/util/GlobalValidationRegex.java | 18 +- .../be/util/TemporaryPasswordGenerator.java | 74 +++---- 10 files changed, 236 insertions(+), 236 deletions(-) diff --git a/be/build.gradle b/be/build.gradle index 5f48c408..4fef763f 100644 --- a/be/build.gradle +++ b/be/build.gradle @@ -1,51 +1,51 @@ plugins { - id 'java' - id 'org.springframework.boot' version '3.1.8' - id 'io.spring.dependency-management' version '1.1.4' + id 'java' + id 'org.springframework.boot' version '3.1.8' + id 'io.spring.dependency-management' version '1.1.4' } group = 'yeonba' version = '0.0.1-SNAPSHOT' java { - sourceCompatibility = '17' + sourceCompatibility = '17' } configurations { - compileOnly { - extendsFrom annotationProcessor - } + compileOnly { + extendsFrom annotationProcessor + } } repositories { - mavenCentral() + mavenCentral() } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.boot:spring-boot-starter-webflux' - compileOnly 'org.projectlombok:lombok' - runtimeOnly 'com.mysql:mysql-connector-j' - annotationProcessor 'org.projectlombok:lombok' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation 'io.projectreactor:reactor-test' - - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' - - // aws s3 - implementation 'software.amazon.awssdk:aws-sdk-java:2.16.83' - implementation 'software.amazon.awssdk:s3:2.16.83' - - // spring mail - implementation 'org.springframework.boot:spring-boot-starter-mail:3.2.2' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-webflux' + compileOnly 'org.projectlombok:lombok' + runtimeOnly 'com.mysql:mysql-connector-j' + annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'io.projectreactor:reactor-test' + + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' + + // aws s3 + implementation 'software.amazon.awssdk:aws-sdk-java:2.16.83' + implementation 'software.amazon.awssdk:s3:2.16.83' + + // spring mail + implementation 'org.springframework.boot:spring-boot-starter-mail:3.2.2' } tasks.named('bootBuildImage') { - builder = 'paketobuildpacks/builder-jammy-base:latest' + builder = 'paketobuildpacks/builder-jammy-base:latest' } tasks.named('test') { - useJUnitPlatform() + useJUnitPlatform() } diff --git a/be/src/main/java/yeonba/be/config/MailConfig.java b/be/src/main/java/yeonba/be/config/MailConfig.java index 53aebf03..94eeced4 100644 --- a/be/src/main/java/yeonba/be/config/MailConfig.java +++ b/be/src/main/java/yeonba/be/config/MailConfig.java @@ -10,31 +10,31 @@ @Configuration public class MailConfig { - @Value("${GOOGLE_SMTP_HOST}") - private String serverHost; + @Value("${GOOGLE_SMTP_HOST}") + private String serverHost; - @Value("${GOOGLE_SMTP_PORT}") - private int serverPort; + @Value("${GOOGLE_SMTP_PORT}") + private int serverPort; - @Value("${GOOGLE_SMTP_USERNAME}") - private String username; + @Value("${GOOGLE_SMTP_USERNAME}") + private String username; - @Value("${GOOGLE_SMTP_PASSWORD}") - private String password; + @Value("${GOOGLE_SMTP_PASSWORD}") + private String password; - @Bean - public JavaMailSender javaMailSender() { - JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); - mailSender.setHost(serverHost); - mailSender.setPort(serverPort); - mailSender.setUsername(username); - mailSender.setPassword(password); + @Bean + public JavaMailSender javaMailSender() { + JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); + mailSender.setHost(serverHost); + mailSender.setPort(serverPort); + mailSender.setUsername(username); + mailSender.setPassword(password); - Properties properties = mailSender.getJavaMailProperties(); - properties.put("mail.transport.protocol", "smtp"); - properties.put("mail.smtp.auth", "true"); - properties.put("mail.smtp.starttls.enable", "true"); + Properties properties = mailSender.getJavaMailProperties(); + properties.put("mail.transport.protocol", "smtp"); + properties.put("mail.smtp.auth", "true"); + properties.put("mail.smtp.starttls.enable", "true"); - return mailSender; - } + return mailSender; + } } diff --git a/be/src/main/java/yeonba/be/login/controller/LoginController.java b/be/src/main/java/yeonba/be/login/controller/LoginController.java index e9f4c69c..a2fc39ba 100644 --- a/be/src/main/java/yeonba/be/login/controller/LoginController.java +++ b/be/src/main/java/yeonba/be/login/controller/LoginController.java @@ -27,99 +27,99 @@ @RequiredArgsConstructor public class LoginController { - private final LoginService loginService; - - @Operation(summary = "회원가입", description = "회원가입을 할 수 있습니다.") - @PostMapping("/users/join") - public ResponseEntity> join( - @RequestBody UserJoinRequest request) { - - String createdJwt = "created"; - - return ResponseEntity - .ok() - .body(new CustomResponse<>(new UserJoinResponse(createdJwt))); - } - - @Operation( - summary = "전화번호 인증 코드 전송", - description = "전화번호 인증을 위해 해당 번호로 인증 코드를 발송합니다." - ) - @ApiResponse( - responseCode = "204", - description = "전화번호 인증 코드 전송 성공" - ) - @PostMapping("/users/help/id-inquiry/verification-code") - public ResponseEntity> verifyPhoneNumber( - @RequestBody UserPhoneNumberVerifyRequest request) { - - return ResponseEntity - .accepted() - .body(new CustomResponse<>()); - } - - @Operation( - summary = "아이디 찾기", - description = "인증 코드를 바탕으로 아이디를 찾을 수 있습니다." - ) - @ApiResponse( - responseCode = "200", - description = "아이디 찾기 정상 처리" - ) - @PostMapping("/users/help/id-inquiry") - public ResponseEntity> idInquiry( - @RequestBody UserIdInquiryRequest request) { - - return ResponseEntity - .ok() - .body(new CustomResponse<>(new UserIdInquiryResponse("mj3242@naver.com"))); - } - - @Operation(summary = "비밀번호 찾기", description = "이메일로 임시 비밀번호를 발급받을 수 있습니다.") - @ApiResponse(responseCode = "202", description = "임시 비밀번호 발급(비밀번호 찾기) 정상 처리") - @PostMapping("/users/help/pw-inquiry") - public ResponseEntity> passwordInquiry( - @Valid @RequestBody UserPasswordInquiryRequest request) { - - loginService.sendTemporaryPasswordMail(request); - - return ResponseEntity - .accepted() - .body(new CustomResponse<>()); - } - - @Operation(summary = "로그인", description = "로그인을 할 수 있습니다.") - @PostMapping("/users/login") - public ResponseEntity> login( - @RequestBody UserLoginRequest request) { - - return ResponseEntity - .ok() - .body(new CustomResponse<>( - new UserLoginResponse( - """ - eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 - .eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ - .SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c""", - """ - eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 - .eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ - .SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c""" - ))); - } - - @Operation( - summary = "access token 재발급", - description = "refresh token을 통해 access token을 재발급받을 수 있습니다." - ) - @PostMapping("/users/refresh") - public ResponseEntity> refresh( - @RequestBody UserRefreshTokenRequest request) { - - String createdJwt = "created"; - - return ResponseEntity - .ok() - .body(new CustomResponse<>(new UserRefreshTokenResponse(createdJwt))); - } + private final LoginService loginService; + + @Operation(summary = "회원가입", description = "회원가입을 할 수 있습니다.") + @PostMapping("/users/join") + public ResponseEntity> join( + @RequestBody UserJoinRequest request) { + + String createdJwt = "created"; + + return ResponseEntity + .ok() + .body(new CustomResponse<>(new UserJoinResponse(createdJwt))); + } + + @Operation( + summary = "전화번호 인증 코드 전송", + description = "전화번호 인증을 위해 해당 번호로 인증 코드를 발송합니다." + ) + @ApiResponse( + responseCode = "204", + description = "전화번호 인증 코드 전송 성공" + ) + @PostMapping("/users/help/id-inquiry/verification-code") + public ResponseEntity> verifyPhoneNumber( + @RequestBody UserPhoneNumberVerifyRequest request) { + + return ResponseEntity + .accepted() + .body(new CustomResponse<>()); + } + + @Operation( + summary = "아이디 찾기", + description = "인증 코드를 바탕으로 아이디를 찾을 수 있습니다." + ) + @ApiResponse( + responseCode = "200", + description = "아이디 찾기 정상 처리" + ) + @PostMapping("/users/help/id-inquiry") + public ResponseEntity> idInquiry( + @RequestBody UserIdInquiryRequest request) { + + return ResponseEntity + .ok() + .body(new CustomResponse<>(new UserIdInquiryResponse("mj3242@naver.com"))); + } + + @Operation(summary = "비밀번호 찾기", description = "이메일로 임시 비밀번호를 발급받을 수 있습니다.") + @ApiResponse(responseCode = "202", description = "임시 비밀번호 발급(비밀번호 찾기) 정상 처리") + @PostMapping("/users/help/pw-inquiry") + public ResponseEntity> passwordInquiry( + @Valid @RequestBody UserPasswordInquiryRequest request) { + + loginService.sendTemporaryPasswordMail(request); + + return ResponseEntity + .accepted() + .body(new CustomResponse<>()); + } + + @Operation(summary = "로그인", description = "로그인을 할 수 있습니다.") + @PostMapping("/users/login") + public ResponseEntity> login( + @RequestBody UserLoginRequest request) { + + return ResponseEntity + .ok() + .body(new CustomResponse<>( + new UserLoginResponse( + """ + eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 + .eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ + .SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c""", + """ + eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 + .eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ + .SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c""" + ))); + } + + @Operation( + summary = "access token 재발급", + description = "refresh token을 통해 access token을 재발급받을 수 있습니다." + ) + @PostMapping("/users/refresh") + public ResponseEntity> refresh( + @RequestBody UserRefreshTokenRequest request) { + + String createdJwt = "created"; + + return ResponseEntity + .ok() + .body(new CustomResponse<>(new UserRefreshTokenResponse(createdJwt))); + } } diff --git a/be/src/main/java/yeonba/be/login/dto/request/UserPasswordInquiryRequest.java b/be/src/main/java/yeonba/be/login/dto/request/UserPasswordInquiryRequest.java index 02036027..a140a261 100644 --- a/be/src/main/java/yeonba/be/login/dto/request/UserPasswordInquiryRequest.java +++ b/be/src/main/java/yeonba/be/login/dto/request/UserPasswordInquiryRequest.java @@ -9,11 +9,11 @@ @NoArgsConstructor public class UserPasswordInquiryRequest { - @Schema(type = "string", - description = "임시 비밀번호를 받을 이메일", - example = "mj3242@naver.com") - @Pattern( - regexp = "[a-zA-Z0-9_!#$%&’*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$", - message = "유효하지 않은 이메일 형식입니다.") - private String email; + @Schema(type = "string", + description = "임시 비밀번호를 받을 이메일", + example = "mj3242@naver.com") + @Pattern( + regexp = "[a-zA-Z0-9_!#$%&’*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$", + message = "유효하지 않은 이메일 형식입니다.") + private String email; } diff --git a/be/src/main/java/yeonba/be/login/service/LoginService.java b/be/src/main/java/yeonba/be/login/service/LoginService.java index 373c9ecc..73a7d2fd 100644 --- a/be/src/main/java/yeonba/be/login/service/LoginService.java +++ b/be/src/main/java/yeonba/be/login/service/LoginService.java @@ -13,10 +13,10 @@ @RequiredArgsConstructor public class LoginService { - private final String TEMPORARY_PASSWORD_EMAIL_SUBJECT = "연바(연애는 바로 지금) 임시비밀번호 발급"; - private final String TEMPORARY_PASSWORD_EMAIL_TEXT = "임시비밀번호 : %s"; - private final UserQuery userQuery; - private final EmailService emailService; + private final String TEMPORARY_PASSWORD_EMAIL_SUBJECT = "연바(연애는 바로 지금) 임시비밀번호 발급"; + private final String TEMPORARY_PASSWORD_EMAIL_TEXT = "임시비밀번호 : %s"; + private final UserQuery userQuery; + private final EmailService emailService; /* 임시 비밀번호는 다음 과정을 거친다. @@ -26,20 +26,20 @@ public class LoginService { 4. 임시 비밀번호 발급 메일 전송 */ - // TODO : 비밀번호 암호화 로직 추가 + // TODO : 비밀번호 암호화 로직 추가 - @Transactional - public void sendTemporaryPasswordMail(UserPasswordInquiryRequest request) { + @Transactional + public void sendTemporaryPasswordMail(UserPasswordInquiryRequest request) { - String email = request.getEmail(); - User user = userQuery.findByEmail(email); + String email = request.getEmail(); + User user = userQuery.findByEmail(email); - String temporaryPassword = TemporaryPasswordGenerator.generatePassword(); + String temporaryPassword = TemporaryPasswordGenerator.generatePassword(); - String encryptedPassword = temporaryPassword; - user.changePassword(encryptedPassword); + String encryptedPassword = temporaryPassword; + user.changePassword(encryptedPassword); - String text = String.format(TEMPORARY_PASSWORD_EMAIL_TEXT, temporaryPassword); - emailService.sendMail(email, TEMPORARY_PASSWORD_EMAIL_SUBJECT, text); - } + String text = String.format(TEMPORARY_PASSWORD_EMAIL_TEXT, temporaryPassword); + emailService.sendMail(email, TEMPORARY_PASSWORD_EMAIL_SUBJECT, text); + } } diff --git a/be/src/main/java/yeonba/be/user/repository/UserQuery.java b/be/src/main/java/yeonba/be/user/repository/UserQuery.java index 9a47f7c5..dedc56da 100644 --- a/be/src/main/java/yeonba/be/user/repository/UserQuery.java +++ b/be/src/main/java/yeonba/be/user/repository/UserQuery.java @@ -10,17 +10,17 @@ @RequiredArgsConstructor public class UserQuery { - private final UserRepository userRepository; + private final UserRepository userRepository; - public User findById(long userId) { + public User findById(long userId) { - return userRepository.findById(userId) - .orElseThrow(() -> new GeneralException(UserException.USER_NOT_FOUND)); - } + return userRepository.findById(userId) + .orElseThrow(() -> new GeneralException(UserException.USER_NOT_FOUND)); + } - public User findByEmail(String email) { + public User findByEmail(String email) { - return userRepository.findByEmail(email) - .orElseThrow(() -> new GeneralException(UserException.USER_NOT_FOUND)); - } + return userRepository.findByEmail(email) + .orElseThrow(() -> new GeneralException(UserException.USER_NOT_FOUND)); + } } diff --git a/be/src/main/java/yeonba/be/user/repository/UserRepository.java b/be/src/main/java/yeonba/be/user/repository/UserRepository.java index 41aada43..7d3a98b2 100644 --- a/be/src/main/java/yeonba/be/user/repository/UserRepository.java +++ b/be/src/main/java/yeonba/be/user/repository/UserRepository.java @@ -8,5 +8,5 @@ @Repository public interface UserRepository extends JpaRepository { - Optional findByEmail(String email); + Optional findByEmail(String email); } diff --git a/be/src/main/java/yeonba/be/util/EmailService.java b/be/src/main/java/yeonba/be/util/EmailService.java index d8706038..484164f9 100644 --- a/be/src/main/java/yeonba/be/util/EmailService.java +++ b/be/src/main/java/yeonba/be/util/EmailService.java @@ -10,21 +10,21 @@ @RequiredArgsConstructor public class EmailService { - @Value("${GOOGLE_SMTP_USERNAME}") - private String serviceEmail; + @Value("${GOOGLE_SMTP_USERNAME}") + private String serviceEmail; - private final JavaMailSender mailSender; + private final JavaMailSender mailSender; - public void sendMail( - String to, - String subject, - String text) { - SimpleMailMessage mailMessage = new SimpleMailMessage(); - mailMessage.setFrom(serviceEmail); - mailMessage.setTo(to); - mailMessage.setSubject(subject); - mailMessage.setText(text); + public void sendMail( + String to, + String subject, + String text) { + SimpleMailMessage mailMessage = new SimpleMailMessage(); + mailMessage.setFrom(serviceEmail); + mailMessage.setTo(to); + mailMessage.setSubject(subject); + mailMessage.setText(text); - mailSender.send(mailMessage); - } + mailSender.send(mailMessage); + } } diff --git a/be/src/main/java/yeonba/be/util/GlobalValidationRegex.java b/be/src/main/java/yeonba/be/util/GlobalValidationRegex.java index ece19604..b496a24e 100644 --- a/be/src/main/java/yeonba/be/util/GlobalValidationRegex.java +++ b/be/src/main/java/yeonba/be/util/GlobalValidationRegex.java @@ -2,17 +2,17 @@ public enum GlobalValidationRegex { - EMAIL("[a-zA-Z0-9_!#$%&’*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$"), - PASSWORD("^(?=.*[a-zA-Z])(?=.*\\d)(?=.*[~#@!]).{8,20}$"); + EMAIL("[a-zA-Z0-9_!#$%&’*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$"), + PASSWORD("^(?=.*[a-zA-Z])(?=.*\\d)(?=.*[~#@!]).{8,20}$"); - private final String pattern; + private final String pattern; - GlobalValidationRegex(String pattern) { - this.pattern = pattern; - } + GlobalValidationRegex(String pattern) { + this.pattern = pattern; + } - public String getPattern() { + public String getPattern() { - return pattern; - } + return pattern; + } } diff --git a/be/src/main/java/yeonba/be/util/TemporaryPasswordGenerator.java b/be/src/main/java/yeonba/be/util/TemporaryPasswordGenerator.java index eb9a4d2e..2d396b38 100644 --- a/be/src/main/java/yeonba/be/util/TemporaryPasswordGenerator.java +++ b/be/src/main/java/yeonba/be/util/TemporaryPasswordGenerator.java @@ -8,41 +8,41 @@ public class TemporaryPasswordGenerator { - private static final int MIN_LENGTH = 8; - - private static final String DIGITS = "0123456789"; - private static final String LOWER_CASES = "abcdefghijklmnopqrstuvwxyz"; - private static final String SPECIAL_CHARS = "~#@!"; - private static final String UPPER_CASES = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - - public static String generatePassword() { - - // 각 카테고리에서 최소 한 문자씩 선택 - StringBuilder password = new StringBuilder(); - password.append(RandomStringUtils.random(1, LOWER_CASES)) - .append(RandomStringUtils.random(1, UPPER_CASES)) - .append(RandomStringUtils.random(1, DIGITS)) - .append(RandomStringUtils.random(1, SPECIAL_CHARS)); - - // 모든 가능한 문자를 포함하는 문자열 - String allPossibleChars = DIGITS.concat(LOWER_CASES) - .concat(SPECIAL_CHARS) - .concat(UPPER_CASES); - - // 나머지 길이 채우기 - password.append(RandomStringUtils.random(MIN_LENGTH - 4, allPossibleChars)); - - // 문자열 섞기 - List passwordChars = new ArrayList<>( - password.chars() - .mapToObj(c -> (char) c) - .toList()); - Collections.shuffle(passwordChars); - - // 최종 임시 비밀번호 생성 및 반환 - - return passwordChars.stream() - .map(String::valueOf) - .collect(Collectors.joining()); - } + private static final int MIN_LENGTH = 8; + + private static final String DIGITS = "0123456789"; + private static final String LOWER_CASES = "abcdefghijklmnopqrstuvwxyz"; + private static final String SPECIAL_CHARS = "~#@!"; + private static final String UPPER_CASES = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + public static String generatePassword() { + + // 각 카테고리에서 최소 한 문자씩 선택 + StringBuilder password = new StringBuilder(); + password.append(RandomStringUtils.random(1, LOWER_CASES)) + .append(RandomStringUtils.random(1, UPPER_CASES)) + .append(RandomStringUtils.random(1, DIGITS)) + .append(RandomStringUtils.random(1, SPECIAL_CHARS)); + + // 모든 가능한 문자를 포함하는 문자열 + String allPossibleChars = DIGITS.concat(LOWER_CASES) + .concat(SPECIAL_CHARS) + .concat(UPPER_CASES); + + // 나머지 길이 채우기 + password.append(RandomStringUtils.random(MIN_LENGTH - 4, allPossibleChars)); + + // 문자열 섞기 + List passwordChars = new ArrayList<>( + password.chars() + .mapToObj(c -> (char) c) + .toList()); + Collections.shuffle(passwordChars); + + // 최종 임시 비밀번호 생성 및 반환 + + return passwordChars.stream() + .map(String::valueOf) + .collect(Collectors.joining()); + } }