Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
33 changes: 33 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"permissions": {
"allow": [
"Bash(git add:*)"
]
},
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "/home/ubuntu/.claude-hooks/notify.sh",
"timeout": 15
}
]
}
],
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "/home/ubuntu/.claude-hooks/notify.sh",
"timeout": 15
}
]
}
]
}
}
353 changes: 229 additions & 124 deletions CLAUDE.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,32 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.scheduling.annotation.EnableScheduling;
import java.util.TimeZone;

/**
* Commerce API 메인 애플리케이션 클래스.
* REST API 서버를 기동하며, 타임존을 Asia/Seoul로 설정하고 스케줄링을 활성화한다.
*/
@ConfigurationPropertiesScan
@EnableScheduling
@SpringBootApplication
public class CommerceApiApplication {

/**
* 애플리케이션 기동 후 타임존을 Asia/Seoul로 설정한다.
*/
@PostConstruct
public void started() {
// set timezone
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul"));
}

/**
* 애플리케이션 진입점.
*
* @param args 커맨드라인 인수
*/
public static void main(String[] args) {
SpringApplication.run(CommerceApiApplication.class, args);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package com.loopers.application.brand;

import com.loopers.domain.brand.BrandService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
* 브랜드 도메인 Application Service.
* 도메인 서비스를 호출하고 Model → Info 변환을 담당한다.
*/
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class BrandAppService {

private final BrandService brandService;

/**
* 새 브랜드를 등록한다.
*
* @param brandName 브랜드명
* @param description 설명
* @param address 주소
* @return 생성된 브랜드 정보 DTO
*/
@Transactional
public BrandInfo createBrand(String brandName, String description, String address) {
return BrandInfo.from(brandService.createBrand(brandName, description, address));
}

/**
* 관리자용 전체 브랜드 목록을 조회한다 (삭제 포함).
*
* @return 전체 브랜드 정보 DTO 목록
*/
public List<BrandInfo> findAllForAdmin() {
return brandService.findAllForAdmin().stream()
.map(BrandInfo::from)
.toList();
}

/**
* 고객에게 노출 가능한 브랜드를 ID로 조회한다.
*
* @param brandId 브랜드 ID
* @return 브랜드 정보 DTO
*/
public BrandInfo findVisibleById(String brandId) {
return BrandInfo.from(brandService.findVisibleById(brandId));
}

/**
* 고객에게 노출 가능한 브랜드 목록을 조회한다. 키워드가 있으면 검색한다.
*
* @param keyword 검색 키워드 (null이면 전체 조회)
* @return 브랜드 정보 DTO 목록
*/
public List<BrandInfo> findAllVisibleBrands(String keyword) {
return brandService.findAllVisibleBrands(keyword).stream()
.map(BrandInfo::from)
.toList();
}

/**
* 브랜드 정보를 수정한다.
*
* @param brandId 브랜드 ID
* @param brandName 새 브랜드명
* @param description 새 설명
* @param address 새 주소
* @return 수정된 브랜드 정보 DTO
*/
@Transactional
public BrandInfo updateBrand(String brandId, String brandName, String description, String address) {
return BrandInfo.from(brandService.updateBrand(brandId, brandName, description, address));
}

/**
* 브랜드를 소프트 삭제한다.
*
* @param brandId 삭제할 브랜드 ID
*/
@Transactional
public void deleteBrand(String brandId) {
brandService.deleteBrand(brandId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.loopers.application.brand;

import com.loopers.domain.brand.BrandModel;
import com.loopers.support.enums.DisplayStatus;
import lombok.Builder;
import lombok.Getter;

import java.time.ZonedDateTime;

/**
* 브랜드 정보 DTO.
* 도메인 모델({@link BrandModel})을 직접 노출하지 않고 인터페이스 레이어에 전달하기 위한 응답 객체이다.
*/
@Getter
@Builder
public class BrandInfo {
private final String brandId;
private final String brandName;
private final String description;
private final String address;
private final DisplayStatus displayStatus;
private final String attachFile;
private final String delYn;
private final ZonedDateTime deletedAt;
private final ZonedDateTime createdAt;

/**
* BrandModel을 BrandInfo DTO로 변환한다.
*
* @param model 변환할 브랜드 엔티티
* @return 브랜드 정보 DTO
*/
public static BrandInfo from(BrandModel model) {
return BrandInfo.builder()
.brandId(model.getBrandId())
.brandName(model.getBrandName())
.description(model.getDescription())
.address(model.getAddress())
.displayStatus(model.getDisplayStatus())
.attachFile(model.getAttachFile())
.delYn(model.getDelYn())
.deletedAt(model.getDeletedAt())
.createdAt(model.getCreatedAt())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.loopers.application.cart;

import com.loopers.domain.cart.CartService;
import com.loopers.domain.user.UserModel;
import com.loopers.domain.user.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
* 장바구니 도메인 Application Service.
*
* <p>단일 도메인 서비스(CartService)만 호출하는 얇은 메서드를 담당한다.
* 인증 후 CartService에 위임한다.</p>
*/
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class CartAppService {

private final CartService cartService;
private final UserService userService;

/**
* 장바구니에서 상품을 삭제한다.
*
* @param loginId 로그인 ID
* @param loginPw 로그인 비밀번호
* @param productId 삭제할 상품 ID
*/
@Transactional
public void removeItem(String loginId, String loginPw, String productId) {
UserModel user = userService.authenticate(loginId, loginPw);
cartService.removeItem(user.getUserId(), productId);
}
}
Loading