diff --git a/.github/workflows/dev_deploy.yml b/.github/workflows/dev_deploy.yml new file mode 100644 index 0000000..3ad06d2 --- /dev/null +++ b/.github/workflows/dev_deploy.yml @@ -0,0 +1,11 @@ +name: UMC Dev CI/CD // 여러분들 맘대로 이름 지으세요 + +on: + pull_request: + types: [closed] + workflow_dispatch: # (2).수동 실행도 가능하도록 + +jobs: + build: + runs-on: ubuntu-latest # (3).OS환경 + if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'develop' \ No newline at end of file diff --git a/build.gradle b/build.gradle index 6f21bb7..db24019 100644 --- a/build.gradle +++ b/build.gradle @@ -33,6 +33,17 @@ dependencies { testRuntimeOnly 'org.junit.platform:junit-platform-launcher' implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-security' + testImplementation 'org.springframework.security:spring-security-test' + + implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6:3.1.1.RELEASE' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' } diff --git a/src/main/java/sw_workbook/spring/Repository/FoodCategoryRepository.java b/src/main/java/sw_workbook/spring/Repository/FoodCategoryRepository.java new file mode 100644 index 0000000..6024ca3 --- /dev/null +++ b/src/main/java/sw_workbook/spring/Repository/FoodCategoryRepository.java @@ -0,0 +1,7 @@ +package sw_workbook.spring.Repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import sw_workbook.spring.domain.FoodCategory; + +public interface FoodCategoryRepository extends JpaRepository { +} diff --git a/src/main/java/sw_workbook/spring/Repository/MemberRepository.java b/src/main/java/sw_workbook/spring/Repository/MemberRepository.java new file mode 100644 index 0000000..2b80611 --- /dev/null +++ b/src/main/java/sw_workbook/spring/Repository/MemberRepository.java @@ -0,0 +1,18 @@ +package sw_workbook.spring.Repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import sw_workbook.spring.domain.Member; +import sw_workbook.spring.domain.enums.MemberStatus; + +import java.util.List; +import java.util.Optional; + +public interface MemberRepository extends JpaRepository { + + @Query("SELECT m FROM Member m WHERE m.name = :name AND m.status = :status") + List findByNameAndStatus(@Param("name") String name, @Param("status") MemberStatus status); + + Optional findByEmail(String email); +} \ No newline at end of file diff --git a/src/main/java/sw_workbook/spring/apiPayload/code/exception/handler/FoodCategoryHandler.java b/src/main/java/sw_workbook/spring/apiPayload/code/exception/handler/FoodCategoryHandler.java new file mode 100644 index 0000000..a5bde28 --- /dev/null +++ b/src/main/java/sw_workbook/spring/apiPayload/code/exception/handler/FoodCategoryHandler.java @@ -0,0 +1,11 @@ +package sw_workbook.spring.apiPayload.code.exception.handler; + + +import sw_workbook.spring.apiPayload.code.BaseErrorCode; +import sw_workbook.spring.apiPayload.code.exception.GeneralException; + +public class FoodCategoryHandler extends GeneralException { + public FoodCategoryHandler(BaseErrorCode errorCode) { + super(errorCode); + } +} \ No newline at end of file diff --git a/src/main/java/sw_workbook/spring/apiPayload/code/status/ErrorStatus.java b/src/main/java/sw_workbook/spring/apiPayload/code/status/ErrorStatus.java index 6267f97..f740fc3 100644 --- a/src/main/java/sw_workbook/spring/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/sw_workbook/spring/apiPayload/code/status/ErrorStatus.java @@ -23,6 +23,9 @@ public enum ErrorStatus implements BaseErrorCode { MEMBER_NOT_FOUND(HttpStatus.BAD_REQUEST, "MEMBER4001", "사용자가 없습니다."), NICKNAME_NOT_EXIST(HttpStatus.BAD_REQUEST, "MEMBER4002", "닉네임은 필수 입니다."), + // FoodCategory Error + FOOD_CATEGORY_NOT_FOUND(HttpStatus.NOT_FOUND, "FOOD_CATEGORY4001", "음식 카테고리가 없습니다."), + // Article Error ARTICLE_NOT_FOUND(HttpStatus.NOT_FOUND, "ARTICLE4001", "게시글이 없습니다."); @@ -46,6 +49,7 @@ public ErrorReasonDTO getReasonHttpStatus() { .code(code) .isSuccess(false) .httpStatus(httpStatus) - .build(); + .build() + ; } } \ No newline at end of file diff --git a/src/main/java/sw_workbook/spring/config/CustomOAuth2UserService.java b/src/main/java/sw_workbook/spring/config/CustomOAuth2UserService.java new file mode 100644 index 0000000..0d32548 --- /dev/null +++ b/src/main/java/sw_workbook/spring/config/CustomOAuth2UserService.java @@ -0,0 +1,70 @@ +package sw_workbook.spring.config; + +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.DefaultOAuth2User; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; +import sw_workbook.spring.Repository.MemberRepository; +import sw_workbook.spring.domain.Member; +import sw_workbook.spring.domain.enums.Gender; +import sw_workbook.spring.domain.enums.Role; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class CustomOAuth2UserService extends DefaultOAuth2UserService {//DefaultOAuth2UserService<< OAuth2 로그인시 사용자 정보 가져옴 + private final MemberRepository memberRepository; + private final PasswordEncoder passwordEncoder; + + @Override + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException{ + + OAuth2User oAuth2User = super.loadUser(userRequest);//loadUser(userRequest): OAuth2 로그인 요청이 들어오면 실행되는 메서드입니다. + //카톡에서 제공하는 사용자 정보 오어스2 객체로 받아옴 + + Map attributes = oAuth2User.getAttributes();//attribute는 사용자의 기본정보 포함 + Map properties = (Map) attributes.get("properties"); + //Auth2 로그인에서 반환된 사용자 정보 중 특정 서브 정보를 추출, properties라는 속성 내에 추가적인 사용자 정보를 저장 + + String nickname = (String) properties.get("nickname"); + String email = nickname + "@kakao.com"; // 임시 이메일 생성 + + // 사용자 정보 저장 또는 업데이트 + Member member = saveOrUpdateUser(email, nickname); + + // 이메일을 Principal로 사용하기 위해 attributes 수정 + Map modifiedAttributes = new HashMap<>(attributes); + modifiedAttributes.put("email", email); + + return new DefaultOAuth2User( + oAuth2User.getAuthorities(), + modifiedAttributes, + "email" // email Principal로 설정 + ); + } + + private Member saveOrUpdateUser(String email, String nickname) { + Member member = memberRepository.findByEmail(email) + .orElse(Member.builder() + .email(email) + .name(nickname) + .password(passwordEncoder.encode("OAUTH_USER_" + UUID.randomUUID())) + .gender(Gender.NONE) // 기본값 설정 + .address("소셜로그인") // 기본값 설정 + .specAddress("소셜로그인") // 기본값 설정 + .role(Role.USER) + .build()); + + return memberRepository.save(member); + } + + + +} diff --git a/src/main/java/sw_workbook/spring/config/CustomUserDetailsService.java b/src/main/java/sw_workbook/spring/config/CustomUserDetailsService.java new file mode 100644 index 0000000..09db749 --- /dev/null +++ b/src/main/java/sw_workbook/spring/config/CustomUserDetailsService.java @@ -0,0 +1,29 @@ +package sw_workbook.spring.config; + + +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import sw_workbook.spring.Repository.MemberRepository; +import sw_workbook.spring.domain.Member; + +@Service +@RequiredArgsConstructor +public class CustomUserDetailsService implements UserDetailsService { + + private final MemberRepository memberRepository; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + Member member = memberRepository.findByEmail(username) + .orElseThrow(() -> new UsernameNotFoundException("해당 이메일을 가진 유저가 존재하지 않습니다: " + username)); + + return org.springframework.security.core.userdetails.User + .withUsername(member.getEmail()) + .password(member.getPassword()) + .roles(member.getRole().name()) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/sw_workbook/spring/config/SecurityConfig.java b/src/main/java/sw_workbook/spring/config/SecurityConfig.java new file mode 100644 index 0000000..9edbbb4 --- /dev/null +++ b/src/main/java/sw_workbook/spring/config/SecurityConfig.java @@ -0,0 +1,47 @@ +package sw_workbook.spring.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; + +@EnableWebSecurity +@Configuration +public class SecurityConfig { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests((requests) -> requests + .requestMatchers("/", "/home", "/signup", "/members/signup", "api/hello","/css/**").permitAll() + .requestMatchers("/admin/**").hasRole("ADMIN") + .anyRequest().authenticated() + ) + .formLogin((form) -> form + .loginPage("/login") + .defaultSuccessUrl("/home", true) + .permitAll() + ) + .logout((logout) -> logout + .logoutUrl("/logout") + .logoutSuccessUrl("/login?logout") + .permitAll() + ) + .oauth2Login(oauth2 -> oauth2 + .loginPage("/login") + .defaultSuccessUrl("/home",true) + .permitAll() + ); + + + return http.build(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} \ No newline at end of file diff --git a/src/main/java/sw_workbook/spring/config/WebConfig.java b/src/main/java/sw_workbook/spring/config/WebConfig.java new file mode 100644 index 0000000..0c3c6bf --- /dev/null +++ b/src/main/java/sw_workbook/spring/config/WebConfig.java @@ -0,0 +1,19 @@ +package sw_workbook.spring.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry registry){ + registry.addMapping("/**") + .allowedOrigins("http://localhost:5173") + .allowedMethods("GET","POST","PUT","DELETE") + .allowedHeaders("*") + .allowCredentials(true); + } + +} diff --git a/src/main/java/sw_workbook/spring/converter/MemberConverter.java b/src/main/java/sw_workbook/spring/converter/MemberConverter.java new file mode 100644 index 0000000..adf8146 --- /dev/null +++ b/src/main/java/sw_workbook/spring/converter/MemberConverter.java @@ -0,0 +1,46 @@ +package sw_workbook.spring.converter; + +import sw_workbook.spring.domain.Member; +import sw_workbook.spring.domain.enums.Gender; +import sw_workbook.spring.web.dto.MemberRequestDTO; +import sw_workbook.spring.web.dto.MemberResponseDTO; + +import java.time.LocalDateTime; +import java.util.ArrayList; + +public class MemberConverter { + + public static MemberResponseDTO.JoinResultDTO toJoinResultDTO(Member member){ + return MemberResponseDTO.JoinResultDTO.builder() + .memberId(member.getId()) + .createdAt(LocalDateTime.now()) + .build(); + } + public static Member toMember(MemberRequestDTO.JoinDto request){ + + Gender gender = null; + + switch (request.getGender()){ + case 1: + gender = Gender.MALE; + break; + case 2: + gender = Gender.FEMALE; + break; + case 3: + gender = Gender.NONE; + break; + } + + return Member.builder() + .name(request.getName()) + .email(request.getEmail()) // 추가된 코드 + .password(request.getPassword()) // 추가된 코드 + .gender(gender) + .address(request.getAddress()) + .specAddress(request.getSpecAddress()) + .role(request.getRole()) // 추가된 코드 + .memberPreferList(new ArrayList<>()) + .build(); + } +} diff --git a/src/main/java/sw_workbook/spring/converter/MemberPreferConverter.java b/src/main/java/sw_workbook/spring/converter/MemberPreferConverter.java new file mode 100644 index 0000000..850520e --- /dev/null +++ b/src/main/java/sw_workbook/spring/converter/MemberPreferConverter.java @@ -0,0 +1,20 @@ +package sw_workbook.spring.converter; + +import sw_workbook.spring.domain.FoodCategory; +import sw_workbook.spring.domain.mapping.MemberPrefer; + +import java.util.List; +import java.util.stream.Collectors; + +public class MemberPreferConverter { + + public static List toMemberPreferList(List foodCategoryList){ + + return foodCategoryList.stream() + .map(foodCategory -> + MemberPrefer.builder() + .foodCategory(foodCategory) + .build() + ).collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/src/main/java/sw_workbook/spring/domain/Member.java b/src/main/java/sw_workbook/spring/domain/Member.java index fbfc939..8fad416 100644 --- a/src/main/java/sw_workbook/spring/domain/Member.java +++ b/src/main/java/sw_workbook/spring/domain/Member.java @@ -8,6 +8,7 @@ import sw_workbook.spring.domain.common.BaseEntity; import sw_workbook.spring.domain.enums.Gender; import sw_workbook.spring.domain.enums.MemberStatus; +import sw_workbook.spring.domain.enums.Role; import sw_workbook.spring.domain.enums.SocialType; import sw_workbook.spring.domain.mapping.MemberAgree; import sw_workbook.spring.domain.mapping.MemberMission; @@ -53,9 +54,19 @@ public class Member extends BaseEntity { private LocalDate inactiveDate; - // @Column(nullable = false, length = 50) + @Column(unique = true, nullable = false) private String email; + @Column(nullable = false) + private String password; + + @Enumerated(EnumType.STRING) + private Role role; + + public void encodePassword(String password){ + this.password = password; + } + @ColumnDefault("0") private Integer point; @@ -70,4 +81,6 @@ public class Member extends BaseEntity { @OneToMany(mappedBy = "member", cascade = CascadeType.ALL) private List memberMissionList = new ArrayList<>(); + + } \ No newline at end of file diff --git a/src/main/java/sw_workbook/spring/domain/enums/Role.java b/src/main/java/sw_workbook/spring/domain/enums/Role.java new file mode 100644 index 0000000..23ff58c --- /dev/null +++ b/src/main/java/sw_workbook/spring/domain/enums/Role.java @@ -0,0 +1,5 @@ +package sw_workbook.spring.domain.enums; + +public enum Role { + ADMIN, USER +} diff --git a/src/main/java/sw_workbook/spring/service/memberservice/MemberCommandService.java b/src/main/java/sw_workbook/spring/service/memberservice/MemberCommandService.java new file mode 100644 index 0000000..30f11b9 --- /dev/null +++ b/src/main/java/sw_workbook/spring/service/memberservice/MemberCommandService.java @@ -0,0 +1,9 @@ +package sw_workbook.spring.service.memberservice; + +import sw_workbook.spring.domain.Member; +import sw_workbook.spring.web.dto.MemberRequestDTO; + +public interface MemberCommandService { + + Member joinMember(MemberRequestDTO.JoinDto request); +} \ No newline at end of file diff --git a/src/main/java/sw_workbook/spring/service/memberservice/MemberCommandServiceImpl.java b/src/main/java/sw_workbook/spring/service/memberservice/MemberCommandServiceImpl.java new file mode 100644 index 0000000..09bd212 --- /dev/null +++ b/src/main/java/sw_workbook/spring/service/memberservice/MemberCommandServiceImpl.java @@ -0,0 +1,50 @@ +package sw_workbook.spring.service.memberservice; + +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import sw_workbook.spring.Repository.FoodCategoryRepository; +import sw_workbook.spring.Repository.MemberRepository; +import sw_workbook.spring.apiPayload.code.exception.handler.FoodCategoryHandler; +import sw_workbook.spring.apiPayload.code.status.ErrorStatus; +import sw_workbook.spring.converter.MemberConverter; +import sw_workbook.spring.converter.MemberPreferConverter; +import sw_workbook.spring.domain.FoodCategory; +import sw_workbook.spring.domain.Member; +import sw_workbook.spring.domain.mapping.MemberPrefer; +import sw_workbook.spring.web.dto.MemberRequestDTO; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class MemberCommandServiceImpl implements MemberCommandService{ + + private final MemberRepository memberRepository; + private final FoodCategoryRepository foodCategoryRepository; + private final PasswordEncoder passwordEncoder; + @Override + @Transactional + public Member joinMember(MemberRequestDTO.JoinDto request) { + + Member newMember = MemberConverter.toMember(request); + + newMember.encodePassword(passwordEncoder.encode(request.getPassword())); + + List foodCategoryList = request.getPreferCategory().stream() + .map(category -> { + return foodCategoryRepository.findById(category).orElseThrow(() -> new FoodCategoryHandler(ErrorStatus.FOOD_CATEGORY_NOT_FOUND)); + }).collect(Collectors.toList()); + //List foodCategoryList = [FoodCategory(1), FoodCategory(3), FoodCategory(5)]; 들어감 + + List memberPreferList = MemberPreferConverter.toMemberPreferList(foodCategoryList); + //리스트형으로 변환 + + memberPreferList.forEach(memberPrefer -> {memberPrefer.setMember(newMember);}); + + return memberRepository.save(newMember); + + } +} \ No newline at end of file diff --git a/src/main/java/sw_workbook/spring/service/memberservice/MemberQueryService.java b/src/main/java/sw_workbook/spring/service/memberservice/MemberQueryService.java new file mode 100644 index 0000000..33c54e5 --- /dev/null +++ b/src/main/java/sw_workbook/spring/service/memberservice/MemberQueryService.java @@ -0,0 +1,11 @@ +package sw_workbook.spring.service.memberservice; + +import sw_workbook.spring.domain.Member; + +import java.util.Optional; + +public interface MemberQueryService { + + Optional findMember(Long id); + +} \ No newline at end of file diff --git a/src/main/java/sw_workbook/spring/service/memberservice/MemberQueryServiceImpl.java b/src/main/java/sw_workbook/spring/service/memberservice/MemberQueryServiceImpl.java new file mode 100644 index 0000000..35afce0 --- /dev/null +++ b/src/main/java/sw_workbook/spring/service/memberservice/MemberQueryServiceImpl.java @@ -0,0 +1,23 @@ +package sw_workbook.spring.service.memberservice; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import sw_workbook.spring.Repository.MemberRepository; +import sw_workbook.spring.domain.Member; + +import java.util.Optional; + + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class MemberQueryServiceImpl implements MemberQueryService{ + + private final MemberRepository memberRepository; + + @Override + public Optional findMember(Long id) { + return memberRepository.findById(id); + } +} \ No newline at end of file diff --git a/src/main/java/sw_workbook/spring/vaildation/annotation/ExistCategories.java b/src/main/java/sw_workbook/spring/vaildation/annotation/ExistCategories.java new file mode 100644 index 0000000..493b2ef --- /dev/null +++ b/src/main/java/sw_workbook/spring/vaildation/annotation/ExistCategories.java @@ -0,0 +1,18 @@ +package sw_workbook.spring.vaildation.annotation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import sw_workbook.spring.vaildation.vaildator.CategoriesExistValidator; + +import java.lang.annotation.*; + +@Documented +@Constraint(validatedBy = CategoriesExistValidator.class) +@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExistCategories { + + String message() default "해당하는 카테고리가 존재하지 않습니다."; + Class[] groups() default {}; + Class[] payload() default {}; +} diff --git a/src/main/java/sw_workbook/spring/vaildation/vaildator/CategoriesExistValidator.java b/src/main/java/sw_workbook/spring/vaildation/vaildator/CategoriesExistValidator.java new file mode 100644 index 0000000..f7df05d --- /dev/null +++ b/src/main/java/sw_workbook/spring/vaildation/vaildator/CategoriesExistValidator.java @@ -0,0 +1,37 @@ +package sw_workbook.spring.vaildation.vaildator; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import sw_workbook.spring.Repository.FoodCategoryRepository; +import sw_workbook.spring.apiPayload.code.status.ErrorStatus; +import sw_workbook.spring.vaildation.annotation.ExistCategories; + +import java.util.List; + +@Component +@RequiredArgsConstructor +public class CategoriesExistValidator implements ConstraintValidator> { + + private final FoodCategoryRepository foodCategoryRepository; + + @Override + public void initialize(ExistCategories constraintAnnotation) { + ConstraintValidator.super.initialize(constraintAnnotation); + } + + @Override + public boolean isValid(List values, ConstraintValidatorContext context) { + boolean isValid = values.stream() + .allMatch(value -> foodCategoryRepository.existsById(value)); + + if (!isValid) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(ErrorStatus.FOOD_CATEGORY_NOT_FOUND.toString()).addConstraintViolation(); + } + + return isValid; + + } +} \ No newline at end of file diff --git a/src/main/java/sw_workbook/spring/web/controller/MemberRestController.java b/src/main/java/sw_workbook/spring/web/controller/MemberRestController.java new file mode 100644 index 0000000..8d9d41c --- /dev/null +++ b/src/main/java/sw_workbook/spring/web/controller/MemberRestController.java @@ -0,0 +1,33 @@ +package sw_workbook.spring.web.controller; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.*; +import sw_workbook.spring.apiPayload.ApiResponse; +import sw_workbook.spring.converter.MemberConverter; +import sw_workbook.spring.domain.Member; +import sw_workbook.spring.service.memberservice.MemberCommandService; +import sw_workbook.spring.web.dto.MemberRequestDTO; +import sw_workbook.spring.web.dto.MemberResponseDTO; + +@RestController +@RequiredArgsConstructor +public class MemberRestController { + + private final MemberCommandService memberCommandService; + +// @PostMapping("/") +// public ApiResponse join(@RequestBody @Valid MemberRequestDTO.JoinDto request){ +// Member member = memberCommandService.joinMember(request); +// return ApiResponse.onSuccess(MemberConverter.toJoinResultDTO(member)); +// } + @GetMapping("api/hello") + public String index(){ + return "HEllo from SPring BOOT"; + } + + +} diff --git a/src/main/java/sw_workbook/spring/web/controller/MemberViewController.java b/src/main/java/sw_workbook/spring/web/controller/MemberViewController.java new file mode 100644 index 0000000..c545262 --- /dev/null +++ b/src/main/java/sw_workbook/spring/web/controller/MemberViewController.java @@ -0,0 +1,57 @@ +package sw_workbook.spring.web.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import sw_workbook.spring.service.memberservice.MemberCommandService; +import sw_workbook.spring.web.dto.MemberRequestDTO; + +@Controller +@RequiredArgsConstructor +public class MemberViewController { + + private final MemberCommandService memberCommandService; + + + + @GetMapping("/home") + public String home() { + return "home"; + } + + @GetMapping("/admin") + public String admin() { + return "admin"; + } + @GetMapping("/login") + public String loginPage() { + return "login"; + } + @GetMapping("/signup") + public String signupPage(Model model) { + model.addAttribute("memberJoinDto", new MemberRequestDTO.JoinDto()); // 모델에 DTO 객체 추가 + return "signup"; + } + @PostMapping("members/signup") + public String joinMember(@ModelAttribute("memberJoinDto") MemberRequestDTO.JoinDto request, + BindingResult bindingResult, + Model model) { + + if (bindingResult.hasErrors()) { + return "signup"; + } + try { + memberCommandService.joinMember(request); + return "redirect:/login"; + } catch (Exception e) { + model.addAttribute("error", e.getMessage()); + return "signup"; + } + } + +} diff --git a/src/main/java/sw_workbook/spring/web/dto/JoinDto.java b/src/main/java/sw_workbook/spring/web/dto/JoinDto.java new file mode 100644 index 0000000..6902e38 --- /dev/null +++ b/src/main/java/sw_workbook/spring/web/dto/JoinDto.java @@ -0,0 +1,20 @@ +package sw_workbook.spring.web.dto; + +import lombok.Getter; +import sw_workbook.spring.vaildation.annotation.ExistCategories; + +import java.util.List; + +@Getter +public class JoinDto { + String name; + Integer gender; + Integer birthYear; + Integer birthMonth; + Integer birthDay; + String address; + String specAddress; + + @ExistCategories + List preferCategory; +} diff --git a/src/main/java/sw_workbook/spring/web/dto/MemberRequestDTO.java b/src/main/java/sw_workbook/spring/web/dto/MemberRequestDTO.java new file mode 100644 index 0000000..71097d0 --- /dev/null +++ b/src/main/java/sw_workbook/spring/web/dto/MemberRequestDTO.java @@ -0,0 +1,44 @@ +package sw_workbook.spring.web.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Getter; +import lombok.Setter; +import sw_workbook.spring.domain.enums.Role; +import sw_workbook.spring.vaildation.annotation.ExistCategories; + +import java.util.List; + +public class MemberRequestDTO { + + @Getter + @Setter// tymeleaf에서 사용하기 위해 추가함 + public static class JoinDto{ + @NotBlank + String name; + + @Email + String email; + @NotBlank + String password; + @NotNull + Role role; + + @NotNull + Integer gender; + @NotNull + Integer birthYear; + @NotNull + Integer birthMonth; + @NotNull + Integer birthDay; + @Size(min = 5, max = 12) + String address; + @Size(min = 5, max = 12) + String specAddress; + @ExistCategories + List preferCategory; + } +} \ No newline at end of file diff --git a/src/main/java/sw_workbook/spring/web/dto/MemberResponseDTO.java b/src/main/java/sw_workbook/spring/web/dto/MemberResponseDTO.java new file mode 100644 index 0000000..01450ae --- /dev/null +++ b/src/main/java/sw_workbook/spring/web/dto/MemberResponseDTO.java @@ -0,0 +1,20 @@ +package sw_workbook.spring.web.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +public class MemberResponseDTO { + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class JoinResultDTO{ + Long memberId; + LocalDateTime createdAt; + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 3339144..2f300b9 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -10,10 +10,29 @@ spring: jpa: properties: hibernate: - dialect: org.hibernate.dialect.MySQL8Dialect + dialect: org.hibernate.dialect.MySQLDialect # 수정된 부분 show_sql: true format_sql: true use_sql_comments: true hbm2ddl: auto: update - default_batch_fetch_size: 1000 \ No newline at end of file + default_batch_fetch_size: 1000 + + security: + oauth2: + client: + registration: + kakao: + client-authentication-method: client_secret_post + client-id: 5eea303692395e4d36e5eff8b6133ea6 + client-secret: LxA67TvEovut6iCUEeOpl6Ti1OJaG4iI + redirect-uri: http://localhost:8080/login/oauth2/code/kakao + authorization-grant-type: authorization_code + scope: profile_nickname + client-name: Kakao + provider: + kakao: + authorization-uri: https://kauth.kakao.com/oauth/authorize + token-uri: https://kauth.kakao.com/oauth/token + user-info-uri: https://kapi.kakao.com/v2/user/me + user-name-attribute: id diff --git a/src/main/resources/templates/admin.html b/src/main/resources/templates/admin.html new file mode 100644 index 0000000..55dbff1 --- /dev/null +++ b/src/main/resources/templates/admin.html @@ -0,0 +1,10 @@ + + + + Admin Page + + +

