diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index 6771874..091bccd 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -4,7 +4,7 @@ :toclevels: 3 :source-highlighter: highlightjs :sectlinks: -:snippetsDir: build/generated-snippets +:snippetsDir: ../../../build/generated-snippets = **FitTheMan API Document** @@ -163,6 +163,42 @@ include::{snippetsDir}/emailCodeVerification/1/http-response.adoc[] include::{snippetsDir}/emailCodeVerification/1/response-fields.adoc[] +=== **4. 회원가입시 필요한 정보 목록 조회 api** + +회원가입시 사용자에게 입력 받는 연령대 정보와 관심 해시태그 정보 옵션들을 조회합니다. + + +==== Request +include::{snippetsDir}/userSignupOptions/1/http-request.adoc[] + +==== 성공 Response +include::{snippetsDir}/userSignupOptions/1/http-response.adoc[] + +==== Response Body Fields +include::{snippetsDir}/userSignupOptions/1/response-fields.adoc[] + + +=== **5. 일반 회원가입 api** + +일반 회원가입 api입니다. + +==== Request +include::{snippetsDir}/generalUserSignUp/1/http-request.adoc[] + +==== Request Body Fields +include::{snippetsDir}/generalUserSignUp/1/request-fields.adoc[] + +==== 성공 Response +include::{snippetsDir}/generalUserSignUp/1/http-response.adoc[] + +==== Response Body Fields +include::{snippetsDir}/generalUserSignUp/1/response-fields.adoc[] + +==== 실패 Response +실패1. +include::{snippetsDir}/generalUserSignUp/2/http-response.adoc[] +실패 2 +include::{snippetsDir}/generalUserSignUp/3/http-response.adoc[] == 인증/인가 === **1. 유저 로그인** diff --git a/src/main/java/com/ftm/server/adapter/controller/user/GeneralUserSignupController.java b/src/main/java/com/ftm/server/adapter/controller/user/GeneralUserSignupController.java new file mode 100644 index 0000000..7354cb5 --- /dev/null +++ b/src/main/java/com/ftm/server/adapter/controller/user/GeneralUserSignupController.java @@ -0,0 +1,29 @@ +package com.ftm.server.adapter.controller.user; + +import com.ftm.server.adapter.dto.request.GeneralUserSignupRequest; +import com.ftm.server.common.response.ApiResponse; +import com.ftm.server.common.response.enums.SuccessResponseCode; +import com.ftm.server.domain.dto.command.GeneralUserSignupCommand; +import com.ftm.server.domain.usecase.user.GeneralUserSignupUseCase; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class GeneralUserSignupController { + + private final GeneralUserSignupUseCase generalUserSignupUseCase; + + @PostMapping("/api/users") + public ResponseEntity createGeneralUser( + @Valid @RequestBody GeneralUserSignupRequest request) { + generalUserSignupUseCase.execute(GeneralUserSignupCommand.from(request)); + return ResponseEntity.status(HttpStatus.CREATED) + .body(ApiResponse.success(SuccessResponseCode.CREATED)); + } +} diff --git a/src/main/java/com/ftm/server/adapter/controller/user/GetUserSignupOptionsController.java b/src/main/java/com/ftm/server/adapter/controller/user/GetUserSignupOptionsController.java new file mode 100644 index 0000000..d4d4b4e --- /dev/null +++ b/src/main/java/com/ftm/server/adapter/controller/user/GetUserSignupOptionsController.java @@ -0,0 +1,26 @@ +package com.ftm.server.adapter.controller.user; + +import com.ftm.server.adapter.dto.response.UserSignupOptionsResponse; +import com.ftm.server.common.response.ApiResponse; +import com.ftm.server.common.response.enums.SuccessResponseCode; +import com.ftm.server.domain.usecase.user.GetUserSignupOptionsUseCase; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class GetUserSignupOptionsController { + + private final GetUserSignupOptionsUseCase getUserSignupOptionsUseCase; + + @GetMapping("/api/users/options") + public ResponseEntity> getUserSignupOptions() { + UserSignupOptionsResponse response = + UserSignupOptionsResponse.from(getUserSignupOptionsUseCase.execute()); + return ResponseEntity.status(HttpStatus.OK) + .body(ApiResponse.success(SuccessResponseCode.OK, response)); + } +} diff --git a/src/main/java/com/ftm/server/adapter/dto/request/GeneralUserSignupRequest.java b/src/main/java/com/ftm/server/adapter/dto/request/GeneralUserSignupRequest.java new file mode 100644 index 0000000..8e7fb48 --- /dev/null +++ b/src/main/java/com/ftm/server/adapter/dto/request/GeneralUserSignupRequest.java @@ -0,0 +1,24 @@ +package com.ftm.server.adapter.dto.request; + +import com.ftm.server.entity.enums.AgeGroup; +import com.ftm.server.entity.enums.HashTag; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import java.util.List; +import lombok.Data; + +@Data +public class GeneralUserSignupRequest { + + @Pattern( + regexp = "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$", + message = "이메일 형식이 올바르지 않습니다.") + private final String email; + + @NotBlank private final String password; + + @NotNull private final AgeGroup age; + + private final List hashtags; +} diff --git a/src/main/java/com/ftm/server/adapter/dto/response/UserSignupOptionsResponse.java b/src/main/java/com/ftm/server/adapter/dto/response/UserSignupOptionsResponse.java new file mode 100644 index 0000000..a9f47b1 --- /dev/null +++ b/src/main/java/com/ftm/server/adapter/dto/response/UserSignupOptionsResponse.java @@ -0,0 +1,28 @@ +package com.ftm.server.adapter.dto.response; + +import com.ftm.server.domain.dto.vo.UserSignupOptionsVo; +import com.ftm.server.entity.enums.AgeGroup; +import com.ftm.server.entity.enums.HashTag; +import java.util.List; +import lombok.Data; + +@Data +public class UserSignupOptionsResponse { + + private final List ages; + private final List hashtags; + + public static UserSignupOptionsResponse from(UserSignupOptionsVo vo) { + return new UserSignupOptionsResponse(vo.getAges(), vo.getHashtags()); + } + + public record EnumDescriptors(String value, String description) { + public static EnumDescriptors from(HashTag hashTag) { + return new EnumDescriptors(hashTag.name(), hashTag.getValue()); + } + + public static EnumDescriptors from(AgeGroup ageGroup) { + return new EnumDescriptors(ageGroup.name(), ageGroup.getValue()); + } + } +} diff --git a/src/main/java/com/ftm/server/adapter/gateway/AuthenticationGateway.java b/src/main/java/com/ftm/server/adapter/gateway/AuthenticationGateway.java index 7483572..e4d231a 100644 --- a/src/main/java/com/ftm/server/adapter/gateway/AuthenticationGateway.java +++ b/src/main/java/com/ftm/server/adapter/gateway/AuthenticationGateway.java @@ -16,4 +16,6 @@ void saveAuthenticatedSession( Authentication authentication, HttpServletRequest request, HttpServletResponse response); + + String passwordEncode(String password); } diff --git a/src/main/java/com/ftm/server/adapter/gateway/repository/EmailVerificationLogsRepository.java b/src/main/java/com/ftm/server/adapter/gateway/repository/EmailVerificationLogsRepository.java index b8a1628..0c2c168 100644 --- a/src/main/java/com/ftm/server/adapter/gateway/repository/EmailVerificationLogsRepository.java +++ b/src/main/java/com/ftm/server/adapter/gateway/repository/EmailVerificationLogsRepository.java @@ -13,4 +13,6 @@ public interface EmailVerificationLogsRepository Optional findByVerificationCodeAndEmail( String verificationCode, String email); + + Optional findByEmailAndIsVerified(String email, Boolean isVerified); } diff --git a/src/main/java/com/ftm/server/common/response/enums/ErrorResponseCode.java b/src/main/java/com/ftm/server/common/response/enums/ErrorResponseCode.java index ee1f5b5..a96abd2 100644 --- a/src/main/java/com/ftm/server/common/response/enums/ErrorResponseCode.java +++ b/src/main/java/com/ftm/server/common/response/enums/ErrorResponseCode.java @@ -24,6 +24,7 @@ public enum ErrorResponseCode { USER_ALREADY_EXISTS(HttpStatus.CONFLICT, "E409_001", "이미 존재하는 사용자입니다."), PASSWORD_NOT_MATCHED(HttpStatus.CONFLICT, "E409_002", "비밀번호가 일치하지 않습니다."), EXCEED_NUMBER_OF_TRIAL(HttpStatus.CONFLICT, "E409_003", "시도 가능 횟수를 초과했습니다. 잠시 후에 다시 시도 해 주세요."), + EMAIL_NOT_VERIFIED(HttpStatus.CONFLICT, "E409_004", "이메일 인증이 완료되지 않았습니다."), // 500번 UNKNOWN_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "E500_001", "알 수 없는 서버 에러가 발생했습니다."), diff --git a/src/main/java/com/ftm/server/common/utils/RandomNickNameCreator.java b/src/main/java/com/ftm/server/common/utils/RandomNickNameCreator.java new file mode 100644 index 0000000..e1334a4 --- /dev/null +++ b/src/main/java/com/ftm/server/common/utils/RandomNickNameCreator.java @@ -0,0 +1,47 @@ +package com.ftm.server.common.utils; + +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +public class RandomNickNameCreator { + + private static final int ADJECTIVE_SIZE = 100; + private static final int ANIMAL_SIZE = 100; + + private static final List ADJECTIVES = + List.of( + "귀여운", "멋진", "사랑스러운", "친절한", "씩씩한", "활발한", "상냥한", "유쾌한", "웃기는", "엉뚱한", "도도한", + "시크한", "깜찍한", "착한", "말많은", "용감한", "천진난만한", "엉성한", "듬직한", "달콤한", "부드러운", "영리한", + "총명한", "통통 튀는", "재빠른", "고요한", "섬세한", "반짝이는", "튼튼한", "단단한", "활기찬", "청량한", "용감한", + "깨끗한", "상쾌한", "애교 많은", "신중한", "무뚝뚝한", "익살스러운", "어리숙한", "눈부신", "자유로운", "평화로운", + "느긋한", "발랄한", "늠름한", "새침한", "정많은", "상큼한", "놀라운", "행복한", "고집있는", "진지한", "명랑한", + "건강한", "감성적인", "논리적인", "활달한", "기특한", "온순한", "열정적인", "기운찬", "근엄한", "장난스러운", + "차분한", "엉뚱발랄한", "풍부한", "다정한", "순수한", "현명한", "현실적인", "적극적인", "낙천적인", "이성적인", + "창의적인", "겁먹은", "반짝반짝한", "대담한", "친근한", "자신감넘치는", "배려 깊은", "쾌활한", "낭만적인", "강직한", + "근면한", "용의주도한", "깨방정인", "활짝웃는", "배짱있는", "운동중인", "반짝이는", "차가운", "따뜻한", "달빛 같은", + "빛나는", "신비로운", "열렬한", "야무진", "물먹는", "밥먹는"); + + private static final List ANIMALS = + List.of( + "고양이", "강아지", "호랑이", "사자", "토끼", "곰", "부엉이", "올빼미", "여우", "늑대", "다람쥐", "고슴도치", + "판다", "햄스터", "코끼리", "기린", "하마", "원숭이", "돼지", "소", "닭", "오리", "펭귄", "돌고래", "고래", + "상어", "물개", "바다표범", "너구리", "스컹크", "치타", "재규어", "카피바라", "알파카", "라마", "낙타", + "하이에나", "캥거루", "왈라비", "이구아나", "카멜레온", "악어", "도마뱀", "코알라", "수달", "너울고래", "족제비", + "삵", "담비", "멧돼지", "청설모", "기러기", "기러기", "참새", "까마귀", "까치", "앵무새", "홍학", "청둥오리", + "황새", "매", "독수리", "수리부엉이", "제비", "해달", "해파리", "문어", "낙지", "거북이", "자라", "이빨고기", + "복어", "불가사리", "연어", "잉어", "피라냐", "참치", "가오리", "도요새", "두루미", "삵쾡이", "바다사자", "사슴", + "노루", "염소", "양", "라마", "들소", "야크", "바이슨", "고라니", "아르마딜로", "너구리", "프레리도그", "페럿", + "고래상어", "벌", "나비", "잠자리", "딱정벌레"); + + public static String generateNickname() { + String adjective = ADJECTIVES.get(randomIndex(ADJECTIVE_SIZE)); + String animal = ANIMALS.get(randomIndex(ANIMAL_SIZE)); + int number = ThreadLocalRandom.current().nextInt(0, 1000); // 0~999 + + return String.format("%s%s%d", adjective, animal, number); + } + + private static int randomIndex(int size) { + return ThreadLocalRandom.current().nextInt(size); + } +} diff --git a/src/main/java/com/ftm/server/domain/dto/command/GeneralUserCreationCommand.java b/src/main/java/com/ftm/server/domain/dto/command/GeneralUserCreationCommand.java new file mode 100644 index 0000000..83f29fb --- /dev/null +++ b/src/main/java/com/ftm/server/domain/dto/command/GeneralUserCreationCommand.java @@ -0,0 +1,19 @@ +package com.ftm.server.domain.dto.command; + +import com.ftm.server.entity.enums.AgeGroup; +import com.ftm.server.entity.enums.HashTag; +import lombok.Data; + +@Data +public class GeneralUserCreationCommand { + private final String email; + private final String password; + private final String nickName; + private final AgeGroup ageGroup; + private final HashTag[] hashTags; + + public static GeneralUserCreationCommand of( + String email, String password, String nickName, AgeGroup ageGroup, HashTag[] hashTag) { + return new GeneralUserCreationCommand(email, password, nickName, ageGroup, hashTag); + } +} diff --git a/src/main/java/com/ftm/server/domain/dto/command/GeneralUserSignupCommand.java b/src/main/java/com/ftm/server/domain/dto/command/GeneralUserSignupCommand.java new file mode 100644 index 0000000..c829063 --- /dev/null +++ b/src/main/java/com/ftm/server/domain/dto/command/GeneralUserSignupCommand.java @@ -0,0 +1,21 @@ +package com.ftm.server.domain.dto.command; + +import com.ftm.server.adapter.dto.request.GeneralUserSignupRequest; +import com.ftm.server.entity.enums.AgeGroup; +import com.ftm.server.entity.enums.HashTag; +import java.util.List; +import lombok.Data; + +@Data +public class GeneralUserSignupCommand { + + private final String email; + private final String password; + private final AgeGroup age; + private final List hashtags; + + public static GeneralUserSignupCommand from(GeneralUserSignupRequest request) { + return new GeneralUserSignupCommand( + request.getEmail(), request.getPassword(), request.getAge(), request.getHashtags()); + } +} diff --git a/src/main/java/com/ftm/server/domain/dto/query/FindEmailVerificationLogsByEmailQuery.java b/src/main/java/com/ftm/server/domain/dto/query/FindEmailVerificationLogsByEmailQuery.java new file mode 100644 index 0000000..4cfea25 --- /dev/null +++ b/src/main/java/com/ftm/server/domain/dto/query/FindEmailVerificationLogsByEmailQuery.java @@ -0,0 +1,12 @@ +package com.ftm.server.domain.dto.query; + +import lombok.Data; + +@Data +public class FindEmailVerificationLogsByEmailQuery { + private final String email; + + public static FindEmailVerificationLogsByEmailQuery of(String email) { + return new FindEmailVerificationLogsByEmailQuery(email); + } +} diff --git a/src/main/java/com/ftm/server/domain/dto/vo/UserSignupOptionsVo.java b/src/main/java/com/ftm/server/domain/dto/vo/UserSignupOptionsVo.java new file mode 100644 index 0000000..8cdf2e5 --- /dev/null +++ b/src/main/java/com/ftm/server/domain/dto/vo/UserSignupOptionsVo.java @@ -0,0 +1,18 @@ +package com.ftm.server.domain.dto.vo; + +import com.ftm.server.adapter.dto.response.UserSignupOptionsResponse; +import java.util.List; +import lombok.Data; + +@Data +public class UserSignupOptionsVo { + + private final List ages; + private final List hashtags; + + public static UserSignupOptionsVo of( + List ages, + List hashtags) { + return new UserSignupOptionsVo(ages, hashtags); + } +} diff --git a/src/main/java/com/ftm/server/domain/service/EmailVerificationLogsService.java b/src/main/java/com/ftm/server/domain/service/EmailVerificationLogsService.java index 4df816e..ba90172 100644 --- a/src/main/java/com/ftm/server/domain/service/EmailVerificationLogsService.java +++ b/src/main/java/com/ftm/server/domain/service/EmailVerificationLogsService.java @@ -4,6 +4,7 @@ import com.ftm.server.domain.dto.command.EmailVerificationLogCreationCommand; import com.ftm.server.domain.dto.query.EmailCodeVerificationQuery; import com.ftm.server.domain.dto.query.FindByEmailQuery; +import com.ftm.server.domain.dto.query.FindEmailVerificationLogsByEmailQuery; import com.ftm.server.entity.entities.EmailVerificationLogs; import java.util.Optional; import lombok.RequiredArgsConstructor; @@ -28,4 +29,9 @@ public Optional findByAuthenticationCode( return emailVerificationLogsRepository.findByVerificationCodeAndEmail( query.getCode(), query.getEmail()); } + + public Optional findVerifiedOneByEmail( + FindEmailVerificationLogsByEmailQuery query) { + return emailVerificationLogsRepository.findByEmailAndIsVerified(query.getEmail(), true); + } } diff --git a/src/main/java/com/ftm/server/domain/service/UserImageService.java b/src/main/java/com/ftm/server/domain/service/UserImageService.java index 7eb7eac..1b6b9dd 100644 --- a/src/main/java/com/ftm/server/domain/service/UserImageService.java +++ b/src/main/java/com/ftm/server/domain/service/UserImageService.java @@ -2,6 +2,7 @@ import com.ftm.server.adapter.gateway.repository.UserImageRepository; import com.ftm.server.domain.dto.query.FindByUserIdQuery; +import com.ftm.server.entity.entities.User; import com.ftm.server.entity.entities.UserImage; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -17,4 +18,9 @@ public UserImage queryUserImageByUserId(FindByUserIdQuery query) { .findByUserId(query.getUserId()) .orElseThrow(() -> new RuntimeException("")); } + + public void saveUserDefaultImage(User user) { + UserImage userImage = UserImage.createUserImage(user); + userImageRepository.save(userImage); + } } diff --git a/src/main/java/com/ftm/server/domain/service/UserService.java b/src/main/java/com/ftm/server/domain/service/UserService.java index e1edc59..0141fe9 100644 --- a/src/main/java/com/ftm/server/domain/service/UserService.java +++ b/src/main/java/com/ftm/server/domain/service/UserService.java @@ -2,15 +2,18 @@ import com.ftm.server.adapter.gateway.repository.UserRepository; import com.ftm.server.common.exception.CustomException; +import com.ftm.server.domain.dto.command.GeneralUserCreationCommand; import com.ftm.server.domain.dto.query.FindByEmailQuery; import com.ftm.server.domain.dto.query.FindByIdQuery; import com.ftm.server.domain.dto.vo.EmailDuplicationVo; import com.ftm.server.entity.entities.User; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor +@Slf4j public class UserService { private final UserRepository userRepository; @@ -24,4 +27,14 @@ public User queryUser(FindByIdQuery query) { .findById(query.getId()) .orElseThrow(() -> CustomException.USER_NOT_FOUND); } + + public User createGeneralUser(GeneralUserCreationCommand command) { + User user = User.createGeneralUser(command); + userRepository.save(user); + return user; + } + + public Boolean userCheckByEmail(FindByEmailQuery query) { + return userRepository.existsByEmail(query.getEmail()); + } } diff --git a/src/main/java/com/ftm/server/domain/usecase/user/GeneralUserSignupUseCase.java b/src/main/java/com/ftm/server/domain/usecase/user/GeneralUserSignupUseCase.java new file mode 100644 index 0000000..fa651c3 --- /dev/null +++ b/src/main/java/com/ftm/server/domain/usecase/user/GeneralUserSignupUseCase.java @@ -0,0 +1,62 @@ +package com.ftm.server.domain.usecase.user; + +import com.ftm.server.adapter.gateway.AuthenticationGateway; +import com.ftm.server.common.annotation.UseCase; +import com.ftm.server.common.exception.CustomException; +import com.ftm.server.common.response.enums.ErrorResponseCode; +import com.ftm.server.common.utils.RandomNickNameCreator; +import com.ftm.server.domain.dto.command.GeneralUserCreationCommand; +import com.ftm.server.domain.dto.command.GeneralUserSignupCommand; +import com.ftm.server.domain.dto.query.FindByEmailQuery; +import com.ftm.server.domain.dto.query.FindEmailVerificationLogsByEmailQuery; +import com.ftm.server.domain.service.EmailVerificationLogsService; +import com.ftm.server.domain.service.UserImageService; +import com.ftm.server.domain.service.UserService; +import com.ftm.server.entity.entities.EmailVerificationLogs; +import com.ftm.server.entity.entities.User; +import com.ftm.server.entity.enums.HashTag; +import java.util.Optional; +import lombok.RequiredArgsConstructor; + +@UseCase +@RequiredArgsConstructor +public class GeneralUserSignupUseCase { + + // service + private final EmailVerificationLogsService emailVerificationLogsService; + private final UserService userService; + private final UserImageService userImageService; + + // gateway + private final AuthenticationGateway authenticationGateway; + + public void execute(GeneralUserSignupCommand command) { + + String email = command.getEmail(); + Optional emailVerificationLogs = + emailVerificationLogsService.findVerifiedOneByEmail( + FindEmailVerificationLogsByEmailQuery.of(email)); + + if (userService.userCheckByEmail(FindByEmailQuery.of(email))) { // 기존에 가입된 회원인지 검사 + throw new CustomException(ErrorResponseCode.USER_ALREADY_EXISTS); + } + + if (emailVerificationLogs.isEmpty()) { // 이메일 인증이 완료되지 않음. + throw new CustomException(ErrorResponseCode.EMAIL_NOT_VERIFIED); + } + + String nickname = RandomNickNameCreator.generateNickname(); // random 닉네임 생성 + int size = command.getHashtags().size(); + + GeneralUserCreationCommand convertedCommand = + GeneralUserCreationCommand.of( + command.getEmail(), + authenticationGateway.passwordEncode(command.getPassword()), + nickname, + command.getAge(), + command.getHashtags().toArray(new HashTag[size])); + + User user = userService.createGeneralUser(convertedCommand); + userImageService.saveUserDefaultImage(user); + } +} diff --git a/src/main/java/com/ftm/server/domain/usecase/user/GetUserSignupOptionsUseCase.java b/src/main/java/com/ftm/server/domain/usecase/user/GetUserSignupOptionsUseCase.java new file mode 100644 index 0000000..211215a --- /dev/null +++ b/src/main/java/com/ftm/server/domain/usecase/user/GetUserSignupOptionsUseCase.java @@ -0,0 +1,28 @@ +package com.ftm.server.domain.usecase.user; + +import com.ftm.server.adapter.dto.response.UserSignupOptionsResponse; +import com.ftm.server.common.annotation.UseCase; +import com.ftm.server.domain.dto.vo.UserSignupOptionsVo; +import com.ftm.server.entity.enums.AgeGroup; +import com.ftm.server.entity.enums.HashTag; +import java.util.Arrays; +import java.util.List; +import lombok.RequiredArgsConstructor; + +@UseCase +@RequiredArgsConstructor +public class GetUserSignupOptionsUseCase { + + public UserSignupOptionsVo execute() { + List ages = + Arrays.stream(AgeGroup.values()) + .map(UserSignupOptionsResponse.EnumDescriptors::from) + .toList(); + + List hashtags = + Arrays.stream(HashTag.values()) + .map(UserSignupOptionsResponse.EnumDescriptors::from) + .toList(); + return UserSignupOptionsVo.of(ages, hashtags); + } +} diff --git a/src/main/java/com/ftm/server/entity/entities/User.java b/src/main/java/com/ftm/server/entity/entities/User.java index 1c2838a..00c96aa 100644 --- a/src/main/java/com/ftm/server/entity/entities/User.java +++ b/src/main/java/com/ftm/server/entity/entities/User.java @@ -1,5 +1,6 @@ package com.ftm.server.entity.entities; +import com.ftm.server.domain.dto.command.GeneralUserCreationCommand; import com.ftm.server.entity.enums.AgeGroup; import com.ftm.server.entity.enums.HashTag; import com.ftm.server.entity.enums.SocialProvider; @@ -104,4 +105,17 @@ private User( this.isDeleted = isDeleted; this.deletedAt = deletedAt; } + + public static User createGeneralUser(GeneralUserCreationCommand command) { + return User.builder() + .email(command.getEmail()) + .password(command.getPassword()) + .nickname(command.getNickName()) + .ageGroup(command.getAgeGroup()) + .favoriteHashtags(command.getHashTags()) + .groomingScore(0) + .isDeleted(false) + .role(UserRole.USER) + .build(); + } } diff --git a/src/main/java/com/ftm/server/entity/entities/UserImage.java b/src/main/java/com/ftm/server/entity/entities/UserImage.java index 6247ee4..1013a1c 100644 --- a/src/main/java/com/ftm/server/entity/entities/UserImage.java +++ b/src/main/java/com/ftm/server/entity/entities/UserImage.java @@ -27,4 +27,8 @@ private UserImage(User user, String objectKey) { this.user = user; this.objectKey = objectKey; } + + public static UserImage createUserImage(User user) { + return UserImage.builder().user(user).objectKey("users/default-image.png").build(); + } } diff --git a/src/main/java/com/ftm/server/infrastructure/security/AuthenticationService.java b/src/main/java/com/ftm/server/infrastructure/security/AuthenticationService.java index e70a0e0..3740bbb 100644 --- a/src/main/java/com/ftm/server/infrastructure/security/AuthenticationService.java +++ b/src/main/java/com/ftm/server/infrastructure/security/AuthenticationService.java @@ -13,6 +13,7 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.context.SecurityContextRepository; @Slf4j @@ -22,6 +23,9 @@ public class AuthenticationService implements AuthenticationGateway { private final AuthenticationManager authenticationManager; private final SecurityContextRepository securityContextRepository; + private final PasswordEncoder passwordEncoder; + + // end::[]] @Override public Authentication createAuthenticationFromCredentials(UserLoginCommand command) @@ -46,4 +50,9 @@ public void saveAuthenticatedSession( // 생성한 시큐리티 컨텍스트를 Redis 세션에 저장 securityContextRepository.saveContext(context, request, response); } + + @Override + public String passwordEncode(String password) { + return passwordEncoder.encode(password); + } } diff --git a/src/main/java/com/ftm/server/infrastructure/security/SecurityConfig.java b/src/main/java/com/ftm/server/infrastructure/security/SecurityConfig.java index 968de66..bbf94f0 100644 --- a/src/main/java/com/ftm/server/infrastructure/security/SecurityConfig.java +++ b/src/main/java/com/ftm/server/infrastructure/security/SecurityConfig.java @@ -46,10 +46,15 @@ public class SecurityConfig { "https://dev-api.fittheman.site", // 개발 환경 서버 도메인 "https://fittheman.site"); // 개발 환경 클라이언트 도메인 - private static final String[] GET_ANONYMOUS_MATCHERS = {"/api/users/email/duplication"}; + private static final String[] GET_ANONYMOUS_MATCHERS = { + "/api/users/email/duplication", "/api/users/options" + }; private static final String[] POST_ANONYMOUS_MATCHERS = { - "/api/users/email/authentication", "/api/users/email/authentication/code", "/api/auth/login" + "/api/users/email/authentication", + "/api/users/email/authentication/code", + "/api/auth/login", + "/api/users" }; private static final String[] ANONYMOUS_MATCHERS = {"/docs/**"}; diff --git a/src/test/java/com/ftm/server/user/GeneralUserSignupTest.java b/src/test/java/com/ftm/server/user/GeneralUserSignupTest.java new file mode 100644 index 0000000..cc54c36 --- /dev/null +++ b/src/test/java/com/ftm/server/user/GeneralUserSignupTest.java @@ -0,0 +1,162 @@ +package com.ftm.server.user; + +import static com.epages.restdocs.apispec.ResourceDocumentation.resource; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.epages.restdocs.apispec.ResourceSnippetParameters; +import com.ftm.server.BaseTest; +import com.ftm.server.adapter.dto.request.GeneralUserSignupRequest; +import com.ftm.server.adapter.gateway.repository.EmailVerificationLogsRepository; +import com.ftm.server.common.response.enums.ErrorResponseCode; +import com.ftm.server.domain.dto.command.EmailVerificationLogCreationCommand; +import com.ftm.server.domain.service.UserService; +import com.ftm.server.entity.entities.EmailVerificationLogs; +import com.ftm.server.entity.enums.AgeGroup; +import com.ftm.server.entity.enums.HashTag; +import jakarta.transaction.Transactional; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.restdocs.payload.FieldDescriptor; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; + +public class GeneralUserSignupTest extends BaseTest { + + @Autowired private EmailVerificationLogsRepository emailVerificationLogsRepository; + + @Autowired private UserService userService; + + private final List requestFieldDescriptors = + List.of( + fieldWithPath("email").type(JsonFieldType.STRING).description("회원가입 email"), + fieldWithPath("password") + .type(JsonFieldType.STRING) + .description("회원가입 password"), + fieldWithPath("age") + .type(JsonFieldType.STRING) + .description("연령대. 사용자 정보 옵션 조회 api에서 반환받은 value 값을 전달해 주세요."), + fieldWithPath("hashtags") + .type(JsonFieldType.ARRAY) + .description("관심 해시태그. 사용자 정보 옵션 조회 api에서 반환받은 value 값을 전달해 주세요.")); + + private final List responseFieldDescriptors = + List.of( + fieldWithPath("status").type(JsonFieldType.NUMBER).description("응답 상태"), + fieldWithPath("code").type(JsonFieldType.STRING).description("상태 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("메시지"), + fieldWithPath("data") + .type(JsonFieldType.OBJECT) + .optional() + .description("data")); + + private ResultActions getResultActions(GeneralUserSignupRequest request) throws Exception { + return mockMvc.perform( // api 실행 + RestDocumentationRequestBuilders.post("/api/users") + .contentType(MediaType.APPLICATION_JSON) // request body content type + .content(mapper.writeValueAsString(request))); + } + + // 문서화 반환 함수 + private RestDocumentationResultHandler getDocument(Integer identifier) { + return document( + "generalUserSignUp/" + identifier, + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint(), getModifiedHeader()), + responseFields(responseFieldDescriptors), + requestFields(requestFieldDescriptors), + resource( + ResourceSnippetParameters.builder() + .tag("회원") + .summary("일반 회원가입 api") + .description("일반 회원가입 api입니다.") + .responseFields(responseFieldDescriptors) + .requestFields(requestFieldDescriptors) + .build())); + } + + @Test + @Transactional + void 일반회원가입_성공() throws Exception { + // given + String email = "test1@gmail.com"; + String code = "123456"; + + EmailVerificationLogCreationCommand command = + new EmailVerificationLogCreationCommand(email, code); + EmailVerificationLogs data = EmailVerificationLogs.from(command); + data.updateVerificationStatus(true); + emailVerificationLogsRepository.save(data); + + GeneralUserSignupRequest request = + new GeneralUserSignupRequest( + email, "123456", AgeGroup.FIFTIES, List.of(HashTag.PERFUME)); + + // when + ResultActions resultActions = getResultActions(request); + + // then + resultActions.andExpect(status().isCreated()); + + // documentation + resultActions.andDo(getDocument(1)); + } + + @Test + @Transactional + void 일반회원가입_실패1() throws Exception { + // given + GeneralUserSignupRequest request = + new GeneralUserSignupRequest( + "test1@gmail.com", "123456", AgeGroup.FIFTIES, List.of(HashTag.PERFUME)); + + // when + ResultActions resultActions = getResultActions(request); + + // then + resultActions + .andExpect( + status().is(ErrorResponseCode.EMAIL_NOT_VERIFIED.getHttpStatus().value())) + .andExpect(jsonPath("code").value(ErrorResponseCode.EMAIL_NOT_VERIFIED.getCode())); + + // documentation + resultActions.andDo(getDocument(2)); + } + + @Test + @Transactional + void 일반회원가입_실패2() throws Exception { + // given + + // String email = "test@gmail.com"; + // HashTag[] hashTags = {HashTag.PERFUME}; + // GeneralUserCreationCommand command = + // new GeneralUserCreationCommand(email, "123456", "닉넴", AgeGroup.FIFTIES, + // hashTags); + // userService.createGeneralUser(command); + + GeneralUserSignupRequest request = + new GeneralUserSignupRequest( + "test@gmail.com", "123456", AgeGroup.FIFTIES, List.of(HashTag.PERFUME)); + + // when + ResultActions resultActions = getResultActions(request); + + // then + resultActions + .andExpect( + status().is(ErrorResponseCode.USER_ALREADY_EXISTS.getHttpStatus().value())) + .andExpect(jsonPath("code").value(ErrorResponseCode.USER_ALREADY_EXISTS.getCode())); + + // documentation + resultActions.andDo(getDocument(3)); + } +} diff --git a/src/test/java/com/ftm/server/user/GetUserSignupOptionsTest.java b/src/test/java/com/ftm/server/user/GetUserSignupOptionsTest.java new file mode 100644 index 0000000..142a265 --- /dev/null +++ b/src/test/java/com/ftm/server/user/GetUserSignupOptionsTest.java @@ -0,0 +1,79 @@ +package com.ftm.server.user; + +import static com.epages.restdocs.apispec.ResourceDocumentation.resource; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.epages.restdocs.apispec.ResourceSnippetParameters; +import com.ftm.server.BaseTest; +import jakarta.transaction.Transactional; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.restdocs.payload.FieldDescriptor; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; + +public class GetUserSignupOptionsTest extends BaseTest { + + private final List responseFieldDescriptors = + List.of( + fieldWithPath("status").type(JsonFieldType.NUMBER).description("응답 상태"), + fieldWithPath("code").type(JsonFieldType.STRING).description("상태 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("메시지"), + fieldWithPath("data").type(JsonFieldType.OBJECT).optional().description("data"), + fieldWithPath("data.ages").type(JsonFieldType.ARRAY).description("연령대 목록"), + fieldWithPath("data.ages[].value") + .type(JsonFieldType.STRING) + .description("연령대 고유 값. 회원가입시 전달"), + fieldWithPath("data.ages[].description") + .type(JsonFieldType.STRING) + .description("연령대 고유 값 한글 설명"), + fieldWithPath("data.hashtags").type(JsonFieldType.ARRAY).description("해시태그 목록"), + fieldWithPath("data.hashtags[].value") + .type(JsonFieldType.STRING) + .description("해시태그 고유 값. 회원가입시 전달"), + fieldWithPath("data.hashtags[].description") + .type(JsonFieldType.STRING) + .description("해시태그 고유 값 한글 설명")); + + private ResultActions getResultActions() throws Exception { + return mockMvc.perform( // api 실행 + RestDocumentationRequestBuilders.get("/api/users/options")); + } + + // 문서화 반환 함수 + private RestDocumentationResultHandler getDocument(Integer identifier) { + return document( + "userSignupOptions/" + identifier, + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint(), getModifiedHeader()), + responseFields(responseFieldDescriptors), + resource( + ResourceSnippetParameters.builder() + .tag("회원") + .summary("회원가입시 필요한 정보 목록 조회 api") + .description("회원가입시 사용자에게 받는 연령대 정보와 관심 해시태그 정보 옵션들을 조회합니다.") + .responseFields(responseFieldDescriptors) + .build())); + } + + @Test + @Transactional + void 사용자_정보_옵션조회_성공() throws Exception { + // given + + // when + ResultActions resultActions = getResultActions(); + + // then + resultActions.andExpect(status().isOk()); + + // documentation + resultActions.andDo(getDocument(1)); + } +}