diff --git a/build.gradle b/build.gradle index ae19a442..98b67c8f 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,6 @@ plugins { id 'java' id 'org.springframework.boot' version '3.3.5' id 'io.spring.dependency-management' version '1.1.6' - id 'com.ewerk.gradle.plugins.querydsl' version '1.0.10' } group = 'com.example' @@ -28,7 +27,6 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-data-mongodb' - implementation 'org.springframework.boot:spring-boot-starter-websocket' compileOnly 'org.projectlombok:lombok' // runtimeOnly 'com.mysql:mysql-connector-j' @@ -54,6 +52,7 @@ dependencies { // SMTP implementation 'org.springframework.boot:spring-boot-starter-mail' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" // JWT @@ -88,6 +87,15 @@ dependencies { //객체 간 매핑 처리 implementation 'org.modelmapper:modelmapper:3.1.0' + + // OAUTH2 + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + + // MOCKITO + testImplementation "org.mockito:mockito-core:3.+" + + //WIREMOCK (외부 의존성 테스트용) + implementation 'org.wiremock.integrations:wiremock-spring-boot:3.3.0' } tasks.named('test') { @@ -114,4 +122,4 @@ sourceSets { srcDirs = ['src/test/java'] } } -} +} \ No newline at end of file diff --git a/gradlew b/gradlew index f5feea6d..b26d4110 100755 --- a/gradlew +++ b/gradlew @@ -249,4 +249,4 @@ eval "set -- $( tr '\n' ' ' )" '"$@"' -exec "$JAVACMD" "$@" +exec "$JAVACMD" "$@" \ No newline at end of file diff --git a/src/main/generated/com/example/api/account/entity/QLocation.java b/src/main/generated/com/example/api/account/entity/QLocation.java new file mode 100644 index 00000000..c15f6d8b --- /dev/null +++ b/src/main/generated/com/example/api/account/entity/QLocation.java @@ -0,0 +1,43 @@ +package com.example.api.account.entity; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QLocation is a Querydsl query type for Location + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QLocation extends EntityPathBase { + + private static final long serialVersionUID = -590127238L; + + public static final QLocation location = new QLocation("location"); + + public final StringPath address = createString("address"); + + public final StringPath detailAddress = createString("detailAddress"); + + public final NumberPath id = createNumber("id", Long.class); + + public final StringPath zipcode = createString("zipcode"); + + public QLocation(String variable) { + super(Location.class, forVariable(variable)); + } + + public QLocation(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QLocation(PathMetadata metadata) { + super(Location.class, metadata); + } + +} + diff --git a/src/main/generated/com/example/api/domain/QBusiness.java b/src/main/generated/com/example/api/domain/QBusiness.java index 8cc945d1..e3b8979c 100644 --- a/src/main/generated/com/example/api/domain/QBusiness.java +++ b/src/main/generated/com/example/api/domain/QBusiness.java @@ -35,7 +35,7 @@ public class QBusiness extends EntityPathBase { public final QAccount employer; - public final StringPath location = createString("location"); + public final com.example.api.account.entity.QLocation location; public final DatePath openDate = createDate("openDate", java.time.LocalDate.class); @@ -65,6 +65,7 @@ public QBusiness(PathMetadata metadata, PathInits inits) { public QBusiness(Class type, PathMetadata metadata, PathInits inits) { super(type, metadata, inits); this.employer = inits.isInitialized("employer") ? new QAccount(forProperty("employer")) : null; + this.location = inits.isInitialized("location") ? new com.example.api.account.entity.QLocation(forProperty("location")) : null; } } diff --git a/src/main/java/com/example/api/account/controller/AccountController.java b/src/main/java/com/example/api/account/controller/AccountController.java index 4604cf6f..2478fefc 100644 --- a/src/main/java/com/example/api/account/controller/AccountController.java +++ b/src/main/java/com/example/api/account/controller/AccountController.java @@ -1,10 +1,8 @@ package com.example.api.account.controller; -import com.example.api.account.dto.EmailCodeRequest; -import com.example.api.account.dto.EmailRequest; +import com.example.api.account.dto.*; import com.example.api.account.entity.Code; import com.example.api.account.service.AccountService; -import com.example.api.account.dto.SignUpRequest; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -12,6 +10,7 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; + @RestController @RequestMapping("/api/v1/account") @RequiredArgsConstructor @@ -32,9 +31,15 @@ public ResponseEntity verifyEmail(@Valid @RequestBody final EmailCodeReq return ResponseEntity.ok(successMessage); } - @PostMapping("/sign-up") - public ResponseEntity signUp(@Valid @RequestBody final SignUpRequest request) { - String successMessage = signUpService.signUp(request); + @PostMapping("/sign-up/employee") + public ResponseEntity signUpEmployee(@Valid @RequestBody final SignUpEmployeeRequest request) { + String successMessage = signUpService.signUpEmployee(request); + return ResponseEntity.status(HttpStatus.CREATED).body(successMessage); + } + + @PostMapping("/sign-up/employer") + public ResponseEntity signUpEmployer(@Valid @RequestBody final SignUpEmployerRequest request) { + String successMessage = signUpService.signUpEmployer(request); return ResponseEntity.status(HttpStatus.CREATED).body(successMessage); } @@ -50,4 +55,16 @@ public ResponseEntity deleteAccount( accountService.deleteAccount(memberId); return ResponseEntity.ok("delete account"); } + + @PostMapping("/validation/business-number") + public ResponseEntity verifyBusinessNumber(@Valid @RequestBody final BusinessNumberRequest request) { + String successMessage = signUpService.verifyBusinessNumber(request); + return ResponseEntity.ok(successMessage); + } + + @PostMapping("/sign-up/employer") + public ResponseEntity signUpEmployer(@Valid @RequestBody final SignUpEmployerRequest request) { + String successMessage = signUpService.signUpEmployer(request); + return ResponseEntity.status(HttpStatus.CREATED).body(successMessage); + } } \ No newline at end of file diff --git a/src/main/java/com/example/api/account/dto/BusinessNumberRequest.java b/src/main/java/com/example/api/account/dto/BusinessNumberRequest.java new file mode 100644 index 00000000..06744c94 --- /dev/null +++ b/src/main/java/com/example/api/account/dto/BusinessNumberRequest.java @@ -0,0 +1,14 @@ +package com.example.api.account.dto; + +import jakarta.validation.constraints.NotBlank; + +public record BusinessNumberRequest( + @NotBlank + String businessRegistrationNumber, + @NotBlank + String businessName, + @NotBlank + String representationName, + @NotBlank + String businessOpenDate) { +} diff --git a/src/main/java/com/example/api/account/dto/BusinessNumberResponse.java b/src/main/java/com/example/api/account/dto/BusinessNumberResponse.java new file mode 100644 index 00000000..a312d946 --- /dev/null +++ b/src/main/java/com/example/api/account/dto/BusinessNumberResponse.java @@ -0,0 +1,46 @@ +package com.example.api.account.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + + +public record BusinessNumberResponse( + @JsonProperty("request_cnt") int requestCount, + @JsonProperty("valid_cnt") int validCount, + @JsonProperty("status_code") String statusCode, + List data +) { + private record Data( + @JsonProperty("b_no") String businessNumber, + String valid, + @JsonProperty("request_param") RequestParam requestParam, + Status status + ) { + } + private record RequestParam( + @JsonProperty("b_no") String businessNumber, + @JsonProperty("start_dt") String startDate, + @JsonProperty("p_nm") String name, + @JsonProperty("b_nm") String businessName + ) { + } + private record Status( + @JsonProperty("b_no") String businessNumber, + @JsonProperty("b_stt") String businessStatus, + @JsonProperty("b_stt_cd") String businessStatusCode, + @JsonProperty("tax_type") String taxType, + @JsonProperty("tax_type_cd") String taxTypeCode, + @JsonProperty("end_dt") String endDate, + @JsonProperty("utcc_yn") String utccYn, + @JsonProperty("tax_type_change_dt") String taxTypeChangeDate, + @JsonProperty("invoice_apply_dt") String invoiceApplyDate, + @JsonProperty("rbf_tax_type") String rbfTaxType, + @JsonProperty("rbf_tax_type_cd") String rbfTaxTypeCode + ) { + } + + public String getValid(){ + return data.get(0).valid; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/api/account/dto/SignUpRequest.java b/src/main/java/com/example/api/account/dto/SignUpEmployeeRequest.java similarity index 89% rename from src/main/java/com/example/api/account/dto/SignUpRequest.java rename to src/main/java/com/example/api/account/dto/SignUpEmployeeRequest.java index bbd37f5d..3ac7a843 100644 --- a/src/main/java/com/example/api/account/dto/SignUpRequest.java +++ b/src/main/java/com/example/api/account/dto/SignUpEmployeeRequest.java @@ -3,11 +3,10 @@ import com.example.api.account.entity.Nationality; import com.example.api.account.entity.UserRole; import com.example.api.global.config.resolver.ValidEmail; -import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -public record SignUpRequest( +public record SignUpEmployeeRequest( @NotBlank String loginId, @NotBlank diff --git a/src/main/java/com/example/api/account/dto/SignUpEmployerRequest.java b/src/main/java/com/example/api/account/dto/SignUpEmployerRequest.java new file mode 100644 index 00000000..1c9fda8e --- /dev/null +++ b/src/main/java/com/example/api/account/dto/SignUpEmployerRequest.java @@ -0,0 +1,34 @@ +package com.example.api.account.dto; + +import com.example.api.account.entity.Location; +import com.example.api.account.entity.Nationality; +import com.example.api.account.entity.UserRole; +import com.example.api.global.config.resolver.ValidEmail; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +public record SignUpEmployerRequest( + @NotBlank + String loginId, // 로그인 id + @NotBlank + String password, // 비밀번호 + @ValidEmail + String email, // 이메일 + @NotBlank + String businessRegistrationNumber, // 사업자 번호 + @NotBlank + String businessName, // 회사명 + @NotBlank + String representationName, // 대표명 + @NotBlank + String businessOpenDate, // 개업연월일 + @NotNull + Location location, + @NotNull + Nationality nationality, // 국적 + @NotNull + UserRole role, // 권한 + @NotBlank + String phoneNumber // 휴대폰 번호 +) { +} \ No newline at end of file diff --git a/src/main/java/com/example/api/account/entity/Location.java b/src/main/java/com/example/api/account/entity/Location.java new file mode 100644 index 00000000..813025fb --- /dev/null +++ b/src/main/java/com/example/api/account/entity/Location.java @@ -0,0 +1,28 @@ +package com.example.api.account.entity; + +import jakarta.persistence.*; +import lombok.Getter; + +@Entity +@Getter +public class Location { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "LOCATION_UNIQUE_ID") + private Long id; + @Column(name = "LOCATION_ZIPCODE") + private String zipcode; + @Column(name = "LOCATION_ADDRESS") + private String address; + @Column(name = "LOCATION_DETAIL_ADDRESS") + private String detailAddress; + + public Location() { + } + + public Location(String zipcode, String address, String detailAddress) { + this.zipcode = zipcode; + this.address = address; + this.detailAddress = detailAddress; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/api/account/repository/AccountRepository.java b/src/main/java/com/example/api/account/repository/AccountRepository.java index 04853654..37cbb752 100644 --- a/src/main/java/com/example/api/account/repository/AccountRepository.java +++ b/src/main/java/com/example/api/account/repository/AccountRepository.java @@ -1,7 +1,9 @@ package com.example.api.account.repository; import com.example.api.domain.Account; +import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import java.util.Optional; @@ -10,5 +12,8 @@ public interface AccountRepository extends JpaRepository { boolean existsByEmail(String email); + @EntityGraph(attributePaths = "roles") Optional findByEmail(String email); + + Optional findUserByLoginId(String loginId); } \ No newline at end of file diff --git a/src/main/java/com/example/api/account/repository/LocationRepository.java b/src/main/java/com/example/api/account/repository/LocationRepository.java new file mode 100644 index 00000000..80b9b8c3 --- /dev/null +++ b/src/main/java/com/example/api/account/repository/LocationRepository.java @@ -0,0 +1,9 @@ +package com.example.api.account.repository; + +import com.example.api.account.entity.Location; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface LocationRepository extends JpaRepository { +} diff --git a/src/main/java/com/example/api/account/service/AccountService.java b/src/main/java/com/example/api/account/service/AccountService.java index b16b8cc5..d92d4ce3 100644 --- a/src/main/java/com/example/api/account/service/AccountService.java +++ b/src/main/java/com/example/api/account/service/AccountService.java @@ -1,35 +1,54 @@ package com.example.api.account.service; -import com.example.api.account.dto.EmailCodeRequest; -import com.example.api.account.dto.EmailRequest; -import com.example.api.account.dto.LoginIdRequest; +import com.example.api.account.dto.*; import com.example.api.account.entity.Code; +import com.example.api.account.entity.Location; import com.example.api.account.entity.UserRole; import com.example.api.account.repository.AccountRepository; -import com.example.api.account.dto.SignUpRequest; import com.example.api.account.repository.CodeRepository; import com.example.api.account.entity.MailSender; +import com.example.api.account.repository.LocationRepository; +import com.example.api.business.BusinessRepository; import com.example.api.domain.Account; +import com.example.api.domain.Business; import com.example.api.exception.BusinessException; import com.example.api.exception.ErrorCode; - +import com.example.api.global.properties.VendorProperties; +import jakarta.transaction.Transactional; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; +import org.springframework.web.client.RestTemplate; -import java.util.Collection; -import java.util.List; -import java.util.Optional; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.*; @Service @RequiredArgsConstructor +@Slf4j public class AccountService { private final AccountRepository accountRepository; private final CodeRepository codeRepository; private final PasswordEncoder passwordEncoder; private final MailSender mailSender; + private final AccountRepository accountRepository; + private final BusinessRepository businessRepository; + private final RestTemplate restTemplate; + private final VendorProperties vendorProperties; + private final LocationRepository locationRepository; public Code sendEmail(@Validated final EmailRequest request) throws BusinessException { // 이미 가입된 이메일인지 검증 @@ -41,7 +60,6 @@ public Code sendEmail(@Validated final EmailRequest request) throws BusinessExce public String saveCode(@Validated final Code code){ try { codeRepository.save(code); - return "이메일 전송을 완료하였습니다."; } catch (Exception e){ throw new BusinessException(ErrorCode.FAIL_SAVE_CODE); @@ -62,14 +80,24 @@ public String verifyEmail(@Validated final EmailCodeRequest request) { } @Transactional - public String signUp(@Validated final SignUpRequest request) { + public String signUpEmployee(@Validated final SignUpEmployeeRequest request) { // 중복 로그인 ID 확인 validateDuplicateLoginId(new LoginIdRequest(request.loginId())); // 계정 저장 - saveAccount(request); + saveEmployeeAccount(request); + return "회원가입이 완료되었습니다"; + } + + @Transactional + public String signUpEmployer(@Valid final SignUpEmployerRequest request) { + // 중복 로그인 ID 확인 (사장) + validateDuplicateLoginId(new LoginIdRequest(request.loginId())); + // 계정 저장 + saveEmployerAccount(request); return "회원가입이 완료되었습니다"; } + private void saveEmployeeAccount(final SignUpEmployeeRequest request) { @Transactional(readOnly = true) public Account loadAccount(final Long requestMemberId) { return accountRepository.findById(requestMemberId) @@ -96,7 +124,31 @@ private void saveAccount(final SignUpRequest request) { roles, request.emailReceivable() ); + accountRepository.save(account); + } + private void saveEmployerAccount(final SignUpEmployerRequest request) { + Collection roles = List.of(request.role()); + Account account = new Account( + request.loginId(), + passwordEncoder.encode(request.password()), + request.email(), + request.phoneNumber(), + request.nationality(), + roles + ); + Account savedUser = accountRepository.save(account); + + Location savedLocation = locationRepository.save(request.location()); + Business business = new Business( + savedUser, + request.businessRegistrationNumber(), + request.businessName(), + request.representationName(), + request.businessOpenDate(), + savedLocation + ); + businessRepository.save(business); accountRepository.save(account); } @@ -111,4 +163,57 @@ private void validateDuplicateEmail(final EmailRequest emailRequest) { throw new BusinessException(ErrorCode.DUPLICATE_EMAIL); } } + + public Account loadAccount(final Long requestMemberId) { + return accountRepository.findById(requestMemberId) + .orElseThrow(() -> new BusinessException(ErrorCode.ACCOUNT_NOT_FOUND_EXCEPTION)); + } + + @Transactional + public String verifyBusinessNumber(@Validated final BusinessNumberRequest request){ + URI uri = createUrl(); + HttpEntity> requestEntity = getBusinessValidateApiRequestEntity(request); + + ResponseEntity response = restTemplate.exchange( + uri, + HttpMethod.POST, + requestEntity, + BusinessNumberResponse.class + ); + + log.info("response.getBody()={}", response.getBody()); + + String valid = Optional.ofNullable(response.getBody()) + .map(BusinessNumberResponse::getValid) + .orElseThrow(() -> new BusinessException(ErrorCode.INVALID_BUSINESS_NUMBER)); + + if (!"01".equals(valid)) { + throw new BusinessException(ErrorCode.INVALID_BUSINESS_NUMBER); + } + + return "유효한 사업자 등록 정보입니다."; + } + + @NotNull + private URI createUrl() { + try { + String encodedServiceKey = URLEncoder.encode(vendorProperties.getServiceKey(), StandardCharsets.UTF_8); + String url = vendorProperties.getBaseUrl() + "?serviceKey=" + encodedServiceKey; + return new URI(url); + } catch (Exception e) { + log.error(e.getMessage()); + // 프론트 서버 배포 후 수정, 회원가입 end point로 변경 + return URI.create("http://localhost:3000/"); + } + } + + @NotNull + private HttpEntity> getBusinessValidateApiRequestEntity(BusinessNumberRequest request) { + Map business = new HashMap<>(); + business.put("b_no", request.businessRegistrationNumber()); + business.put("start_dt", request.businessOpenDate()); + business.put("p_nm", request.representationName()); + business.put("b_nm", request.businessName()); + return new HttpEntity<>(Collections.singletonMap("businesses", Collections.singletonList(business))); + } } \ No newline at end of file diff --git a/src/main/java/com/example/api/auth/repository/AuthRepository.java b/src/main/java/com/example/api/auth/repository/AuthRepository.java deleted file mode 100644 index d7181ad9..00000000 --- a/src/main/java/com/example/api/auth/repository/AuthRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.example.api.auth.repository; - -import com.example.api.domain.Account; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.Optional; - -public interface AuthRepository extends JpaRepository { - Optional findUserByLoginId(String loginId); -} \ No newline at end of file diff --git a/src/main/java/com/example/api/auth/service/AuthService.java b/src/main/java/com/example/api/auth/service/AuthService.java index 8c250aa9..35bf5a05 100644 --- a/src/main/java/com/example/api/auth/service/AuthService.java +++ b/src/main/java/com/example/api/auth/service/AuthService.java @@ -1,8 +1,8 @@ package com.example.api.auth.service; +import com.example.api.account.repository.AccountRepository; import com.example.api.auth.entitiy.RefreshToken; import com.example.api.auth.dto.*; -import com.example.api.auth.repository.AuthRepository; import com.example.api.auth.repository.TokenRepository; import com.example.api.domain.Account; import com.example.api.exception.BusinessException; @@ -16,7 +16,7 @@ @Service @RequiredArgsConstructor public class AuthService { - private final AuthRepository authRepository; + private final AccountRepository accountRepository; private final PasswordEncoder passwordEncoder; private final JwtTokenProvider jwtTokenProvider; private final TokenRepository tokenRepository; @@ -29,7 +29,7 @@ public LoginSuccessResponse login(@Validated final LoginRequest request) { } private Account getUserByLoginId(final String loginId) { - final Account user = authRepository.findUserByLoginId(loginId) + final Account user = accountRepository.findUserByLoginId(loginId) .orElseThrow(() -> new BusinessException(ErrorCode.NULL_USER)); if(user.isDeleted()) @@ -84,6 +84,6 @@ public LoginSuccessResponse logout(@Validated final LoginUserRequest loginUserRe } private Account getUserById(final Long userId) { - return authRepository.findById(userId).orElseThrow(() -> new BusinessException(ErrorCode.NULL_USER)); + return accountRepository.findById(userId).orElseThrow(() -> new BusinessException(ErrorCode.NULL_USER)); } } \ No newline at end of file diff --git a/src/main/java/com/example/api/board/service/BoardService.java b/src/main/java/com/example/api/board/service/BoardService.java index 54c1b5eb..5084cde4 100644 --- a/src/main/java/com/example/api/board/service/BoardService.java +++ b/src/main/java/com/example/api/board/service/BoardService.java @@ -6,7 +6,6 @@ import com.example.api.domain.repository.FlavoredRepository; import com.example.api.domain.repository.MyInfoRepository; import com.example.api.domain.repository.OfferEmploymentRepository; -import com.example.api.possbileboard.PossibleBoardRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; diff --git a/src/main/java/com/example/api/board/service/EmployeeService.java b/src/main/java/com/example/api/board/service/EmployeeService.java index 4f6615a9..d1149429 100644 --- a/src/main/java/com/example/api/board/service/EmployeeService.java +++ b/src/main/java/com/example/api/board/service/EmployeeService.java @@ -5,9 +5,6 @@ import com.example.api.board.dto.response.ExternalCareerDTO; import com.example.api.board.dto.response.MyInfoDTO; import com.example.api.board.dto.response.PossibleBoardDTO; -import com.example.api.board.dto.update.UpdateOpenStatusRequest; -import com.example.api.board.dto.update.UpdateUserInfoRequest; -import com.example.api.board.entitiy.update.UpdateAccountConditionManager; import com.example.api.domain.Account; import com.example.api.domain.Category; import com.example.api.domain.ExternalCareer; @@ -21,6 +18,7 @@ import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; + import java.util.HashSet; import java.util.List; import java.util.Set; @@ -33,12 +31,11 @@ public class EmployeeService { private final ExternalCareerRepository externalCareerRepository; private final FlavoredRepository flavoredRepository; private final PossibleBoardRepository possibleBoardRepository; - private final UpdateAccountConditionManager updateAccountConditionManager; @Transactional public Boolean changeOpenStatus(final EmployeeIdRequest employeeIdRequest, boolean openStatus) { return employeeRepository.findByAccountId(employeeIdRequest.employeeId()).map(employee -> { - updateAccountConditionManager.updateAccount(employee, new UpdateOpenStatusRequest(openStatus)); + employee.setOpenStatus(openStatus); employeeRepository.save(employee); return true; }).orElse(false); @@ -47,15 +44,7 @@ public Boolean changeOpenStatus(final EmployeeIdRequest employeeIdRequest, boole @Transactional public boolean updateUserInfo(final EmployeeIdRequest employeeIdRequest, MyInfoDTO myInfo) { return employeeRepository.findByAccountId(employeeIdRequest.employeeId()).map(employee -> { - updateAccountConditionManager.updateAccount(employee, new UpdateUserInfoRequest( - myInfo.getName(), - myInfo.getSex(), - myInfo.getAge(), - myInfo.getPhone(), - myInfo.getEmail(), - myInfo.getNickname() - ) - ); + setUserInfo(employee, myInfo); employeeRepository.save(employee); updateExternalCareer(employee, myInfo.getExternalCareerList()); @@ -64,7 +53,14 @@ public boolean updateUserInfo(final EmployeeIdRequest employeeIdRequest, MyInfoD return true; }).orElse(false); } - + void setUserInfo(Account employee, MyInfoDTO myInfo) { + employee.setName(myInfo.getName()); + employee.setSex(myInfo.getSex()); + employee.setAge(myInfo.getAge()); + employee.setPhoneNumber(myInfo.getPhone()); + employee.setEmail(myInfo.getEmail()); + employee.setNickname(myInfo.getNickname()); + } public void updateExternalCareer(Account employee, List newExternalCareerList) { List existList = externalCareerRepository.findAllByEmployeeAccountId(employee.getAccountId()); Set newSet = new HashSet<>(newExternalCareerList); diff --git a/src/main/java/com/example/api/contracts/dto/BusinessInfoDTO.java b/src/main/java/com/example/api/contracts/dto/BusinessInfoDTO.java index 96c46da4..7ea7942d 100644 --- a/src/main/java/com/example/api/contracts/dto/BusinessInfoDTO.java +++ b/src/main/java/com/example/api/contracts/dto/BusinessInfoDTO.java @@ -1,5 +1,6 @@ package com.example.api.contracts.dto; +import com.example.api.account.entity.Location; import lombok.*; import java.time.LocalDateTime; @@ -14,7 +15,7 @@ public class BusinessInfoDTO { private String representationName; private LocalDateTime startTime; private LocalDateTime endTime; - private String location; + private Location location; private String businessPhone; private LocalDateTime signedDate; } diff --git a/src/main/java/com/example/api/domain/Account.java b/src/main/java/com/example/api/domain/Account.java index 2bd87a09..bd9b7bd7 100644 --- a/src/main/java/com/example/api/domain/Account.java +++ b/src/main/java/com/example/api/domain/Account.java @@ -6,12 +6,16 @@ import com.example.api.board.dto.update.UpdateOpenStatusRequest; import com.example.api.board.dto.update.UpdateUserInfoRequest; import jakarta.persistence.*; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import java.util.Collection; +import static jakarta.persistence.FetchType.*; + @Entity @Getter @@ -22,7 +26,6 @@ public class Account extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "ACCOUNT_UNIQUE_ID") private Long accountId; - @Column(name = "ACCOUNT_ID") private String loginId; @Column(name = "ACCOUNT_PASSWORD") @@ -83,6 +86,15 @@ public Account(String loginId, String password, String name, String nickname, St this.emailReceivable = emailReceivable; } + public Account(String loginId, String password, String email, String phoneNumber, Nationality nationality, Collection roles) { + this.loginId = loginId; + this.password = password; + this.email = email; + this.phoneNumber = phoneNumber; + this.nationality = nationality; + this.roles = roles; + } + public LoginUserRequest getLoginUser(){ return new LoginUserRequest(accountId); } diff --git a/src/main/java/com/example/api/domain/Business.java b/src/main/java/com/example/api/domain/Business.java index 37398ff8..f816a60b 100644 --- a/src/main/java/com/example/api/domain/Business.java +++ b/src/main/java/com/example/api/domain/Business.java @@ -1,10 +1,10 @@ package com.example.api.domain; +import com.example.api.account.entity.Location; import jakarta.persistence.*; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.Setter; import java.time.LocalDate; import java.util.ArrayList; @@ -32,8 +32,9 @@ public class Business extends BaseEntity { @Column(name = "BUSINESS_NAME") private String businessName; - @Column(name = "BUSINESS_LOCATION") - private String location; + @OneToOne(fetch = LAZY) + @JoinColumn(name = "BUSINESS_LOCATION") + private Location location; private String representationName; @@ -50,6 +51,18 @@ public void setBusinessName(String businessName) { public void setLocation(String location) { this.location = location; } + public Business() { + } + + public Business(Account user, String businessRegistrationNumber, String businessName, String representationName, String businessOpenDate, Location location) { + this.employer = user; + this.registrationNumber = businessRegistrationNumber; + this.businessName = businessName; + this.representationName = representationName; + this.openDate = LocalDate.parse(businessOpenDate); + this.location = location; + } +} public Business(String businessName, String location, String representationName) { this.businessName = businessName; diff --git a/src/main/java/com/example/api/exception/ErrorCode.java b/src/main/java/com/example/api/exception/ErrorCode.java index 3936d1c8..494a7034 100644 --- a/src/main/java/com/example/api/exception/ErrorCode.java +++ b/src/main/java/com/example/api/exception/ErrorCode.java @@ -15,6 +15,7 @@ public enum ErrorCode { INCORRECT_PASSWORD(HttpStatus.BAD_REQUEST, "-107", "틀린 비밀번호입니다."), INCORRECT_DATA(HttpStatus.BAD_REQUEST, "-108", "올바르지 않은 정보입니다."), INVALID_REDIRECT_URI(HttpStatus.BAD_REQUEST,"-109","유효하지 않은 REDIRECT URI입니다."), + INVALID_BUSINESS_NUMBER(HttpStatus.BAD_REQUEST,"-110","사업자 등록 정보를 확인할 수 없습니다."), INVALID_TOKEN(HttpStatus.BAD_REQUEST, "-T1", "올바르지 않은 AccessToken입니다."), EXPIRED_ACCESS_TOKEN(HttpStatus.UNAUTHORIZED, "-T2", "만료된 AccessToken입니다."), diff --git a/src/main/java/com/example/api/global/config/RestTemplateConfig.java b/src/main/java/com/example/api/global/config/RestTemplateConfig.java new file mode 100644 index 00000000..708cbf0e --- /dev/null +++ b/src/main/java/com/example/api/global/config/RestTemplateConfig.java @@ -0,0 +1,23 @@ +package com.example.api.global.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class RestTemplateConfig { + @Bean + public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) { + return new RestTemplate(clientHttpRequestFactory); + } + + @Bean + public ClientHttpRequestFactory clientHttpRequestFactory() { + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setConnectTimeout(5000); // 연결 타임아웃 5초 + factory.setReadTimeout(5000); // 읽기 타임아웃 5초 + return factory; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/api/global/config/SecurityConfig.java b/src/main/java/com/example/api/global/config/SecurityConfig.java index 0d3eabd6..c0f4bcc8 100644 --- a/src/main/java/com/example/api/global/config/SecurityConfig.java +++ b/src/main/java/com/example/api/global/config/SecurityConfig.java @@ -89,7 +89,7 @@ static class FailedAuthenticationEntryPoint implements AuthenticationEntryPoint @Override public void commence(HttpServletRequest request, HttpServletResponse response, - AuthenticationException authException) throws IOException, ServletException { + AuthenticationException authException) throws IOException { response.setContentType("application/json"); response.setStatus(HttpServletResponse.SC_FORBIDDEN); diff --git a/src/main/java/com/example/api/global/properties/VendorProperties.java b/src/main/java/com/example/api/global/properties/VendorProperties.java new file mode 100644 index 00000000..643c0fba --- /dev/null +++ b/src/main/java/com/example/api/global/properties/VendorProperties.java @@ -0,0 +1,14 @@ +package com.example.api.global.properties; + +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Getter +@Component +public class VendorProperties { + @Value("${vendor.api.base-url}") + private String baseUrl; + @Value("${vendor.api.service-key}") + private String serviceKey; +} \ No newline at end of file diff --git a/src/main/java/com/example/api/oauth2/entity/handler/OAuth2AuthenticationSuccessHandler.java b/src/main/java/com/example/api/oauth2/entity/handler/OAuth2AuthenticationSuccessHandler.java index 21dcd530..68d0ab9a 100644 --- a/src/main/java/com/example/api/oauth2/entity/handler/OAuth2AuthenticationSuccessHandler.java +++ b/src/main/java/com/example/api/oauth2/entity/handler/OAuth2AuthenticationSuccessHandler.java @@ -19,6 +19,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; @@ -34,6 +35,7 @@ import static com.example.api.oauth2.entity.HttpCookieOAuth2AuthorizationRequestRepository.REDIRECT_URI_PARAM_COOKIE_NAME; @Component +@Slf4j @RequiredArgsConstructor public class OAuth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { private final JwtTokenProvider tokenProvider; diff --git a/src/main/java/com/example/api/oauth2/service/CustomOauth2UserService.java b/src/main/java/com/example/api/oauth2/service/CustomOauth2UserService.java index 4702091e..08b2c24c 100644 --- a/src/main/java/com/example/api/oauth2/service/CustomOauth2UserService.java +++ b/src/main/java/com/example/api/oauth2/service/CustomOauth2UserService.java @@ -30,9 +30,7 @@ public class CustomOauth2UserService extends DefaultOAuth2UserService { @Override @Transactional public OAuth2User loadUser(@Validated final OAuth2UserRequest request) throws OAuth2AuthenticationException { - log.info("OAuth2UserRequest: {}", request.getAdditionalParameters()); OAuth2User oAuth2User = super.loadUser(request); - log.info("OAuth2User : {} ", oAuth2User); String registrationId = request.getClientRegistration().getRegistrationId(); OAuth2Response oAuth2Response = createOAuth2Response(registrationId, oAuth2User); diff --git a/src/main/java/com/example/api/possbileboard/PossibleBoardRepository.java b/src/main/java/com/example/api/possbileboard/PossibleBoardRepository.java index 0eefac4a..5c325a45 100644 --- a/src/main/java/com/example/api/possbileboard/PossibleBoardRepository.java +++ b/src/main/java/com/example/api/possbileboard/PossibleBoardRepository.java @@ -21,7 +21,7 @@ public interface PossibleBoardRepository extends JpaRepository findAllByEmployeeAccountId(Long employeeId); + @Query("select new com.example.api.board.controller.domain.response.PossibleBoardDTO(p.possibleId, p.startTime, p.endTime) " + + "from PossibleBoard p where p.employee.accountId = :employeeId") + List findAllDTOByEmployeeAccountId(@Param("employeeId")Long employeeId); + +} + @Query("select new com.example.api.board.dto.response.PossibleBoardDTO(p.possibleId, p.startTime, p.endTime) " + "from PossibleBoard p where p.employee.accountId = :employeeId") List findAllDTOByEmployeeAccountId(@Param("employeeId")Long employeeId); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 714f320e..5c44b70a 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -52,7 +52,7 @@ jwt.access_token_valid_time=3600 # 30? jwt.refresh_token_valid_time=2592000 -# ??? ?? ?? +# mail code code.length=6 code.digit_range=10 @@ -85,4 +85,8 @@ spring.security.oauth2.client.provider.naver.user-info-uri=https://openapi.naver spring.security.oauth2.client.provider.naver.user-name-attribute=response # redirect allow path -app.oauth2.authorized-redirect-uris=* \ No newline at end of file +app.oauth2.authorized-redirect-uris=* + +# vendor +vendor.api.base-url=https://api.odcloud.kr/api/nts-businessman/v1/validate +vendor.api.service-key=${VENDOR_API_SERVICE-KEY} \ No newline at end of file diff --git a/src/test/java/com/example/api/account/service/AccountServiceTest.java b/src/test/java/com/example/api/account/service/AccountServiceTest.java new file mode 100644 index 00000000..61997561 --- /dev/null +++ b/src/test/java/com/example/api/account/service/AccountServiceTest.java @@ -0,0 +1,62 @@ +package com.example.api.account.service; + +import com.example.api.account.dto.BusinessNumberRequest; +import com.example.api.account.dto.SignUpEmployerRequest; +import com.example.api.account.repository.AccountRepository; +import com.example.api.exception.BusinessException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@SpringBootTest +class UserServiceTest { + @MockBean + private AccountRepository accountRepository; + @Autowired + private AccountService accountService; + + @Test + @DisplayName("중복된 ID로 회원가입 시 예외가 발생해야 한다") + void shouldThrowExceptionWhenUserIdAlreadyExists() { + String userId = "existingUser"; + SignUpEmployerRequest mockRequest = mock(SignUpEmployerRequest.class); + when(mockRequest.loginId()).thenReturn(userId); + when(accountRepository.existsByLoginId(userId)).thenReturn(true); + + BusinessException thrown = assertThrows(BusinessException.class, + () -> accountService.signUpEmployer(mockRequest)); + assertEquals("중복된 ID입니다.", thrown.getErrorCode().getErrorDescription()); + } + + @Test + @DisplayName("유효하지 않은 사업자 번호로 검증 요청 시 예외가 발생해야 한다") + void shouldThrowExceptionWhenBusinessNumberIsInvalid() { + BusinessNumberRequest businessNumberRequest = new BusinessNumberRequest( + "1041736263 가짜지롱", + "20231123", + "김태영", + "김태영닷컴"); + + BusinessException thrown = assertThrows(BusinessException.class, + () -> accountService.verifyBusinessNumber(businessNumberRequest)); + assertEquals("사업자 등록 정보를 확인할 수 없습니다.", thrown.getErrorCode().getErrorDescription()); + } + + @Test + @DisplayName("유효한 사업자 번호로 검증 요청 시 성공해야 한다.") + void shouldReturnSuccessWhenValidBusinessNumberIsProvided() { + BusinessNumberRequest businessNumberRequest = new BusinessNumberRequest( + "1041736263", + "김태영닷컴", + "김태영", + "20231123"); + + String isValid = accountService.verifyBusinessNumber(businessNumberRequest); + assertEquals("유효한 사업자 등록 정보입니다.", isValid); + } +} diff --git a/src/test/java/com/example/api/global/BaseIntegrationTest.java b/src/test/java/com/example/api/global/BaseIntegrationTest.java index 4a73db13..36adccaa 100644 --- a/src/test/java/com/example/api/global/BaseIntegrationTest.java +++ b/src/test/java/com/example/api/global/BaseIntegrationTest.java @@ -2,6 +2,11 @@ import com.example.api.board.dto.response.InnerCareerDTO; import com.example.api.board.dto.response.PossibleBoardDTO; +import com.example.api.account.entity.Location; +import com.example.api.board.controller.domain.response.CategoryDTO; +import com.example.api.board.controller.domain.response.ExternalCareerDTO; +import com.example.api.board.controller.domain.response.InnerCareerDTO; +import com.example.api.board.controller.domain.response.PossibleBoardDTO; import com.example.api.business.BusinessRepository; import com.example.api.contracts.ContractRepository; import com.example.api.domain.Account; @@ -25,12 +30,20 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import com.example.api.domain.*; +import com.example.api.domain.repository.*; +import com.example.api.possbileboard.PossibleBoardRepository; import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.Rollback; import org.springframework.test.context.ActiveProfiles; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + @SpringBootTest @ActiveProfiles("test") @Rollback(false) @@ -94,7 +107,7 @@ void setUpData() { Business business = new Business(); business.setBusinessName("Tech Solutions Inc."); - business.setLocation("Seoul, South Korea"); + business.setLocation(new Location("우편번호", "주소", "상세주소")); business.setRepresentationName("James"); business.setOpenDate(LocalDate.now()); business.setRegistrationNumber("123-456-789"); @@ -160,4 +173,4 @@ void setUpData() { flavoredCategoryList = new ArrayList<>(); flavoredCategoryList.addAll(flavoredRepository.findAllCategoryDTOByEmployeeId(1L)); } -} +} \ No newline at end of file