Skip to content

Commit

Permalink
Merge pull request #180 from BudgetBuddiesTeam/fix/#174
Browse files Browse the repository at this point in the history
[fix] 동일한 이름의 카테고리 생성 관련 이슈 해결
  • Loading branch information
ryogaeng authored Sep 4, 2024
2 parents 4f99893 + ebe5052 commit aa295bd
Show file tree
Hide file tree
Showing 11 changed files with 104 additions and 64 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.bbteam.budgetbuddies.domain.category.controller;

import com.bbteam.budgetbuddies.domain.category.dto.CategoryRequestDTO;
import com.bbteam.budgetbuddies.domain.category.dto.CategoryResponseDTO;
import com.bbteam.budgetbuddies.domain.category.dto.CategoryRequestDto;
import com.bbteam.budgetbuddies.domain.category.dto.CategoryResponseDto;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
Expand All @@ -16,31 +16,31 @@

public interface CategoryApi {

@Operation(summary = "카테고리 추가", description = "사용자가 직접 카테고리를 추가합니다.")
@Operation(summary = "[User] 카테고리 추가", description = "사용자가 직접 카테고리를 추가합니다.")
@ApiResponses({
@ApiResponse(responseCode = "COMMON200", description = "OK, 성공"),
@ApiResponse(responseCode = "AUTH003", description = "access 토큰을 주세요!", content = @Content(schema = @Schema(implementation = ApiResponse.class))),
@ApiResponse(responseCode = "AUTH004", description = "access 토큰 만료", content = @Content(schema = @Schema(implementation = ApiResponse.class))),
@ApiResponse(responseCode = "AUTH006", description = "access 토큰 모양이 이상함", content = @Content(schema = @Schema(implementation = ApiResponse.class)))
})
@PostMapping("/add/{userId}")
ResponseEntity<CategoryResponseDTO> createCategory(
ResponseEntity<CategoryResponseDto> createCategory(
@PathVariable Long userId,
@Parameter(description = "user_id, name(사용자가 입력한 카테고리명), is_default(default 카테고리 여부)로 request")
@RequestBody CategoryRequestDTO categoryRequestDTO);
@RequestBody CategoryRequestDto categoryRequestDTO);


@Operation(summary = "유저 개인의 카테고리 조회", description = "유저의 카테고리(default + 개인 custom)를 조회합니다.")
@Operation(summary = "[User] 유저 개인의 카테고리 조회", description = "유저의 카테고리(default + 개인 custom)를 조회합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK"),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "403", description = "Forbidden"),
@ApiResponse(responseCode = "404", description = "Not Found")
})
@GetMapping("/get/{userId}")
ResponseEntity<List<CategoryResponseDTO>> getUserCategories(@PathVariable Long userId);
ResponseEntity<List<CategoryResponseDto>> getUserCategories(@PathVariable Long userId);

@Operation(summary = "특정 카테고리 삭제하기 API", description = "특정 카테고리를 삭제하는 API이며, 사용자의 ID를 입력하여 사용합니다. (추후 토큰으로 검증)")
@Operation(summary = "[User] 커스텀 카테고리 삭제", description = "특정 카테고리를 삭제하는 API이며, 사용자의 ID를 입력하여 사용합니다. (추후 토큰으로 검증)")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공"),
// @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "AUTH003", description = "access 토큰을 주세요!", content = @Content(schema = @Schema(implementation = ApiResponse.class))),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@

import java.util.List;

import com.bbteam.budgetbuddies.domain.category.dto.CategoryRequestDto;
import com.bbteam.budgetbuddies.domain.category.dto.CategoryResponseDto;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import com.bbteam.budgetbuddies.domain.category.dto.CategoryRequestDTO;
import com.bbteam.budgetbuddies.domain.category.dto.CategoryResponseDTO;
import com.bbteam.budgetbuddies.domain.category.service.CategoryService;

