Skip to content

Commit 63a1f04

Browse files
authored
Merge pull request #52 from nimuy99/feat/51-like
#51 Feat: 콜렉션 등록/삭제 기능
2 parents 33d70b2 + 2fa9a7a commit 63a1f04

File tree

18 files changed

+208
-55
lines changed

18 files changed

+208
-55
lines changed

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,24 @@
66
import org.springframework.data.jpa.repository.JpaRepository;
77
import org.springframework.data.jpa.repository.Query;
88

9+
import java.math.BigDecimal;
10+
import java.time.LocalDateTime;
911
import java.util.List;
1012

1113
public interface ChartDataRepository extends JpaRepository<ChartData, Long> {
12-
@Query("SELECT SUM(c.volume) FROM ChartData c")
13-
Long findTotalVolume();
14+
@Query("SELECT SUM(c.volume) FROM ChartData c " +
15+
"WHERE c.recordedTime = " +
16+
"(SELECT MAX(c2.recordedTime) FROM ChartData c2 WHERE c2.memeCoin = c.memeCoin)")
17+
BigDecimal findTotalVolume();
1418

15-
@EntityGraph(attributePaths = {"memeCoin"})
16-
List<ChartData> findTop5ByOrderByVolumeDesc();
19+
@Query("SELECT c FROM ChartData c " +
20+
"WHERE c.recordedTime = " +
21+
"(SELECT MAX(c2.recordedTime) FROM ChartData c2 WHERE c2.memeCoin = c.memeCoin) " +
22+
"ORDER BY c.volume DESC LIMIT 5")
23+
List<ChartData> findTop5OrderByVolumeDesc();
24+
25+
@Query("SELECT MAX(c.recordedTime) FROM ChartData c WHERE c.memeCoin.id = 1")
26+
LocalDateTime findRecordedTimeByCoinId1();
1727

1828
List<ChartData> findByMemeCoinOrderByRecordedTimeDesc(MemeCoin memeCoin);
1929
}

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

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

3+
import com.memesphere.domain.collection.service.CollectionCommandService;
34
import com.memesphere.global.apipayload.ApiResponse;
45
import com.memesphere.domain.collection.entity.Collection;
56
import com.memesphere.domain.collection.dto.response.CollectionPageResponse;
67
import com.memesphere.domain.collection.service.CollectionQueryService;
8+
import com.memesphere.global.jwt.CustomUserDetails;
9+
import com.memesphere.global.jwt.TokenProvider;
710
import com.memesphere.global.validation.annotation.CheckPage;
811
import io.swagger.v3.oas.annotations.Operation;
12+
import io.swagger.v3.oas.annotations.Parameter;
13+
import io.swagger.v3.oas.annotations.tags.Tag;
14+
import jakarta.servlet.http.HttpServletRequest;
915
import lombok.RequiredArgsConstructor;
1016
import org.springframework.data.domain.Page;
17+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
1118
import org.springframework.web.bind.annotation.*;
1219
import org.springframework.web.bind.annotation.RestController;
1320
import com.memesphere.domain.collection.converter.CollectionConverter;
1421

22+
@Tag(name="콜렉션", description = "콜렉션 관련 API")
1523
@RestController
1624
@RequiredArgsConstructor
1725
//@RequestMapping("/collection")
1826
public class CollectionRestController {
1927
private final CollectionQueryService collectionQueryService;
28+
private final CollectionCommandService collectionCommandService;
29+
private final TokenProvider tokenProvider;
2030

2131
@GetMapping("/collection")
2232
@Operation(summary = "사용자의 밈코인 콜렉션 모음 조회 API")
@@ -32,4 +42,26 @@ public ApiResponse<CollectionPageResponse> getCollectionList (
3242
return ApiResponse.onSuccess(CollectionConverter.toCollectionPageDTO(collectionPage));
3343
}
3444

45+
@PostMapping("/collection/{coinId}")
46+
@Operation(summary = "밈코인 콜렉션 등록 API",
47+
description = "코인 Id를 입력하면 사용자의 콜렉션에 등록")
48+
public ApiResponse<String> postCollectCoin (@Parameter(hidden = true) @AuthenticationPrincipal CustomUserDetails customUserDetails,
49+
@PathVariable Long coinId) {
50+
51+
Long userId = customUserDetails.getUser().getId();
52+
53+
return ApiResponse.onSuccess(collectionCommandService.addCollectCoin(userId, coinId));
54+
}
55+
56+
@DeleteMapping("/collection/{coinId}")
57+
@Operation(summary = "밈코인 콜렉션 삭제 API",
58+
description = "코인 Id를 입력하면 사용자의 콜렉션에서 삭제")
59+
public ApiResponse<String> deleteCollectCoin (@Parameter(hidden = true) @AuthenticationPrincipal CustomUserDetails customUserDetails,
60+
@PathVariable Long coinId) {
61+
62+
Long userId = customUserDetails.getUser().getId();
63+
64+
return ApiResponse.onSuccess(collectionCommandService.removeCollectCoin(userId, coinId));
65+
}
66+
3567
}

