diff --git a/src/main/java/com/yam/app/account/application/AccountFacade.java b/src/main/java/com/yam/app/account/application/AccountFacade.java index 1f3b380..05b578a 100644 --- a/src/main/java/com/yam/app/account/application/AccountFacade.java +++ b/src/main/java/com/yam/app/account/application/AccountFacade.java @@ -7,6 +7,8 @@ import com.yam.app.account.domain.LoginAccountProcessor; import com.yam.app.account.domain.RegisterAccountConfirmEvent; import com.yam.app.account.domain.RegisterAccountEvent; import com.yam.app.account.domain.RegisterAccountProcessor; +import com.yam.app.account.domain.UpdateAccountEvent; +import com.yam.app.account.domain.UpdateAccountProcessor; import com.yam.app.account.presentation.AccountResponse; import com.yam.app.account.presentation.ConfirmRegisterAccountCommand; import com.yam.app.account.presentation.LoginAccountCommand; @@ -26,17 +28,20 @@ public class AccountFacade { private final ConfirmRegisterAccountProcessor confirmRegisterProcessor; private final LoginAccountProcessor loginProcessor; private final AccountReader accountReader; + private final UpdateAccountProcessor updateProcessor; public AccountFacade(RegisterAccountProcessor registerProcessor, AccountTranslator translator, ApplicationEventPublisher publisher, ConfirmRegisterAccountProcessor confirmRegisterProcessor, - LoginAccountProcessor loginProcessor, AccountReader accountReader) { + LoginAccountProcessor loginProcessor, AccountReader accountReader, + UpdateAccountProcessor updateProcessor) { this.registerProcessor = registerProcessor; this.translator = translator; this.publisher = publisher; this.confirmRegisterProcessor = confirmRegisterProcessor; this.loginProcessor = loginProcessor; this.accountReader = accountReader; + this.updateProcessor = updateProcessor; } @Transactional @@ -65,7 +70,11 @@ public class AccountFacade { } @Transactional - public void update(UpdateAccountCommand command, Authentication authentication) { - + public void update(Authentication authentication, UpdateAccountCommand command) { + updateProcessor.update(authentication.getCredentials(), command.getPassword()); + publisher.publishEvent(new UpdateAccountEvent( + authentication.getMemberId(), + command.getNickname(), + command.getImage())); } } diff --git a/src/main/java/com/yam/app/account/domain/Account.java b/src/main/java/com/yam/app/account/domain/Account.java index f672bbb..d3aabb6 100644 --- a/src/main/java/com/yam/app/account/domain/Account.java +++ b/src/main/java/com/yam/app/account/domain/Account.java @@ -55,4 +55,8 @@ public final class Account { public void addMember(Long memberId) { this.memberId = memberId; } + + public void changePassword(String password) { + this.password = password; + } } diff --git a/src/main/java/com/yam/app/account/domain/UpdateAccountEvent.java b/src/main/java/com/yam/app/account/domain/UpdateAccountEvent.java new file mode 100644 index 0000000..8465e39 --- /dev/null +++ b/src/main/java/com/yam/app/account/domain/UpdateAccountEvent.java @@ -0,0 +1,17 @@ +package com.yam.app.account.domain; + +import lombok.Getter; + +@Getter +public final class UpdateAccountEvent { + + private final Long memberId; + private final String nickname; + private final String image; + + public UpdateAccountEvent(Long memberId, String nickname, String image) { + this.memberId = memberId; + this.nickname = nickname; + this.image = image; + } +} diff --git a/src/main/java/com/yam/app/account/domain/UpdateAccountProcessor.java b/src/main/java/com/yam/app/account/domain/UpdateAccountProcessor.java new file mode 100644 index 0000000..5d9e1de --- /dev/null +++ b/src/main/java/com/yam/app/account/domain/UpdateAccountProcessor.java @@ -0,0 +1,23 @@ +package com.yam.app.account.domain; + +public final class UpdateAccountProcessor { + + private final AccountReader accountReader; + private final AccountRepository accountRepository; + private final PasswordEncrypter passwordEncrypter; + + public UpdateAccountProcessor(AccountReader accountReader, + AccountRepository accountRepository, + PasswordEncrypter passwordEncrypter) { + this.accountReader = accountReader; + this.accountRepository = accountRepository; + this.passwordEncrypter = passwordEncrypter; + } + + public void update(String email, String password) { + var account = accountReader.findByEmail(email) + .orElseThrow(() -> new AccountNotFoundException(email)); + account.changePassword(passwordEncrypter.encode(password)); + accountRepository.update(account); + } +} diff --git a/src/main/java/com/yam/app/account/infrastructure/AccountModuleConfiguration.java b/src/main/java/com/yam/app/account/infrastructure/AccountModuleConfiguration.java index 55af42a..c0a9953 100644 --- a/src/main/java/com/yam/app/account/infrastructure/AccountModuleConfiguration.java +++ b/src/main/java/com/yam/app/account/infrastructure/AccountModuleConfiguration.java @@ -7,6 +7,7 @@ import com.yam.app.account.domain.LoginAccountProcessor; import com.yam.app.account.domain.PasswordEncrypter; import com.yam.app.account.domain.RegisterAccountProcessor; import com.yam.app.account.domain.TokenVerifier; +import com.yam.app.account.domain.UpdateAccountProcessor; import javax.servlet.http.HttpSession; import org.mybatis.spring.SqlSessionTemplate; import org.springframework.context.annotation.Bean; @@ -71,4 +72,10 @@ public class AccountModuleConfiguration { new SessionManager(httpSession)); } + @Bean + public UpdateAccountProcessor updateAccountProcessor(AccountReader accountReader, + AccountRepository accountRepository, PasswordEncrypter passwordEncrypter) { + return new UpdateAccountProcessor(accountReader, accountRepository, passwordEncrypter); + } + } diff --git a/src/main/java/com/yam/app/account/presentation/AccountCommandApi.java b/src/main/java/com/yam/app/account/presentation/AccountCommandApi.java index 7582761..a1891e0 100644 --- a/src/main/java/com/yam/app/account/presentation/AccountCommandApi.java +++ b/src/main/java/com/yam/app/account/presentation/AccountCommandApi.java @@ -73,7 +73,7 @@ public final class AccountCommandApi { public ResponseEntity update( @RequestBody @Valid UpdateAccountCommand command, @AuthenticationPrincipal Authentication authentication) { - accountFacade.update(command, authentication); + accountFacade.update(authentication, command); return ResponseEntity.ok().build(); } } diff --git a/src/main/java/com/yam/app/account/presentation/UpdateAccountCommand.java b/src/main/java/com/yam/app/account/presentation/UpdateAccountCommand.java index 74a0bd0..99f487c 100644 --- a/src/main/java/com/yam/app/account/presentation/UpdateAccountCommand.java +++ b/src/main/java/com/yam/app/account/presentation/UpdateAccountCommand.java @@ -7,15 +7,15 @@ import lombok.Data; @Data public final class UpdateAccountCommand { - @NotBlank - @Pattern(regexp = "^[A-Za-z1-9~!@#$%^&*()+|=]{8,12}$", - message = "Please enter the password in English, numbers, " - + "and special characters within 8-12 digits.") - private String password; - @NotBlank private String nickname; @NotBlank private String image; + + @NotBlank + @Pattern(regexp = "^[A-Za-z1-9~!@#$%^&*()+|=]{8,12}$", + message = "Please enter the password in English, numbers, " + + "and special characters within 8-12 digits.") + private String password; } diff --git a/src/main/java/com/yam/app/adapter/DomainEventTranslator.java b/src/main/java/com/yam/app/adapter/DomainEventTranslator.java index eace2e1..02610c8 100644 --- a/src/main/java/com/yam/app/adapter/DomainEventTranslator.java +++ b/src/main/java/com/yam/app/adapter/DomainEventTranslator.java @@ -1,6 +1,7 @@ package com.yam.app.adapter; import com.yam.app.account.domain.RegisterAccountConfirmEvent; +import com.yam.app.account.domain.UpdateAccountEvent; import com.yam.app.member.domain.GenerateMemberEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.event.EventListener; @@ -28,4 +29,10 @@ class DomainEventTranslator { event.getEmail())); } + @EventListener + public void translate(UpdateAccountEvent event) { + publisher.publishEvent( + new com.yam.app.member.domain.UpdateAccountEvent( + event.getMemberId(), event.getNickname(), event.getImage())); + } } diff --git a/src/main/java/com/yam/app/member/domain/Member.java b/src/main/java/com/yam/app/member/domain/Member.java index 5f0ce4e..2e5c94f 100644 --- a/src/main/java/com/yam/app/member/domain/Member.java +++ b/src/main/java/com/yam/app/member/domain/Member.java @@ -15,4 +15,9 @@ public final class Member { this.nickname = nickname; this.image = image; } + + public void changeProfile(String nickname, String image) { + this.nickname = nickname; + this.image = image; + } } diff --git a/src/main/java/com/yam/app/member/domain/MemberReader.java b/src/main/java/com/yam/app/member/domain/MemberReader.java index 2b4a534..87eb3bf 100644 --- a/src/main/java/com/yam/app/member/domain/MemberReader.java +++ b/src/main/java/com/yam/app/member/domain/MemberReader.java @@ -5,4 +5,6 @@ import java.util.Optional; public interface MemberReader { Optional findByNickname(String nickname); + + Optional findById(Long memberId); } diff --git a/src/main/java/com/yam/app/member/domain/MemberRepository.java b/src/main/java/com/yam/app/member/domain/MemberRepository.java index a4ef6dd..6291955 100644 --- a/src/main/java/com/yam/app/member/domain/MemberRepository.java +++ b/src/main/java/com/yam/app/member/domain/MemberRepository.java @@ -3,4 +3,6 @@ package com.yam.app.member.domain; public interface MemberRepository { void save(Member entity); + + void update(Member entity); } diff --git a/src/main/java/com/yam/app/member/domain/UpdateAccountEvent.java b/src/main/java/com/yam/app/member/domain/UpdateAccountEvent.java new file mode 100644 index 0000000..fa274a5 --- /dev/null +++ b/src/main/java/com/yam/app/member/domain/UpdateAccountEvent.java @@ -0,0 +1,17 @@ +package com.yam.app.member.domain; + +import lombok.Getter; + +@Getter +public final class UpdateAccountEvent { + + private final Long memberId; + private final String nickname; + private final String image; + + public UpdateAccountEvent(Long memberId, String nickname, String image) { + this.memberId = memberId; + this.nickname = nickname; + this.image = image; + } +} diff --git a/src/main/java/com/yam/app/member/infrastructure/MemberEventListener.java b/src/main/java/com/yam/app/member/infrastructure/MemberEventListener.java index c3afd47..cf800cb 100644 --- a/src/main/java/com/yam/app/member/infrastructure/MemberEventListener.java +++ b/src/main/java/com/yam/app/member/infrastructure/MemberEventListener.java @@ -5,6 +5,7 @@ import com.yam.app.member.domain.Member; import com.yam.app.member.domain.MemberReader; import com.yam.app.member.domain.MemberRepository; import com.yam.app.member.domain.RegisterAccountConfirmEvent; +import com.yam.app.member.domain.UpdateAccountEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; @@ -32,4 +33,12 @@ public class MemberEventListener { .orElseThrow(IllegalArgumentException::new); publisher.publishEvent(new GenerateMemberEvent(member.getId(), event.getEmail())); } + + @EventListener + public void handle(UpdateAccountEvent event) { + var member = memberReader.findById(event.getMemberId()) + .orElseThrow(IllegalArgumentException::new); + member.changeProfile(event.getNickname(), event.getImage()); + memberRepository.update(member); + } } diff --git a/src/main/java/com/yam/app/member/infrastructure/MybatisMemberRepository.java b/src/main/java/com/yam/app/member/infrastructure/MybatisMemberRepository.java index 13c212c..d82f006 100644 --- a/src/main/java/com/yam/app/member/infrastructure/MybatisMemberRepository.java +++ b/src/main/java/com/yam/app/member/infrastructure/MybatisMemberRepository.java @@ -11,6 +11,7 @@ public final class MybatisMemberRepository implements MemberRepository, MemberRe private final SqlSessionTemplate template; private static final String SAVE_FQCN = "com.yam.app.member.domain.MemberRepository.save"; + private static final String UPDATE_FQCN = "com.yam.app.member.domain.MemberRepository.update"; public MybatisMemberRepository(SqlSessionTemplate template) { this.template = template; @@ -21,6 +22,11 @@ public final class MybatisMemberRepository implements MemberRepository, MemberRe return template.getMapper(MemberReader.class).findByNickname(nickname); } + @Override + public Optional findById(Long memberId) { + return template.getMapper(MemberReader.class).findById(memberId); + } + @Override public void save(Member entity) { int result = template.insert(SAVE_FQCN, entity); @@ -29,4 +35,13 @@ public final class MybatisMemberRepository implements MemberRepository, MemberRe String.format("There was a problem saving the object : %s", entity)); } } + + @Override + public void update(Member entity) { + int result = template.update(UPDATE_FQCN, entity); + if (result != 1) { + throw new RuntimeException( + String.format("There was a problem updating the object : %s", entity)); + } + } } diff --git a/src/main/resources/mapper/xml/MemberCommandMapper.xml b/src/main/resources/mapper/xml/MemberCommandMapper.xml index caea510..7f46422 100644 --- a/src/main/resources/mapper/xml/MemberCommandMapper.xml +++ b/src/main/resources/mapper/xml/MemberCommandMapper.xml @@ -9,4 +9,11 @@ VALUES (#{nickname}, #{image}) + + UPDATE MEMBER + SET nickname = #{nickname}, + image = #{image} + WHERE id = #{id} + + diff --git a/src/main/resources/mapper/xml/MemberQueryMapper.xml b/src/main/resources/mapper/xml/MemberQueryMapper.xml index fabf30f..aff099f 100644 --- a/src/main/resources/mapper/xml/MemberQueryMapper.xml +++ b/src/main/resources/mapper/xml/MemberQueryMapper.xml @@ -8,4 +8,9 @@ SELECT * FROM member WHERE nickname = #{nickname} + + diff --git a/src/main/resources/sql/dml.sql b/src/main/resources/sql/dml.sql index 903bd75..c46e0e6 100644 --- a/src/main/resources/sql/dml.sql +++ b/src/main/resources/sql/dml.sql @@ -5,3 +5,12 @@ values ('jiwonDev@gmail.com', 'emailchecktoken', now(), false, now(), now(), 'pa ('loginCheck@gmail.com', 'emailchecktoken1', now(), true, now(), now(), '$2a$10$EqbMbYB0vcZnuA5CClqa9uiLDnjA6pCjxn208ZchzA2q3ofqnkhcq', false, 'DEFAULT'); + +insert into member(nickname, image) +values ('rebwon', 'temp.png'); + +insert into account(email, email_check_token, email_check_token_generated_at, email_verified, + joined_at, last_modified_at, password, withdraw, role, member_id) +values ('rebwon@gmail.com', 'emailchecktoken1', now(), true, now(), now(), + '$2a$10$EqbMbYB0vcZnuA5CClqa9uiLDnjA6pCjxn208ZchzA2q3ofqnkhcq', + false, 'DEFAULT', 1); diff --git a/src/test/java/com/yam/app/account/domain/UpdateAccountProcessorTest.java b/src/test/java/com/yam/app/account/domain/UpdateAccountProcessorTest.java new file mode 100644 index 0000000..f01230d --- /dev/null +++ b/src/test/java/com/yam/app/account/domain/UpdateAccountProcessorTest.java @@ -0,0 +1,26 @@ +package com.yam.app.account.domain; + +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("계정 정보 수정 도메인 서비스") +class UpdateAccountProcessorTest { + + @Test + @DisplayName("계정을 가져오는데 실패하여 예외를 발생한다.") + void should_update_fail_find_by_email_error() { + // Arrange + final var fakeObject = new FakeAccountRepository(); + final var accountRepository = fakeObject; + final var accountReader = fakeObject; + final var passwordEncrypter = new PasswordEncrypterStub(); + var sut = new UpdateAccountProcessor(accountReader, + accountRepository, passwordEncrypter); + + // Act & Assert + assertThatExceptionOfType(AccountNotFoundException.class) + .isThrownBy(() -> sut.update("wrong@gmail.com", "password!")); + } +} diff --git a/src/test/java/com/yam/app/account/integration/AccountIntegrationTests.java b/src/test/java/com/yam/app/account/integration/AccountIntegrationTests.java index 373f95c..d4cc2e2 100644 --- a/src/test/java/com/yam/app/account/integration/AccountIntegrationTests.java +++ b/src/test/java/com/yam/app/account/integration/AccountIntegrationTests.java @@ -5,7 +5,9 @@ import static com.yam.app.account.presentation.AccountApiUri.FIND_INFO; import static com.yam.app.account.presentation.AccountApiUri.LOGIN; import static com.yam.app.account.presentation.AccountApiUri.LOGOUT; import static com.yam.app.account.presentation.AccountApiUri.REGISTER; +import static com.yam.app.account.presentation.AccountApiUri.UPDATE; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -16,6 +18,7 @@ import static org.springframework.test.web.servlet.setup.SharedHttpSessionConfig import com.fasterxml.jackson.databind.ObjectMapper; import com.yam.app.account.presentation.LoginAccountCommand; import com.yam.app.account.presentation.RegisterAccountCommand; +import com.yam.app.account.presentation.UpdateAccountCommand; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -74,7 +77,8 @@ final class AccountIntegrationTests { } @Test - @DisplayName("이메일 인증에 적절한 토큰과 이메일 정보가 입력되고, 이메일 인증 상태가 성공적으로 압데이트 된다.") + @DisplayName("유효한 토큰과 이메일 정보를 통해 이메일 인증을 처리하고," + + "사용자의 기본 회원 정보를 기입하는 시나리오 테스트.") void email_and_token_verify_request_in_correctly() throws Exception { // Act final var actions = mockMvc.perform(get(EMAIL_CONFIRM) @@ -154,4 +158,36 @@ final class AccountIntegrationTests { }); } + @Test + @DisplayName("인증된 사용자가 자신의 정보를 수정하는 시나리오 테스트.") + void should_authentication_user_info_update_success_scenarios() throws Exception { + // Arrange + var loginCommand = new LoginAccountCommand(); + loginCommand.setEmail("rebwon@gmail.com"); + loginCommand.setPassword("password!"); + var updateCommand = new UpdateAccountCommand(); + updateCommand.setNickname("jiwonKim"); + updateCommand.setPassword("password!2"); + updateCommand.setImage("jiwon.png"); + + // Act & Assert + mockMvc.perform(post(LOGIN) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(loginCommand)) + ) + .andExpect(status().isOk()) + .andDo( + result -> { + final var actions = mockMvc.perform(patch(UPDATE) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(updateCommand)) + ); + + actions + .andExpect(status().isOk()); + } + ); + } }