Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/#46 Certification 테이블 생성, 회사 인증 등록 API 구현 #53

Merged
merged 26 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8162eb4
refactor: User 내부 컬럼으로 관리했던 인증 정보 추출해서 Certification Table 생성
smartandhandsome Oct 25, 2023
7d72ae3
feat: CertificationRepository 구현
smartandhandsome Oct 25, 2023
123c43b
test: CertificationRepository 테스트
smartandhandsome Oct 25, 2023
c59ae59
test: Certification 픽스처 생성
smartandhandsome Oct 25, 2023
3c1e630
refactor: final 추가
smartandhandsome Oct 25, 2023
505a0e1
refactor: import 제거
smartandhandsome Oct 25, 2023
d07e2b6
refactor: 변경된 필드명으로 메서드 이름 변경
smartandhandsome Oct 25, 2023
efdcc23
refactor: table 분리로 인해 연관 없어진 메서드 제거
smartandhandsome Oct 25, 2023
58c74b1
refactor: table 분리로 인한 response 변경
smartandhandsome Oct 25, 2023
1a85025
refactor: 순환참조가 발생해서 Service와 Repository 사이 Repository 가공 Layer 생성
smartandhandsome Oct 25, 2023
c135bf8
refactor: PK와 FK(user_id) 동일하게 설정
smartandhandsome Oct 25, 2023
6409889
refactor: 닉네임 조회 -> 아이디 조회로 변경
smartandhandsome Oct 25, 2023
ff0caaf
refactor: 다른 도메인의 서비스 의존이 아닌 Repository 가공 클래스 의존으로 변경
smartandhandsome Oct 25, 2023
f80907c
feat: 회사 정보 등록 api 구현
smartandhandsome Oct 25, 2023
359b2fc
refactor: DTO 컨벤션에 맞게 변경
smartandhandsome Oct 25, 2023
0c88f42
feat: repository 가공 클래스 생성
smartandhandsome Oct 25, 2023
d95a464
refactor: class 명 수정
smartandhandsome Oct 25, 2023
1abc25d
refactor: 레포지토리 가공 테이블 필드 설정
smartandhandsome Oct 25, 2023
69c41df
refactor: Transactional(readOnly = true) 추가
smartandhandsome Oct 25, 2023
b059c93
refactor: 상수 삭제
smartandhandsome Oct 25, 2023
0d54dee
refactor: 불필요한 Transactional 삭제
smartandhandsome Oct 25, 2023
63860cb
Merge branch 'dev' into feat/#46
Oct 26, 2023
257ef54
fix: merge conflict 오류 수정
Oct 26, 2023
a4982e7
refactor: applyIfCertifiedUser 위치 command로 변경
smartandhandsome Oct 26, 2023
948910f
refactor: 사용하지 않는 메서드 삭제
smartandhandsome Oct 26, 2023
fd1ce0b
Merge branch 'feat/#46' of https://github.com/coffee-meet/backend int…
smartandhandsome Oct 26, 2023
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 @@ -25,38 +25,32 @@ public class CertificationController {
private final CertificationService certificationService;

@PostMapping("/users/business-card")
public ResponseEntity<Void> uploadBusinessCard(
@Login
AuthInfo authInfo,
@NotNull
@RequestPart("businessCard")
MultipartFile businessCard
public ResponseEntity<Void> registerCompanyInfo(
@Login AuthInfo authInfo,
@RequestPart("companyEmail") @NotNull String companyEmail,
@RequestPart("department") @NotNull String department,
@RequestPart("businessCard") @NotNull MultipartFile businessCardImage
) {
certificationService.uploadBusinessCard(authInfo.userId(),
FileUtils.convertMultipartFileToFile(businessCard));
certificationService.registerCertification(authInfo.userId(), companyEmail, department,
FileUtils.convertMultipartFileToFile(businessCardImage));
return ResponseEntity.ok().build();
}

@PostMapping("/users/company-mail")
public ResponseEntity<Void> sendVerificationCodeByEmail(
@Login
AuthInfo authInfo,
@Valid @RequestBody
EmailDto emailDto
@Login AuthInfo authInfo,
@Valid @RequestBody EmailDto.Request request
) {
certificationService.sendVerificationMail(authInfo.userId(), emailDto.companyEmail());
certificationService.sendVerificationMail(authInfo.userId(), request.companyEmail());
return ResponseEntity.ok().build();

}

@PostMapping("/users/company-mail/verification")
public ResponseEntity<Void> verifyEmail(
@Login
AuthInfo authInfo,
@Valid @RequestBody
VerificationCodeDto verificationCodeDto
@Login AuthInfo authInfo,
@Valid @RequestBody VerificationCodeDto.Request request
) {
certificationService.verifyEmail(authInfo.userId(), verificationCodeDto.verificationCode());
certificationService.compareCode(authInfo.userId(), request.verificationCode());
return ResponseEntity.ok().build();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package coffeemeet.server.certification.domain;

import coffeemeet.server.common.entity.AdvancedBaseEntity;
import coffeemeet.server.user.domain.User;
import jakarta.persistence.Column;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.MapsId;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;

@Entity
@Getter
@Table(name = "certifications")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Certification extends AdvancedBaseEntity {

@Id
private Long id;

@MapsId
@OneToOne(fetch = FetchType.LAZY)
private User user;

@Embedded
@Column(nullable = false)
private CompanyEmail companyEmail;

@Column(nullable = false)
private String businessCardUrl;

@Enumerated(EnumType.STRING)
private Department department;

@Column(nullable = false)
private boolean isCertificated;

@Builder
private Certification(
@NonNull CompanyEmail companyEmail, @NonNull String businessCardUrl,
@NonNull Department department, @NonNull User user) {
this.companyEmail = companyEmail;
this.businessCardUrl = businessCardUrl;
this.department = department;
this.isCertificated = false;
this.user = user;
}

}
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
package coffeemeet.server.user.domain;
package coffeemeet.server.certification.domain;

import coffeemeet.server.common.util.Patterns;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Embeddable
@NoArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class CompanyEmail {

private String companyEmail;
@Column(name = "company_email")
private String value;

public CompanyEmail(String companyEmail) {
validateCompanyEmail(companyEmail);
this.companyEmail = companyEmail;
public CompanyEmail(String value) {
validateCompanyEmail(value);
this.value = value;
}

private void validateCompanyEmail(String companyEmail) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package coffeemeet.server.certification.domain;

public enum Department {
IT, MANAGEMENT, SALES, DISTRIBUTION, DESIGN, MANUFACTURING, RESEARCH_AND_DEVELOPMENT, MARKETING, PLANNING
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package coffeemeet.server.certification.domain;

import coffeemeet.server.user.domain.CompanyEmail;
import java.time.LocalDateTime;
import lombok.Getter;
import org.springframework.data.annotation.Id;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull;

public record EmailDto(
@Email @NotNull
String companyEmail
) {
public sealed interface EmailDto permits EmailDto.Request {

record Request(
@Email @NotNull
String companyEmail
) implements EmailDto {

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@
import jakarta.validation.constraints.NotNull;
import org.hibernate.validator.constraints.Length;

public record VerificationCodeDto(
@NotNull @Length(min = 6, max = 6)
String verificationCode
) {
public sealed interface VerificationCodeDto permits VerificationCodeDto.Request {

record Request(
@NotNull @Length(min = 6, max = 6)
String verificationCode
) implements VerificationCodeDto {

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package coffeemeet.server.certification.repository;

import coffeemeet.server.certification.domain.Certification;
import coffeemeet.server.certification.domain.CompanyEmail;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

public interface CertificationRepository extends JpaRepository<Certification, Long> {

Optional<Certification> findByUserId(Long userId);

boolean existsByCompanyEmail(CompanyEmail companyEmail);

boolean existsByUserId(Long userId);

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
import coffeemeet.server.certification.domain.EmailVerification;
import org.springframework.data.repository.CrudRepository;

public interface VerificationVoRepository extends CrudRepository<EmailVerification, Long> {
public interface EmailVerificationRepository extends CrudRepository<EmailVerification, Long> {

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

import static coffeemeet.server.common.media.S3MediaService.KeyType.BUSINESS_CARD;

import coffeemeet.server.certification.domain.EmailVerification;
import coffeemeet.server.certification.repository.VerificationVoRepository;
import coffeemeet.server.certification.domain.CompanyEmail;
import coffeemeet.server.certification.domain.Department;
import coffeemeet.server.certification.service.cq.CertificationCommand;
import coffeemeet.server.certification.service.cq.CertificationQuery;
import coffeemeet.server.certification.service.cq.EmailVerificationCommand;
import coffeemeet.server.certification.service.cq.EmailVerificationQuery;
import coffeemeet.server.common.media.EmailService;
import coffeemeet.server.common.media.S3MediaService;
import coffeemeet.server.common.util.FileUtils;
import coffeemeet.server.user.domain.CompanyEmail;
import coffeemeet.server.user.domain.User;
import coffeemeet.server.user.service.UserService;
import java.io.File;
import java.util.random.RandomGenerator;
Expand All @@ -18,47 +22,57 @@
@RequiredArgsConstructor
public class CertificationService {

public static final String VERIFICATION_CODE_NOT_FOUND = "인증코드 기간이 만료되었거나 해당 유저가 인증 번호를 요청한 기록이 없습니다.";
public static final String WRONG_VERIFICATION_CODE = "잘못된 인증코드입니다.";
private static final String WRONG_VERIFICATION_CODE = "잘못된 인증코드입니다.";
private static final RandomGenerator RANDOM_GENERATOR = RandomGenerator.getDefault();

private final S3MediaService s3MediaService;
private final UserService userService;
private final EmailService emailService;
private final VerificationVoRepository verificationVoRepository;
private final UserService userService;
private final CertificationCommand certificationCommand;
private final CertificationQuery certificationQuery;
private final EmailVerificationCommand emailVerificationCommand;
private final EmailVerificationQuery emailVerificationQuery;

public void uploadBusinessCard(long userId, File file) {
public void registerCertification(long userId, String email, String departmentName,
File businessCardImage) {
String key = s3MediaService.generateKey(BUSINESS_CARD);
s3MediaService.upload(key, file);
userService.updateBusinessCardUrl(userId, s3MediaService.getUrl(key));
uploadBusinessCard(userId, key, businessCardImage);

FileUtils.delete(file);
CompanyEmail companyEmail = new CompanyEmail(email);
String businessCardUrl = s3MediaService.getUrl(key);
Department department = Department.valueOf(departmentName);
User user = userService.getUserById(userId);
certificationCommand.newCertification(companyEmail, businessCardUrl, department, user);
}

private void uploadBusinessCard(long userId, String key, File businessCardUrl) {
certificationCommand.applyIfCertifiedUser(userId, certification -> {
String oldKey = s3MediaService.extractKey(certification.getBusinessCardUrl(), BUSINESS_CARD);
s3MediaService.delete(oldKey);
});

s3MediaService.upload(key, businessCardUrl);
FileUtils.delete(businessCardUrl);
}

public void sendVerificationMail(Long userId, String email) {
CompanyEmail companyEmail = new CompanyEmail(email);
userService.validateDuplicatedCompanyEmail(companyEmail);
certificationQuery.checkDuplicatedCompanyEmail(companyEmail);

String verificationCode = generateVerificationCode();
emailService.sendVerificationCode(companyEmail, verificationCode);
verificationVoRepository.save(
new EmailVerification(userId, companyEmail, verificationCode));
}

private String generateVerificationCode() {
return String.format("%06d", RANDOM_GENERATOR.nextInt(1000000));
emailVerificationCommand.newEmailVerification(userId, companyEmail, verificationCode);
}

public void verifyEmail(Long userId, String verificationCode) {
EmailVerification emailVerification = verificationVoRepository.findById(userId)
.orElseThrow(
() -> new IllegalArgumentException(VERIFICATION_CODE_NOT_FOUND));

if (!emailVerification.getCode().equals(verificationCode)) {
public void compareCode(Long userId, String verificationCode) {
String correctCode = emailVerificationQuery.getCodeById(userId);
if (!correctCode.equals(verificationCode)) {
throw new IllegalArgumentException(WRONG_VERIFICATION_CODE);
}
}

userService.updateCompanyEmail(userId, emailVerification.getCompanyEmail());
private String generateVerificationCode() {
return String.format("%06d", RANDOM_GENERATOR.nextInt(1000000));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package coffeemeet.server.certification.service.cq;

import coffeemeet.server.certification.domain.Certification;
import coffeemeet.server.certification.domain.CompanyEmail;
import coffeemeet.server.certification.domain.Department;
import coffeemeet.server.certification.repository.CertificationRepository;
import coffeemeet.server.user.domain.User;
import java.util.function.Consumer;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Transactional
@RequiredArgsConstructor
@Service
public class CertificationCommand {

private final CertificationRepository certificationRepository;

public void newCertification(CompanyEmail companyEmail, String businessCardUrl,
Department department, User user) {
certificationRepository.save(
Certification.builder()
.companyEmail(companyEmail)
.businessCardUrl(businessCardUrl)
.department(department)
.user(user)
.build()
);
}

@Transactional(readOnly = true)
public void applyIfCertifiedUser(Long userId, Consumer<? super Certification> consumer) {
certificationRepository.findByUserId(userId).ifPresent(consumer);
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Command로 보아야 될지 Query로 보아야 할 지 잘 모르겠습니다.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 Query로 보는게 맞다고 생각됩니다


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package coffeemeet.server.certification.service.cq;

import coffeemeet.server.certification.domain.Certification;
import coffeemeet.server.certification.domain.CompanyEmail;
import coffeemeet.server.certification.repository.CertificationRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Transactional(readOnly = true)
@RequiredArgsConstructor
@Service
public class CertificationQuery {

public static final String CERTIFICATION_NOT_FOUND = "해당 사용자의 인증정보를 찾을 수 없습니다.";
private static final String EXISTED_COMPANY_EMAIL = "이미 사용 중인 회사 이메일입니다.";

private final CertificationRepository certificationRepository;

public Certification getCertificationByUserId(Long userId) {
return certificationRepository.findByUserId(userId)
.orElseThrow(() -> new IllegalArgumentException(CERTIFICATION_NOT_FOUND));
}

public void checkDuplicatedCompanyEmail(CompanyEmail companyEmail) {
if (certificationRepository.existsByCompanyEmail(companyEmail)) {
throw new IllegalArgumentException(EXISTED_COMPANY_EMAIL);
}
}


}
Loading