diff --git a/src/main/java/com/sb/web/account/service/DefaultCategorySeeder.java b/src/main/java/com/sb/web/account/service/DefaultCategorySeeder.java new file mode 100644 index 0000000..e9164b0 --- /dev/null +++ b/src/main/java/com/sb/web/account/service/DefaultCategorySeeder.java @@ -0,0 +1,75 @@ +package com.sb.web.account.service; + +import com.sb.web.account.domain.DefaultCategory; +import com.sb.web.account.mapper.DefaultCategoryMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * 기본(디폴트) 분류 초기 시드. default_category 가 비어 있을 때만 한 번 생성한다. + * (관리자가 이후 자유롭게 수정/삭제 가능 — 비어 있을 때만 채우므로 운영 데이터를 덮어쓰지 않음) + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class DefaultCategorySeeder implements ApplicationRunner { + + private final DefaultCategoryMapper mapper; + + // 대분류 → 소분류 목록 (입력 순서 유지) + private static final Map> EXPENSE = new LinkedHashMap<>(); + private static final Map> INCOME = new LinkedHashMap<>(); + + static { + EXPENSE.put("식비", List.of("외식", "식료품", "카페/간식", "배달")); + EXPENSE.put("교통", List.of("대중교통", "택시", "주유", "주차/통행료")); + EXPENSE.put("주거/통신", List.of("월세/관리비", "공과금", "통신비", "인터넷")); + EXPENSE.put("생활", List.of("생필품", "의류/미용", "가구/가전")); + EXPENSE.put("건강/의료", List.of("병원", "약국", "운동")); + EXPENSE.put("문화/여가", List.of("영화/공연", "여행", "취미", "구독서비스")); + EXPENSE.put("교육", List.of("학원", "도서", "강의")); + EXPENSE.put("경조사", List.of("축의금", "부의금", "선물")); + EXPENSE.put("금융", List.of("보험", "대출이자", "수수료")); + EXPENSE.put("기타", List.of()); + + INCOME.put("급여", List.of("월급", "상여금")); + INCOME.put("부수입", List.of("용돈", "이자/배당", "환급")); + INCOME.put("기타수입", List.of("중고판매", "기타")); + } + + @Override + @Transactional + public void run(ApplicationArguments args) { + if (!mapper.findAll().isEmpty()) { + return; // 이미 데이터가 있으면 시드하지 않음 + } + int n = seed("EXPENSE", EXPENSE) + seed("INCOME", INCOME); + log.info("[default-category] 기본 분류 시드 생성: {}건", n); + } + + private int seed(String type, Map> tree) { + int count = 0; + int majorOrder = 0; + for (Map.Entry> e : tree.entrySet()) { + DefaultCategory major = DefaultCategory.builder() + .type(type).name(e.getKey()).parentId(null).sortOrder(majorOrder++).build(); + mapper.insert(major); // 생성키(id) 채워짐 + count++; + int subOrder = 0; + for (String child : e.getValue()) { + mapper.insert(DefaultCategory.builder() + .type(type).name(child).parentId(major.getId()).sortOrder(subOrder++).build()); + count++; + } + } + return count; + } +} diff --git a/src/main/java/com/sb/web/auth/controller/AuthController.java b/src/main/java/com/sb/web/auth/controller/AuthController.java index bbecf68..46cbffd 100644 --- a/src/main/java/com/sb/web/auth/controller/AuthController.java +++ b/src/main/java/com/sb/web/auth/controller/AuthController.java @@ -87,6 +87,13 @@ public class AuthController { return java.util.Map.of("points", pointService.getPoints(current.getId())); } + /** 포인트 적립/차감 내역 (최신순) */ + @GetMapping("/point-history") + public java.util.List pointHistory( + @RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) { + return pointService.getHistory(current.getId()); + } + @PutMapping("/password") public ResponseEntity changePassword( @Valid @RequestBody PasswordChangeRequest req, diff --git a/src/main/java/com/sb/web/common/config/WebConfig.java b/src/main/java/com/sb/web/common/config/WebConfig.java index b58a1b6..3eff788 100644 --- a/src/main/java/com/sb/web/common/config/WebConfig.java +++ b/src/main/java/com/sb/web/common/config/WebConfig.java @@ -25,7 +25,8 @@ public class WebConfig implements WebMvcConfigurer { public void addInterceptors(InterceptorRegistry registry) { // 인증: 로그인 필요한 경로 (관리자 경로 포함 — SessionUser 세팅용으로 먼저 실행) registry.addInterceptor(authInterceptor) - .addPathPatterns("/api/auth/me", "/api/auth/points", "/api/auth/logout", "/api/auth/password", + .addPathPatterns("/api/auth/me", "/api/auth/points", "/api/auth/point-history", + "/api/auth/logout", "/api/auth/password", "/api/auth/verify-password", "/api/auth/profile", "/api/auth/profile-image", "/api/board/**", "/api/admin/**", "/api/account/**"); // 인가: 관리자 전용 경로 (authInterceptor 다음에 실행되어 role 검사) diff --git a/src/main/java/com/sb/web/point/PointHistoryResponse.java b/src/main/java/com/sb/web/point/PointHistoryResponse.java new file mode 100644 index 0000000..3acfd4f --- /dev/null +++ b/src/main/java/com/sb/web/point/PointHistoryResponse.java @@ -0,0 +1,16 @@ +package com.sb.web.point; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 포인트 적립/차감 내역 항목. + */ +@Data +public class PointHistoryResponse { + + private Integer amount; // 증감 포인트(+/-) + private String reason; // BOARD_WRITE 등 + private LocalDateTime createdAt; +} diff --git a/src/main/java/com/sb/web/point/PointService.java b/src/main/java/com/sb/web/point/PointService.java index 02f5829..2498a10 100644 --- a/src/main/java/com/sb/web/point/PointService.java +++ b/src/main/java/com/sb/web/point/PointService.java @@ -38,4 +38,9 @@ public class PointService { Long p = pointMapper.getPoints(memberId); return p == null ? 0L : p; } + + /** 최근 포인트 적립/차감 내역 (최신순) */ + public java.util.List getHistory(Long memberId) { + return pointMapper.findHistory(memberId, 100); + } } diff --git a/src/main/java/com/sb/web/point/mapper/PointMapper.java b/src/main/java/com/sb/web/point/mapper/PointMapper.java index 2a83005..f6b696a 100644 --- a/src/main/java/com/sb/web/point/mapper/PointMapper.java +++ b/src/main/java/com/sb/web/point/mapper/PointMapper.java @@ -1,8 +1,11 @@ package com.sb.web.point.mapper; +import com.sb.web.point.PointHistoryResponse; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; +import java.util.List; + /** * 포인트(member.points) 및 적립 이력(point_history) 매퍼. */ @@ -17,4 +20,7 @@ public interface PointMapper { int addPoints(@Param("memberId") Long memberId, @Param("amount") int amount); Long getPoints(@Param("memberId") Long memberId); + + /** 최근 적립/차감 내역 (최신순, 최대 limit건) */ + List findHistory(@Param("memberId") Long memberId, @Param("limit") int limit); } diff --git a/src/main/resources/mapper/PointMapper.xml b/src/main/resources/mapper/PointMapper.xml index c81497d..0d65d46 100644 --- a/src/main/resources/mapper/PointMapper.xml +++ b/src/main/resources/mapper/PointMapper.xml @@ -21,4 +21,12 @@ SELECT points FROM member WHERE id = #{memberId} + +