src/main/java/com/memesphere/domain/collection/converter/CollectionConverter.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,19 @@
55
import com.memesphere.domain.memecoin.entity.MemeCoin;
66
import com.memesphere.domain.collection.dto.response.CollectionPageResponse;
77
import com.memesphere.domain.collection.dto.response.CollectionPreviewResponse;
8+
import com.memesphere.domain.user.entity.User;
89
import org.springframework.data.domain.Page;
910

1011
import java.util.List;
1112
import java.util.stream.Collectors;
1213

1314
public class CollectionConverter {
15+
16+
public static Collection toCollection(User user, MemeCoin coin) {
17+
return Collection.builder()
18+
.user(user).memeCoin(coin).build();
19+
}
20+
1421
public static CollectionPageResponse toCollectionPageDTO(Page<Collection> collectionPage) {
1522
List<CollectionPreviewResponse> collectionItems = collectionPage.getContent().stream()
1623
.map(collection -> toCollectionPreviewDTO(collection))

src/main/java/com/memesphere/domain/collection/entity/Collection.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
@Entity
1212
@Getter
13+
@Builder
1314
@DynamicUpdate
1415
@DynamicInsert
1516
@AllArgsConstructor
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
package com.memesphere.domain.collection.repository;
22

33
import com.memesphere.domain.collection.entity.Collection;
4+
import com.memesphere.domain.memecoin.entity.MemeCoin;
5+
import com.memesphere.domain.user.entity.User;
46
import org.springframework.data.domain.Page;
57
import org.springframework.data.domain.Pageable;
68
import org.springframework.data.jpa.repository.JpaRepository;
79

810
import java.util.List;
11+
import java.util.Optional;
912

1013
public interface CollectionRepository extends JpaRepository<Collection, Long> {
1114
Page<Collection> findAllByUserId(Long userId, Pageable pageable);
1215
List<Collection> findAllByUserId(Long userId);
16+
Optional<Collection> findByUserAndMemeCoin(User user, MemeCoin memeCoin);
1317
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.memesphere.domain.collection.service;
2+
3+
import com.memesphere.domain.collection.entity.Collection;
4+
import org.springframework.data.domain.Page;
5+
6+
import java.util.List;
7+
8+
public interface CollectionCommandService {
9+
String addCollectCoin(Long userId, Long coinId);
10+
String removeCollectCoin(Long userId, Long coinId);
11+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.memesphere.domain.collection.service;
2+
3+
import com.memesphere.domain.collection.converter.CollectionConverter;
4+
import com.memesphere.domain.collection.entity.Collection;
5+
import com.memesphere.domain.collection.repository.CollectionRepository;
6+
import com.memesphere.domain.memecoin.entity.MemeCoin;
7+
import com.memesphere.domain.memecoin.repository.MemeCoinRepository;
8+
import com.memesphere.domain.user.entity.User;
9+
import com.memesphere.domain.user.repository.UserRepository;
10+
import com.memesphere.global.apipayload.code.status.ErrorStatus;
11+
import com.memesphere.global.apipayload.exception.GeneralException;
12+
import lombok.RequiredArgsConstructor;
13+
import org.springframework.stereotype.Service;
14+
15+
@Service
16+
@RequiredArgsConstructor
17+
public class CollectionCommandServiceImpl implements CollectionCommandService {
18+
private final CollectionRepository collectionRepository;
19+
private final UserRepository userRepository;
20+
private final MemeCoinRepository coinRepository;
21+
22+
public String addCollectCoin(Long userId, Long coinId) {
23+
User user = userRepository.findById(userId)
24+
.orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND));
25+
26+
MemeCoin coin = coinRepository.findById(coinId)
27+
.orElseThrow(() -> new GeneralException(ErrorStatus.MEMECOIN_NOT_FOUND));
28+
29+
// 이미 등록한 콜렉션의 경우
30+
if (collectionRepository.findByUserAndMemeCoin(user, coin).isPresent()) {
31+
throw new GeneralException(ErrorStatus.COLLECTION_ALREADY_EXISTS);
32+
}
33+
34+
// 새로운 컬렉션 저장
35+
Collection collection = CollectionConverter.toCollection(user, coin);
36+
collectionRepository.save(collection);
37+
38+
return "[coinId] " + coinId + " 등록 완료.";
39+
}
40+
41+
@Override
42+
public String removeCollectCoin(Long userId, Long coinId) {
43+
User user = userRepository.findById(userId)
44+
.orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND));
45+
46+
MemeCoin coin = coinRepository.findById(coinId)
47+
.orElseThrow(() -> new GeneralException(ErrorStatus.MEMECOIN_NOT_FOUND));
48+
49+
// 이미 삭제한 콜렉션의 경우
50+
Collection collection = collectionRepository.findByUserAndMemeCoin(user, coin)
51+
.orElseThrow(() -> new GeneralException(ErrorStatus.COLLECTION_NOT_FOUND));
52+
53+
collectionRepository.delete(collection);
54+
55+
return "[coinId] " + coinId + " 삭제 완료.";
56+
}
57+
58+
}

