Skip to content

Commit d8c668d

Browse files
authored
Merge pull request #122 from TeamMemeSphere/develop
Develop 변경사항을 Main에 반영
2 parents 146641a + 10fd6c3 commit d8c668d

File tree

47 files changed

+713
-111
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+713
-111
lines changed

build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ dependencies {
6161

6262
// redis
6363
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
64+
65+
// 이메일 전송
66+
implementation 'org.springframework.boot:spring-boot-starter-mail'
6467
}
6568

6669
tasks.named('test') {

src/main/java/com/memesphere/domain/chartdata/repository/ChartDataRepository.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import java.math.BigDecimal;
1212
import java.time.LocalDateTime;
1313
import java.util.List;
14+
import java.util.Optional;
1415

1516
public interface ChartDataRepository extends JpaRepository<ChartData, Long> {
1617
@Query("SELECT SUM(c.volume) FROM ChartData c " +
@@ -29,7 +30,11 @@ public interface ChartDataRepository extends JpaRepository<ChartData, Long> {
2930

3031
List<ChartData> findByMemeCoinOrderByRecordedTimeDesc(MemeCoin memeCoin);
3132

32-
//TODO: 위아래 코드 합치는 방법 찾기
33-
List<ChartData> findByMemeCoinOrderByRecordedTimeDesc(MemeCoin memeCoin, Pageable pageable);
33+
List<ChartData> findByMemeCoinAndRecordedTimeAfterOrderByRecordedTimeDesc(MemeCoin memeCoin, LocalDateTime recordedTime, Pageable pageable);
3434

35+
@Query("SELECT c FROM ChartData c " +
36+
"WHERE c.memeCoin.id = :coinId " +
37+
"AND c.recordedTime = " +
38+
"(SELECT MAX(c2.recordedTime) FROM ChartData c2 WHERE c2.memeCoin = c.memeCoin)")
39+
Optional<ChartData> findLatestByCoinId(Long coinId);
3540
}

src/main/java/com/memesphere/domain/chartdata/scheduler/ChartDataScheduler.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@
33
import com.memesphere.domain.binance.dto.response.BinanceTickerResponse;
44
import com.memesphere.domain.binance.service.BinanceQueryService;
55
import com.memesphere.domain.chartdata.entity.ChartData;
6+
import com.memesphere.domain.notification.service.PushNotificationService;
67
import com.memesphere.global.apipayload.code.status.ErrorStatus;
78
import com.memesphere.global.apipayload.exception.GeneralException;
89
import com.memesphere.domain.memecoin.entity.MemeCoin;
910
import com.memesphere.domain.memecoin.repository.MemeCoinRepository;
1011
import com.memesphere.domain.memecoin.service.MemeCoinQueryService;
12+
import com.memesphere.global.jwt.LoggedInUserStore;
1113
import lombok.RequiredArgsConstructor;
1214
import org.springframework.scheduling.annotation.Scheduled;
1315
import org.springframework.stereotype.Component;
1416
import org.springframework.transaction.annotation.Transactional;
1517

1618
import java.util.List;
19+
import java.util.Set;
1720

1821
import static com.memesphere.domain.chartdata.converter.ChartDataConverter.toChartData;
1922

@@ -23,25 +26,34 @@ public class ChartDataScheduler {
2326
private final MemeCoinRepository memeCoinRepository;
2427
private final BinanceQueryService binanceQueryService;
2528
private final MemeCoinQueryService memeCoinQueryService;
29+
private final LoggedInUserStore loggedInUserStore;
30+
private final PushNotificationService pushNotificationService;
2631

2732
@Scheduled(cron = "0 0/10 * * * ?") // 0, 10, 20, 30, 40, 50분에 실행
2833
@Transactional
2934
public void updateChartData() {
35+
36+
Set<Long> loggedInUsers = loggedInUserStore.getLoggedInUsers();
37+
3038
List<MemeCoin> memeCoins = memeCoinRepository.findAll();
3139

3240
for (MemeCoin memeCoin : memeCoins) {
3341
try {
3442
String symbol = memeCoin.getSymbol() + "USDT";
3543
BinanceTickerResponse response = binanceQueryService.getTickerData(symbol);
3644

37-
ChartData chartData = toChartData(memeCoin,response);
45+
ChartData chartData = toChartData(memeCoin, response);
3846

3947
memeCoinQueryService.updateChartData(memeCoin.getId(), chartData);
4048

4149
} catch (Exception e) {
4250
throw new GeneralException(ErrorStatus.CANNOT_LOAD_CHARTDATA);
4351
}
4452
}
53+
54+
for (Long userId : loggedInUsers) {
55+
pushNotificationService.send(userId);
56+
}
4557
}
4658
}
4759

src/main/java/com/memesphere/domain/chartdata/service/ChartDataQueryServiceImpl.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.memesphere.domain.chartdata.entity.ChartData;
44
import com.memesphere.domain.chartdata.repository.ChartDataRepository;
55
import com.memesphere.domain.memecoin.entity.MemeCoin;
6+
import com.memesphere.domain.notification.service.PushNotificationService;
67
import lombok.RequiredArgsConstructor;
78
import org.springframework.stereotype.Service;
89
import org.springframework.transaction.annotation.Transactional;

src/main/java/com/memesphere/domain/collection/controller/CollectionRestController.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.memesphere.domain.collection.controller;
22

33
import com.memesphere.domain.collection.service.CollectionCommandService;
4+
import com.memesphere.domain.search.entity.SortType;
5+
import com.memesphere.domain.search.entity.ViewType;
46
import com.memesphere.global.apipayload.ApiResponse;
57
import com.memesphere.domain.collection.entity.Collection;
68
import com.memesphere.domain.collection.dto.response.CollectionPageResponse;
@@ -32,6 +34,8 @@ public class CollectionRestController {
3234
@GetMapping("/collection")
3335
@Operation(summary = "사용자의 밈코인 콜렉션 모음 조회 API")
3436
public ApiResponse<CollectionPageResponse> getCollectionList (
37+
@RequestParam(name = "viewType", defaultValue = "GRID") ViewType viewType, // 뷰 타입 (grid 또는 list)
38+
@RequestParam(name = "sortType", defaultValue = "PRICE_CHANGE") SortType sortType, // 정렬 기준 (MKTCap, 24h Volume, Price)
3539
@AuthenticationPrincipal CustomUserDetails userDetails, // 현재 로그인한 사용자
3640
@CheckPage @RequestParam(name = "page") Integer page // 페이지 번호
3741
) {
@@ -41,8 +45,8 @@ public ApiResponse<CollectionPageResponse> getCollectionList (
4145
// 유저를 찾지 못하면(로그인을 안 했으면) 콜렉션 접근 못하도록 에러 처리
4246
if (userId == null) throw new GeneralException(ErrorStatus.USER_NOT_FOUND);
4347

44-
Page<Collection> collectionPage = collectionQueryService.getCollectionPage(userId, pageNumber);
45-
return ApiResponse.onSuccess(CollectionConverter.toCollectionPageDTO(collectionPage));
48+
Page<Collection> collectionPage = collectionQueryService.getCollectionPage(userId, pageNumber, viewType, sortType);
49+
return ApiResponse.onSuccess(CollectionConverter.toCollectionPageDTO(collectionPage, viewType));
4650
}
4751

4852
@PostMapping("/collection/{coinId}")
Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
package com.memesphere.domain.collection.converter;
22

33
import com.memesphere.domain.chartdata.entity.ChartData;
4+
import com.memesphere.domain.collection.dto.response.CollectionListPreviewResponse;
45
import com.memesphere.domain.collection.entity.Collection;
56
import com.memesphere.domain.memecoin.entity.MemeCoin;
67
import com.memesphere.domain.collection.dto.response.CollectionPageResponse;
7-
import com.memesphere.domain.collection.dto.response.CollectionPreviewResponse;
8+
import com.memesphere.domain.collection.dto.response.CollectionGridPreviewResponse;
9+
import com.memesphere.domain.search.entity.ViewType;
810
import com.memesphere.domain.user.entity.User;
11+
import io.swagger.v3.oas.annotations.media.Schema;
912
import org.springframework.data.domain.Page;
1013

14+
import java.math.BigDecimal;
1115
import java.util.List;
1216
import java.util.stream.Collectors;
1317

@@ -18,26 +22,36 @@ public static Collection toCollection(User user, MemeCoin coin) {
1822
.user(user).memeCoin(coin).build();
1923
}
2024

21-
public static CollectionPageResponse toCollectionPageDTO(Page<Collection> collectionPage) {
22-
List<CollectionPreviewResponse> collectionItems = collectionPage.getContent().stream()
23-
.map(collection -> toCollectionPreviewDTO(collection))
24-
.collect(Collectors.toList());
25+
public static CollectionPageResponse toCollectionPageDTO(Page<Collection> collectionPage, ViewType viewType) {
26+
List<CollectionGridPreviewResponse> gridItems = null;
27+
List<CollectionListPreviewResponse> listItems = null;
28+
29+
if (viewType == ViewType.GRID) {
30+
gridItems = collectionPage.stream()
31+
.map(collection -> toCollectionGridPreviewDTO(collection))
32+
.collect(Collectors.toList());
33+
} else if (viewType == ViewType.LIST) {
34+
listItems = collectionPage.stream()
35+
.map(collection -> toCollectionListPreviewDTO(collection))
36+
.collect(Collectors.toList());
37+
}
2538

2639
return CollectionPageResponse.builder()
27-
.collectionItems(collectionItems)
28-
.listSize(collectionItems.size())
40+
.gridItems(gridItems)
41+
.listItems(listItems)
42+
.listSize(collectionPage.getContent().size())
2943
.totalPage(collectionPage.getTotalPages())
3044
.totalElements(collectionPage.getTotalElements())
3145
.isFirst(collectionPage.isFirst())
3246
.isLast(collectionPage.isLast())
3347
.build();
3448
}
3549

36-
private static CollectionPreviewResponse toCollectionPreviewDTO(Collection collection) {
50+
private static CollectionGridPreviewResponse toCollectionGridPreviewDTO(Collection collection) {
3751
MemeCoin memeCoin = collection.getMemeCoin();
3852
ChartData chartData = memeCoin.getChartDataList().get(0);
3953

40-
return CollectionPreviewResponse.builder()
54+
return CollectionGridPreviewResponse.builder()
4155
.coinId(memeCoin.getId())
4256
.name(memeCoin.getName())
4357
.symbol(memeCoin.getSymbol())
@@ -49,4 +63,19 @@ private static CollectionPreviewResponse toCollectionPreviewDTO(Collection colle
4963
.priceChangeRate(chartData.getPriceChangeRate())
5064
.build();
5165
}
66+
67+
public static CollectionListPreviewResponse toCollectionListPreviewDTO(Collection collection) {
68+
MemeCoin memeCoin = collection.getMemeCoin();
69+
ChartData chartData = memeCoin.getChartDataList().get(0);
70+
71+
return CollectionListPreviewResponse.builder()
72+
.coinId(memeCoin.getId())
73+
.name(memeCoin.getName())
74+
.symbol(memeCoin.getSymbol())
75+
.currentPrice(chartData.getPrice())
76+
.priceChangeRate(chartData.getPriceChangeRate())
77+
.weightedAveragePrice(chartData.getWeighted_average_price())
78+
.volume(chartData.getVolume())
79+
.build();
80+
}
5281
}

src/main/java/com/memesphere/domain/collection/dto/response/CollectionPreviewResponse.java renamed to src/main/java/com/memesphere/domain/collection/dto/response/CollectionGridPreviewResponse.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
@Builder
1313
@AllArgsConstructor
1414
@NoArgsConstructor
15-
public class CollectionPreviewResponse {
15+
public class CollectionGridPreviewResponse {
1616
@Schema(description = "밈코인 id", example = "1")
1717
Long coinId;
1818
@Schema(description = "밈코인 name", example = "도지코인")
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.memesphere.domain.collection.dto.response;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Builder;
6+
import lombok.Getter;
7+
import lombok.NoArgsConstructor;
8+
9+
import java.math.BigDecimal;
10+
11+
@Getter
12+
@Builder
13+
@AllArgsConstructor
14+
@NoArgsConstructor
15+
public class CollectionListPreviewResponse {
16+
@Schema(description = "밈코인 id", example = "1")
17+
Long coinId;
18+
@Schema(description = "밈코인 name", example = "도지코인")
19+
String name;
20+
@Schema(description = "밈코인 symbol", example = "DOGE")
21+
String symbol;
22+
@Schema(description = "차트 데이터의 price", example = "2000")
23+
BigDecimal currentPrice;
24+
@Schema(description = "차트 데이터의 price_change_rate", example = "+2.4%")
25+
BigDecimal priceChangeRate;
26+
@Schema(description = "차트 데이터의 weighted average price", example = "10000")
27+
BigDecimal weightedAveragePrice; // market cap 대신 사용
28+
@Schema(description = "차트 데이터의 volume", example = "5")
29+
BigDecimal volume;
30+
}
31+

src/main/java/com/memesphere/domain/collection/dto/response/CollectionPageResponse.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package com.memesphere.domain.collection.dto.response;
22

3+
import com.memesphere.domain.collection.entity.Collection;
4+
import com.memesphere.domain.search.dto.response.SearchGridPreviewResponse;
5+
import com.memesphere.domain.search.dto.response.SearchListPreviewResponse;
36
import io.swagger.v3.oas.annotations.media.Schema;
47
import lombok.AllArgsConstructor;
58
import lombok.Builder;
@@ -13,8 +16,10 @@
1316
@AllArgsConstructor
1417
@NoArgsConstructor
1518
public class CollectionPageResponse {
16-
@Schema(description = "콜렉션 아이템들")
17-
List<CollectionPreviewResponse> collectionItems;
19+
@Schema(description = "gridView용 컬렉션 아이템들")
20+
List<CollectionGridPreviewResponse> gridItems; // Grid View용 데이터
21+
@Schema(description = "listView용 컬렉션 아이템들")
22+
List<CollectionListPreviewResponse> listItems; // List View용 데이터
1823
Integer listSize;
1924
Integer totalPage;
2025
Long totalElements;

src/main/java/com/memesphere/domain/collection/repository/CollectionRepository.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,27 @@
66
import org.springframework.data.domain.Page;
77
import org.springframework.data.domain.Pageable;
88
import org.springframework.data.jpa.repository.JpaRepository;
9+
import org.springframework.data.jpa.repository.Query;
10+
import org.springframework.data.repository.query.Param;
911

1012
import java.util.List;
1113
import java.util.Optional;
1214

1315
public interface CollectionRepository extends JpaRepository<Collection, Long> {
14-
Page<Collection> findAllByUserId(Long userId, Pageable pageable);
1516
List<Collection> findAllByUserId(Long userId);
1617
Optional<Collection> findByUserAndMemeCoin(User user, MemeCoin memeCoin);
18+
19+
@Query("SELECT c FROM Collection c " +
20+
"JOIN c.memeCoin m " +
21+
"JOIN m.chartDataList cd " +
22+
"WHERE c.user.id = :userId " +
23+
"AND cd.recordedTime = (SELECT MAX(cd2.recordedTime) FROM ChartData cd2 WHERE cd2.memeCoin = m) " +
24+
"ORDER BY " +
25+
" CASE WHEN :sortField = 'priceChange' THEN cd.priceChange END DESC, " +
26+
" CASE WHEN :sortField = 'volume' THEN cd.volume END DESC, " +
27+
" CASE WHEN :sortField = 'price' THEN cd.price END DESC")
28+
Page<Collection> findWithLatestChartDataSorted(
29+
@Param("userId") Long userId,
30+
@Param("sortField") String sortField,
31+
Pageable pageable);
1732
}

0 commit comments

Comments
 (0)