diff --git a/build.gradle b/build.gradle index 0f10cfc0..306c7975 100644 --- a/build.gradle +++ b/build.gradle @@ -93,6 +93,7 @@ dependencies { // Apple implementation 'com.apple.itunes.storekit:app-store-server-library:3.4.0' + testImplementation 'org.springframework.security:spring-security-test' } tasks.named('test') { diff --git a/src/main/java/umc/th/juinjang/api/limjang/controller/NoteControllerV2.java b/src/main/java/umc/th/juinjang/api/limjang/controller/NoteControllerV2.java index 8120692a..71c9ee89 100644 --- a/src/main/java/umc/th/juinjang/api/limjang/controller/NoteControllerV2.java +++ b/src/main/java/umc/th/juinjang/api/limjang/controller/NoteControllerV2.java @@ -15,6 +15,7 @@ import lombok.RequiredArgsConstructor; import umc.th.juinjang.api.dto.ApiResponse; import umc.th.juinjang.api.limjang.controller.parameter.LimjangSortOptions; +import umc.th.juinjang.api.limjang.controller.request.NoteInitRequest; import umc.th.juinjang.api.limjang.controller.request.NotePatchRequest; import umc.th.juinjang.api.limjang.controller.request.NotePostRequest; import umc.th.juinjang.api.limjang.service.NoteCommandServiceV2; @@ -42,6 +43,13 @@ public ApiResponse createNote(@RequestBody @Valid NotePostRequ return ApiResponse.of(SuccessStatus._CREATED, noteCommandService.createNote(request, member)); } + @Operation(summary = "임장 생성 API INIT V2 - 간편한 임장 생성") + @PostMapping("/notes/init") + public ApiResponse initNote(@RequestBody @Valid NoteInitRequest request, + @AuthenticationPrincipal Member member) { + return ApiResponse.of(SuccessStatus._CREATED, noteCommandService.initNote(request, member)); + } + @Operation(summary = "마이 노트 조회 API V2") @GetMapping("/notes") public ApiResponse findUsersNotes( diff --git a/src/main/java/umc/th/juinjang/api/limjang/controller/request/NoteInitRequest.java b/src/main/java/umc/th/juinjang/api/limjang/controller/request/NoteInitRequest.java new file mode 100644 index 00000000..de7db96d --- /dev/null +++ b/src/main/java/umc/th/juinjang/api/limjang/controller/request/NoteInitRequest.java @@ -0,0 +1,32 @@ +package umc.th.juinjang.api.limjang.controller.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import umc.th.juinjang.domain.limjang.model.Limjang; +import umc.th.juinjang.domain.limjang.model.LimjangPrice; +import umc.th.juinjang.domain.limjang.model.LimjangPriceType; +import umc.th.juinjang.domain.limjang.model.LimjangPropertyType; +import umc.th.juinjang.domain.limjang.model.LimjangPurpose; +import umc.th.juinjang.domain.limjang.repository.NotePriceFactory; +import umc.th.juinjang.domain.member.model.Member; + +public record NoteInitRequest( + @NotNull + LimjangPurpose purposeType, + @NotNull + LimjangPropertyType propertyType, + @NotNull + LimjangPriceType priceType, + @NotBlank + @Pattern(regexp = "^[0-9]+$", message = "가격은 숫자만 입력해야 합니다.") + String price, + @Pattern(regexp = "^[0-9]+$", message = "가격은 숫자만 입력해야 합니다.") + String monthlyRent +) { + public Limjang toEntity(Member member) { + LimjangPrice limjangPrice = NotePriceFactory.create(purposeType, priceType, price, monthlyRent); + + return Limjang.initNote(member, limjangPrice, purposeType, propertyType, priceType); + } +} diff --git a/src/main/java/umc/th/juinjang/api/limjang/service/NoteCommandServiceV2.java b/src/main/java/umc/th/juinjang/api/limjang/service/NoteCommandServiceV2.java index 20fda0ca..4dac1847 100644 --- a/src/main/java/umc/th/juinjang/api/limjang/service/NoteCommandServiceV2.java +++ b/src/main/java/umc/th/juinjang/api/limjang/service/NoteCommandServiceV2.java @@ -3,10 +3,12 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import umc.th.juinjang.api.address.service.AddressUpdater; +import umc.th.juinjang.api.limjang.controller.request.NoteInitRequest; import umc.th.juinjang.api.limjang.controller.request.NotePatchRequest; import umc.th.juinjang.api.limjang.controller.request.NotePostRequest; -import umc.th.juinjang.api.address.service.AddressUpdater; import umc.th.juinjang.api.limjang.service.response.NotePostResponse; import umc.th.juinjang.common.code.status.ErrorStatus; import umc.th.juinjang.common.exception.handler.LimjangHandler; @@ -60,4 +62,14 @@ private void validatePriceType(LimjangPurpose purposeType, LimjangPriceType pric throw new LimjangHandler(ErrorStatus.LIMJANG_POST_TYPE_ERROR); } } + + public NotePostResponse initNote(@Valid NoteInitRequest request, Member member) { + Limjang note = request.toEntity(member); + validatePriceType(request.purposeType(), request.priceType()); + + notePriceUpdater.save(note.getLimjangPrice()); + Limjang savedNote = noteUpdater.save(note); + + return NotePostResponse.of(savedNote.getLimjangId()); + } } diff --git a/src/main/java/umc/th/juinjang/domain/limjang/model/Limjang.java b/src/main/java/umc/th/juinjang/domain/limjang/model/Limjang.java index 4e218ef2..c6cc09fd 100644 --- a/src/main/java/umc/th/juinjang/domain/limjang/model/Limjang.java +++ b/src/main/java/umc/th/juinjang/domain/limjang/model/Limjang.java @@ -1,5 +1,7 @@ package umc.th.juinjang.domain.limjang.model; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; @@ -180,6 +182,21 @@ public static Limjang create(Member member, LimjangPrice price, LimjangPurpose p .build(); } + public static Limjang initNote(Member member, LimjangPrice price, LimjangPurpose purpose, + LimjangPropertyType propertyType, LimjangPriceType priceType) { + String nickname = LocalDateTime.now() + .format(DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm")) + " 매물노트"; + + return Limjang.builder() + .memberId(member) + .limjangPrice(price) + .priceType(priceType) + .purpose(purpose) + .propertyType(propertyType) + .nickname(nickname) + .build(); + } + public void updateNote(String nickname, LimjangPriceType limjangPriceType, String floor, int pyong) { this.nickname = nickname; this.priceType = limjangPriceType; diff --git a/src/main/java/umc/th/juinjang/domain/limjang/model/LimjangPurpose.java b/src/main/java/umc/th/juinjang/domain/limjang/model/LimjangPurpose.java index 432a817a..f9af200f 100644 --- a/src/main/java/umc/th/juinjang/domain/limjang/model/LimjangPurpose.java +++ b/src/main/java/umc/th/juinjang/domain/limjang/model/LimjangPurpose.java @@ -1,30 +1,31 @@ package umc.th.juinjang.domain.limjang.model; import java.util.Arrays; + import umc.th.juinjang.common.code.status.ErrorStatus; import umc.th.juinjang.common.exception.handler.LimjangHandler; public enum LimjangPurpose { - INVESTMENT(0), // 투자 목적 - RESIDENTIAL_PURPOSE(1); // 거주 목적 + INVESTMENT(0), // 투자 목적 + RESIDENTIAL_PURPOSE(1); // 거주 목적, 직접 입주 - private final int value; + private final int value; - LimjangPurpose(int value) { - this.value = value; - } + LimjangPurpose(int value) { + this.value = value; + } - // 숫자 리턴 - public int getValue() { - return value; - } + // 숫자 리턴 + public int getValue() { + return value; + } - public static LimjangPurpose find(int inputValue) { - return Arrays.stream(LimjangPurpose.values()) - .filter(it -> it.value == inputValue) - .findAny() - .orElseThrow(() -> new LimjangHandler(ErrorStatus.LIMJANG_POST_TYPE_ERROR)); - } + public static LimjangPurpose find(int inputValue) { + return Arrays.stream(LimjangPurpose.values()) + .filter(it -> it.value == inputValue) + .findAny() + .orElseThrow(() -> new LimjangHandler(ErrorStatus.LIMJANG_POST_TYPE_ERROR)); + } } diff --git a/src/test/java/umc/th/juinjang/api/ControllerTestSupport.java b/src/test/java/umc/th/juinjang/api/ControllerTestSupport.java index 1e5f2cb3..e1d97b3e 100644 --- a/src/test/java/umc/th/juinjang/api/ControllerTestSupport.java +++ b/src/test/java/umc/th/juinjang/api/ControllerTestSupport.java @@ -7,6 +7,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; +import umc.th.juinjang.api.limjang.controller.NoteControllerV2; +import umc.th.juinjang.api.limjang.service.NoteCommandServiceV2; +import umc.th.juinjang.api.limjang.service.NoteQueryServiceV2; import umc.th.juinjang.api.pencil.controller.PencilController; import umc.th.juinjang.api.pencil.service.PencilCommandService; import umc.th.juinjang.api.pencil.service.PencilQueryService; @@ -15,7 +18,8 @@ @WebMvcTest(controllers = { PencilController.class, - PencilAccountController.class + PencilAccountController.class, + NoteControllerV2.class }) public abstract class ControllerTestSupport { @@ -33,4 +37,10 @@ public abstract class ControllerTestSupport { @MockBean protected PencilCommandService pencilCommandService; + + @MockBean + protected NoteCommandServiceV2 noteCommandServiceV2; + + @MockBean + protected NoteQueryServiceV2 noteQueryServiceV2; } diff --git a/src/test/java/umc/th/juinjang/api/limjang/service/NoteCommandServiceV2Test.java b/src/test/java/umc/th/juinjang/api/limjang/service/NoteCommandServiceV2Test.java new file mode 100644 index 00000000..17c1cc78 --- /dev/null +++ b/src/test/java/umc/th/juinjang/api/limjang/service/NoteCommandServiceV2Test.java @@ -0,0 +1,100 @@ +package umc.th.juinjang.api.limjang.service; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import umc.th.juinjang.api.IntegrationTestSupport; +import umc.th.juinjang.api.limjang.controller.request.NoteInitRequest; +import umc.th.juinjang.api.limjang.service.response.NotePostResponse; +import umc.th.juinjang.domain.limjang.model.Limjang; +import umc.th.juinjang.domain.limjang.model.LimjangPriceType; +import umc.th.juinjang.domain.limjang.model.LimjangPropertyType; +import umc.th.juinjang.domain.limjang.model.LimjangPurpose; +import umc.th.juinjang.domain.limjang.repository.LimjangRepository; +import umc.th.juinjang.domain.member.model.Member; +import umc.th.juinjang.domain.member.repository.MemberRepository; + +class NoteCommandServiceV2Test extends IntegrationTestSupport { + + Member firstMember; + Member secondMember; + Member thirdMember; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private NoteCommandServiceV2 noteCommandServiceV2; + + @Autowired + private LimjangRepository limjangRepository; + + private void flushAndTestUsers() { + memberRepository.deleteAll(); + + // 첫 번째 멤버 (Apple) + firstMember = Member.createAppleMember( + "first@apple.com", + "apple_sub_001", + "첫번째유저", + "1.0.0" + ); + memberRepository.save(firstMember); + + // 두 번째 멤버 (Kakao) + secondMember = Member.createKakaoMember( + "second@kakao.com", + 12345L, + "두번째유저", + "1.0.0" + ); + memberRepository.save(secondMember); + + // 세 번째 멤버 (Apple) + thirdMember = Member.createAppleMember( + "third@apple.com", + "apple_sub_002", + "세번째유저", + "1.0.0" + ); + memberRepository.save(thirdMember); + + memberRepository.flush(); + } + + @Test + void initNote() { + flushAndTestUsers(); + + NoteInitRequest request = new NoteInitRequest( + LimjangPurpose.RESIDENTIAL_PURPOSE, + LimjangPropertyType.APARTMENT, + LimjangPriceType.MONTHLY_RENT, + "50000", + "4000" + ); + NotePostResponse response = noteCommandServiceV2.initNote(request, firstMember); + Long createNoteId = response.noteId(); + + Limjang note = limjangRepository.findById(createNoteId).orElseThrow(); + assertThat(note).extracting( + "memberId.memberId", + "purpose", + "propertyType", + "priceType", + "deleted", + "recordCount" + ).containsExactly( + firstMember.getMemberId(), + LimjangPurpose.RESIDENTIAL_PURPOSE, + LimjangPropertyType.APARTMENT, + LimjangPriceType.MONTHLY_RENT, + false, + 0 + ); + assertThat(note).extracting("limjangPrice").isNotNull(); + assertThat(note).extracting("nickname").satisfies(n -> assertThat((String)n).endsWith(" 매물노트")); + } +} \ No newline at end of file