src/main/java/com/memesphere/domain/dashboard/controller/DashboardController.java

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,27 @@
44
import com.memesphere.domain.dashboard.service.DashboardQueryService;
55
import com.memesphere.global.apipayload.ApiResponse;
66
import com.memesphere.domain.dashboard.dto.response.DashboardTrendListResponse;
7+
import com.memesphere.global.jwt.CustomUserDetails;
8+
import com.memesphere.global.jwt.TokenProvider;
79
import com.memesphere.global.validation.annotation.CheckPage;
810
import com.memesphere.domain.search.dto.response.SearchPageResponse;
911
import com.memesphere.domain.search.entity.SortType;
1012
import com.memesphere.domain.search.entity.ViewType;
1113
import io.swagger.v3.oas.annotations.Operation;
14+
import io.swagger.v3.oas.annotations.Parameter;
1215
import io.swagger.v3.oas.annotations.tags.Tag;
16+
import jakarta.servlet.http.HttpServletRequest;
1317
import lombok.RequiredArgsConstructor;
14-
import org.springframework.web.bind.annotation.GetMapping;
15-
import org.springframework.web.bind.annotation.RequestMapping;
16-
import org.springframework.web.bind.annotation.RequestParam;
17-
import org.springframework.web.bind.annotation.RestController;
18+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
19+
import org.springframework.web.bind.annotation.*;
1820

