Implements Update Account UseCase

- Implements update account usecase
- Add account, member domain event
This commit is contained in:
Rebwon
2021-09-30 15:44:32 +09:00
committed by MaengSol
parent fd5a1b53bc
commit cb78015db1
19 changed files with 211 additions and 11 deletions

View File

@@ -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()));
}
}

View File

@@ -55,4 +55,8 @@ public final class Account {
public void addMember(Long memberId) {
this.memberId = memberId;
}
public void changePassword(String password) {
this.password = password;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -73,7 +73,7 @@ public final class AccountCommandApi {
public ResponseEntity<Void> update(
@RequestBody @Valid UpdateAccountCommand command,
@AuthenticationPrincipal Authentication authentication) {
accountFacade.update(command, authentication);
accountFacade.update(authentication, command);
return ResponseEntity.ok().build();
}
}

View File

@@ -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;
}

View File

@@ -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()));
}
}

View File

@@ -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;
}
}

View File

@@ -5,4 +5,6 @@ import java.util.Optional;
public interface MemberReader {
Optional<Member> findByNickname(String nickname);
Optional<Member> findById(Long memberId);
}

View File

@@ -3,4 +3,6 @@ package com.yam.app.member.domain;
public interface MemberRepository {
void save(Member entity);
void update(Member entity);
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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<Member> 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));
}
}
}

View File

@@ -9,4 +9,11 @@
VALUES (#{nickname}, #{image})
</insert>
<update id="update" parameterType="com.yam.app.member.domain.Member">
UPDATE MEMBER
SET nickname = #{nickname},
image = #{image}
WHERE id = #{id}
</update>
</mapper>

View File

@@ -8,4 +8,9 @@
SELECT * FROM member WHERE nickname = #{nickname}
</select>
<select id="findById" parameterType="Long"
resultType="com.yam.app.member.domain.Member">
SELECT * FROM member WHERE id = #{memberId}
</select>
</mapper>

View File

@@ -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);

View File

@@ -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!"));
}
}

View File

@@ -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());
}
);
}
}