Skip to content

Commit d31560f

Browse files
committed
#120 Fix: Merge conflict
2 parents 401e235 + d8c668d commit d31560f

File tree

51 files changed

+786
-145
lines changed

Some content is hidden

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

51 files changed

+786
-145
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') {

docker-compose.yml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,17 @@ services:
1212
ports:
1313
- "8080:8080"
1414
depends_on:
15-
- redis
15+
- redis
16+
17+
nginx:
18+
image: nginx
19+
container_name: nginx
20+
restart: always
21+
volumes:
22+
- ./conf/:/etc/nginx/conf.d
23+
- /etc/letsencrypt:/etc/letsencrypt
24+
ports:
25+
- 80:80
26+
- 443:443
27+
depends_on:
28+
- spring

src/main/java/com/memesphere/MemesphereApplication.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
package com.memesphere;
22

3+
import jakarta.annotation.PostConstruct;
34
import org.springframework.boot.SpringApplication;
45
import org.springframework.boot.autoconfigure.SpringBootApplication;
56
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
67
import org.springframework.scheduling.annotation.EnableScheduling;
78

9+
import java.util.TimeZone;
10+
811
@SpringBootApplication
912
@EnableJpaAuditing
1013
@EnableScheduling
1114
public class MemesphereApplication {
1215

16+
@PostConstruct
17+
public void started() {
18+
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul"));
19+
}
20+
1321
public static void main(String[] args) {
1422
SpringApplication.run(MemesphereApplication.class, args);
1523
}

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

Lines changed: 9 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 " +
@@ -28,6 +29,12 @@ public interface ChartDataRepository extends JpaRepository<ChartData, Long> {
2829
LocalDateTime findRecordedTimeByCoinId1();
2930

3031
List<ChartData> findByMemeCoinOrderByRecordedTimeDesc(MemeCoin memeCoin);
31-
//TODO: 위아래 코드 합치는 방법 찾기
32-
List<ChartData> findByMemeCoinOrderByRecordedTimeDesc(MemeCoin memeCoin, Pageable pageable);
32+
33+
List<ChartData> findByMemeCoinAndRecordedTimeAfterOrderByRecordedTimeDesc(MemeCoin memeCoin, LocalDateTime recordedTime, Pageable pageable);
34+
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);
3340
}

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: 39 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,37 @@ 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()
55+
.coinId(memeCoin.getId())
4156
.name(memeCoin.getName())
4257
.symbol(memeCoin.getSymbol())
4358
.image(memeCoin.getImage())
@@ -48,4 +63,19 @@ private static CollectionPreviewResponse toCollectionPreviewDTO(Collection colle
4863
.priceChangeRate(chartData.getPriceChangeRate())
4964
.build();
5065
}
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+
}
5181
}

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: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
@Builder
1313
@AllArgsConstructor
1414
@NoArgsConstructor
15-
public class CollectionPreviewResponse {
15+
public class CollectionGridPreviewResponse {
16+
@Schema(description = "밈코인 id", example = "1")
17+
Long coinId;
1618
@Schema(description = "밈코인 name", example = "도지코인")
1719
String name;
1820
@Schema(description = "밈코인 symbol", example = "DOGE")
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+

0 commit comments

Comments
 (0)