1921
@Tag(name="대시보드", description = "대시보드 관련 API")
2022
@RestController
2123
@RequestMapping("/dashboard")
2224
@RequiredArgsConstructor
2325
public class DashboardController {
2426
private final DashboardQueryService dashboardQueryService;
27+
private final TokenProvider tokenProvider;
2528

2629
@GetMapping("/overview")
2730
@Operation(summary = "밈코인 총 거래량 및 총 개수 조회 API",
@@ -48,15 +51,17 @@ public ApiResponse<DashboardOverviewResponse> getOverview() {
4851
4952
**응답 형식**:
5053
```
54+
- "LocalDateTime": 기록된 시간 (확인용, 코인 아이디 1을 기준으로 함)
5155
- "trendList": 등록된 밈코인의 트렌드 순위 리스트(5위 까지)
5256
- "coinId": 밈코인 아이디
5357
- "image": 밈코인 이미지
5458
- "name": 밈코인 이름
5559
- "symbol": 밈코인 심볼
60+
- "volume": 밈코인 거래량(확인용)
5661
- "price": 밈코인 현재가
5762
- "priceChange": 가격 변화량
5863
- "changeAbsolute": 가격 변화량(절대값)
59-
- "changeDirection": 밈코인 상승(up)/하락(down)/유지(= 변화량 0일 때, -) 방향
64+
- "changeDirection": 밈코인 상승(up, 0 이상)/하락(down)
6065
- "changeRate": 가격 변화율
6166
```""")
6267
public ApiResponse<DashboardTrendListResponse> getTrendList() {
@@ -67,11 +72,12 @@ public ApiResponse<DashboardTrendListResponse> getTrendList() {
6772
@Operation(summary = "차트 조회 API",
6873
description = """
6974
밈스피어에 등록된 밈코인의 차트 데이터를 보기 방식과 정렬 기준에 따라 보여줍니다. \n
70-
75+
검색 결과 조회 api(/search) 참고 \n
76+
7177
**요청 형식**:
7278
```
73-
- "viewType": 보기 방식 (GRID / LIST)
74-
- "sortType": 정렬 기준 (MKT_CAP / VOLUME_24H / PRICE)
79+
- "viewType": 보기 방식 (GRID(default) / LIST)
80+
- "sortType": 정렬 기준 (PRICE_CHANGE(default) / VOLUME_24H / PRICE)
7581
- "page": 페이지 번호
7682
```
7783
@@ -95,14 +101,14 @@ public ApiResponse<DashboardTrendListResponse> getTrendList() {
95101
- "isFirst": 첫 페이지인지(true) / 아닌지(false)
96102
- "isLast": 마지막 페이지인지(true) / 아닌지(false)
97103
```""")
98-
public ApiResponse<SearchPageResponse> getChartList(// TODO: userID 변경 -> 로그인한 유저
104+
public ApiResponse<SearchPageResponse> getChartList(@Parameter(hidden = true) @AuthenticationPrincipal CustomUserDetails customUserDetails,
99105
@RequestParam(name = "viewType", defaultValue = "GRID") ViewType viewType,
100-
@RequestParam(name = "sortType", defaultValue = "MKT_CAP") SortType sortType,
106+
@RequestParam(name = "sortType", defaultValue = "PRICE_CHANGE") SortType sortType,
101107
@CheckPage @RequestParam(name = "page") Integer page) {
102108
Integer pageNumber = page - 1;
103-
// TODO: userID 변경 -> 로그인한 유저
104-
// Long userId = user.getId();
105-
Long userId = 1L;
109+
110+
Long userId = customUserDetails.getUser().getId();
111+
106112

107113
return ApiResponse.onSuccess(dashboardQueryService.getChartPage(userId, viewType, sortType, pageNumber));
108114
}

src/main/java/com/memesphere/domain/dashboard/converter/DashboardConverter.java

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,28 @@
66
import com.memesphere.domain.dashboard.dto.response.DashboardTrendListResponse;
77
import com.memesphere.domain.dashboard.dto.response.DashboardTrendResponse;
88

9+
import java.math.BigDecimal;
10+
import java.time.LocalDateTime;
911
import java.util.List;
1012
import java.util.stream.Collectors;
1113

1214
public class DashboardConverter {
1315
// ** 총 거래량 및 총 개수 응답 형식 ** //
14-
public static DashboardOverviewResponse toOverView(Long totalVolume, Long totalCoin) {
16+
public static DashboardOverviewResponse toOverView(BigDecimal totalVolume, Long totalCoin) {
1517
return DashboardOverviewResponse.builder()
1618
.totalVolume(totalVolume)
1719
.totalCoin(totalCoin)
1820
.build();
1921
}
2022

2123
// ** 트렌드 응답 형식 ** //
22-
public static DashboardTrendListResponse toTrendList(List<ChartData> dataList) {
24+
public static DashboardTrendListResponse toTrendList(LocalDateTime recordedTime, List<ChartData> dataList) {
2325
List<DashboardTrendResponse> trendList = dataList.stream()
2426
.map(data -> toTrend(data))
2527
.collect(Collectors.toList());
2628

2729
return DashboardTrendListResponse.builder()
30+
.timestamp(recordedTime)
2831
.trendList(trendList)
2932
.build();
3033
}
@@ -37,11 +40,12 @@ public static DashboardTrendResponse toTrend(ChartData data) {
3740
.image(memeCoin.getImage())
3841
.name(memeCoin.getName())
3942
.symbol(memeCoin.getSymbol())
40-
.price(data.getPrice().intValue()) // todo: 외부 api 응답 형식 보고 엔티티 자료형 변경
41-
.priceChange(data.getPriceChange().intValue())
42-
.changeAbsolute(Math.abs(data.getPriceChange().intValue()))
43-
.changeDirection(data.getPriceChange().intValue() > 0? "up" : "down")
44-
.changeRate(null) // todo: string할건지 float형 할건지 결정
43+
.volume(data.getVolume())
44+
.price(data.getPrice())
45+
.priceChange(data.getPriceChange())
46+
.changeAbsolute(data.getPriceChange().abs())
47+
.changeDirection(data.getPriceChangeRate().compareTo(BigDecimal.ZERO) < 0 ? "down" : "up")
48+
.changeRate(data.getPriceChangeRate())
4549
.build();
4650
}
4751
}

src/main/java/com/memesphere/domain/dashboard/dto/response/DashboardOverviewResponse.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@
44
import lombok.Builder;
55
import lombok.Getter;
66

7+
import java.math.BigDecimal;
8+
import java.text.DecimalFormat;
9+
710
@Getter
811
@Builder
912
public class DashboardOverviewResponse {
10-
@Schema(description = "등록된 밈코인의 총 거래량", example = "29960")
11-
private Long totalVolume;
13+
@Schema(description = "등록된 밈코인의 총 거래량", example = "32,431,877,180,618.15")
14+
private BigDecimal totalVolume;
1215

1316
@Schema(description = "등록된 밈코인의 총 개수", example = "125")
1417
private Long totalCoin;

0 commit comments

Comments
 (0)