-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: ✨ Spending History Sharing API (#233)
* feat: add spending list read method with day to spending repository * feat: add spending list read method with day to spending service * feat: impl daily spending aggregate service * test: aggregate slice test * fix: change return type to pair * rename: fix test name * feat: spending-chat-share-event * feat: add spending-chat-share-exchange-properties * chore: add spending chat share property to infra yml * feat: impl spending chat share event handler * fix: spending-on-dates type error in the spending-chat-share-event * feat: impl spending-chat-share-helper * feat: add share-to-chat-room to spending usecase * feat: add invalid share type error code to spending-error-code * feat: impl spending share enum type * feat: add spending-chat-share-query dto * feat: add missing-share-param error code to spending error code * feat: add share-spending api to controller * docs: write swagger docs about spending share api * chore: apply binder & queue & event handler for share to chat room * fix: invalid validation in the spending chat share event's name field * feat: add share constant to the message category type * feat: add default create message method to the send-message-command * fix: add sender id field to spending-chat-share-event * fix: add user id to event parameter when publish event * feat: impl sending-share-event listener * fix: add headers to send message command * refactor: convert spending-share-event-listener to kotlin * fix: add date field to spending-chat-share-event * fix: add date to chat message's header
- Loading branch information
1 parent
bf5dbd1
commit dc8c0c2
Showing
22 changed files
with
516 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
20 changes: 20 additions & 0 deletions
20
...y-app-external-api/src/main/java/kr/co/pennyway/api/apis/ledger/dto/SpendingShareReq.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package kr.co.pennyway.api.apis.ledger.dto; | ||
|
||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import kr.co.pennyway.api.common.query.SpendingShareType; | ||
|
||
import java.util.List; | ||
|
||
public class SpendingShareReq { | ||
@Schema(description = "지출 공유 요청") | ||
public record ShareQueryParam( | ||
@Schema(description = "공유 타입 (대/소문자 허용)", example = "chat_room") | ||
SpendingShareType type, | ||
int year, | ||
int month, | ||
int day, | ||
@Schema(description = "공유할 채팅방 ID 배열. 공유 타입이 chat_room인 경우 필수", example = "1") | ||
List<Long> chatRoomIds | ||
) { | ||
} | ||
} |
49 changes: 49 additions & 0 deletions
49
...rnal-api/src/main/java/kr/co/pennyway/api/apis/ledger/helper/SpendingChatShareHelper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package kr.co.pennyway.api.apis.ledger.helper; | ||
|
||
import kr.co.pennyway.api.apis.ledger.service.DailySpendingAggregateService; | ||
import kr.co.pennyway.common.annotation.Helper; | ||
import kr.co.pennyway.domain.context.account.service.UserService; | ||
import kr.co.pennyway.domain.context.chat.service.ChatMemberService; | ||
import kr.co.pennyway.domain.domains.user.exception.UserErrorCode; | ||
import kr.co.pennyway.domain.domains.user.exception.UserErrorException; | ||
import kr.co.pennyway.infra.common.event.SpendingChatShareEvent; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.context.ApplicationEventPublisher; | ||
|
||
import java.time.LocalDate; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
@Slf4j | ||
@Helper | ||
@RequiredArgsConstructor | ||
public class SpendingChatShareHelper { | ||
private final DailySpendingAggregateService dailySpendingAggregateService; | ||
|
||
private final UserService userService; | ||
private final ChatMemberService chatMemberService; | ||
|
||
private final ApplicationEventPublisher eventPublisher; | ||
|
||
public void execute(Long userId, List<Long> chatRoomIds, LocalDate date) { | ||
var user = userService.readUser(userId) | ||
.orElseThrow(() -> new UserErrorException(UserErrorCode.NOT_FOUND)); | ||
var aggregatedSpendings = dailySpendingAggregateService.execute(userId, date.getYear(), date.getMonthValue(), date.getDayOfMonth()); | ||
var joinedChatRoomIds = chatMemberService.readChatRoomIdsByUserId(userId); | ||
|
||
var spendingOnDate = new ArrayList<SpendingChatShareEvent.SpendingOnDate>(); | ||
for (var pair : aggregatedSpendings) { | ||
var categoryInfo = pair.getFirst(); | ||
var amount = pair.getSecond(); | ||
|
||
spendingOnDate.add(SpendingChatShareEvent.SpendingOnDate.of(categoryInfo.id(), categoryInfo.name(), categoryInfo.icon().name(), amount)); | ||
} | ||
|
||
chatRoomIds.stream() | ||
.filter(joinedChatRoomIds::contains) | ||
.forEach(chatRoomId -> { | ||
eventPublisher.publishEvent(new SpendingChatShareEvent(chatRoomId, user.getName(), user.getId(), date, spendingOnDate)); | ||
}); | ||
} | ||
} |
39 changes: 39 additions & 0 deletions
39
...i/src/main/java/kr/co/pennyway/api/apis/ledger/service/DailySpendingAggregateService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package kr.co.pennyway.api.apis.ledger.service; | ||
|
||
import kr.co.pennyway.domain.domains.spending.domain.Spending; | ||
import kr.co.pennyway.domain.domains.spending.dto.CategoryInfo; | ||
import kr.co.pennyway.domain.domains.spending.service.SpendingRdbService; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.data.util.Pair; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
import java.util.List; | ||
|
||
import static java.util.stream.Collectors.groupingBy; | ||
import static java.util.stream.Collectors.summingLong; | ||
|
||
@Slf4j | ||
@Service | ||
@RequiredArgsConstructor | ||
public class DailySpendingAggregateService { | ||
private final SpendingRdbService spendingRdbService; | ||
|
||
@Transactional(readOnly = true) | ||
public List<Pair<CategoryInfo, Long>> execute(Long userId, int year, int month, int day) { | ||
var spendings = spendingRdbService.readSpendings(userId, year, month, day); | ||
|
||
return spendings.stream() | ||
.collect( | ||
groupingBy( | ||
Spending::getCategory, | ||
summingLong(Spending::getAmount) | ||
) | ||
) | ||
.entrySet().stream() | ||
.map(entry -> Pair.of(entry.getKey(), entry.getValue())) | ||
.sorted((o1, o2) -> (int) (o2.getSecond() - o1.getSecond())) | ||
.toList(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 17 additions & 0 deletions
17
...nal-api/src/main/java/kr/co/pennyway/api/common/converter/SpendingShareTypeConverter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package kr.co.pennyway.api.common.converter; | ||
|
||
import kr.co.pennyway.api.common.query.SpendingShareType; | ||
import kr.co.pennyway.domain.domains.spending.exception.SpendingErrorCode; | ||
import kr.co.pennyway.domain.domains.spending.exception.SpendingErrorException; | ||
import org.springframework.core.convert.converter.Converter; | ||
|
||
public class SpendingShareTypeConverter implements Converter<String, SpendingShareType> { | ||
@Override | ||
public SpendingShareType convert(String type) { | ||
try { | ||
return SpendingShareType.valueOf(type.toUpperCase()); | ||
} catch (IllegalArgumentException e) { | ||
throw new SpendingErrorException(SpendingErrorCode.INVALID_SHARE_TYPE); | ||
} | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
...way-app-external-api/src/main/java/kr/co/pennyway/api/common/query/SpendingShareType.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package kr.co.pennyway.api.common.query; | ||
|
||
public enum SpendingShareType { | ||
CHAT_ROOM("chat_room"); | ||
|
||
private final String type; | ||
|
||
SpendingShareType(String type) { | ||
this.type = type; | ||
} | ||
} |
78 changes: 78 additions & 0 deletions
78
...c/test/java/kr/co/pennyway/api/apis/ledger/service/DailySpendingAggregateServiceTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package kr.co.pennyway.api.apis.ledger.service; | ||
|
||
import kr.co.pennyway.api.config.ExternalApiDBTestConfig; | ||
import kr.co.pennyway.api.config.ExternalApiIntegrationTest; | ||
import kr.co.pennyway.api.config.fixture.UserFixture; | ||
import kr.co.pennyway.domain.domains.spending.domain.Spending; | ||
import kr.co.pennyway.domain.domains.spending.domain.SpendingCustomCategory; | ||
import kr.co.pennyway.domain.domains.spending.repository.SpendingCustomCategoryRepository; | ||
import kr.co.pennyway.domain.domains.spending.repository.SpendingRepository; | ||
import kr.co.pennyway.domain.domains.spending.type.SpendingCategory; | ||
import kr.co.pennyway.domain.domains.user.domain.User; | ||
import kr.co.pennyway.domain.domains.user.repository.UserRepository; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.junit.jupiter.api.Test; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
|
||
import java.time.LocalDateTime; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
|
||
@Slf4j | ||
@ExternalApiIntegrationTest | ||
public class DailySpendingAggregateServiceTest extends ExternalApiDBTestConfig { | ||
@Autowired | ||
private UserRepository userRepository; | ||
|
||
@Autowired | ||
private SpendingRepository spendingRepository; | ||
|
||
@Autowired | ||
private SpendingCustomCategoryRepository spendingCustomCategoryRepository; | ||
|
||
@Autowired | ||
private DailySpendingAggregateService dailySpendingAggregateService; | ||
|
||
private static Spending createSpending(String accountName, LocalDateTime spendAt, SpendingCategory category, Integer amount, SpendingCustomCategory spendingCustomCategory, User user) { | ||
return Spending.builder() | ||
.accountName(accountName) | ||
.spendAt(spendAt) | ||
.category(category) | ||
.amount(amount) | ||
.spendingCustomCategory(spendingCustomCategory) | ||
.user(user) | ||
.build(); | ||
} | ||
|
||
@Test | ||
public void shouldReturnDailySpendingDescOrder() { | ||
// given | ||
var user = userRepository.save(UserFixture.GENERAL_USER.toUser()); | ||
var spendingCustomCategory1 = spendingCustomCategoryRepository.save(SpendingCustomCategory.of("커스텀1", SpendingCategory.EDUCATION, user)); | ||
var spendingCustomCategory2 = spendingCustomCategoryRepository.save(SpendingCustomCategory.of("커스텀2", SpendingCategory.FOOD, user)); | ||
|
||
var today = LocalDateTime.now(); | ||
|
||
var defaultFoodSpending1 = spendingRepository.save(createSpending("시스템 카테고리 지출1", today, SpendingCategory.FOOD, 10000, null, user)); | ||
var defaultFoodSpending2 = spendingRepository.save(createSpending("시스템 카테고리 지출2", today, SpendingCategory.FOOD, 20000, null, user)); | ||
var defaultEducationSpending1 = spendingRepository.save(createSpending("시스템 카테고리 지출3", today, SpendingCategory.EDUCATION, 30000, null, user)); | ||
var defaultEducationSpending2 = spendingRepository.save(createSpending("시스템 카테고리 지출4", today, SpendingCategory.EDUCATION, 40000, null, user)); | ||
var systemEducationSpending1 = spendingRepository.save(createSpending("커스텀 카테고리 지출1", today, SpendingCategory.CUSTOM, 50000, spendingCustomCategory1, user)); | ||
var systemEducationSpending2 = spendingRepository.save(createSpending("커스텀 카테고리 지출2", today, SpendingCategory.CUSTOM, 60000, spendingCustomCategory1, user)); | ||
var systemFoodSpending1 = spendingRepository.save(createSpending("커스텀 카테고리 지출3", today, SpendingCategory.CUSTOM, 70000, spendingCustomCategory2, user)); | ||
var systemFoodSpending2 = spendingRepository.save(createSpending("커스텀 카테고리 지출4", today, SpendingCategory.CUSTOM, 80000, spendingCustomCategory2, user)); | ||
|
||
// when | ||
var result = dailySpendingAggregateService.execute(user.getId(), today.getYear(), today.getMonthValue(), today.getDayOfMonth()); | ||
|
||
// then | ||
assertEquals(result.get(0).getFirst(), systemFoodSpending1.getCategory()); | ||
assertEquals(result.get(0).getSecond(), systemFoodSpending1.getAmount() + systemFoodSpending2.getAmount()); | ||
assertEquals(result.get(1).getFirst(), systemEducationSpending1.getCategory()); | ||
assertEquals(result.get(1).getSecond(), systemEducationSpending1.getAmount() + systemEducationSpending2.getAmount()); | ||
assertEquals(result.get(2).getFirst(), defaultEducationSpending1.getCategory()); | ||
assertEquals(result.get(2).getSecond(), defaultEducationSpending1.getAmount() + defaultEducationSpending2.getAmount()); | ||
assertEquals(result.get(3).getFirst(), defaultFoodSpending1.getCategory()); | ||
assertEquals(result.get(3).getSecond(), defaultFoodSpending1.getAmount() + defaultFoodSpending2.getAmount()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.