Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;

import in.koreatech.koin.domain.owner.dto.OwnerPasswordResetVerifyRequest;
import in.koreatech.koin.domain.owner.dto.OwnerPasswordUpdateRequest;
import in.koreatech.koin.domain.owner.dto.OwnerRegisterRequest;
import in.koreatech.koin.domain.owner.dto.OwnerResponse;
import in.koreatech.koin.domain.owner.dto.OwnerVerificationRequest;
import in.koreatech.koin.domain.owner.dto.OwnerSendEmailRequest;
import in.koreatech.koin.domain.owner.dto.OwnerVerifyRequest;
import in.koreatech.koin.domain.owner.dto.OwnerVerifyResponse;
import in.koreatech.koin.domain.owner.dto.VerifyEmailRequest;
import in.koreatech.koin.global.auth.Auth;
Expand Down Expand Up @@ -80,6 +84,46 @@ ResponseEntity<Void> register(
@SecurityRequirement(name = "Jwt Authentication")
@PostMapping("/owners/verification/code")
ResponseEntity<OwnerVerifyResponse> codeVerification(
@Valid @RequestBody OwnerVerificationRequest request
@Valid @RequestBody OwnerVerifyRequest request
);

@ApiResponses(
value = {
@ApiResponse(responseCode = "200"),
@ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))),
}
)
@Operation(summary = "사장님 비밀번호 변경 인증번호 이메일 발송")
@SecurityRequirement(name = "Jwt Authentication")
@PostMapping("/owners/password/reset/verification")
ResponseEntity<Void> sendResetPasswordEmail(
@Valid @RequestBody OwnerSendEmailRequest request
) ;

@ApiResponses(
value = {
@ApiResponse(responseCode = "200"),
@ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "409", content = @Content(schema = @Schema(hidden = true))),
}
)
@Operation(summary = "사장님 비밀번호 변경 인증번호 인증")
@SecurityRequirement(name = "Jwt Authentication")
@PostMapping("/owners/password/reset/send")
ResponseEntity<Void> sendVerifyCode(
@Valid @RequestBody OwnerPasswordResetVerifyRequest request
) ;

