From 16d3ee1e7383a06350b679bad196728a32f5ac7a Mon Sep 17 00:00:00 2001 From: Jiyun Date: Thu, 13 Feb 2025 13:15:28 +0900 Subject: [PATCH 01/20] =?UTF-8?q?#99=20Fix:=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20isOn=20true=EB=A1=9C=20=EB=82=98=EC=98=A4?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/notification/converter/NotificationConverter.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/memesphere/domain/notification/converter/NotificationConverter.java b/src/main/java/com/memesphere/domain/notification/converter/NotificationConverter.java index e2da352c..68d8bde4 100644 --- a/src/main/java/com/memesphere/domain/notification/converter/NotificationConverter.java +++ b/src/main/java/com/memesphere/domain/notification/converter/NotificationConverter.java @@ -16,6 +16,7 @@ public static Notification toNotification(NotificationRequest notificationReques .volatility(notificationRequest.getVolatility()) .stTime(notificationRequest.getStTime()) .isRising(notificationRequest.getIsRising()) + .isOn(true) .build(); } @@ -27,6 +28,7 @@ public static NotificationResponse toNotificationCreateResponse(Notification not .volatility(notification.getVolatility()) .stTime(notification.getStTime()) .isRising(notification.getIsRising()) + .isOn(notification.getIsOn()) .build(); } From 158a806f5c1d3488618edeb0c510783f082da7a2 Mon Sep 17 00:00:00 2001 From: Jiyun Date: Thu, 13 Feb 2025 13:51:02 +0900 Subject: [PATCH 02/20] #99 Fix: fix notification converter --- .../domain/notification/converter/NotificationConverter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/memesphere/domain/notification/converter/NotificationConverter.java b/src/main/java/com/memesphere/domain/notification/converter/NotificationConverter.java index 1fce2d59..fb1f53c9 100644 --- a/src/main/java/com/memesphere/domain/notification/converter/NotificationConverter.java +++ b/src/main/java/com/memesphere/domain/notification/converter/NotificationConverter.java @@ -30,6 +30,7 @@ public static NotificationResponse toNotificationCreateResponse(Notification not .volatility(notification.getVolatility()) .stTime(notification.getStTime()) .isRising(notification.getIsRising()) + .isOn(notification.getIsOn()) .build(); } From b78c70c8ece7228fcd4493621b7de148932ab9a0 Mon Sep 17 00:00:00 2001 From: noeyeyh <126255206+noeyeyh@users.noreply.github.com> Date: Thu, 13 Feb 2025 16:23:46 +0900 Subject: [PATCH 03/20] =?UTF-8?q?Fix:=20=EC=95=8C=EB=A6=BC=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20api=20=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notification/service/CoinNotificationServiceImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/memesphere/domain/notification/service/CoinNotificationServiceImpl.java b/src/main/java/com/memesphere/domain/notification/service/CoinNotificationServiceImpl.java index 56aaf93b..c7d6c5c8 100644 --- a/src/main/java/com/memesphere/domain/notification/service/CoinNotificationServiceImpl.java +++ b/src/main/java/com/memesphere/domain/notification/service/CoinNotificationServiceImpl.java @@ -77,8 +77,8 @@ public String modifyNotification(Long notificationId) { @Override public NotificationListResponse removeNotification(Long notificationId) { - MemeCoin memeCoin = memeCoinRepository.findById(notificationId) - .orElseThrow(() -> new GeneralException(ErrorStatus.MEMECOIN_NOT_FOUND)); + Notification existingNotification = notificationRepository.findById(notificationId) + .orElseThrow(() -> new GeneralException(ErrorStatus.NOTIFICATION_NOT_FOUND)); notificationRepository.deleteById(notificationId); From 78b190dc68321ef0562be873153e091ce8d15777 Mon Sep 17 00:00:00 2001 From: marshmallowing <114673063+marshmallowing@users.noreply.github.com> Date: Thu, 13 Feb 2025 20:30:24 +0900 Subject: [PATCH 04/20] =?UTF-8?q?#107=20Fix:=20=EA=B8=B0=EB=B3=B8=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=20=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/memesphere/domain/image/controller/ImageController.java | 2 +- .../com/memesphere/domain/image/service/ProfileService.java | 1 - .../com/memesphere/domain/user/dto/request/SignUpRequest.java | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/memesphere/domain/image/controller/ImageController.java b/src/main/java/com/memesphere/domain/image/controller/ImageController.java index 7e214166..f22c44ae 100644 --- a/src/main/java/com/memesphere/domain/image/controller/ImageController.java +++ b/src/main/java/com/memesphere/domain/image/controller/ImageController.java @@ -43,7 +43,7 @@ public ApiResponse getProfile(@AuthenticationPrincipal CustomUserDetails // 현재 로그인한 유저 정보에서 프로필 이미지 가져오기 String profileImage = profileService.getProfileImage(customUserDetails); - return ApiResponse.onSuccess((profileImage == null || profileImage.isEmpty()) ? "null" : profileImage); + return ApiResponse.onSuccess(profileImage); } } diff --git a/src/main/java/com/memesphere/domain/image/service/ProfileService.java b/src/main/java/com/memesphere/domain/image/service/ProfileService.java index f133f40b..5250ed61 100644 --- a/src/main/java/com/memesphere/domain/image/service/ProfileService.java +++ b/src/main/java/com/memesphere/domain/image/service/ProfileService.java @@ -1,7 +1,6 @@ package com.memesphere.domain.image.service; import com.memesphere.domain.user.entity.User; -import com.memesphere.domain.user.repository.UserRepository; import com.memesphere.global.apipayload.code.status.ErrorStatus; import com.memesphere.global.apipayload.exception.GeneralException; import com.memesphere.global.jwt.CustomUserDetails; diff --git a/src/main/java/com/memesphere/domain/user/dto/request/SignUpRequest.java b/src/main/java/com/memesphere/domain/user/dto/request/SignUpRequest.java index d2283f35..1877cc1a 100644 --- a/src/main/java/com/memesphere/domain/user/dto/request/SignUpRequest.java +++ b/src/main/java/com/memesphere/domain/user/dto/request/SignUpRequest.java @@ -30,6 +30,7 @@ public class SignUpRequest { @Schema(description = "사용자 생년월일", example = "20001010") String birth; + @NotEmpty @Schema(description = "프로필 이미지", example = "http://umc..jpg") String profileImage; } \ No newline at end of file From d227f8d872dc1c8cc976a2b17ac5c60f2f638e50 Mon Sep 17 00:00:00 2001 From: nimuy99 Date: Thu, 13 Feb 2025 20:58:39 +0900 Subject: [PATCH 05/20] =?UTF-8?q?#105=20Feat:=20=EC=BD=94=EC=9D=B8=20?= =?UTF-8?q?=EB=94=94=ED=85=8C=EC=9D=BC=20=EA=B0=80=EA=B2=A9=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/ChartDataRepository.java | 6 +++ .../detail/controller/DetailController.java | 35 +++++++++++++++-- .../detail/converter/DetailConverter.java | 18 +++++++++ .../dto/response/PriceInfoResponse.java | 38 +++++++++++++++++++ .../detail/service/DetailQueryService.java | 10 +++++ ...rvice.java => DetailQueryServiceImpl.java} | 23 ++++++++--- .../apipayload/code/status/ErrorStatus.java | 1 + 7 files changed, 123 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/memesphere/domain/detail/dto/response/PriceInfoResponse.java create mode 100644 src/main/java/com/memesphere/domain/detail/service/DetailQueryService.java rename src/main/java/com/memesphere/domain/detail/service/{DetailService.java => DetailQueryServiceImpl.java} (57%) diff --git a/src/main/java/com/memesphere/domain/chartdata/repository/ChartDataRepository.java b/src/main/java/com/memesphere/domain/chartdata/repository/ChartDataRepository.java index 7cc5e8eb..a6a163a1 100644 --- a/src/main/java/com/memesphere/domain/chartdata/repository/ChartDataRepository.java +++ b/src/main/java/com/memesphere/domain/chartdata/repository/ChartDataRepository.java @@ -11,6 +11,7 @@ import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; public interface ChartDataRepository extends JpaRepository { @Query("SELECT SUM(c.volume) FROM ChartData c " + @@ -32,4 +33,9 @@ public interface ChartDataRepository extends JpaRepository { //TODO: 위아래 코드 합치는 방법 찾기 List findByMemeCoinOrderByRecordedTimeDesc(MemeCoin memeCoin, Pageable pageable); + @Query("SELECT c FROM ChartData c " + + "WHERE c.memeCoin.id = :coinId " + + "AND c.recordedTime = " + + "(SELECT MAX(c2.recordedTime) FROM ChartData c2 WHERE c2.memeCoin = c.memeCoin)") + Optional findLatestByCoinId(Long coinId); } diff --git a/src/main/java/com/memesphere/domain/detail/controller/DetailController.java b/src/main/java/com/memesphere/domain/detail/controller/DetailController.java index 8b37fa89..c08a01d8 100644 --- a/src/main/java/com/memesphere/domain/detail/controller/DetailController.java +++ b/src/main/java/com/memesphere/domain/detail/controller/DetailController.java @@ -1,8 +1,10 @@ package com.memesphere.domain.detail.controller; +import com.memesphere.domain.detail.dto.response.PriceInfoResponse; +import com.memesphere.domain.detail.service.DetailQueryServiceImpl; import com.memesphere.global.apipayload.ApiResponse; import com.memesphere.domain.detail.dto.response.DetailGetResponse; -import com.memesphere.domain.detail.service.DetailService; +import com.memesphere.domain.detail.service.DetailQueryService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; @@ -17,7 +19,7 @@ @RequestMapping("/detail") public class DetailController { - private final DetailService detailService; + private final DetailQueryService detailQueryService; @GetMapping("/{memeId}") @Operation(summary = "밈코인 세부 정보 조회 API", @@ -38,7 +40,34 @@ public class DetailController { ```""") public ApiResponse getDetail(@PathVariable("memeId") Long memeId) { - DetailGetResponse detailGetResponse = detailService.getDetail(memeId); + DetailGetResponse detailGetResponse = detailQueryService.getDetail(memeId); return ApiResponse.onSuccess(detailGetResponse); } + + @GetMapping("/{memeId}/price-info") + @Operation(summary = "밈코인 가격 정보 조회 API", + description = """ + 해당 밈코인의 24시간 기준 가격 정보를 보여줍니다. \n + + **요청 형식**: + ``` + "memeId": 코인 아이디 + ``` + \n + **응답 형식**: + ``` + - "coinId": 코인 아이디 + - "price": 현재가 + - "priceChange": 가격 변화량 + - "priceChangeAbsolute": 가격 변화량(절대값) + - "priceChangeDirection": 밈코인 상승(up, 0 이상)/하락(down) + - "priceChangeRate": 가격 변화율 + - "weightedAveragePrice": 거래량 가중 평균 가격 + - "highPrice": 24h 최고가 + - "lowPrice": 24h 최저가 + ```""") + public ApiResponse getPriceInfo(@PathVariable("memeId") Long coinId) { + + return ApiResponse.onSuccess(detailQueryService.findPriceInfo(coinId)); + } } diff --git a/src/main/java/com/memesphere/domain/detail/converter/DetailConverter.java b/src/main/java/com/memesphere/domain/detail/converter/DetailConverter.java index 3a6150be..d1865fae 100644 --- a/src/main/java/com/memesphere/domain/detail/converter/DetailConverter.java +++ b/src/main/java/com/memesphere/domain/detail/converter/DetailConverter.java @@ -1,8 +1,12 @@ package com.memesphere.domain.detail.converter; +import com.memesphere.domain.chartdata.entity.ChartData; +import com.memesphere.domain.detail.dto.response.PriceInfoResponse; import com.memesphere.domain.memecoin.entity.MemeCoin; import com.memesphere.domain.detail.dto.response.DetailGetResponse; +import java.math.BigDecimal; + public class DetailConverter { public static DetailGetResponse toDetailGetResponse(MemeCoin memeCoin) { @@ -17,4 +21,18 @@ public static DetailGetResponse toDetailGetResponse(MemeCoin memeCoin) { .collectionActive(memeCoin.getCollectionList().isEmpty()) .build(); } + + public static PriceInfoResponse toPriceInfoResponse(MemeCoin memeCoin, ChartData data) { + return PriceInfoResponse.builder() + .coinId(memeCoin.getId()) + .price(data.getPrice()) + .priceChange(data.getPriceChange()) + .priceChangeAbsolute(data.getPriceChange().abs()) + .priceChangeDirection(data.getPriceChangeRate().compareTo(BigDecimal.ZERO) < 0 ? "down" : "up") + .priceChangeRate(data.getPriceChangeRate()) + .weightedAveragePrice(data.getWeighted_average_price()) + .highPrice(data.getHigh_price()) + .lowPrice(data.getLow_price()) + .build(); + } } diff --git a/src/main/java/com/memesphere/domain/detail/dto/response/PriceInfoResponse.java b/src/main/java/com/memesphere/domain/detail/dto/response/PriceInfoResponse.java new file mode 100644 index 00000000..b1cd2c02 --- /dev/null +++ b/src/main/java/com/memesphere/domain/detail/dto/response/PriceInfoResponse.java @@ -0,0 +1,38 @@ +package com.memesphere.domain.detail.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; + +import java.math.BigDecimal; + +@Getter +@Builder +public class PriceInfoResponse { + @Schema(description = "밈코인 아이디", example = "1") + private Long coinId; + + @Schema(description = "현재가", example = "0.20") + private BigDecimal price; + + @Schema(description = "가격 변화량", example = "-0.03") + private BigDecimal priceChange; + + @Schema(description = "가격 변화량 절대값", example = "0.03") + private BigDecimal priceChangeAbsolute; + + @Schema(description = "가격 변화 방향", example = "down") + private String priceChangeDirection; + + @Schema(description = "가격 변화율", example = "-6.35") + private BigDecimal priceChangeRate; + + @Schema(description = "거래량 가중 평균 가격", example = "-942.38") + private BigDecimal weightedAveragePrice; + + @Schema(description = "24h 최고가", example = "2500") + private BigDecimal highPrice; + + @Schema(description = "24h 최저가", example = "1500") + private BigDecimal lowPrice; +} diff --git a/src/main/java/com/memesphere/domain/detail/service/DetailQueryService.java b/src/main/java/com/memesphere/domain/detail/service/DetailQueryService.java new file mode 100644 index 00000000..8840b825 --- /dev/null +++ b/src/main/java/com/memesphere/domain/detail/service/DetailQueryService.java @@ -0,0 +1,10 @@ +package com.memesphere.domain.detail.service; + + +import com.memesphere.domain.detail.dto.response.DetailGetResponse; +import com.memesphere.domain.detail.dto.response.PriceInfoResponse; + +public interface DetailQueryService { + DetailGetResponse getDetail(Long meme_id); + PriceInfoResponse findPriceInfo(Long coinId); +} diff --git a/src/main/java/com/memesphere/domain/detail/service/DetailService.java b/src/main/java/com/memesphere/domain/detail/service/DetailQueryServiceImpl.java similarity index 57% rename from src/main/java/com/memesphere/domain/detail/service/DetailService.java rename to src/main/java/com/memesphere/domain/detail/service/DetailQueryServiceImpl.java index ef1eb011..7c4fc148 100644 --- a/src/main/java/com/memesphere/domain/detail/service/DetailService.java +++ b/src/main/java/com/memesphere/domain/detail/service/DetailQueryServiceImpl.java @@ -1,20 +1,23 @@ package com.memesphere.domain.detail.service; +import com.memesphere.domain.chartdata.entity.ChartData; +import com.memesphere.domain.chartdata.repository.ChartDataRepository; import com.memesphere.domain.detail.converter.DetailConverter; -import com.memesphere.global.apipayload.code.status.ErrorStatus; -import com.memesphere.global.apipayload.exception.GeneralException; -import com.memesphere.domain.memecoin.entity.MemeCoin; import com.memesphere.domain.detail.dto.response.DetailGetResponse; +import com.memesphere.domain.detail.dto.response.PriceInfoResponse; +import com.memesphere.domain.memecoin.entity.MemeCoin; import com.memesphere.domain.memecoin.repository.MemeCoinRepository; +import com.memesphere.global.apipayload.code.status.ErrorStatus; +import com.memesphere.global.apipayload.exception.GeneralException; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor -public class DetailService { - +public class DetailQueryServiceImpl implements DetailQueryService { private final MemeCoinRepository memeCoinRepository; + private final ChartDataRepository chartDataRepository; @Transactional public DetailGetResponse getDetail(Long meme_id) { @@ -26,4 +29,14 @@ public DetailGetResponse getDetail(Long meme_id) { return detailGetResponse; } + + public PriceInfoResponse findPriceInfo(Long coinId) { + MemeCoin memeCoin = memeCoinRepository.findById(coinId) + .orElseThrow(() -> new GeneralException(ErrorStatus.MEMECOIN_NOT_FOUND)); + + ChartData data = chartDataRepository.findLatestByCoinId(coinId) + .orElseThrow(() -> new GeneralException(ErrorStatus.CHARTDATA_NOT_FOUND)); + + return DetailConverter.toPriceInfoResponse(memeCoin, data); + } } diff --git a/src/main/java/com/memesphere/global/apipayload/code/status/ErrorStatus.java b/src/main/java/com/memesphere/global/apipayload/code/status/ErrorStatus.java index d7ebc7de..e46d7ea3 100644 --- a/src/main/java/com/memesphere/global/apipayload/code/status/ErrorStatus.java +++ b/src/main/java/com/memesphere/global/apipayload/code/status/ErrorStatus.java @@ -31,6 +31,7 @@ public enum ErrorStatus implements BaseCode { // ChartData load 에러 CANNOT_LOAD_CHARTDATA(HttpStatus.BAD_REQUEST, "CANNOT LOAD CHARTDATA", "ChartData를 Binance에서 로드할 수 없습니다."), + CHARTDATA_NOT_FOUND(HttpStatus.NOT_FOUND, "CHARTDATA NOT FOUND", "차트 데이터를 찾을 수 없습니다."), // notification 에러 CANNOT_CHECK_VOLATILITY(HttpStatus.NOT_FOUND, "CANNOT CHECK VOLATILITY", "변동성을 확인할 수 없습니다."), From a1ca030ebd40c6805d45d1f563b38600a74effba Mon Sep 17 00:00:00 2001 From: nimuy99 Date: Thu, 13 Feb 2025 21:17:39 +0900 Subject: [PATCH 06/20] =?UTF-8?q?#105=20Feat:=20=EC=BD=94=EC=9D=B8=20?= =?UTF-8?q?=EB=94=94=ED=85=8C=EC=9D=BC=20=EC=88=9C=EC=9C=84=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../memesphere/domain/detail/controller/DetailController.java | 1 + .../memesphere/domain/detail/converter/DetailConverter.java | 1 + .../domain/detail/dto/response/DetailGetResponse.java | 3 +++ 3 files changed, 5 insertions(+) diff --git a/src/main/java/com/memesphere/domain/detail/controller/DetailController.java b/src/main/java/com/memesphere/domain/detail/controller/DetailController.java index c08a01d8..e8335c6f 100644 --- a/src/main/java/com/memesphere/domain/detail/controller/DetailController.java +++ b/src/main/java/com/memesphere/domain/detail/controller/DetailController.java @@ -37,6 +37,7 @@ public class DetailController { - "image": 밈코인 이미지 - "keywords": 밈코인 키워드 (리스트 형식) - "collectionActive": 컬렉션 유무 (저장 유무) + - "rank": 밈코인 순위(1 ~ 5위까지, 나머지 순위는 null) ```""") public ApiResponse getDetail(@PathVariable("memeId") Long memeId) { diff --git a/src/main/java/com/memesphere/domain/detail/converter/DetailConverter.java b/src/main/java/com/memesphere/domain/detail/converter/DetailConverter.java index d1865fae..4185c102 100644 --- a/src/main/java/com/memesphere/domain/detail/converter/DetailConverter.java +++ b/src/main/java/com/memesphere/domain/detail/converter/DetailConverter.java @@ -19,6 +19,7 @@ public static DetailGetResponse toDetailGetResponse(MemeCoin memeCoin) { .image(memeCoin.getImage()) .keywords(memeCoin.getKeywords()) .collectionActive(memeCoin.getCollectionList().isEmpty()) + .rank(memeCoin.getTrendRank()) .build(); } diff --git a/src/main/java/com/memesphere/domain/detail/dto/response/DetailGetResponse.java b/src/main/java/com/memesphere/domain/detail/dto/response/DetailGetResponse.java index ad6bc729..b26a0f80 100644 --- a/src/main/java/com/memesphere/domain/detail/dto/response/DetailGetResponse.java +++ b/src/main/java/com/memesphere/domain/detail/dto/response/DetailGetResponse.java @@ -30,4 +30,7 @@ public class DetailGetResponse { @Schema(description = "컬렉션 유무", example = "True") private boolean collectionActive; + + @Schema(description = "밈코인 순위", example = "1") + private Integer rank; } From 999067d7b77404732de20a8287902f3f3e1d938a Mon Sep 17 00:00:00 2001 From: Jiyun Date: Fri, 14 Feb 2025 11:14:32 +0900 Subject: [PATCH 07/20] =?UTF-8?q?#104=20Fix:=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EB=B3=80=EB=8F=99=EC=84=B1=20=EC=B2=B4=ED=81=AC=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/ChartDataRepository.java | 3 +- .../scheduler/ChartDataScheduler.java | 16 ++++- .../service/ChartDataQueryService.java | 2 +- .../service/ChartDataQueryServiceImpl.java | 6 +- .../service/MemeCoinQueryService.java | 2 +- .../service/MemeCoinQueryServiceImpl.java | 4 +- .../service/PushNotificationService.java | 3 +- .../service/PushNotificationServiceImpl.java | 59 +++++++++++-------- 8 files changed, 61 insertions(+), 34 deletions(-) diff --git a/src/main/java/com/memesphere/domain/chartdata/repository/ChartDataRepository.java b/src/main/java/com/memesphere/domain/chartdata/repository/ChartDataRepository.java index 7cc5e8eb..0fa24659 100644 --- a/src/main/java/com/memesphere/domain/chartdata/repository/ChartDataRepository.java +++ b/src/main/java/com/memesphere/domain/chartdata/repository/ChartDataRepository.java @@ -29,7 +29,6 @@ public interface ChartDataRepository extends JpaRepository { List findByMemeCoinOrderByRecordedTimeDesc(MemeCoin memeCoin); - //TODO: 위아래 코드 합치는 방법 찾기 - List findByMemeCoinOrderByRecordedTimeDesc(MemeCoin memeCoin, Pageable pageable); + List findByMemeCoinAndRecordedTimeAfterOrderByRecordedTimeDesc(MemeCoin memeCoin, LocalDateTime recordedTime, Pageable pageable); } diff --git a/src/main/java/com/memesphere/domain/chartdata/scheduler/ChartDataScheduler.java b/src/main/java/com/memesphere/domain/chartdata/scheduler/ChartDataScheduler.java index b7b69441..549bb884 100644 --- a/src/main/java/com/memesphere/domain/chartdata/scheduler/ChartDataScheduler.java +++ b/src/main/java/com/memesphere/domain/chartdata/scheduler/ChartDataScheduler.java @@ -8,8 +8,11 @@ import com.memesphere.domain.memecoin.entity.MemeCoin; import com.memesphere.domain.memecoin.repository.MemeCoinRepository; import com.memesphere.domain.memecoin.service.MemeCoinQueryService; +import com.memesphere.global.jwt.CustomUserDetails; import lombok.RequiredArgsConstructor; import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -24,9 +27,18 @@ public class ChartDataScheduler { private final BinanceQueryService binanceQueryService; private final MemeCoinQueryService memeCoinQueryService; - @Scheduled(cron = "0 0/10 * * * ?") // 0, 10, 20, 30, 40, 50분에 실행 + @Scheduled(cron = "0 0/1 * * * ?") // 0, 10, 20, 30, 40, 50분에 실행 @Transactional public void updateChartData() { + + Long userId = null; + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + // `authentication.getPrincipal()`을 `CustomUserDetails`로 변환 + if (authentication != null && authentication.getPrincipal() instanceof CustomUserDetails) { + CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); + userId = userDetails.getUser().getId(); + } List memeCoins = memeCoinRepository.findAll(); for (MemeCoin memeCoin : memeCoins) { @@ -36,7 +48,7 @@ public void updateChartData() { ChartData chartData = toChartData(memeCoin,response); - memeCoinQueryService.updateChartData(memeCoin.getId(), chartData); + memeCoinQueryService.updateChartData(memeCoin.getId(), chartData, userId); } catch (Exception e) { throw new GeneralException(ErrorStatus.CANNOT_LOAD_CHARTDATA); diff --git a/src/main/java/com/memesphere/domain/chartdata/service/ChartDataQueryService.java b/src/main/java/com/memesphere/domain/chartdata/service/ChartDataQueryService.java index 7a5f636f..1c58a7a1 100644 --- a/src/main/java/com/memesphere/domain/chartdata/service/ChartDataQueryService.java +++ b/src/main/java/com/memesphere/domain/chartdata/service/ChartDataQueryService.java @@ -4,5 +4,5 @@ import com.memesphere.domain.memecoin.entity.MemeCoin; public interface ChartDataQueryService { - void saveChartData(MemeCoin memeCoin, ChartData chartData); + void saveChartData(MemeCoin memeCoin, ChartData chartData, Long userId); } diff --git a/src/main/java/com/memesphere/domain/chartdata/service/ChartDataQueryServiceImpl.java b/src/main/java/com/memesphere/domain/chartdata/service/ChartDataQueryServiceImpl.java index d5435d5d..705df823 100644 --- a/src/main/java/com/memesphere/domain/chartdata/service/ChartDataQueryServiceImpl.java +++ b/src/main/java/com/memesphere/domain/chartdata/service/ChartDataQueryServiceImpl.java @@ -3,6 +3,7 @@ import com.memesphere.domain.chartdata.entity.ChartData; import com.memesphere.domain.chartdata.repository.ChartDataRepository; import com.memesphere.domain.memecoin.entity.MemeCoin; +import com.memesphere.domain.notification.service.PushNotificationService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -12,12 +13,13 @@ @Service @RequiredArgsConstructor public class ChartDataQueryServiceImpl implements ChartDataQueryService { + private final PushNotificationService pushNotificationService; private final ChartDataRepository chartDataRepository; private static final int MAX_CHART_DATA_CNT = 6; @Override @Transactional - public void saveChartData(MemeCoin memeCoin, ChartData newChartData) { + public void saveChartData(MemeCoin memeCoin, ChartData newChartData, Long userId) { List chartDataList = chartDataRepository.findByMemeCoinOrderByRecordedTimeDesc(memeCoin); if (chartDataList.size() >= MAX_CHART_DATA_CNT) { @@ -28,5 +30,7 @@ public void saveChartData(MemeCoin memeCoin, ChartData newChartData) { newChartData.setMemeCoin(memeCoin); chartDataRepository.save(newChartData); + + pushNotificationService.send(userId); } } diff --git a/src/main/java/com/memesphere/domain/memecoin/service/MemeCoinQueryService.java b/src/main/java/com/memesphere/domain/memecoin/service/MemeCoinQueryService.java index 1d6fd810..158f5fdd 100644 --- a/src/main/java/com/memesphere/domain/memecoin/service/MemeCoinQueryService.java +++ b/src/main/java/com/memesphere/domain/memecoin/service/MemeCoinQueryService.java @@ -3,5 +3,5 @@ import com.memesphere.domain.chartdata.entity.ChartData; public interface MemeCoinQueryService { - void updateChartData(Long memeCoinId, ChartData newChartData); + void updateChartData(Long memeCoinId, ChartData newChartData, Long userId); } diff --git a/src/main/java/com/memesphere/domain/memecoin/service/MemeCoinQueryServiceImpl.java b/src/main/java/com/memesphere/domain/memecoin/service/MemeCoinQueryServiceImpl.java index 7f82f8e1..314093e5 100644 --- a/src/main/java/com/memesphere/domain/memecoin/service/MemeCoinQueryServiceImpl.java +++ b/src/main/java/com/memesphere/domain/memecoin/service/MemeCoinQueryServiceImpl.java @@ -18,10 +18,10 @@ public class MemeCoinQueryServiceImpl implements MemeCoinQueryService { @Transactional @Override - public void updateChartData(Long memeCoinId, ChartData newChartData) { + public void updateChartData(Long memeCoinId, ChartData newChartData, Long userId) { MemeCoin memeCoin = memeCoinRepository.findById(memeCoinId) .orElseThrow(() -> new GeneralException(ErrorStatus.MEMECOIN_NOT_FOUND)); - chartDataService.saveChartData(memeCoin, newChartData); + chartDataService.saveChartData(memeCoin, newChartData, userId); } } diff --git a/src/main/java/com/memesphere/domain/notification/service/PushNotificationService.java b/src/main/java/com/memesphere/domain/notification/service/PushNotificationService.java index f004a884..edc2ae64 100644 --- a/src/main/java/com/memesphere/domain/notification/service/PushNotificationService.java +++ b/src/main/java/com/memesphere/domain/notification/service/PushNotificationService.java @@ -1,9 +1,8 @@ package com.memesphere.domain.notification.service; -import com.memesphere.domain.notification.entity.Notification; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; public interface PushNotificationService { SseEmitter subscribe(Long userId, String lastEventId); - void send(Notification notification, Long userId); + void send(Long userId); } diff --git a/src/main/java/com/memesphere/domain/notification/service/PushNotificationServiceImpl.java b/src/main/java/com/memesphere/domain/notification/service/PushNotificationServiceImpl.java index 2252361b..df779556 100644 --- a/src/main/java/com/memesphere/domain/notification/service/PushNotificationServiceImpl.java +++ b/src/main/java/com/memesphere/domain/notification/service/PushNotificationServiceImpl.java @@ -11,6 +11,7 @@ import com.memesphere.global.apipayload.ApiResponse; import com.memesphere.global.apipayload.code.status.ErrorStatus; import com.memesphere.global.apipayload.exception.GeneralException; +import com.memesphere.global.jwt.CustomUserDetails; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.springframework.data.domain.PageRequest; @@ -24,6 +25,8 @@ import java.math.RoundingMode; import java.time.LocalDateTime; import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.stream.Collectors; @Log4j2 @@ -34,6 +37,7 @@ public class PushNotificationServiceImpl implements PushNotificationService { private final EmitterRepository emitterRepository; private final NotificationRepository notificationRepository; private final ChartDataRepository chartDataRepository; + private final ExecutorService executorService = Executors.newSingleThreadExecutor(); // 연결 지속 시간 설정 : 한시간 private static final Long DEFAULT_TIMEOUT = 60L * 1000 * 60; @@ -61,18 +65,6 @@ public SseEmitter subscribe(Long userId, String lastEventId) { .forEach(entry -> sendToClient(emitter, entry.getKey(), entry.getValue())); } - List notifications = notificationRepository.findAllByUserId(userId); // 사용자가 등록한 알림 전부 가져오기 - - // 변동성을 초과하는 알림 필터링 - List filteredNotifications = notifications.stream() - .filter(notification -> isVolatilityExceeded(notification)) - .collect(Collectors.toList()); - - if (!filteredNotifications.isEmpty()) { - notifications.forEach(notification -> { - send(notification, userId); - }); - } return emitter; } @@ -91,15 +83,35 @@ private void sendToClient(SseEmitter emitter, String emitterId, Object data) { } @Override - public void send(Notification notification, Long userId) { + public void send(Long userId) { - // 실시간 알림 전송 - 로그인 한 유저의 SseEmitter 모두 가져오기 - Map sseEmitters = emitterRepository.findAllEmitterStartWithByUserId(String.valueOf(userId)); + List notifications = notificationRepository.findAllByUserId(userId); // 사용자가 등록한 알림 전부 가져오기 - sseEmitters.forEach((key, emitter) -> { - emitterRepository.saveEventCache(key, notification); - sendToClient(emitter, key, NotificationConverter.toNotificationCreateResponse(notification, notification.getMemeCoin())); - }); + // 변동성을 초과하는 알림 필터링 + List filteredNotifications = notifications.stream() + .filter(notification -> isVolatilityExceeded(notification)) + .collect(Collectors.toList()); + + if (!filteredNotifications.isEmpty()) { + // 실시간 알림 전송 - 로그인 한 유저의 SseEmitter 모두 가져오기 + Map sseEmitters = emitterRepository.findAllEmitterStartWithByUserId(String.valueOf(userId)); + + sseEmitters.forEach((key, emitter) -> { + executorService.submit(() -> { + filteredNotifications.forEach(notification -> { + + emitterRepository.saveEventCache(key, notification); + sendToClient(emitter, key, NotificationConverter.toNotificationCreateResponse(notification, notification.getMemeCoin())); + + try { + Thread.sleep(500); // 0.5초 간격으로 전송 + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); + }); + }); + } } private boolean isVolatilityExceeded(Notification notification) { @@ -113,18 +125,20 @@ private boolean isVolatilityExceeded(Notification notification) { throw new GeneralException(ErrorStatus.CANNOT_LOAD_CHARTDATA); } + LocalDateTime notificationTime = notification.getCreatedAt(); + Integer count = notification.getStTime() / 10; //몇 번 가져올 것인지 결정 Pageable pageable = (Pageable) PageRequest.of(0, count, Sort.by(Sort.Direction.DESC, "createdAt")); - List lastNData = chartDataRepository.findByMemeCoinOrderByRecordedTimeDesc(memeCoin, pageable); + List lastNData = chartDataRepository.findByMemeCoinAndRecordedTimeAfterOrderByRecordedTimeDesc(memeCoin, notificationTime, pageable); if (lastNData.size() < count) { return false; // 비교할 데이터가 부족하면 알림을 보내지 않음 } BigDecimal sum = lastNData.stream() - .map(ChartData::getPrice) + .map(ChartData::getPriceChangeRate) .reduce(BigDecimal.ZERO, BigDecimal::add); - BigDecimal average = sum.divide(BigDecimal.valueOf(lastNData.size()), 4, RoundingMode.HALF_UP); + BigDecimal average = sum.divide(BigDecimal.valueOf(count), 4, RoundingMode.HALF_UP); BigDecimal definedVolatility = new BigDecimal(notification.getVolatility()); if (notification.getIsRising()) { // 상승인 경우 @@ -133,5 +147,4 @@ private boolean isVolatilityExceeded(Notification notification) { return average.compareTo(definedVolatility) < 0; } } - } From 4f089ca6dfe49c16ff4ed9278b2c88823b0660a7 Mon Sep 17 00:00:00 2001 From: Jiyun Date: Fri, 14 Feb 2025 21:22:53 +0900 Subject: [PATCH 08/20] =?UTF-8?q?#104=20Fix:=20=EC=8A=A4=EC=BC=80=EC=A4=84?= =?UTF-8?q?=EB=9F=AC=20=EC=8B=9C=EA=B0=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chartdata/scheduler/ChartDataScheduler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/memesphere/domain/chartdata/scheduler/ChartDataScheduler.java b/src/main/java/com/memesphere/domain/chartdata/scheduler/ChartDataScheduler.java index 549bb884..d8655deb 100644 --- a/src/main/java/com/memesphere/domain/chartdata/scheduler/ChartDataScheduler.java +++ b/src/main/java/com/memesphere/domain/chartdata/scheduler/ChartDataScheduler.java @@ -27,7 +27,7 @@ public class ChartDataScheduler { private final BinanceQueryService binanceQueryService; private final MemeCoinQueryService memeCoinQueryService; - @Scheduled(cron = "0 0/1 * * * ?") // 0, 10, 20, 30, 40, 50분에 실행 + @Scheduled(cron = "0 0/10 * * * ?") // 0, 10, 20, 30, 40, 50분에 실행 @Transactional public void updateChartData() { From a97e4b377352ebe5e3ab5b58c522b6c4b7e1fa67 Mon Sep 17 00:00:00 2001 From: marshmallowing <114673063+marshmallowing@users.noreply.github.com> Date: Fri, 14 Feb 2025 22:06:40 +0900 Subject: [PATCH 09/20] =?UTF-8?q?#112=20Feat:=20=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EB=B2=84=20=EA=B2=80=EC=83=89=EC=96=B4=20=ED=8A=B8=EB=A0=8C?= =?UTF-8?q?=EB=93=9C=20API=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/SearchTrendController.java | 69 +++++++++++++++++++ .../naver/dto/request/SearchRequest.java | 35 ++++++++++ .../naver/dto/response/SearchResponse.java | 41 +++++++++++ .../naver/service/SearchTrendService.java | 58 ++++++++++++++++ .../apipayload/code/status/ErrorStatus.java | 6 +- 5 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/memesphere/domain/naver/controller/SearchTrendController.java create mode 100644 src/main/java/com/memesphere/domain/naver/dto/request/SearchRequest.java create mode 100644 src/main/java/com/memesphere/domain/naver/dto/response/SearchResponse.java create mode 100644 src/main/java/com/memesphere/domain/naver/service/SearchTrendService.java diff --git a/src/main/java/com/memesphere/domain/naver/controller/SearchTrendController.java b/src/main/java/com/memesphere/domain/naver/controller/SearchTrendController.java new file mode 100644 index 00000000..b39c9e45 --- /dev/null +++ b/src/main/java/com/memesphere/domain/naver/controller/SearchTrendController.java @@ -0,0 +1,69 @@ +package com.memesphere.domain.naver.controller; + + +import com.memesphere.domain.naver.dto.request.SearchRequest; +import com.memesphere.domain.naver.dto.response.SearchResponse; +import com.memesphere.domain.naver.service.SearchTrendService; +import com.memesphere.global.apipayload.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; + + +@RestController +@RequestMapping("/naver") +@RequiredArgsConstructor +public class SearchTrendController { + + private final SearchTrendService searchTrendService; + + @ResponseBody + @PostMapping("/trends") + @Operation( + summary = "네이버 검색어 트렌드 조회 API", + description = """ + 해당 키워드에 대한 검색량 비율을 제공합니다. + groupName은 분류 목적이고, 검색 트렌드는 keywords 기준으로 계산됩니다. + 아래 예제와 같이 그룹으로 묶어서 한번에 하나 이상의 요청을 보낼 수 있습니다. + - `timeUnit` 옵션: `date` (일간) / `week` (주간) / `month` (월간) + """ + ) + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "검색 트렌드 요청 데이터", + required = true, + content = @Content( + mediaType = "application/json", + examples = @ExampleObject( + name = "트렌드 조회 예제", + summary = "검색 트렌드 조회 요청 예제", + value = """ + { + "startDate": "2025-02-07", + "endDate": "2025-02-13", + "timeUnit": "date", + "keywordGroups": [ + { + "groupName": "밈코인", + "keywords": ["도지코인"] + }, + { + "groupName": "밈코인", + "keywords": ["시바이누"] + } + ] + } + """ + ) + ) + ) + public ApiResponse getSearchTrends( + @RequestBody SearchRequest searchRequest + ) { + SearchResponse response = searchTrendService.getSearchTrends(searchRequest); + return ApiResponse.onSuccess(response); + } +} + diff --git a/src/main/java/com/memesphere/domain/naver/dto/request/SearchRequest.java b/src/main/java/com/memesphere/domain/naver/dto/request/SearchRequest.java new file mode 100644 index 00000000..078b39dc --- /dev/null +++ b/src/main/java/com/memesphere/domain/naver/dto/request/SearchRequest.java @@ -0,0 +1,35 @@ +package com.memesphere.domain.naver.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; + +import java.util.List; + +@Getter +@Builder +public class SearchRequest { + + @Schema(description = "조회 기간 시작 날짜", example = "2025-02-07") + private String startDate; + + @Schema(description = "조회 기간 종료 날짜", example = "2025-02-14") + private String endDate; + + @Schema(description = "구간 단위", example = "date") + private String timeUnit; + + @Schema(description = "검색어 그룹") + private List keywordGroups; + + @Getter + @Builder + public static class KeywordGroup { + @Schema(description = "그룹 이름", example = "밈코인") + private String groupName; + + @Schema(description = "조회할 검색어", example = "[\"도지코인\"]") + private List keywords; + } +} + diff --git a/src/main/java/com/memesphere/domain/naver/dto/response/SearchResponse.java b/src/main/java/com/memesphere/domain/naver/dto/response/SearchResponse.java new file mode 100644 index 00000000..aa554403 --- /dev/null +++ b/src/main/java/com/memesphere/domain/naver/dto/response/SearchResponse.java @@ -0,0 +1,41 @@ +package com.memesphere.domain.naver.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; + +import java.util.List; + +@Getter +@Builder +public class SearchResponse { + @Schema(description = "조회 기간 시작 날짜", example = "2025-02-07") + private String startDate; + + @Schema(description = "조회 기간 종료 날짜", example = "2025-02-13") + private String endDate; + + @Schema(description = "구간 단위", example = "date") + private String timeUnit; + + private List results; + + @Getter + @Builder + public static class Result { + private String title; + private List keywords; + private List data; + + @Getter + @Builder + public static class Data { + @Schema(description = "구간별 시작 날짜", example = "2025-02-14") + private String period; + + @Schema(description = "구간별 검색량의 상대적 비율", example = "70.2") + private double ratio; + } + } +} + diff --git a/src/main/java/com/memesphere/domain/naver/service/SearchTrendService.java b/src/main/java/com/memesphere/domain/naver/service/SearchTrendService.java new file mode 100644 index 00000000..39165bde --- /dev/null +++ b/src/main/java/com/memesphere/domain/naver/service/SearchTrendService.java @@ -0,0 +1,58 @@ +package com.memesphere.domain.naver.service; + +import com.memesphere.domain.naver.dto.request.SearchRequest; +import com.memesphere.domain.naver.dto.response.SearchResponse; +import com.memesphere.global.apipayload.code.status.ErrorStatus; +import com.memesphere.global.apipayload.exception.GeneralException; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.HttpServerErrorException; +import org.springframework.web.client.RestTemplate; + +@Service +@RequiredArgsConstructor +public class SearchTrendService { + + private final RestTemplate restTemplate; + + @Value("${naver.url}") + private String apiUrl; + + @Value("${naver.client-id}") + private String clientId; + + @Value("${naver.secret}") + private String clientSecret; + + public SearchResponse getSearchTrends(SearchRequest request) { + try { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("X-Naver-Client-Id", clientId); + headers.set("X-Naver-Client-Secret", clientSecret); + + HttpEntity entity = new HttpEntity<>(request, headers); + + ResponseEntity responseEntity = restTemplate.exchange( + apiUrl, + HttpMethod.POST, + entity, + SearchResponse.class + ); + + return responseEntity.getBody(); + + } catch (HttpClientErrorException.BadRequest e) { + throw new GeneralException(ErrorStatus.BAD_REQUEST); + } catch (HttpClientErrorException.Unauthorized e) { + throw new GeneralException(ErrorStatus.KEY_UNAUTHORIZED); + } catch (HttpClientErrorException.Forbidden e) { + throw new GeneralException(ErrorStatus.API_FORBIDDEN); + } catch (HttpServerErrorException e) { + throw new GeneralException(ErrorStatus.INTERNAL_SERVER_ERROR); + } + } +} diff --git a/src/main/java/com/memesphere/global/apipayload/code/status/ErrorStatus.java b/src/main/java/com/memesphere/global/apipayload/code/status/ErrorStatus.java index e46d7ea3..a45eaac7 100644 --- a/src/main/java/com/memesphere/global/apipayload/code/status/ErrorStatus.java +++ b/src/main/java/com/memesphere/global/apipayload/code/status/ErrorStatus.java @@ -55,7 +55,11 @@ public enum ErrorStatus implements BaseCode { PRESIGNED_URL_FAILED(HttpStatus.BAD_REQUEST, "PRESIGNED URL GENERATION FAILED", "presigned URL 생성에 실패했습니다."), // 채팅 에러 - CHAT_NOT_FOUND(HttpStatus.NOT_FOUND, "CHAT NOT FOUND", "채팅을 찾을 수 없습니다."); + CHAT_NOT_FOUND(HttpStatus.NOT_FOUND, "CHAT NOT FOUND", "채팅을 찾을 수 없습니다."), + + // 네이버 api 에러 + KEY_UNAUTHORIZED(HttpStatus.UNAUTHORIZED,"NAVER 401 ERROR", "인증 실패. 클라이언트 ID 또는 시크릿이 올바르지 않습니다."), + API_FORBIDDEN(HttpStatus.FORBIDDEN, "NAVER 403 ERROR","API 호출 횟수를 초과했습니다."); private final HttpStatus httpStatus; private final String code; From c9d67450109d4ce60dc343c62056e7824fddf5ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Fri, 14 Feb 2025 23:52:45 +0900 Subject: [PATCH 10/20] =?UTF-8?q?#106=20Chore:=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 +++ .../com/memesphere/domain/user/controller/UserController.java | 2 ++ .../com/memesphere/domain/user/service/AuthServiceImpl.java | 1 + 3 files changed, 6 insertions(+) diff --git a/build.gradle b/build.gradle index b893e71e..5b2a378b 100644 --- a/build.gradle +++ b/build.gradle @@ -61,6 +61,9 @@ dependencies { // redis implementation 'org.springframework.boot:spring-boot-starter-data-redis' + + // 이메일 전송 + implementation 'org.springframework.boot:spring-boot-starter-mail' } tasks.named('test') { diff --git a/src/main/java/com/memesphere/domain/user/controller/UserController.java b/src/main/java/com/memesphere/domain/user/controller/UserController.java index 5ed37b30..49a32121 100644 --- a/src/main/java/com/memesphere/domain/user/controller/UserController.java +++ b/src/main/java/com/memesphere/domain/user/controller/UserController.java @@ -115,4 +115,6 @@ public ApiResponse isNicknameValidate(@RequestBody NicknameRequest nicknameRe return ApiResponse.onSuccess("사용 가능한 닉네임입니다."); } } + + } diff --git a/src/main/java/com/memesphere/domain/user/service/AuthServiceImpl.java b/src/main/java/com/memesphere/domain/user/service/AuthServiceImpl.java index 49762452..45c5ca7d 100644 --- a/src/main/java/com/memesphere/domain/user/service/AuthServiceImpl.java +++ b/src/main/java/com/memesphere/domain/user/service/AuthServiceImpl.java @@ -111,4 +111,5 @@ public void checkPassword(User user, String password) { public boolean checkNicknameDuplicate(String nickname) { return userRepository.findByNickname(nickname).isPresent(); } + } From 2c505fff1660be35c14b7956ddcfaa566b6aad6c Mon Sep 17 00:00:00 2001 From: Jiyun Date: Sat, 15 Feb 2025 00:07:51 +0900 Subject: [PATCH 11/20] =?UTF-8?q?#104=20Fix:=20loggedInUser=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scheduler/ChartDataScheduler.java | 22 +++++++-------- .../service/ChartDataQueryService.java | 2 +- .../service/ChartDataQueryServiceImpl.java | 5 +--- .../service/MemeCoinQueryService.java | 2 +- .../service/MemeCoinQueryServiceImpl.java | 4 +-- .../PushNotificationController.java | 2 ++ .../repository/EmitterRepositoryImpl.java | 2 +- .../service/PushNotificationServiceImpl.java | 15 ++++++++--- .../global/jwt/LoggedInUserStore.java | 27 +++++++++++++++++++ 9 files changed, 58 insertions(+), 23 deletions(-) create mode 100644 src/main/java/com/memesphere/global/jwt/LoggedInUserStore.java diff --git a/src/main/java/com/memesphere/domain/chartdata/scheduler/ChartDataScheduler.java b/src/main/java/com/memesphere/domain/chartdata/scheduler/ChartDataScheduler.java index d8655deb..888f846b 100644 --- a/src/main/java/com/memesphere/domain/chartdata/scheduler/ChartDataScheduler.java +++ b/src/main/java/com/memesphere/domain/chartdata/scheduler/ChartDataScheduler.java @@ -3,20 +3,20 @@ import com.memesphere.domain.binance.dto.response.BinanceTickerResponse; import com.memesphere.domain.binance.service.BinanceQueryService; import com.memesphere.domain.chartdata.entity.ChartData; +import com.memesphere.domain.notification.service.PushNotificationService; import com.memesphere.global.apipayload.code.status.ErrorStatus; import com.memesphere.global.apipayload.exception.GeneralException; import com.memesphere.domain.memecoin.entity.MemeCoin; import com.memesphere.domain.memecoin.repository.MemeCoinRepository; import com.memesphere.domain.memecoin.service.MemeCoinQueryService; -import com.memesphere.global.jwt.CustomUserDetails; +import com.memesphere.global.jwt.LoggedInUserStore; import lombok.RequiredArgsConstructor; import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.Set; import static com.memesphere.domain.chartdata.converter.ChartDataConverter.toChartData; @@ -26,19 +26,15 @@ public class ChartDataScheduler { private final MemeCoinRepository memeCoinRepository; private final BinanceQueryService binanceQueryService; private final MemeCoinQueryService memeCoinQueryService; + private final LoggedInUserStore loggedInUserStore; + private final PushNotificationService pushNotificationService; @Scheduled(cron = "0 0/10 * * * ?") // 0, 10, 20, 30, 40, 50분에 실행 @Transactional public void updateChartData() { - Long userId = null; + Set loggedInUsers = loggedInUserStore.getLoggedInUsers(); - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - // `authentication.getPrincipal()`을 `CustomUserDetails`로 변환 - if (authentication != null && authentication.getPrincipal() instanceof CustomUserDetails) { - CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); - userId = userDetails.getUser().getId(); - } List memeCoins = memeCoinRepository.findAll(); for (MemeCoin memeCoin : memeCoins) { @@ -48,7 +44,11 @@ public void updateChartData() { ChartData chartData = toChartData(memeCoin,response); - memeCoinQueryService.updateChartData(memeCoin.getId(), chartData, userId); + memeCoinQueryService.updateChartData(memeCoin.getId(), chartData); + + for (Long userId : loggedInUsers) { + pushNotificationService.send(userId); + } } catch (Exception e) { throw new GeneralException(ErrorStatus.CANNOT_LOAD_CHARTDATA); diff --git a/src/main/java/com/memesphere/domain/chartdata/service/ChartDataQueryService.java b/src/main/java/com/memesphere/domain/chartdata/service/ChartDataQueryService.java index 1c58a7a1..7a5f636f 100644 --- a/src/main/java/com/memesphere/domain/chartdata/service/ChartDataQueryService.java +++ b/src/main/java/com/memesphere/domain/chartdata/service/ChartDataQueryService.java @@ -4,5 +4,5 @@ import com.memesphere.domain.memecoin.entity.MemeCoin; public interface ChartDataQueryService { - void saveChartData(MemeCoin memeCoin, ChartData chartData, Long userId); + void saveChartData(MemeCoin memeCoin, ChartData chartData); } diff --git a/src/main/java/com/memesphere/domain/chartdata/service/ChartDataQueryServiceImpl.java b/src/main/java/com/memesphere/domain/chartdata/service/ChartDataQueryServiceImpl.java index 705df823..651ded2a 100644 --- a/src/main/java/com/memesphere/domain/chartdata/service/ChartDataQueryServiceImpl.java +++ b/src/main/java/com/memesphere/domain/chartdata/service/ChartDataQueryServiceImpl.java @@ -13,13 +13,12 @@ @Service @RequiredArgsConstructor public class ChartDataQueryServiceImpl implements ChartDataQueryService { - private final PushNotificationService pushNotificationService; private final ChartDataRepository chartDataRepository; private static final int MAX_CHART_DATA_CNT = 6; @Override @Transactional - public void saveChartData(MemeCoin memeCoin, ChartData newChartData, Long userId) { + public void saveChartData(MemeCoin memeCoin, ChartData newChartData) { List chartDataList = chartDataRepository.findByMemeCoinOrderByRecordedTimeDesc(memeCoin); if (chartDataList.size() >= MAX_CHART_DATA_CNT) { @@ -30,7 +29,5 @@ public void saveChartData(MemeCoin memeCoin, ChartData newChartData, Long userId newChartData.setMemeCoin(memeCoin); chartDataRepository.save(newChartData); - - pushNotificationService.send(userId); } } diff --git a/src/main/java/com/memesphere/domain/memecoin/service/MemeCoinQueryService.java b/src/main/java/com/memesphere/domain/memecoin/service/MemeCoinQueryService.java index 158f5fdd..1d6fd810 100644 --- a/src/main/java/com/memesphere/domain/memecoin/service/MemeCoinQueryService.java +++ b/src/main/java/com/memesphere/domain/memecoin/service/MemeCoinQueryService.java @@ -3,5 +3,5 @@ import com.memesphere.domain.chartdata.entity.ChartData; public interface MemeCoinQueryService { - void updateChartData(Long memeCoinId, ChartData newChartData, Long userId); + void updateChartData(Long memeCoinId, ChartData newChartData); } diff --git a/src/main/java/com/memesphere/domain/memecoin/service/MemeCoinQueryServiceImpl.java b/src/main/java/com/memesphere/domain/memecoin/service/MemeCoinQueryServiceImpl.java index 314093e5..7f82f8e1 100644 --- a/src/main/java/com/memesphere/domain/memecoin/service/MemeCoinQueryServiceImpl.java +++ b/src/main/java/com/memesphere/domain/memecoin/service/MemeCoinQueryServiceImpl.java @@ -18,10 +18,10 @@ public class MemeCoinQueryServiceImpl implements MemeCoinQueryService { @Transactional @Override - public void updateChartData(Long memeCoinId, ChartData newChartData, Long userId) { + public void updateChartData(Long memeCoinId, ChartData newChartData) { MemeCoin memeCoin = memeCoinRepository.findById(memeCoinId) .orElseThrow(() -> new GeneralException(ErrorStatus.MEMECOIN_NOT_FOUND)); - chartDataService.saveChartData(memeCoin, newChartData, userId); + chartDataService.saveChartData(memeCoin, newChartData); } } diff --git a/src/main/java/com/memesphere/domain/notification/controller/PushNotificationController.java b/src/main/java/com/memesphere/domain/notification/controller/PushNotificationController.java index 4a2b1529..90b0d38d 100644 --- a/src/main/java/com/memesphere/domain/notification/controller/PushNotificationController.java +++ b/src/main/java/com/memesphere/domain/notification/controller/PushNotificationController.java @@ -26,6 +26,8 @@ public class PushNotificationController { @GetMapping(value = "/subscribe", produces = MediaType.TEXT_EVENT_STREAM_VALUE) //서버가 클라이언트에게 이벤트 스트림을 전송한다는 것을 명시 @Operation(summary = "알림 전송 API", description = """ + 클라이언트와 서버 연결을 시작합니다. \n + 연결은 1시간 동안 유지됩니다. 등록한 알림이 기준 시간 내 변동성에 해당하는 경우 알림을 전송합니다. \n 변동성은 직접 계산하지 않고 외부 API에서 받아오는 정보를 기준으로 하고 있습니다. """) diff --git a/src/main/java/com/memesphere/domain/notification/repository/EmitterRepositoryImpl.java b/src/main/java/com/memesphere/domain/notification/repository/EmitterRepositoryImpl.java index 299f8a38..01097d91 100644 --- a/src/main/java/com/memesphere/domain/notification/repository/EmitterRepositoryImpl.java +++ b/src/main/java/com/memesphere/domain/notification/repository/EmitterRepositoryImpl.java @@ -29,7 +29,7 @@ public void deleteById(String emitterId) { @Override public Map findAllEmitterStartWithByUserId(String UserId) { return emitters.entrySet().stream() - .filter(entry -> entry.getKey().startsWith(UserId)) + .filter(entry -> entry.getKey().startsWith(UserId + "_")) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } diff --git a/src/main/java/com/memesphere/domain/notification/service/PushNotificationServiceImpl.java b/src/main/java/com/memesphere/domain/notification/service/PushNotificationServiceImpl.java index df779556..3155e832 100644 --- a/src/main/java/com/memesphere/domain/notification/service/PushNotificationServiceImpl.java +++ b/src/main/java/com/memesphere/domain/notification/service/PushNotificationServiceImpl.java @@ -12,6 +12,7 @@ import com.memesphere.global.apipayload.code.status.ErrorStatus; import com.memesphere.global.apipayload.exception.GeneralException; import com.memesphere.global.jwt.CustomUserDetails; +import com.memesphere.global.jwt.LoggedInUserStore; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.springframework.data.domain.PageRequest; @@ -38,6 +39,7 @@ public class PushNotificationServiceImpl implements PushNotificationService { private final NotificationRepository notificationRepository; private final ChartDataRepository chartDataRepository; private final ExecutorService executorService = Executors.newSingleThreadExecutor(); + private final LoggedInUserStore loggedInUserStore; // 연결 지속 시간 설정 : 한시간 private static final Long DEFAULT_TIMEOUT = 60L * 1000 * 60; @@ -48,11 +50,19 @@ public SseEmitter subscribe(Long userId, String lastEventId) { // 고유한 아이디 생성 String emitterId = userId + "_" + System.currentTimeMillis(); // 사용자 id + 현재 시간을 밀리초 단위의 long값 SseEmitter emitter = emitterRepository.save(emitterId, new SseEmitter(DEFAULT_TIMEOUT)); + loggedInUserStore.addUser(userId); // 클라이언트가 SSE 연결을 종료하면 실행됨 - emitter.onCompletion(() -> emitterRepository.deleteById(emitterId)); + emitter.onCompletion(() -> { + emitterRepository.deleteById(emitterId); + loggedInUserStore.removeUser(userId); + + }); // 지정된 시간이 지나거나 클라이언트가 요청을 안하면 실행됨 - emitter.onTimeout(() -> emitterRepository.deleteById(emitterId)); + emitter.onTimeout(() -> { + emitterRepository.deleteById(emitterId); + loggedInUserStore.removeUser(userId); + }); // 최초 연결 더미데이터가 없으면 503 에러가 나므로 더미 데이터 생성 sendToClient(emitter, emitterId, "EventStream Created. [userId=" + userId + "]"); @@ -84,7 +94,6 @@ private void sendToClient(SseEmitter emitter, String emitterId, Object data) { @Override public void send(Long userId) { - List notifications = notificationRepository.findAllByUserId(userId); // 사용자가 등록한 알림 전부 가져오기 // 변동성을 초과하는 알림 필터링 diff --git a/src/main/java/com/memesphere/global/jwt/LoggedInUserStore.java b/src/main/java/com/memesphere/global/jwt/LoggedInUserStore.java new file mode 100644 index 00000000..2dde4f7c --- /dev/null +++ b/src/main/java/com/memesphere/global/jwt/LoggedInUserStore.java @@ -0,0 +1,27 @@ +package com.memesphere.global.jwt; + +import org.springframework.stereotype.Component; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +@Component +public class LoggedInUserStore { + private final Set loggedInUsers = ConcurrentHashMap.newKeySet(); + + public void addUser(Long userId) { + loggedInUsers.add(userId); + } + + public void removeUser(Long userId) { + loggedInUsers.remove(userId); + } + + public boolean isUserLoggedIn(Long userId) { + return loggedInUsers.contains(userId); + } + + public Set getLoggedInUsers() { + return loggedInUsers; + } +} From 604e54db59891d0eab44c44305d8965266e8272b Mon Sep 17 00:00:00 2001 From: Jiyun Date: Sat, 15 Feb 2025 00:50:34 +0900 Subject: [PATCH 12/20] =?UTF-8?q?#104=20Fix:=20chartdatascheduler=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/chartdata/scheduler/ChartDataScheduler.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/memesphere/domain/chartdata/scheduler/ChartDataScheduler.java b/src/main/java/com/memesphere/domain/chartdata/scheduler/ChartDataScheduler.java index 888f846b..fab1ad8d 100644 --- a/src/main/java/com/memesphere/domain/chartdata/scheduler/ChartDataScheduler.java +++ b/src/main/java/com/memesphere/domain/chartdata/scheduler/ChartDataScheduler.java @@ -46,14 +46,14 @@ public void updateChartData() { memeCoinQueryService.updateChartData(memeCoin.getId(), chartData); - for (Long userId : loggedInUsers) { - pushNotificationService.send(userId); - } - } catch (Exception e) { throw new GeneralException(ErrorStatus.CANNOT_LOAD_CHARTDATA); } } + + for (Long userId : loggedInUsers) { + pushNotificationService.send(userId); + } } } From 42843eb4429f03137c10530dcf3f90bf3b6b1ef1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Mon, 17 Feb 2025 02:42:52 +0900 Subject: [PATCH 13/20] =?UTF-8?q?#106=20Chore:=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../memesphere/domain/user/controller/UserController.java | 7 +------ .../memesphere/domain/user/service/AuthServiceImpl.java | 2 +- .../com/memesphere/domain/user/service/UserService.java | 1 - .../memesphere/domain/user/service/UserServiceImpl.java | 3 --- 4 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/memesphere/domain/user/controller/UserController.java b/src/main/java/com/memesphere/domain/user/controller/UserController.java index 49a32121..09bd2a30 100644 --- a/src/main/java/com/memesphere/domain/user/controller/UserController.java +++ b/src/main/java/com/memesphere/domain/user/controller/UserController.java @@ -1,9 +1,6 @@ package com.memesphere.domain.user.controller; -import com.memesphere.domain.user.dto.request.ReissueRequest; -import com.memesphere.domain.user.dto.request.NicknameRequest; -import com.memesphere.domain.user.dto.request.SignInRequest; -import com.memesphere.domain.user.dto.request.SignUpRequest; +import com.memesphere.domain.user.dto.request.*; import com.memesphere.domain.user.dto.response.GoogleUserInfoResponse; import com.memesphere.domain.user.dto.response.TokenResponse; import com.memesphere.domain.user.dto.response.KakaoUserInfoResponse; @@ -115,6 +112,4 @@ public ApiResponse isNicknameValidate(@RequestBody NicknameRequest nicknameRe return ApiResponse.onSuccess("사용 가능한 닉네임입니다."); } } - - } diff --git a/src/main/java/com/memesphere/domain/user/service/AuthServiceImpl.java b/src/main/java/com/memesphere/domain/user/service/AuthServiceImpl.java index 45c5ca7d..bf646d36 100644 --- a/src/main/java/com/memesphere/domain/user/service/AuthServiceImpl.java +++ b/src/main/java/com/memesphere/domain/user/service/AuthServiceImpl.java @@ -38,7 +38,7 @@ public void handleUserRegistration(SignUpRequest signUpRequest) { } User newUser = UserConverter.toAuthUser(signUpRequest, passwordEncoder); - userServiceImpl.save(newUser); + userRepository.save(newUser); } public LoginResponse handleUserLogin(SignInRequest signInRequest) { diff --git a/src/main/java/com/memesphere/domain/user/service/UserService.java b/src/main/java/com/memesphere/domain/user/service/UserService.java index 6ac9ed24..1b3e4790 100644 --- a/src/main/java/com/memesphere/domain/user/service/UserService.java +++ b/src/main/java/com/memesphere/domain/user/service/UserService.java @@ -4,5 +4,4 @@ public interface UserService { User findByLoginId(Long loginId); - void save(User user); } diff --git a/src/main/java/com/memesphere/domain/user/service/UserServiceImpl.java b/src/main/java/com/memesphere/domain/user/service/UserServiceImpl.java index 977a7f27..8c3c34ff 100644 --- a/src/main/java/com/memesphere/domain/user/service/UserServiceImpl.java +++ b/src/main/java/com/memesphere/domain/user/service/UserServiceImpl.java @@ -15,7 +15,4 @@ public User findByLoginId(Long loginId) { return userRepository.findByLoginId(loginId).orElse(null); } - public void save(User user){ - userRepository.save(user); - } } \ No newline at end of file From b8d16580be3bcbb125dd7c83d26ec7c6df96f5cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Mon, 17 Feb 2025 03:54:40 +0900 Subject: [PATCH 14/20] =?UTF-8?q?#106=20Feat:=20=EC=9D=B4=EB=A9=94?= =?UTF-8?q?=EC=9D=BC=20=EC=A0=84=EC=86=A1=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/controller/UserController.java | 16 +++-- .../user/dto/response/EmailResponse.java | 17 +++++ .../domain/user/service/AuthServiceImpl.java | 1 - .../domain/user/service/MailService.java | 8 +++ .../domain/user/service/MailServiceImpl.java | 66 +++++++++++++++++++ .../apipayload/code/status/ErrorStatus.java | 1 + 6 files changed, 104 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/memesphere/domain/user/dto/response/EmailResponse.java create mode 100644 src/main/java/com/memesphere/domain/user/service/MailService.java create mode 100644 src/main/java/com/memesphere/domain/user/service/MailServiceImpl.java diff --git a/src/main/java/com/memesphere/domain/user/controller/UserController.java b/src/main/java/com/memesphere/domain/user/controller/UserController.java index 09bd2a30..b228c811 100644 --- a/src/main/java/com/memesphere/domain/user/controller/UserController.java +++ b/src/main/java/com/memesphere/domain/user/controller/UserController.java @@ -1,14 +1,12 @@ package com.memesphere.domain.user.controller; import com.memesphere.domain.user.dto.request.*; -import com.memesphere.domain.user.dto.response.GoogleUserInfoResponse; -import com.memesphere.domain.user.dto.response.TokenResponse; -import com.memesphere.domain.user.dto.response.KakaoUserInfoResponse; +import com.memesphere.domain.user.dto.response.*; import com.memesphere.domain.user.service.AuthServiceImpl; import com.memesphere.domain.user.service.GoogleServiceImpl; import com.memesphere.domain.user.service.KakaoServiceImpl; +import com.memesphere.domain.user.service.MailServiceImpl; import com.memesphere.global.apipayload.ApiResponse; -import com.memesphere.domain.user.dto.response.LoginResponse; import com.memesphere.global.apipayload.code.status.ErrorStatus; import com.memesphere.global.apipayload.exception.GeneralException; import com.memesphere.global.jwt.CustomUserDetails; @@ -32,6 +30,7 @@ public class UserController { private final KakaoServiceImpl kakaoServiceImpl; private final GoogleServiceImpl googleServiceImpl; private final AuthServiceImpl authServiceImpl; + private final MailServiceImpl mailServiceImpl; private final JwtAuthenticationFilter jwtAuthenticationFilter; @PostMapping("/login/oauth2/kakao") @@ -112,4 +111,13 @@ public ApiResponse isNicknameValidate(@RequestBody NicknameRequest nicknameRe return ApiResponse.onSuccess("사용 가능한 닉네임입니다."); } } + + @PostMapping("/send/password") + @Operation(summary = "비밀번호 찾기 API") + public ApiResponse sendPassword(@RequestParam("email") String email) { + EmailResponse mail = mailServiceImpl.createMail(email); + mailServiceImpl.sendMail(mail); + + return ApiResponse.onSuccess("이메일 전송이 완료되었습니다."); + } } diff --git a/src/main/java/com/memesphere/domain/user/dto/response/EmailResponse.java b/src/main/java/com/memesphere/domain/user/dto/response/EmailResponse.java new file mode 100644 index 00000000..87c04fa7 --- /dev/null +++ b/src/main/java/com/memesphere/domain/user/dto/response/EmailResponse.java @@ -0,0 +1,17 @@ +package com.memesphere.domain.user.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class EmailResponse { + private String toAddress; // 받는 이메일 주소 + private String title; // 이메일 제목 + private String message; // 이메일 내용 + private String fromAddress; // 보내는 이메일 주소 +} diff --git a/src/main/java/com/memesphere/domain/user/service/AuthServiceImpl.java b/src/main/java/com/memesphere/domain/user/service/AuthServiceImpl.java index bf646d36..5ea119b9 100644 --- a/src/main/java/com/memesphere/domain/user/service/AuthServiceImpl.java +++ b/src/main/java/com/memesphere/domain/user/service/AuthServiceImpl.java @@ -22,7 +22,6 @@ public class AuthServiceImpl implements AuthService{ private final PasswordEncoder passwordEncoder; private final UserRepository userRepository; - private final UserServiceImpl userServiceImpl; private final TokenProvider tokenProvider; private final RedisService redisService; diff --git a/src/main/java/com/memesphere/domain/user/service/MailService.java b/src/main/java/com/memesphere/domain/user/service/MailService.java new file mode 100644 index 00000000..e2a1fdba --- /dev/null +++ b/src/main/java/com/memesphere/domain/user/service/MailService.java @@ -0,0 +1,8 @@ +package com.memesphere.domain.user.service; + +import com.memesphere.domain.user.dto.response.EmailResponse; + +public interface MailService { + public EmailResponse createMail(String memberEmail); + public void sendMail(EmailResponse email); +} diff --git a/src/main/java/com/memesphere/domain/user/service/MailServiceImpl.java b/src/main/java/com/memesphere/domain/user/service/MailServiceImpl.java new file mode 100644 index 00000000..2fef6fde --- /dev/null +++ b/src/main/java/com/memesphere/domain/user/service/MailServiceImpl.java @@ -0,0 +1,66 @@ +package com.memesphere.domain.user.service; + +import com.memesphere.domain.user.dto.response.EmailResponse; +import com.memesphere.domain.user.entity.User; +import com.memesphere.domain.user.repository.UserRepository; +import com.memesphere.global.apipayload.code.status.ErrorStatus; +import com.memesphere.global.apipayload.exception.GeneralException; +import lombok.RequiredArgsConstructor; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Service; + +import java.util.Base64; + +@Service +@RequiredArgsConstructor +public class MailServiceImpl implements MailService { + + private final JavaMailSender mailSender; + private final UserRepository userRepository; + + private static final String title = "MemeSphere 비밀번호 안내 이메일입니다."; + private static final String message = "안녕하세요. MemeSphere 비밀번호 안내 메일입니다. " + +"\n" + "회원님의 비밀번호는 아래와 같습니다."+"\n"; + private static final String fromAddress = "memesphere01@gmail.com"; + + /** 이메일 생성 **/ + @Override + public EmailResponse createMail(String memberEmail) { + User existingUser = userRepository.findByEmail(memberEmail).orElse(null); + /* + 임시 비밀번호 발급 로직 추가 예정 + */ + String password = existingUser.getPassword(); + + if (existingUser == null) { + throw new GeneralException(ErrorStatus.USER_NOT_FOUND); + } + + if (password == null) { + throw new GeneralException(ErrorStatus.SOCIAL_LOGIN_NOT_ALLOWED); + } + + EmailResponse emailResponse = EmailResponse.builder() + .toAddress(memberEmail) + .title(title) + .message(message + password) + .fromAddress(fromAddress) + .build(); + + return emailResponse; + } + + /** 이메일 전송 **/ + @Override + public void sendMail(EmailResponse email) { + SimpleMailMessage mailMessage = new SimpleMailMessage(); + mailMessage.setTo(email.getToAddress()); + mailMessage.setSubject(email.getTitle()); + mailMessage.setText(email.getMessage()); + mailMessage.setFrom(email.getFromAddress()); + mailMessage.setReplyTo(email.getFromAddress()); + + mailSender.send(mailMessage); + } +} diff --git a/src/main/java/com/memesphere/global/apipayload/code/status/ErrorStatus.java b/src/main/java/com/memesphere/global/apipayload/code/status/ErrorStatus.java index e46d7ea3..28ad0bca 100644 --- a/src/main/java/com/memesphere/global/apipayload/code/status/ErrorStatus.java +++ b/src/main/java/com/memesphere/global/apipayload/code/status/ErrorStatus.java @@ -49,6 +49,7 @@ public enum ErrorStatus implements BaseCode { EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "EXPIRED TOKEN", "만료된 토큰입니다."), UNSUPPORTED_TOKEN(HttpStatus.UNAUTHORIZED, "UNSUPPORTED TOKEN", "지원하지 않는 토큰입니다."), INVALID_SIGNATURE(HttpStatus.UNAUTHORIZED, "INVALID SIGNATURE", "잘못된 JWT 서명입니다"), + SOCIAL_LOGIN_NOT_ALLOWED(HttpStatus.FORBIDDEN, "SOCIAL LOGIN NOT ALLOWED", "소셜 로그인 계정으로 비밀번호를 찾을 수 없습니다."), // 이미지 에러 INVALID_FILE_EXTENTION(HttpStatus.BAD_REQUEST, "INVALID FILE EXTENSION", "지원되지 않는 파일 형식입니다."), From b0a193af604ca6d205a4676325a6fddcfc43c244 Mon Sep 17 00:00:00 2001 From: nimuy99 Date: Mon, 17 Feb 2025 10:10:03 +0900 Subject: [PATCH 15/20] =?UTF-8?q?#115=20Fix:=20=EC=84=9C=EC=B9=98=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=9D=91=EB=8B=B5=20=ED=98=95?= =?UTF-8?q?=EC=8B=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/memesphere/domain/search/converter/SearchConverter.java | 2 +- .../domain/search/dto/response/SearchListPreviewResponse.java | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/com/memesphere/domain/search/converter/SearchConverter.java b/src/main/java/com/memesphere/domain/search/converter/SearchConverter.java index bc1385dd..e3a16a54 100644 --- a/src/main/java/com/memesphere/domain/search/converter/SearchConverter.java +++ b/src/main/java/com/memesphere/domain/search/converter/SearchConverter.java @@ -62,7 +62,7 @@ public static SearchListPreviewResponse toSearchListPreviewDTO(MemeCoin memeCoin .name(memeCoin.getName()) .symbol(memeCoin.getSymbol()) .currentPrice(chartData.getPrice()) - .priceChange(chartData.getPriceChangeRate()) + .priceChangeRate(chartData.getPriceChangeRate()) .weightedAveragePrice(chartData.getWeighted_average_price()) .volume(chartData.getVolume()) .isCollected(isCollected) diff --git a/src/main/java/com/memesphere/domain/search/dto/response/SearchListPreviewResponse.java b/src/main/java/com/memesphere/domain/search/dto/response/SearchListPreviewResponse.java index 10367123..830e7f64 100644 --- a/src/main/java/com/memesphere/domain/search/dto/response/SearchListPreviewResponse.java +++ b/src/main/java/com/memesphere/domain/search/dto/response/SearchListPreviewResponse.java @@ -26,8 +26,6 @@ public class SearchListPreviewResponse { BigDecimal weightedAveragePrice; @Schema(description = "차트 데이터의 volume", example = "5") BigDecimal volume; - @Schema(description = "차트 데이터의 price_change", example = "500") - BigDecimal priceChange; @Schema(description = "차트 데이터의 price_change_rate", example = "+2.4%") BigDecimal priceChangeRate; @Schema(description = "collection에 해당 밈코인 유무", example = "true / false") From 06f0412c43bc51b5dcb692d5c990f625ba310c12 Mon Sep 17 00:00:00 2001 From: SeoyeonPark1223 Date: Mon, 17 Feb 2025 17:32:15 +0900 Subject: [PATCH 16/20] =?UTF-8?q?#117=20Feature:=20=EC=BB=AC=EB=A0=89?= =?UTF-8?q?=EC=85=98=20sortType,=20viewType=20=EA=B8=B0=EC=A4=80=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scheduler/ChartDataScheduler.java | 2 +- .../controller/CollectionRestController.java | 8 +++- .../converter/CollectionConverter.java | 47 +++++++++++++++---- ...ava => CollectionGridPreviewResponse.java} | 2 +- .../CollectionListPreviewResponse.java | 31 ++++++++++++ .../dto/response/CollectionPageResponse.java | 9 +++- .../repository/CollectionRepository.java | 17 ++++++- .../service/CollectionQueryService.java | 4 +- .../service/CollectionQueryServiceImpl.java | 25 +++++++++- .../response/SearchListPreviewResponse.java | 1 - 10 files changed, 126 insertions(+), 20 deletions(-) rename src/main/java/com/memesphere/domain/collection/dto/response/{CollectionPreviewResponse.java => CollectionGridPreviewResponse.java} (96%) create mode 100644 src/main/java/com/memesphere/domain/collection/dto/response/CollectionListPreviewResponse.java diff --git a/src/main/java/com/memesphere/domain/chartdata/scheduler/ChartDataScheduler.java b/src/main/java/com/memesphere/domain/chartdata/scheduler/ChartDataScheduler.java index fab1ad8d..f4073eb4 100644 --- a/src/main/java/com/memesphere/domain/chartdata/scheduler/ChartDataScheduler.java +++ b/src/main/java/com/memesphere/domain/chartdata/scheduler/ChartDataScheduler.java @@ -42,7 +42,7 @@ public void updateChartData() { String symbol = memeCoin.getSymbol() + "USDT"; BinanceTickerResponse response = binanceQueryService.getTickerData(symbol); - ChartData chartData = toChartData(memeCoin,response); + ChartData chartData = toChartData(memeCoin, response); memeCoinQueryService.updateChartData(memeCoin.getId(), chartData); diff --git a/src/main/java/com/memesphere/domain/collection/controller/CollectionRestController.java b/src/main/java/com/memesphere/domain/collection/controller/CollectionRestController.java index b65aabdf..cf22cd4c 100644 --- a/src/main/java/com/memesphere/domain/collection/controller/CollectionRestController.java +++ b/src/main/java/com/memesphere/domain/collection/controller/CollectionRestController.java @@ -1,6 +1,8 @@ package com.memesphere.domain.collection.controller; import com.memesphere.domain.collection.service.CollectionCommandService; +import com.memesphere.domain.search.entity.SortType; +import com.memesphere.domain.search.entity.ViewType; import com.memesphere.global.apipayload.ApiResponse; import com.memesphere.domain.collection.entity.Collection; import com.memesphere.domain.collection.dto.response.CollectionPageResponse; @@ -32,6 +34,8 @@ public class CollectionRestController { @GetMapping("/collection") @Operation(summary = "사용자의 밈코인 콜렉션 모음 조회 API") public ApiResponse getCollectionList ( + @RequestParam(name = "viewType", defaultValue = "GRID") ViewType viewType, // 뷰 타입 (grid 또는 list) + @RequestParam(name = "sortType", defaultValue = "PRICE_CHANGE") SortType sortType, // 정렬 기준 (MKTCap, 24h Volume, Price) @AuthenticationPrincipal CustomUserDetails userDetails, // 현재 로그인한 사용자 @CheckPage @RequestParam(name = "page") Integer page // 페이지 번호 ) { @@ -41,8 +45,8 @@ public ApiResponse getCollectionList ( // 유저를 찾지 못하면(로그인을 안 했으면) 콜렉션 접근 못하도록 에러 처리 if (userId == null) throw new GeneralException(ErrorStatus.USER_NOT_FOUND); - Page collectionPage = collectionQueryService.getCollectionPage(userId, pageNumber); - return ApiResponse.onSuccess(CollectionConverter.toCollectionPageDTO(collectionPage)); + Page collectionPage = collectionQueryService.getCollectionPage(userId, pageNumber, viewType, sortType); + return ApiResponse.onSuccess(CollectionConverter.toCollectionPageDTO(collectionPage, viewType)); } @PostMapping("/collection/{coinId}") diff --git a/src/main/java/com/memesphere/domain/collection/converter/CollectionConverter.java b/src/main/java/com/memesphere/domain/collection/converter/CollectionConverter.java index 0d062b6b..c83d19b2 100644 --- a/src/main/java/com/memesphere/domain/collection/converter/CollectionConverter.java +++ b/src/main/java/com/memesphere/domain/collection/converter/CollectionConverter.java @@ -1,13 +1,17 @@ package com.memesphere.domain.collection.converter; import com.memesphere.domain.chartdata.entity.ChartData; +import com.memesphere.domain.collection.dto.response.CollectionListPreviewResponse; import com.memesphere.domain.collection.entity.Collection; import com.memesphere.domain.memecoin.entity.MemeCoin; import com.memesphere.domain.collection.dto.response.CollectionPageResponse; -import com.memesphere.domain.collection.dto.response.CollectionPreviewResponse; +import com.memesphere.domain.collection.dto.response.CollectionGridPreviewResponse; +import com.memesphere.domain.search.entity.ViewType; import com.memesphere.domain.user.entity.User; +import io.swagger.v3.oas.annotations.media.Schema; import org.springframework.data.domain.Page; +import java.math.BigDecimal; import java.util.List; import java.util.stream.Collectors; @@ -18,14 +22,24 @@ public static Collection toCollection(User user, MemeCoin coin) { .user(user).memeCoin(coin).build(); } - public static CollectionPageResponse toCollectionPageDTO(Page collectionPage) { - List collectionItems = collectionPage.getContent().stream() - .map(collection -> toCollectionPreviewDTO(collection)) - .collect(Collectors.toList()); + public static CollectionPageResponse toCollectionPageDTO(Page collectionPage, ViewType viewType) { + List gridItems = null; + List listItems = null; + + if (viewType == ViewType.GRID) { + gridItems = collectionPage.stream() + .map(collection -> toCollectionGridPreviewDTO(collection)) + .collect(Collectors.toList()); + } else if (viewType == ViewType.LIST) { + listItems = collectionPage.stream() + .map(collection -> toCollectionListPreviewDTO(collection)) + .collect(Collectors.toList()); + } return CollectionPageResponse.builder() - .collectionItems(collectionItems) - .listSize(collectionItems.size()) + .gridItems(gridItems) + .listItems(listItems) + .listSize(collectionPage.getContent().size()) .totalPage(collectionPage.getTotalPages()) .totalElements(collectionPage.getTotalElements()) .isFirst(collectionPage.isFirst()) @@ -33,11 +47,11 @@ public static CollectionPageResponse toCollectionPageDTO(Page collec .build(); } - private static CollectionPreviewResponse toCollectionPreviewDTO(Collection collection) { + private static CollectionGridPreviewResponse toCollectionGridPreviewDTO(Collection collection) { MemeCoin memeCoin = collection.getMemeCoin(); ChartData chartData = memeCoin.getChartDataList().get(0); - return CollectionPreviewResponse.builder() + return CollectionGridPreviewResponse.builder() .coinId(memeCoin.getId()) .name(memeCoin.getName()) .symbol(memeCoin.getSymbol()) @@ -49,4 +63,19 @@ private static CollectionPreviewResponse toCollectionPreviewDTO(Collection colle .priceChangeRate(chartData.getPriceChangeRate()) .build(); } + + public static CollectionListPreviewResponse toCollectionListPreviewDTO(Collection collection) { + MemeCoin memeCoin = collection.getMemeCoin(); + ChartData chartData = memeCoin.getChartDataList().get(0); + + return CollectionListPreviewResponse.builder() + .coinId(memeCoin.getId()) + .name(memeCoin.getName()) + .symbol(memeCoin.getSymbol()) + .currentPrice(chartData.getPrice()) + .priceChangeRate(chartData.getPriceChangeRate()) + .weightedAveragePrice(chartData.getWeighted_average_price()) + .volume(chartData.getVolume()) + .build(); + } } diff --git a/src/main/java/com/memesphere/domain/collection/dto/response/CollectionPreviewResponse.java b/src/main/java/com/memesphere/domain/collection/dto/response/CollectionGridPreviewResponse.java similarity index 96% rename from src/main/java/com/memesphere/domain/collection/dto/response/CollectionPreviewResponse.java rename to src/main/java/com/memesphere/domain/collection/dto/response/CollectionGridPreviewResponse.java index 7bea26c9..0fd852b5 100644 --- a/src/main/java/com/memesphere/domain/collection/dto/response/CollectionPreviewResponse.java +++ b/src/main/java/com/memesphere/domain/collection/dto/response/CollectionGridPreviewResponse.java @@ -12,7 +12,7 @@ @Builder @AllArgsConstructor @NoArgsConstructor -public class CollectionPreviewResponse { +public class CollectionGridPreviewResponse { @Schema(description = "밈코인 id", example = "1") Long coinId; @Schema(description = "밈코인 name", example = "도지코인") diff --git a/src/main/java/com/memesphere/domain/collection/dto/response/CollectionListPreviewResponse.java b/src/main/java/com/memesphere/domain/collection/dto/response/CollectionListPreviewResponse.java new file mode 100644 index 00000000..433f9bdc --- /dev/null +++ b/src/main/java/com/memesphere/domain/collection/dto/response/CollectionListPreviewResponse.java @@ -0,0 +1,31 @@ +package com.memesphere.domain.collection.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; + +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class CollectionListPreviewResponse { + @Schema(description = "밈코인 id", example = "1") + Long coinId; + @Schema(description = "밈코인 name", example = "도지코인") + String name; + @Schema(description = "밈코인 symbol", example = "DOGE") + String symbol; + @Schema(description = "차트 데이터의 price", example = "2000") + BigDecimal currentPrice; + @Schema(description = "차트 데이터의 price_change_rate", example = "+2.4%") + BigDecimal priceChangeRate; + @Schema(description = "차트 데이터의 weighted average price", example = "10000") + BigDecimal weightedAveragePrice; // market cap 대신 사용 + @Schema(description = "차트 데이터의 volume", example = "5") + BigDecimal volume; +} + diff --git a/src/main/java/com/memesphere/domain/collection/dto/response/CollectionPageResponse.java b/src/main/java/com/memesphere/domain/collection/dto/response/CollectionPageResponse.java index 92ecaf3f..cae3c7e2 100644 --- a/src/main/java/com/memesphere/domain/collection/dto/response/CollectionPageResponse.java +++ b/src/main/java/com/memesphere/domain/collection/dto/response/CollectionPageResponse.java @@ -1,5 +1,8 @@ package com.memesphere.domain.collection.dto.response; +import com.memesphere.domain.collection.entity.Collection; +import com.memesphere.domain.search.dto.response.SearchGridPreviewResponse; +import com.memesphere.domain.search.dto.response.SearchListPreviewResponse; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; @@ -13,8 +16,10 @@ @AllArgsConstructor @NoArgsConstructor public class CollectionPageResponse { - @Schema(description = "콜렉션 아이템들") - List collectionItems; + @Schema(description = "gridView용 컬렉션 아이템들") + List gridItems; // Grid View용 데이터 + @Schema(description = "listView용 컬렉션 아이템들") + List listItems; // List View용 데이터 Integer listSize; Integer totalPage; Long totalElements; diff --git a/src/main/java/com/memesphere/domain/collection/repository/CollectionRepository.java b/src/main/java/com/memesphere/domain/collection/repository/CollectionRepository.java index c9f9a722..f4edba53 100644 --- a/src/main/java/com/memesphere/domain/collection/repository/CollectionRepository.java +++ b/src/main/java/com/memesphere/domain/collection/repository/CollectionRepository.java @@ -6,12 +6,27 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import java.util.List; import java.util.Optional; public interface CollectionRepository extends JpaRepository { - Page findAllByUserId(Long userId, Pageable pageable); List findAllByUserId(Long userId); Optional findByUserAndMemeCoin(User user, MemeCoin memeCoin); + + @Query("SELECT c FROM Collection c " + + "JOIN c.memeCoin m " + + "JOIN m.chartDataList cd " + + "WHERE c.user.id = :userId " + + "AND cd.recordedTime = (SELECT MAX(cd2.recordedTime) FROM ChartData cd2 WHERE cd2.memeCoin = m) " + + "ORDER BY " + + " CASE WHEN :sortField = 'priceChange' THEN cd.priceChange END DESC, " + + " CASE WHEN :sortField = 'volume' THEN cd.volume END DESC, " + + " CASE WHEN :sortField = 'price' THEN cd.price END DESC") + Page findWithLatestChartDataSorted( + @Param("userId") Long userId, + @Param("sortField") String sortField, + Pageable pageable); } diff --git a/src/main/java/com/memesphere/domain/collection/service/CollectionQueryService.java b/src/main/java/com/memesphere/domain/collection/service/CollectionQueryService.java index d55b7ac1..32693b44 100644 --- a/src/main/java/com/memesphere/domain/collection/service/CollectionQueryService.java +++ b/src/main/java/com/memesphere/domain/collection/service/CollectionQueryService.java @@ -1,11 +1,13 @@ package com.memesphere.domain.collection.service; import com.memesphere.domain.collection.entity.Collection; +import com.memesphere.domain.search.entity.SortType; +import com.memesphere.domain.search.entity.ViewType; import org.springframework.data.domain.Page; import java.util.List; public interface CollectionQueryService { - Page getCollectionPage(Long userId, Integer pageNumber); + Page getCollectionPage(Long userId, Integer pageNumber, ViewType viewType, SortType sortType); List getUserCollectionIds(Long userId); } diff --git a/src/main/java/com/memesphere/domain/collection/service/CollectionQueryServiceImpl.java b/src/main/java/com/memesphere/domain/collection/service/CollectionQueryServiceImpl.java index 54640379..ad3ef60a 100644 --- a/src/main/java/com/memesphere/domain/collection/service/CollectionQueryServiceImpl.java +++ b/src/main/java/com/memesphere/domain/collection/service/CollectionQueryServiceImpl.java @@ -2,10 +2,15 @@ import com.memesphere.domain.collection.entity.Collection; import com.memesphere.domain.collection.repository.CollectionRepository; +import com.memesphere.domain.search.entity.SortType; +import com.memesphere.domain.search.entity.ViewType; import com.memesphere.domain.user.repository.UserRepository; +import com.memesphere.global.apipayload.code.status.ErrorStatus; +import com.memesphere.global.apipayload.exception.GeneralException; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -20,8 +25,24 @@ public class CollectionQueryServiceImpl implements CollectionQueryService { @Transactional(readOnly = true) @Override - public Page getCollectionPage(Long userId, Integer pageNumber) { - Page collectionPage = collectionRepository.findAllByUserId(userId, PageRequest.of(pageNumber, 9 )); + public Page getCollectionPage(Long userId, Integer pageNumber, ViewType viewType, SortType sortType) { + + int pageSize = switch (viewType) { + case GRID -> 9; + case LIST -> 20; + default -> throw new GeneralException(ErrorStatus.UNSUPPORTED_VIEW_TYPE); + }; + + String sortField = switch (sortType) { + case PRICE_CHANGE -> "priceChange"; + case VOLUME_24H -> "volume"; + case PRICE -> "price"; + default -> throw new GeneralException(ErrorStatus.UNSUPPORTED_SORT_TYPE); + }; + + Pageable pageable = PageRequest.of(pageNumber, pageSize); + + Page collectionPage = collectionRepository.findWithLatestChartDataSorted(userId, sortField, pageable); return collectionPage; } diff --git a/src/main/java/com/memesphere/domain/search/dto/response/SearchListPreviewResponse.java b/src/main/java/com/memesphere/domain/search/dto/response/SearchListPreviewResponse.java index 830e7f64..c9498ace 100644 --- a/src/main/java/com/memesphere/domain/search/dto/response/SearchListPreviewResponse.java +++ b/src/main/java/com/memesphere/domain/search/dto/response/SearchListPreviewResponse.java @@ -19,7 +19,6 @@ public class SearchListPreviewResponse { String name; @Schema(description = "밈코인 symbol", example = "DOGE") String symbol; - // market cap = currentPrice x volume @Schema(description = "차트 데이터의 price", example = "2000") BigDecimal currentPrice; @Schema(description = "차트 데이터의 weighted average price", example = "10000") From 0ead12715a88a0cbdd85e653609f934e7ae92896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Mon, 17 Feb 2025 17:55:58 +0900 Subject: [PATCH 17/20] =?UTF-8?q?#106=20Feat:=20=EC=9E=84=EC=8B=9C=20?= =?UTF-8?q?=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=20=EB=B0=9C=EA=B8=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/controller/UserController.java | 14 +++++---- .../memesphere/domain/user/entity/User.java | 4 +++ .../domain/user/service/MailService.java | 2 +- .../domain/user/service/MailServiceImpl.java | 21 ++++--------- .../domain/user/service/UserService.java | 2 ++ .../domain/user/service/UserServiceImpl.java | 31 +++++++++++++++++++ 6 files changed, 52 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/memesphere/domain/user/controller/UserController.java b/src/main/java/com/memesphere/domain/user/controller/UserController.java index b228c811..a82185fc 100644 --- a/src/main/java/com/memesphere/domain/user/controller/UserController.java +++ b/src/main/java/com/memesphere/domain/user/controller/UserController.java @@ -2,10 +2,7 @@ import com.memesphere.domain.user.dto.request.*; import com.memesphere.domain.user.dto.response.*; -import com.memesphere.domain.user.service.AuthServiceImpl; -import com.memesphere.domain.user.service.GoogleServiceImpl; -import com.memesphere.domain.user.service.KakaoServiceImpl; -import com.memesphere.domain.user.service.MailServiceImpl; +import com.memesphere.domain.user.service.*; import com.memesphere.global.apipayload.ApiResponse; import com.memesphere.global.apipayload.code.status.ErrorStatus; import com.memesphere.global.apipayload.exception.GeneralException; @@ -32,6 +29,7 @@ public class UserController { private final AuthServiceImpl authServiceImpl; private final MailServiceImpl mailServiceImpl; private final JwtAuthenticationFilter jwtAuthenticationFilter; + private final UserServiceImpl userServiceImpl; @PostMapping("/login/oauth2/kakao") @Operation(summary = "카카오 로그인/회원가입 API") @@ -115,8 +113,12 @@ public ApiResponse isNicknameValidate(@RequestBody NicknameRequest nicknameRe @PostMapping("/send/password") @Operation(summary = "비밀번호 찾기 API") public ApiResponse sendPassword(@RequestParam("email") String email) { - EmailResponse mail = mailServiceImpl.createMail(email); - mailServiceImpl.sendMail(mail); + // 임시 비밀번호 생성 및 저장 + String tmpPassword = userServiceImpl.getTmpPassword(); + userServiceImpl.updateTmpPassword(tmpPassword, email); + + EmailResponse mailResponse = mailServiceImpl.createMail(tmpPassword, email); + mailServiceImpl.sendMail(mailResponse); return ApiResponse.onSuccess("이메일 전송이 완료되었습니다."); } diff --git a/src/main/java/com/memesphere/domain/user/entity/User.java b/src/main/java/com/memesphere/domain/user/entity/User.java index a1a1024c..3287d4a7 100644 --- a/src/main/java/com/memesphere/domain/user/entity/User.java +++ b/src/main/java/com/memesphere/domain/user/entity/User.java @@ -67,4 +67,8 @@ public class User extends BaseEntity { @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) @Builder.Default private List chatLikeList = new ArrayList<>(); + + public void updatePassword(String password){ + this.password = password; + } } diff --git a/src/main/java/com/memesphere/domain/user/service/MailService.java b/src/main/java/com/memesphere/domain/user/service/MailService.java index e2a1fdba..8bd6b12f 100644 --- a/src/main/java/com/memesphere/domain/user/service/MailService.java +++ b/src/main/java/com/memesphere/domain/user/service/MailService.java @@ -3,6 +3,6 @@ import com.memesphere.domain.user.dto.response.EmailResponse; public interface MailService { - public EmailResponse createMail(String memberEmail); + public EmailResponse createMail(String tmpPassword, String memberEmail); public void sendMail(EmailResponse email); } diff --git a/src/main/java/com/memesphere/domain/user/service/MailServiceImpl.java b/src/main/java/com/memesphere/domain/user/service/MailServiceImpl.java index 2fef6fde..e2445612 100644 --- a/src/main/java/com/memesphere/domain/user/service/MailServiceImpl.java +++ b/src/main/java/com/memesphere/domain/user/service/MailServiceImpl.java @@ -19,24 +19,16 @@ public class MailServiceImpl implements MailService { private final JavaMailSender mailSender; private final UserRepository userRepository; - private static final String title = "MemeSphere 비밀번호 안내 이메일입니다."; - private static final String message = "안녕하세요. MemeSphere 비밀번호 안내 메일입니다. " - +"\n" + "회원님의 비밀번호는 아래와 같습니다."+"\n"; + private static final String title = "MemeSphere 임시 비밀번호 안내 이메일입니다."; + private static final String message = "안녕하세요. MemeSphere 임시 비밀번호 안내 메일입니다. " + +"\n" + "회원님의 임시 비밀번호는 아래와 같습니다. 로그인 후 반드시 비밀번호를 변경해주세요."+"\n"; private static final String fromAddress = "memesphere01@gmail.com"; - /** 이메일 생성 **/ @Override - public EmailResponse createMail(String memberEmail) { - User existingUser = userRepository.findByEmail(memberEmail).orElse(null); - /* - 임시 비밀번호 발급 로직 추가 예정 - */ + public EmailResponse createMail(String tmpPassword, String memberEmail) { + User existingUser = userRepository.findByEmail(memberEmail).orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); String password = existingUser.getPassword(); - if (existingUser == null) { - throw new GeneralException(ErrorStatus.USER_NOT_FOUND); - } - if (password == null) { throw new GeneralException(ErrorStatus.SOCIAL_LOGIN_NOT_ALLOWED); } @@ -44,14 +36,13 @@ public EmailResponse createMail(String memberEmail) { EmailResponse emailResponse = EmailResponse.builder() .toAddress(memberEmail) .title(title) - .message(message + password) + .message(message + tmpPassword) .fromAddress(fromAddress) .build(); return emailResponse; } - /** 이메일 전송 **/ @Override public void sendMail(EmailResponse email) { SimpleMailMessage mailMessage = new SimpleMailMessage(); diff --git a/src/main/java/com/memesphere/domain/user/service/UserService.java b/src/main/java/com/memesphere/domain/user/service/UserService.java index 1b3e4790..1eb83723 100644 --- a/src/main/java/com/memesphere/domain/user/service/UserService.java +++ b/src/main/java/com/memesphere/domain/user/service/UserService.java @@ -4,4 +4,6 @@ public interface UserService { User findByLoginId(Long loginId); + public String getTmpPassword(); + public void updateTmpPassword(String tmpPassword, String memberEmail); } diff --git a/src/main/java/com/memesphere/domain/user/service/UserServiceImpl.java b/src/main/java/com/memesphere/domain/user/service/UserServiceImpl.java index 8c3c34ff..828d90df 100644 --- a/src/main/java/com/memesphere/domain/user/service/UserServiceImpl.java +++ b/src/main/java/com/memesphere/domain/user/service/UserServiceImpl.java @@ -2,17 +2,48 @@ import com.memesphere.domain.user.entity.User; import com.memesphere.domain.user.repository.UserRepository; +import com.memesphere.global.apipayload.code.status.ErrorStatus; +import com.memesphere.global.apipayload.exception.GeneralException; import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor public class UserServiceImpl implements UserService { private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; public User findByLoginId(Long loginId) { return userRepository.findByLoginId(loginId).orElse(null); } + @Override + public String getTmpPassword() { + char[] charSet = new char[]{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', + 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; + + String pwd = ""; + + /* 문자 배열 길이의 값을 랜덤으로 10개를 뽑아 조합 */ + int idx = 0; + for(int i = 0; i < 10; i++){ + idx = (int) (charSet.length * Math.random()); + pwd += charSet[idx]; + } + + return pwd; + } + + @Transactional + @Override + public void updateTmpPassword(String tmpPassword, String memberEmail) { + String encryptPassword = passwordEncoder.encode(tmpPassword); + User existingUser = userRepository.findByEmail(memberEmail).orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + + existingUser.updatePassword(encryptPassword); + } } \ No newline at end of file From 909739befae7e377da6fda93150fdeefb6a061c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Mon, 17 Feb 2025 18:17:43 +0900 Subject: [PATCH 18/20] =?UTF-8?q?#106=20Feat:=20=EB=B9=84=EB=B0=80?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EB=B3=80=EA=B2=BD=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/controller/UserController.java | 14 +++++++++++++- .../domain/user/service/UserService.java | 2 +- .../domain/user/service/UserServiceImpl.java | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/memesphere/domain/user/controller/UserController.java b/src/main/java/com/memesphere/domain/user/controller/UserController.java index a82185fc..59a333e4 100644 --- a/src/main/java/com/memesphere/domain/user/controller/UserController.java +++ b/src/main/java/com/memesphere/domain/user/controller/UserController.java @@ -115,11 +115,23 @@ public ApiResponse isNicknameValidate(@RequestBody NicknameRequest nicknameRe public ApiResponse sendPassword(@RequestParam("email") String email) { // 임시 비밀번호 생성 및 저장 String tmpPassword = userServiceImpl.getTmpPassword(); - userServiceImpl.updateTmpPassword(tmpPassword, email); + userServiceImpl.updatePassword(tmpPassword, email); EmailResponse mailResponse = mailServiceImpl.createMail(tmpPassword, email); mailServiceImpl.sendMail(mailResponse); return ApiResponse.onSuccess("이메일 전송이 완료되었습니다."); } + + @PostMapping("/change/password") + @Operation(summary = "비밀번호 변경 API") + public ApiResponse sendPassword(@RequestParam("newPassword") String newPassword, @AuthenticationPrincipal CustomUserDetails customUserDetails) { + if (customUserDetails == null) { + throw new GeneralException(ErrorStatus.USER_NOT_FOUND); + } + + userServiceImpl.updatePassword(newPassword, customUserDetails.getUser().getEmail()); + + return ApiResponse.onSuccess("비밀번호 변경이 완료되었습니다."); + } } diff --git a/src/main/java/com/memesphere/domain/user/service/UserService.java b/src/main/java/com/memesphere/domain/user/service/UserService.java index 1eb83723..a018fa1f 100644 --- a/src/main/java/com/memesphere/domain/user/service/UserService.java +++ b/src/main/java/com/memesphere/domain/user/service/UserService.java @@ -5,5 +5,5 @@ public interface UserService { User findByLoginId(Long loginId); public String getTmpPassword(); - public void updateTmpPassword(String tmpPassword, String memberEmail); + public void updatePassword(String tmpPassword, String memberEmail); } diff --git a/src/main/java/com/memesphere/domain/user/service/UserServiceImpl.java b/src/main/java/com/memesphere/domain/user/service/UserServiceImpl.java index 828d90df..c86de21a 100644 --- a/src/main/java/com/memesphere/domain/user/service/UserServiceImpl.java +++ b/src/main/java/com/memesphere/domain/user/service/UserServiceImpl.java @@ -40,7 +40,7 @@ public String getTmpPassword() { @Transactional @Override - public void updateTmpPassword(String tmpPassword, String memberEmail) { + public void updatePassword(String tmpPassword, String memberEmail) { String encryptPassword = passwordEncoder.encode(tmpPassword); User existingUser = userRepository.findByEmail(memberEmail).orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); From d3bf232a6d072903f3747982e87ad59d2bcc1f6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Mon, 17 Feb 2025 18:33:21 +0900 Subject: [PATCH 19/20] =?UTF-8?q?#106=20Refactor:=20=EC=9D=B4=EB=A9=94?= =?UTF-8?q?=EC=9D=BC=20=EB=B3=80=ED=99=98=20=EB=A1=9C=EC=A7=81=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/controller/UserController.java | 5 +++-- .../domain/user/converter/UserConverter.java | 10 ++++++++++ .../memesphere/domain/user/service/MailService.java | 4 ++-- .../domain/user/service/MailServiceImpl.java | 12 ++---------- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/memesphere/domain/user/controller/UserController.java b/src/main/java/com/memesphere/domain/user/controller/UserController.java index 59a333e4..72958d6f 100644 --- a/src/main/java/com/memesphere/domain/user/controller/UserController.java +++ b/src/main/java/com/memesphere/domain/user/controller/UserController.java @@ -110,20 +110,21 @@ public ApiResponse isNicknameValidate(@RequestBody NicknameRequest nicknameRe } } - @PostMapping("/send/password") + @PostMapping("/password/send") @Operation(summary = "비밀번호 찾기 API") public ApiResponse sendPassword(@RequestParam("email") String email) { // 임시 비밀번호 생성 및 저장 String tmpPassword = userServiceImpl.getTmpPassword(); userServiceImpl.updatePassword(tmpPassword, email); + // 메일 생성 및 전송 EmailResponse mailResponse = mailServiceImpl.createMail(tmpPassword, email); mailServiceImpl.sendMail(mailResponse); return ApiResponse.onSuccess("이메일 전송이 완료되었습니다."); } - @PostMapping("/change/password") + @PostMapping("/password/change") @Operation(summary = "비밀번호 변경 API") public ApiResponse sendPassword(@RequestParam("newPassword") String newPassword, @AuthenticationPrincipal CustomUserDetails customUserDetails) { if (customUserDetails == null) { diff --git a/src/main/java/com/memesphere/domain/user/converter/UserConverter.java b/src/main/java/com/memesphere/domain/user/converter/UserConverter.java index 9c723807..0e101d4b 100644 --- a/src/main/java/com/memesphere/domain/user/converter/UserConverter.java +++ b/src/main/java/com/memesphere/domain/user/converter/UserConverter.java @@ -1,6 +1,7 @@ package com.memesphere.domain.user.converter; import com.memesphere.domain.user.dto.request.SignUpRequest; +import com.memesphere.domain.user.dto.response.EmailResponse; import com.memesphere.domain.user.dto.response.GoogleUserInfoResponse; import com.memesphere.domain.user.entity.SocialType; import com.memesphere.domain.user.entity.User; @@ -49,5 +50,14 @@ public static User toAuthUser(SignUpRequest signUpRequest, PasswordEncoder passw .build(); } + // 비밀번호 찾기 이메일 + public static EmailResponse toEmailResponse(String tmpPassword, String memberEmail, String title, String message, String fromAddress) { + return EmailResponse.builder() + .toAddress(memberEmail) + .title(title) + .message(message + tmpPassword) + .fromAddress(fromAddress) + .build(); + } } diff --git a/src/main/java/com/memesphere/domain/user/service/MailService.java b/src/main/java/com/memesphere/domain/user/service/MailService.java index 8bd6b12f..f9fbeb28 100644 --- a/src/main/java/com/memesphere/domain/user/service/MailService.java +++ b/src/main/java/com/memesphere/domain/user/service/MailService.java @@ -3,6 +3,6 @@ import com.memesphere.domain.user.dto.response.EmailResponse; public interface MailService { - public EmailResponse createMail(String tmpPassword, String memberEmail); - public void sendMail(EmailResponse email); + EmailResponse createMail(String tmpPassword, String memberEmail); + void sendMail(EmailResponse email); } diff --git a/src/main/java/com/memesphere/domain/user/service/MailServiceImpl.java b/src/main/java/com/memesphere/domain/user/service/MailServiceImpl.java index e2445612..3f9a4cdb 100644 --- a/src/main/java/com/memesphere/domain/user/service/MailServiceImpl.java +++ b/src/main/java/com/memesphere/domain/user/service/MailServiceImpl.java @@ -1,5 +1,6 @@ package com.memesphere.domain.user.service; +import com.memesphere.domain.user.converter.UserConverter; import com.memesphere.domain.user.dto.response.EmailResponse; import com.memesphere.domain.user.entity.User; import com.memesphere.domain.user.repository.UserRepository; @@ -10,8 +11,6 @@ import org.springframework.mail.javamail.JavaMailSender; import org.springframework.stereotype.Service; -import java.util.Base64; - @Service @RequiredArgsConstructor public class MailServiceImpl implements MailService { @@ -33,14 +32,7 @@ public EmailResponse createMail(String tmpPassword, String memberEmail) { throw new GeneralException(ErrorStatus.SOCIAL_LOGIN_NOT_ALLOWED); } - EmailResponse emailResponse = EmailResponse.builder() - .toAddress(memberEmail) - .title(title) - .message(message + tmpPassword) - .fromAddress(fromAddress) - .build(); - - return emailResponse; + return UserConverter.toEmailResponse(tmpPassword, memberEmail, title, message, fromAddress); } @Override From 45759481ceeb885b4a918489640e5b508181eec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=ED=98=9C=EC=97=B0?= Date: Mon, 17 Feb 2025 21:39:07 +0900 Subject: [PATCH 20/20] =?UTF-8?q?#106=20Chore:=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/controller/UserController.java | 7 ++- .../domain/user/service/AuthService.java | 2 + .../domain/user/service/AuthServiceImpl.java | 25 ++++++++++ .../user/service/GoogleServiceImpl.java | 1 - .../domain/user/service/KakaoServiceImpl.java | 3 +- .../domain/user/service/UserService.java | 9 ---- .../domain/user/service/UserServiceImpl.java | 49 ------------------- 7 files changed, 31 insertions(+), 65 deletions(-) delete mode 100644 src/main/java/com/memesphere/domain/user/service/UserService.java delete mode 100644 src/main/java/com/memesphere/domain/user/service/UserServiceImpl.java diff --git a/src/main/java/com/memesphere/domain/user/controller/UserController.java b/src/main/java/com/memesphere/domain/user/controller/UserController.java index 72958d6f..8896cd3b 100644 --- a/src/main/java/com/memesphere/domain/user/controller/UserController.java +++ b/src/main/java/com/memesphere/domain/user/controller/UserController.java @@ -29,7 +29,6 @@ public class UserController { private final AuthServiceImpl authServiceImpl; private final MailServiceImpl mailServiceImpl; private final JwtAuthenticationFilter jwtAuthenticationFilter; - private final UserServiceImpl userServiceImpl; @PostMapping("/login/oauth2/kakao") @Operation(summary = "카카오 로그인/회원가입 API") @@ -114,8 +113,8 @@ public ApiResponse isNicknameValidate(@RequestBody NicknameRequest nicknameRe @Operation(summary = "비밀번호 찾기 API") public ApiResponse sendPassword(@RequestParam("email") String email) { // 임시 비밀번호 생성 및 저장 - String tmpPassword = userServiceImpl.getTmpPassword(); - userServiceImpl.updatePassword(tmpPassword, email); + String tmpPassword = authServiceImpl.getTmpPassword(); + authServiceImpl.updatePassword(tmpPassword, email); // 메일 생성 및 전송 EmailResponse mailResponse = mailServiceImpl.createMail(tmpPassword, email); @@ -131,7 +130,7 @@ public ApiResponse sendPassword(@RequestParam("newPassword") String newPasswo throw new GeneralException(ErrorStatus.USER_NOT_FOUND); } - userServiceImpl.updatePassword(newPassword, customUserDetails.getUser().getEmail()); + authServiceImpl.updatePassword(newPassword, customUserDetails.getUser().getEmail()); return ApiResponse.onSuccess("비밀번호 변경이 완료되었습니다."); } diff --git a/src/main/java/com/memesphere/domain/user/service/AuthService.java b/src/main/java/com/memesphere/domain/user/service/AuthService.java index 0a87b0d4..3053ce2e 100644 --- a/src/main/java/com/memesphere/domain/user/service/AuthService.java +++ b/src/main/java/com/memesphere/domain/user/service/AuthService.java @@ -12,4 +12,6 @@ public interface AuthService { LoginResponse reissueAccessToken(String refreshToken, User existingUser); void checkPassword(User user, String password); boolean checkNicknameDuplicate(String nickname); + String getTmpPassword(); + void updatePassword(String tmpPassword, String memberEmail); } diff --git a/src/main/java/com/memesphere/domain/user/service/AuthServiceImpl.java b/src/main/java/com/memesphere/domain/user/service/AuthServiceImpl.java index 5ea119b9..872f06db 100644 --- a/src/main/java/com/memesphere/domain/user/service/AuthServiceImpl.java +++ b/src/main/java/com/memesphere/domain/user/service/AuthServiceImpl.java @@ -13,6 +13,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; @@ -111,4 +112,28 @@ public boolean checkNicknameDuplicate(String nickname) { return userRepository.findByNickname(nickname).isPresent(); } + public String getTmpPassword() { + char[] charSet = new char[]{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', + 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; + + String pwd = ""; + + // 문자 배열 길이의 값을 랜덤으로 10개를 뽑아 조합 + int idx = 0; + for(int i = 0; i < 10; i++){ + idx = (int) (charSet.length * Math.random()); + pwd += charSet[idx]; + } + + return pwd; + } + + @Transactional + public void updatePassword(String tmpPassword, String memberEmail) { + String encryptPassword = passwordEncoder.encode(tmpPassword); + User existingUser = userRepository.findByEmail(memberEmail).orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + + existingUser.updatePassword(encryptPassword); + } } diff --git a/src/main/java/com/memesphere/domain/user/service/GoogleServiceImpl.java b/src/main/java/com/memesphere/domain/user/service/GoogleServiceImpl.java index a25c2711..9f3d84ca 100644 --- a/src/main/java/com/memesphere/domain/user/service/GoogleServiceImpl.java +++ b/src/main/java/com/memesphere/domain/user/service/GoogleServiceImpl.java @@ -24,7 +24,6 @@ public class GoogleServiceImpl implements GoogleService{ private final TokenProvider tokenProvider; - private final UserServiceImpl userServiceImpl; private final UserRepository userRepository; @Value("${security.oauth2.client.registration.google.client-id}") diff --git a/src/main/java/com/memesphere/domain/user/service/KakaoServiceImpl.java b/src/main/java/com/memesphere/domain/user/service/KakaoServiceImpl.java index a69a28dd..d7857d9a 100644 --- a/src/main/java/com/memesphere/domain/user/service/KakaoServiceImpl.java +++ b/src/main/java/com/memesphere/domain/user/service/KakaoServiceImpl.java @@ -22,7 +22,6 @@ public class KakaoServiceImpl implements KakaoService { private final TokenProvider tokenProvider; - private final UserServiceImpl userServiceImpl; private final UserRepository userRepository; private final RedisService redisService; @@ -70,7 +69,7 @@ public KakaoUserInfoResponse getUserInfo(String accessToken) { } public LoginResponse handleUserLogin(KakaoUserInfoResponse kakaoUserInfoResponse) { - User existingUser = userServiceImpl.findByLoginId(kakaoUserInfoResponse.getId()); + User existingUser = userRepository.findByLoginId(kakaoUserInfoResponse.getId()).orElse(null); String accessToken; if (existingUser != null) { diff --git a/src/main/java/com/memesphere/domain/user/service/UserService.java b/src/main/java/com/memesphere/domain/user/service/UserService.java deleted file mode 100644 index a018fa1f..00000000 --- a/src/main/java/com/memesphere/domain/user/service/UserService.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.memesphere.domain.user.service; - -import com.memesphere.domain.user.entity.User; - -public interface UserService { - User findByLoginId(Long loginId); - public String getTmpPassword(); - public void updatePassword(String tmpPassword, String memberEmail); -} diff --git a/src/main/java/com/memesphere/domain/user/service/UserServiceImpl.java b/src/main/java/com/memesphere/domain/user/service/UserServiceImpl.java deleted file mode 100644 index c86de21a..00000000 --- a/src/main/java/com/memesphere/domain/user/service/UserServiceImpl.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.memesphere.domain.user.service; - -import com.memesphere.domain.user.entity.User; -import com.memesphere.domain.user.repository.UserRepository; -import com.memesphere.global.apipayload.code.status.ErrorStatus; -import com.memesphere.global.apipayload.exception.GeneralException; -import lombok.RequiredArgsConstructor; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -public class UserServiceImpl implements UserService { - - private final UserRepository userRepository; - private final PasswordEncoder passwordEncoder; - - public User findByLoginId(Long loginId) { - return userRepository.findByLoginId(loginId).orElse(null); - } - - @Override - public String getTmpPassword() { - char[] charSet = new char[]{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', - 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; - - String pwd = ""; - - /* 문자 배열 길이의 값을 랜덤으로 10개를 뽑아 조합 */ - int idx = 0; - for(int i = 0; i < 10; i++){ - idx = (int) (charSet.length * Math.random()); - pwd += charSet[idx]; - } - - return pwd; - } - - @Transactional - @Override - public void updatePassword(String tmpPassword, String memberEmail) { - String encryptPassword = passwordEncoder.encode(tmpPassword); - User existingUser = userRepository.findByEmail(memberEmail).orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); - - existingUser.updatePassword(encryptPassword); - } -} \ No newline at end of file