Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
16d3ee1
#99 Fix: 알림 응답 isOn true로 나오도록 수정
kjiyun Feb 13, 2025
3400545
Merge pull request #102 from TeamMemeSphere/main
kjiyun Feb 13, 2025
cc4eebb
#99 Fix: merge conflict
kjiyun Feb 13, 2025
08f3679
Merge pull request #100 from kjiyun/chat
kjiyun Feb 13, 2025
158a806
#99 Fix: fix notification converter
kjiyun Feb 13, 2025
31affde
Merge pull request #103 from kjiyun/chat
kjiyun Feb 13, 2025
b78c70c
Fix: 알림 삭제 api 에러 수정
noeyeyh Feb 13, 2025
78b190d
#107 Fix: 기본 프로필 수정
marshmallowing Feb 13, 2025
a0fb187
Merge pull request #108 from marshmallowing/fix/profile
marshmallowing Feb 13, 2025
d227f8d
#105 Feat: 코인 디테일 가격 정보 조회 기능 구현
Feb 13, 2025
a1ca030
#105 Feat: 코인 디테일 순위 필드 추가
Feb 13, 2025
8cdf511
Merge pull request #109 from nimuy99/feat/105-detail-price-info
nimuyman Feb 13, 2025
999067d
#104 Fix: 알림 변동성 체크 로직 변경
kjiyun Feb 14, 2025
29e598d
Merge pull request #110 from kjiyun/chat
kjiyun Feb 14, 2025
4f089ca
#104 Fix: 스케줄러 시간 변경
kjiyun Feb 14, 2025
ee398a2
Merge pull request #111 from kjiyun/chat
kjiyun Feb 14, 2025
a97e4b3
#112 Feat: 네이버 검색어 트렌드 API 연동
marshmallowing Feb 14, 2025
442b38c
Merge pull request #113 from marshmallowing/feat/naver
marshmallowing Feb 14, 2025
c9d6745
#106 Chore: 의존성 추가
noeyeyh Feb 14, 2025
2c505ff
#104 Fix: loggedInUser 기능 추가
kjiyun Feb 14, 2025
604e54d
#104 Fix: chartdatascheduler 수정
kjiyun Feb 14, 2025
ce5f371
Merge pull request #114 from kjiyun/chat
kjiyun Feb 14, 2025
42843eb
#106 Chore: 불필요한 코드 정리
noeyeyh Feb 16, 2025
b8d1658
#106 Feat: 이메일 전송 기능
noeyeyh Feb 16, 2025
b0a193a
#115 Fix: 서치 리스트 응답 형식 수정
Feb 17, 2025
59e6aaa
Merge pull request #116 from nimuy99/fix/#115-search-list-response
nimuyman Feb 17, 2025
06f0412
#117 Feature: 컬렉션 sortType, viewType 기준 구현
SeoyeonPark1223 Feb 17, 2025
0ead127
#106 Feat: 임시 비밀번호 발급
noeyeyh Feb 17, 2025
fc72cec
Merge pull request #118 from SeoyeonPark1223/feature-117
SeoyeonPark1223 Feb 17, 2025
909739b
#106 Feat: 비밀번호 변경 API
noeyeyh Feb 17, 2025
d3bf232
#106 Refactor: 이메일 변환 로직 분리
noeyeyh Feb 17, 2025
2acf97a
Merge branch 'develop' into feat/password
noeyeyh Feb 17, 2025
4575948
#106 Chore: 불필요한 코드 정리
noeyeyh Feb 17, 2025
10fd6c3
Merge pull request #119 from noeyeyh/feat/password
noeyeyh Feb 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<ChartData, Long> {
@Query("SELECT SUM(c.volume) FROM ChartData c " +
Expand All @@ -29,7 +30,11 @@ public interface ChartDataRepository extends JpaRepository<ChartData, Long> {

List<ChartData> findByMemeCoinOrderByRecordedTimeDesc(MemeCoin memeCoin);

//TODO: 위아래 코드 합치는 방법 찾기
List<ChartData> findByMemeCoinOrderByRecordedTimeDesc(MemeCoin memeCoin, Pageable pageable);
List<ChartData> findByMemeCoinAndRecordedTimeAfterOrderByRecordedTimeDesc(MemeCoin memeCoin, LocalDateTime recordedTime, 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<ChartData> findLatestByCoinId(Long coinId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +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.LoggedInUserStore;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Scheduled;
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;

Expand All @@ -23,25 +26,34 @@ 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() {

Set<Long> loggedInUsers = loggedInUserStore.getLoggedInUsers();

List<MemeCoin> memeCoins = memeCoinRepository.findAll();

for (MemeCoin memeCoin : memeCoins) {
try {
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);

} catch (Exception e) {
throw new GeneralException(ErrorStatus.CANNOT_LOAD_CHARTDATA);
}
}

for (Long userId : loggedInUsers) {
pushNotificationService.send(userId);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -32,6 +34,8 @@ public class CollectionRestController {
@GetMapping("/collection")
@Operation(summary = "사용자의 밈코인 콜렉션 모음 조회 API")
public ApiResponse<CollectionPageResponse> 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 // 페이지 번호
) {
Expand All @@ -41,8 +45,8 @@ public ApiResponse<CollectionPageResponse> getCollectionList (
// 유저를 찾지 못하면(로그인을 안 했으면) 콜렉션 접근 못하도록 에러 처리
if (userId == null) throw new GeneralException(ErrorStatus.USER_NOT_FOUND);

Page<Collection> collectionPage = collectionQueryService.getCollectionPage(userId, pageNumber);
return ApiResponse.onSuccess(CollectionConverter.toCollectionPageDTO(collectionPage));
Page<Collection> collectionPage = collectionQueryService.getCollectionPage(userId, pageNumber, viewType, sortType);
return ApiResponse.onSuccess(CollectionConverter.toCollectionPageDTO(collectionPage, viewType));
}

@PostMapping("/collection/{coinId}")
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

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

public static CollectionPageResponse toCollectionPageDTO(Page<Collection> collectionPage) {
List<CollectionPreviewResponse> collectionItems = collectionPage.getContent().stream()
.map(collection -> toCollectionPreviewDTO(collection))
.collect(Collectors.toList());
public static CollectionPageResponse toCollectionPageDTO(Page<Collection> collectionPage, ViewType viewType) {
List<CollectionGridPreviewResponse> gridItems = null;
List<CollectionListPreviewResponse> 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())
.isLast(collectionPage.isLast())
.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())
Expand All @@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class CollectionPreviewResponse {
public class CollectionGridPreviewResponse {
@Schema(description = "밈코인 id", example = "1")
Long coinId;
@Schema(description = "밈코인 name", example = "도지코인")
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}

Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -13,8 +16,10 @@
@AllArgsConstructor
@NoArgsConstructor
public class CollectionPageResponse {
@Schema(description = "콜렉션 아이템들")
List<CollectionPreviewResponse> collectionItems;
@Schema(description = "gridView용 컬렉션 아이템들")
List<CollectionGridPreviewResponse> gridItems; // Grid View용 데이터
@Schema(description = "listView용 컬렉션 아이템들")
List<CollectionListPreviewResponse> listItems; // List View용 데이터
Integer listSize;
Integer totalPage;
Long totalElements;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Collection, Long> {
Page<Collection> findAllByUserId(Long userId, Pageable pageable);
List<Collection> findAllByUserId(Long userId);
Optional<Collection> 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<Collection> findWithLatestChartDataSorted(
@Param("userId") Long userId,
@Param("sortField") String sortField,
Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -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<Collection> getCollectionPage(Long userId, Integer pageNumber);
Page<Collection> getCollectionPage(Long userId, Integer pageNumber, ViewType viewType, SortType sortType);
List<Long> getUserCollectionIds(Long userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -20,8 +25,24 @@ public class CollectionQueryServiceImpl implements CollectionQueryService {

@Transactional(readOnly = true)
@Override
public Page<Collection> getCollectionPage(Long userId, Integer pageNumber) {
Page<Collection> collectionPage = collectionRepository.findAllByUserId(userId, PageRequest.of(pageNumber, 9 ));
public Page<Collection> 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<Collection> collectionPage = collectionRepository.findWithLatestChartDataSorted(userId, sortField, pageable);
return collectionPage;
}

Expand Down
Loading