@ApiResponses(
value = {
@ApiResponse(responseCode = "200"),
@ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))),
}
)
@Operation(summary = "사장님 비밀번호 변경")
@SecurityRequirement(name = "Jwt Authentication")
@PutMapping("/owners/password/reset")
ResponseEntity<Void> updatePassword(
@Valid @RequestBody OwnerPasswordUpdateRequest request
) ;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import in.koreatech.koin.domain.owner.dto.OwnerPasswordResetVerifyRequest;
import in.koreatech.koin.domain.owner.dto.OwnerPasswordUpdateRequest;
import in.koreatech.koin.domain.owner.dto.OwnerRegisterRequest;
import in.koreatech.koin.domain.owner.dto.OwnerResponse;
import in.koreatech.koin.domain.owner.dto.OwnerVerificationRequest;
import in.koreatech.koin.domain.owner.dto.OwnerSendEmailRequest;
import in.koreatech.koin.domain.owner.dto.OwnerVerifyRequest;
import in.koreatech.koin.domain.owner.dto.OwnerVerifyResponse;
import in.koreatech.koin.domain.owner.dto.VerifyEmailRequest;
import in.koreatech.koin.domain.owner.service.OwnerService;
Expand Down Expand Up @@ -50,9 +54,33 @@ public ResponseEntity<Void> register(

@PostMapping("/owners/verification/code")
public ResponseEntity<OwnerVerifyResponse> codeVerification(
@Valid @RequestBody OwnerVerificationRequest request
@Valid @RequestBody OwnerVerifyRequest request
) {
OwnerVerifyResponse response = ownerService.verifyCode(request);
return ResponseEntity.ok().body(response);
}

@PostMapping("/owners/password/reset/verification")
public ResponseEntity<Void> sendResetPasswordEmail(
@Valid @RequestBody OwnerSendEmailRequest request
) {
ownerService.sendResetPasswordEmail(request);
return ResponseEntity.ok().build();
}

@PostMapping("/owners/password/reset/send")
public ResponseEntity<Void> sendVerifyCode(
@Valid @RequestBody OwnerPasswordResetVerifyRequest request
) {
ownerService.verifyResetPasswordCode(request);
return ResponseEntity.ok().build();
}

@PutMapping("/owners/password/reset")
public ResponseEntity<Void> updatePassword(
@Valid @RequestBody OwnerPasswordUpdateRequest request
) {
ownerService.updatePassword(request);
return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package in.koreatech.koin.domain.owner.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.NotBlank;

@JsonNaming(SnakeCaseStrategy.class)
public record OwnerPasswordResetVerifyRequest(
@JsonProperty(value = "address")
@Schema(description = "사장님 이메일", example = "junho5336@gmail.com")
String email,

@NotBlank(message = "인증 코드는 필수입니다.")
@Digits(integer = 6, fraction = 0, message = "인증 코드는 6자리 정수여야 합니다.")
@Schema(description = "인증 코드", example = "123456")
String certificationCode
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package in.koreatech.koin.domain.owner.dto;

import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;

import com.fasterxml.jackson.annotation.JsonProperty;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull;

public record OwnerPasswordUpdateRequest(
@NotNull(message = "비밀번호는 비워둘 수 없습니다.")
@Schema(description = "비밀번호", requiredMode = REQUIRED, example = "a0240120305812krlakdsflsa;1235")
String password,

@Email(message = "이메일 형식이 올바르지 않습니다. ${validatedValue}")
@NotNull(message = "이메일은 필수입니다.")
@Schema(description = "이메일 주소", requiredMode = REQUIRED, example = "asdf@gmail.com")
@JsonProperty(value = "address")
String email
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package in.koreatech.koin.domain.owner.dto;

import com.fasterxml.jackson.annotation.JsonProperty;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;

public record OwnerSendEmailRequest(
@Email(message = "이메일 형식이 올바르지 않습니다. ${validatedValue}")
@NotBlank(message = "이메일은 필수입니다.")
@JsonProperty(value = "address")
String email
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package in.koreatech.koin.domain.owner.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.NotBlank;

@JsonNaming(SnakeCaseStrategy.class)
public record OwnerVerifyRequest(
@JsonProperty(value = "address")
@Schema(description = "사장님 이메일", example = "junho5336@gmail.com")
String email,

@NotBlank(message = "인증 코드는 필수입니다.")
@Digits(integer = 6, fraction = 0, message = "인증 코드는 6자리 정수여야 합니다.")
@Schema(description = "인증 코드", example = "123456")
String certificationCode
) {

}
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
package in.koreatech.koin.domain.owner.model;

import static lombok.AccessLevel.PRIVATE;

import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor(access = PRIVATE)
@RedisHash(value = "owner@", timeToLive = 60 * 60 * 2)
public class OwnerInVerification {

@Id
private String email;

private String certificationCode;
private boolean isAuthed = false;

public OwnerInVerification(String email, String certificationCode) {
this.email = email;
this.certificationCode = certificationCode;
}

public void verify() {
this.isAuthed = true;
}

public static OwnerInVerification of(String email, String certificationCode) {
return new OwnerInVerification(email, certificationCode);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ default Owner getById(Long ownerId) {
return findById(ownerId).orElseThrow(() -> OwnerNotFoundException.withDetail("ownerId: " + ownerId));
}

Optional<Owner> findByUserId(Long userId);

default Owner getByUserId(Long userId) {
return findByUserId(userId).orElseThrow(() -> OwnerNotFoundException.withDetail("userId: " + userId));
}

Owner save(Owner owner);

Optional<Owner> findByCompanyRegistrationNumber(String companyRegistrationNumber);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package in.koreatech.koin.domain.owner.service;

import static in.koreatech.koin.global.domain.email.model.MailForm.OWNER_REGISTRATION_MAIL_FORM;

import java.time.Clock;
import java.util.List;
import java.util.Objects;

Expand All @@ -10,9 +9,12 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import in.koreatech.koin.domain.owner.dto.OwnerPasswordResetVerifyRequest;
import in.koreatech.koin.domain.owner.dto.OwnerPasswordUpdateRequest;
import in.koreatech.koin.domain.owner.dto.OwnerRegisterRequest;
import in.koreatech.koin.domain.owner.dto.OwnerResponse;
import in.koreatech.koin.domain.owner.dto.OwnerVerificationRequest;
import in.koreatech.koin.domain.owner.dto.OwnerSendEmailRequest;
import in.koreatech.koin.domain.owner.dto.OwnerVerifyRequest;
import in.koreatech.koin.domain.owner.dto.OwnerVerifyResponse;
import in.koreatech.koin.domain.owner.dto.VerifyEmailRequest;
import in.koreatech.koin.domain.owner.exception.DuplicationCompanyNumberException;
Expand All @@ -26,12 +28,15 @@
import in.koreatech.koin.domain.owner.repository.OwnerShopRedisRepository;
import in.koreatech.koin.domain.shop.model.Shop;
import in.koreatech.koin.domain.shop.repository.ShopRepository;
import in.koreatech.koin.domain.user.model.User;
import in.koreatech.koin.domain.user.repository.UserRepository;
import in.koreatech.koin.global.auth.JwtProvider;
import in.koreatech.koin.global.domain.email.exception.DuplicationEmailException;
import in.koreatech.koin.global.domain.email.model.CertificationCode;
import in.koreatech.koin.global.domain.email.model.EmailAddress;
import in.koreatech.koin.global.domain.email.form.OwnerPasswordChangeData;
import in.koreatech.koin.global.domain.email.form.OwnerRegistrationData;
import in.koreatech.koin.global.domain.email.service.MailService;
import in.koreatech.koin.global.domain.random.model.CertificateNumberGenerator;
import in.koreatech.koin.global.exception.DuplicationException;
import lombok.RequiredArgsConstructor;

@Service
Expand All @@ -40,6 +45,7 @@
public class OwnerService {

private final JwtProvider jwtProvider;
private final Clock clock;
private final MailService mailService;
private final UserRepository userRepository;
private final ShopRepository shopRepository;
Expand All @@ -51,24 +57,19 @@ public class OwnerService {

@Transactional
public void requestSignUpEmailVerification(VerifyEmailRequest request) {
EmailAddress email = EmailAddress.from(request.email());
validateEmailExist(email);
CertificationCode certificationCode = mailService.sendMail(email, OWNER_REGISTRATION_MAIL_FORM);
userRepository.findByEmail(request.email()).ifPresent(user -> {
throw DuplicationEmailException.withDetail("email: " + request.email());
});
String certificationCode = CertificateNumberGenerator.generate();
mailService.sendMail(request.email(), new OwnerRegistrationData(certificationCode));
OwnerInVerification ownerInVerification = OwnerInVerification.of(
email.email(),
certificationCode.getValue()
request.email(),
certificationCode
);
ownerInVerificationRedisRepository.save(ownerInVerification);
eventPublisher.publishEvent(new OwnerEmailRequestEvent(ownerInVerification.getEmail()));
}

private void validateEmailExist(EmailAddress email) {
userRepository.findByEmail(email.email())
.ifPresent(user -> {
throw DuplicationEmailException.withDetail("email: " + email.email());
});
}

public OwnerResponse getOwner(Long ownerId) {
Owner foundOwner = ownerRepository.getById(ownerId);
List<Shop> shops = shopRepository.findAllByOwnerId(ownerId);
Expand Down Expand Up @@ -96,13 +97,46 @@ public void register(OwnerRegisterRequest request) {
eventPublisher.publishEvent(new OwnerRegisterEvent(saved));
}

public OwnerVerifyResponse verifyCode(OwnerVerificationRequest request) {
public OwnerVerifyResponse verifyCode(OwnerVerifyRequest request) {
var verify = ownerInVerificationRedisRepository.getByEmail(request.email());
if (!Objects.equals(verify.getCertificationCode(), request.certificationCode())) {
throw new IllegalArgumentException("인증번호가 일치하지 않습니다.");
}
ownerInVerificationRedisRepository.deleteByEmail(request.email());
ownerInVerificationRedisRepository.deleteById(request.email());
String token = jwtProvider.createTemporaryToken();
return new OwnerVerifyResponse(token);
}

@Transactional
public void sendResetPasswordEmail(OwnerSendEmailRequest request) {
String certificationCode = CertificateNumberGenerator.generate();
var verification = OwnerInVerification.of(request.email(), certificationCode);
ownerInVerificationRedisRepository.save(verification);
mailService.sendMail(request.email(), new OwnerPasswordChangeData(request.email(), certificationCode, clock));
}

@Transactional
public void verifyResetPasswordCode(OwnerPasswordResetVerifyRequest request) {
var verification = ownerInVerificationRedisRepository.getByEmail(request.email());
if (!Objects.equals(verification.getCertificationCode(), request.certificationCode())) {
throw new IllegalArgumentException("인증번호가 일치하지 않습니다.");
}
if (verification.isAuthed()) {
throw new DuplicationException("이미 인증이 완료되었습니다.");
}
verification.verify();
ownerInVerificationRedisRepository.save(verification);
}

@Transactional
public void updatePassword(OwnerPasswordUpdateRequest request) {
var verification = ownerInVerificationRedisRepository.getByEmail(request.email());
if (!verification.isAuthed()) {
throw new IllegalArgumentException("인증이 완료되지 않았습니다.");
}
User user = userRepository.getByEmail(request.email());
user.updatePassword(passwordEncoder, request.password());
userRepository.save(user);
ownerInVerificationRedisRepository.deleteById(verification.getEmail());
}
}
Loading