import lombok.RequiredArgsConstructor;
Expand All @@ -20,17 +19,17 @@ public class CategoryController implements CategoryApi {

@Override
@PostMapping("/add/{userId}")
public ResponseEntity<CategoryResponseDTO> createCategory(
public ResponseEntity<CategoryResponseDto> createCategory(
@PathVariable Long userId,
@RequestBody CategoryRequestDTO categoryRequestDTO) {
CategoryResponseDTO response = categoryService.createCategory(userId, categoryRequestDTO);
@RequestBody CategoryRequestDto categoryRequestDTO) {
CategoryResponseDto response = categoryService.createCategory(userId, categoryRequestDTO);
return ResponseEntity.ok(response);
}

@Override
@GetMapping("/get/{userId}")
public ResponseEntity<List<CategoryResponseDTO>> getUserCategories(@PathVariable Long userId) {
List<CategoryResponseDTO> response = categoryService.getUserCategories(userId);
public ResponseEntity<List<CategoryResponseDto>> getUserCategories(@PathVariable Long userId) {
List<CategoryResponseDto> response = categoryService.getUserCategories(userId);
return ResponseEntity.ok(response);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package com.bbteam.budgetbuddies.domain.category.converter;

import com.bbteam.budgetbuddies.domain.category.dto.CategoryRequestDTO;
import com.bbteam.budgetbuddies.domain.category.dto.CategoryResponseDTO;
import com.bbteam.budgetbuddies.domain.category.dto.CategoryRequestDto;
import com.bbteam.budgetbuddies.domain.category.dto.CategoryResponseDto;
import com.bbteam.budgetbuddies.domain.category.entity.Category;
import com.bbteam.budgetbuddies.domain.user.entity.User;
import org.springframework.stereotype.Component;

@Component
public class CategoryConverter {

public Category toCategoryEntity(CategoryRequestDTO categoryRequestDTO, User user) {
public Category toCategoryEntity(CategoryRequestDto categoryRequestDTO, User user) {
return Category.builder()
.name(categoryRequestDTO.getName())
.isDefault(categoryRequestDTO.getIsDefault())
Expand All @@ -18,8 +18,8 @@ public Category toCategoryEntity(CategoryRequestDTO categoryRequestDTO, User use
.build();
}

public CategoryResponseDTO toCategoryResponseDTO(Category category) {
return CategoryResponseDTO.builder()
public CategoryResponseDto toCategoryResponseDto(Category category) {
return CategoryResponseDto.builder()
.id(category.getId())
.name(category.getName())
.userId(category.getUser().getId())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

@Getter
@Setter
public class CategoryRequestDTO {
public class CategoryRequestDto {
private String name;
private Boolean isDefault;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

@Getter
@Builder
public class CategoryResponseDTO {
public class CategoryResponseDto {
private Long id;
private Long userId;
private String name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public interface CategoryRepository extends JpaRepository<Category, Long> {

@Query("SELECT c FROM Category c WHERE c.isDefault = true")
List<Category> findAllByIsDefaultTrue();
boolean existsByUserIdAndName(Long userId, String name);

Optional<Category> findById(Long id);

Optional<Category> findByNameAndUserIdAndDeletedTrue(String name, Long userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@

import java.util.List;

import com.bbteam.budgetbuddies.domain.category.dto.CategoryRequestDTO;
import com.bbteam.budgetbuddies.domain.category.dto.CategoryResponseDTO;
import com.bbteam.budgetbuddies.domain.category.dto.CategoryRequestDto;
import com.bbteam.budgetbuddies.domain.category.dto.CategoryResponseDto;
import com.bbteam.budgetbuddies.domain.category.entity.Category;
import com.bbteam.budgetbuddies.domain.expense.dto.ExpenseUpdateRequestDto;
import com.bbteam.budgetbuddies.domain.expense.entity.Expense;
import com.bbteam.budgetbuddies.domain.user.entity.User;

public interface CategoryService {
CategoryResponseDTO createCategory(Long userId, CategoryRequestDTO categoryRequestDTO);
CategoryResponseDto createCategory(Long userId, CategoryRequestDto categoryRequestDTO);

List<CategoryResponseDTO> getUserCategories(Long userId);
List<CategoryResponseDto> getUserCategories(Long userId);

Category handleCategoryChange(Expense expense, ExpenseUpdateRequestDto request, User user);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@

import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import com.bbteam.budgetbuddies.domain.expense.repository.ExpenseRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.bbteam.budgetbuddies.domain.category.converter.CategoryConverter;
import com.bbteam.budgetbuddies.domain.category.dto.CategoryRequestDTO;
import com.bbteam.budgetbuddies.domain.category.dto.CategoryResponseDTO;
import com.bbteam.budgetbuddies.domain.category.dto.CategoryRequestDto;
import com.bbteam.budgetbuddies.domain.category.dto.CategoryResponseDto;
import com.bbteam.budgetbuddies.domain.category.entity.Category;
import com.bbteam.budgetbuddies.domain.category.repository.CategoryRepository;
import com.bbteam.budgetbuddies.domain.consumptiongoal.entity.ConsumptionGoal;
Expand All @@ -36,38 +37,73 @@ public class CategoryServiceImpl implements CategoryService {
private final ExpenseRepository expenseRepository;

@Override
public CategoryResponseDTO createCategory(Long userId, CategoryRequestDTO categoryRequestDTO) {
@Transactional
public CategoryResponseDto createCategory(Long userId, CategoryRequestDto categoryRequestDto) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new IllegalArgumentException("cannot find user"));
.orElseThrow(() -> new IllegalArgumentException("Invalid user ID"));

// 동일한 이름의 삭제된 카테고리가 존재하는지 확인
Optional<Category> existingCategory = categoryRepository.findByNameAndUserIdAndDeletedTrue(
categoryRequestDto.getName(), userId);

if (existingCategory.isPresent()) {
// 삭제된 카테고리가 존재하면 복구 (deleted = false)
Category categoryToRestore = existingCategory.get();
categoryToRestore.setDeleted(false); // 카테고리 복구
categoryRepository.save(categoryToRestore);

// 해당 카테고리의 삭제된 ConsumptionGoal도 복구
Optional<ConsumptionGoal> existingConsumptionGoal = consumptionGoalRepository.findByUserAndCategoryAndDeletedTrue(
user, categoryToRestore);

if (existingConsumptionGoal.isPresent()) {
ConsumptionGoal consumptionGoalToRestore = existingConsumptionGoal.get();
consumptionGoalToRestore.setDeleted(false); // ConsumptionGoal 복구
consumptionGoalToRestore.setConsumeAmount(0L); // consumeAmount 0으로 초기화
consumptionGoalToRestore.setGoalAmount(0L); // goalAmount 0으로 초기화
consumptionGoalRepository.save(consumptionGoalToRestore);
} else {
// ConsumptionGoal이 존재하지 않으면 새로 생성
ConsumptionGoal newConsumptionGoal = ConsumptionGoal.builder()
.user(user)
.category(categoryToRestore)
.goalMonth(LocalDate.now().withDayOfMonth(1)) // 현재 달로 목표 설정
.consumeAmount(0L)
.goalAmount(0L)
.deleted(false) // 생성할 때 삭제 상태가 아니도록
.build();
consumptionGoalRepository.save(newConsumptionGoal);
}

if (categoryRepository.existsByUserIdAndName(userId, categoryRequestDTO.getName())) {
throw new IllegalArgumentException("User already has a category with the same name");
return categoryConverter.toCategoryResponseDto(categoryToRestore);
} else {
// 새로운 카테고리 생성
Category newCategory = categoryConverter.toCategoryEntity(categoryRequestDto, user);
categoryRepository.save(newCategory);

// 새로운 카테고리에 대한 ConsumptionGoal도 생성
ConsumptionGoal newConsumptionGoal = ConsumptionGoal.builder()
.user(user)
.category(newCategory)
.goalMonth(LocalDate.now().withDayOfMonth(1)) // 현재 달로 목표 설정
.consumeAmount(0L)
.goalAmount(0L)
.deleted(false) // 생성할 때 삭제 상태가 아니도록
.build();
consumptionGoalRepository.save(newConsumptionGoal);

return categoryConverter.toCategoryResponseDto(newCategory);
}

Category category = categoryConverter.toCategoryEntity(categoryRequestDTO, user);
Category savedCategory = categoryRepository.save(category);

// custom 카테고리 생성 -> 소비 목표 테이블에 초기 값 추가
ConsumptionGoal consumptionGoal = ConsumptionGoal.builder()
.user(user)
.category(savedCategory)
.goalMonth(LocalDate.now().withDayOfMonth(1)) // custom 카테고리를 생성한 현재 달(지금)로 설정
.goalAmount(0L)
.consumeAmount(0L)
.build();

consumptionGoalRepository.save(consumptionGoal);
return categoryConverter.toCategoryResponseDTO(savedCategory);
}

@Override
public List<CategoryResponseDTO> getUserCategories(Long userId) {
public List<CategoryResponseDto> getUserCategories(Long userId) {

userRepository.findById(userId)
.orElseThrow(() -> new IllegalArgumentException("User not found with id: " + userId));

List<Category> categories = categoryRepository.findUserCategoryByUserId(userId);
return categories.stream().map(categoryConverter::toCategoryResponseDTO).collect(Collectors.toList());
return categories.stream().map(categoryConverter::toCategoryResponseDto).collect(Collectors.toList());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

public interface ConsumptionGoalApi {

@Operation(summary = "또래들이 가장 큰 계획을 세운 카테고리 조회 Top4 API", description = "특정 사용자의 소비 목표 카테고리별 소비 목표 금액을 조회하는 API 입니다.")
@Operation(summary = "[User] 또래들이 가장 큰 계획을 세운 카테고리 조회 Top4", description = "특정 사용자의 소비 목표 카테고리별 소비 목표 금액을 조회하는 API 입니다.")
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공")})
@Parameters({@Parameter(name = "userId", description = "로그인 한 유저 아이디"),
Expand All @@ -32,7 +32,7 @@ public interface ConsumptionGoalApi {
ApiResponse<List<TopGoalCategoryResponseDto>> getTopConsumptionGoalCategories(
Long userId, int peerAgeStart, int peerAgeEnd, String peerGender);

@Operation(summary = "또래들이 가장 많이 계획한 카테고리와 평균 금액 및 내 목표금액 차이 조회 API", description = "특정 사용자의 또래 소비 카테고리별 평균 목표 금액을 조회하는 API 입니다.")
@Operation(summary = "[User] 또래들이 가장 많이 계획한 카테고리와 평균 금액 및 내 목표금액 차이 조회", description = "특정 사용자의 또래 소비 카테고리별 평균 목표 금액을 조회하는 API 입니다.")
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공")})
@Parameters({@Parameter(name = "userId", description = "로그인 한 유저 아이디"),
Expand All @@ -42,7 +42,7 @@ ApiResponse<List<TopGoalCategoryResponseDto>> getTopConsumptionGoalCategories(
ApiResponse<List<AllConsumptionCategoryResponseDto>> getAllConsumptionGoalCategories(Long userId, int peerAgeStart,
int peerAgeEnd, String peerGender);

@Operation(summary = "또래나이와 성별 조회 API", description = "또래나이와 성별을 조회하는 API 입니다.")
@Operation(summary = "[User] 또래나이와 성별 조회", description = "또래나이와 성별을 조회하는 API 입니다.")
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공")})
@Parameters({@Parameter(name = "userId", description = "로그인 한 유저 아이디"),
Expand All @@ -51,15 +51,15 @@ ApiResponse<List<AllConsumptionCategoryResponseDto>> getAllConsumptionGoalCatego
@Parameter(name = "peerGender", description = "또래 성별")})
ApiResponse<PeerInfoResponseDto> getPeerInfo(Long userId, int peerAgeStart, int peerAgeEnd, String peerGender);

@Operation(summary = "소비 목표 조회 API", description = "date={yyyy-MM-dd} 형식의 query string을 통해서 사용자의 목표 달을 조회하는 API 입니다.")
@Operation(summary = "[User] 소비 목표 조회", description = "date={yyyy-MM-dd} 형식의 query string을 통해서 사용자의 목표 달을 조회하는 API 입니다.")
@Parameters({@Parameter(name = "date", description = "yyyy-MM-dd 형식으로 목표 달의 소비를 조회")})
ApiResponse<ConsumptionGoalResponseListDto> findUserConsumptionGoal(LocalDate date, Long userId);

@Operation(summary = "이번 달 소비 목표 수정 API", description = "다른 달의 소비 목표를 업데이트하는 것은 불가능하고 오직 이번 달의 소비 목표만 업데이트 하는 API 입니다.")
@Operation(summary = "[User] 이번 달 소비 목표 수정", description = "다른 달의 소비 목표를 업데이트하는 것은 불가능하고 오직 이번 달의 소비 목표만 업데이트 하는 API 입니다.")
ResponseEntity<ConsumptionGoalResponseListDto> updateOrElseGenerateConsumptionGoal(Long userId,
ConsumptionGoalListRequestDto consumptionGoalListRequestDto);

@Operation(summary = "또래들이 가장 많이한 소비 카테고리 조회 Top3 API", description = "특정 사용자의 또래 소비 카테고리별 소비 건 수을 조회하는 API 입니다.")
@Operation(summary = "[User] 또래들이 가장 많이한 소비 카테고리 조회 Top3", description = "특정 사용자의 또래 소비 카테고리별 소비 건 수을 조회하는 API 입니다.")
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공")})
@Parameters({@Parameter(name = "userId", description = "로그인 한 유저 아이디"),
Expand All @@ -69,7 +69,7 @@ ResponseEntity<ConsumptionGoalResponseListDto> updateOrElseGenerateConsumptionGo
ApiResponse<List<TopCategoryConsumptionDto>> getTopConsumptionCategories(Long userId, int peerAgeStart,
int peerAgeEnd, String peerGender);

@Operation(summary = "또래들이 가장 많이한 소비 카테고리와 평균 금액 및 내 소비금액 차이 조회 API", description = "특정 사용자의 또래 소비 카테고리별 평균 소비 금액을 조회하는 API 입니다.")
@Operation(summary = "[User] 또래들이 가장 많이한 소비 카테고리와 평균 금액 및 내 소비금액 차이 조회", description = "특정 사용자의 또래 소비 카테고리별 평균 소비 금액을 조회하는 API 입니다.")
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공")})
@Parameters({@Parameter(name = "userId", description = "로그인 한 유저 아이디"),
Expand All @@ -79,7 +79,7 @@ ApiResponse<List<TopCategoryConsumptionDto>> getTopConsumptionCategories(Long us
ApiResponse<List<AllConsumptionCategoryResponseDto>> getAllConsumptionCategories(Long userId, int peerAgeStart,
int peerAgeEnd, String peerGender);

@Operation(summary = "또래들이 가장 큰 목표로 세운 카테고리와 그 카테고리에서 이번주 사용한 금액 조회 API", description = "특정 사용자의 또래 소비 카테고리별 이번주 소비 금액을 조회하는 API 입니다.")
@Operation(summary = "[User] 또래들이 가장 큰 목표로 세운 카테고리와 그 카테고리에서 이번주 사용한 금액 조회", description = "특정 사용자의 또래 소비 카테고리별 이번주 소비 금액을 조회하는 API 입니다.")
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "COMMON200", description = "OK, 성공")})
ApiResponse<ConsumptionAnalysisResponseDto> getTopCategoryAndConsumptionAmount(@PathVariable Long userId);
Expand Down
Loading

0 comments on commit aa295bd

Please sign in to comment.