Admin Page

+

관리자만 접근할 수 있는 페이지입니다.

+ + \ No newline at end of file diff --git a/src/main/resources/templates/home.html b/src/main/resources/templates/home.html new file mode 100644 index 0000000..8c10cb1 --- /dev/null +++ b/src/main/resources/templates/home.html @@ -0,0 +1,20 @@ + + + + Home + + +

Welcome to Home Page!

+ +

+ + + + + +
+ +
+ \ No newline at end of file diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html new file mode 100644 index 0000000..878af08 --- /dev/null +++ b/src/main/resources/templates/login.html @@ -0,0 +1,31 @@ + + + + Login + + +

Login

+
+
+ + +
+
+ + +
+ +
+ +

사용자 이름 또는 비밀번호가 잘못되었습니다.

+

로그아웃되었습니다.

+ + +

계정이 없나요? Sign up

+ + + + + \ No newline at end of file diff --git a/src/main/resources/templates/signup.html b/src/main/resources/templates/signup.html new file mode 100644 index 0000000..96f68b3 --- /dev/null +++ b/src/main/resources/templates/signup.html @@ -0,0 +1,72 @@ + + + + 회원가입 + + + +

회원가입

+
+
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + + +
+
+
+ + +
+ +
+ + \ No newline at end of file