Skip to content

Commit

Permalink
Merge pull request #21 from UMC-7/week8/mission/genie
Browse files Browse the repository at this point in the history
Week8/mission/genie
  • Loading branch information
kimtree24 authored Nov 29, 2024
2 parents cc5d44f + daa223b commit ae9d354
Show file tree
Hide file tree
Showing 67 changed files with 1,568 additions and 19 deletions.
14 changes: 14 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ dependencies {
implementation 'mysql:mysql-connector-java:8.0.33' // MySQL 드라이버 추가
implementation 'org.springframework.boot:spring-boot-starter-validation' // week7 - 에러 핸들러

implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' // week8 - swagger세팅
implementation 'org.springframework.boot:spring-boot-starter-validation' // week8 - 사용자 정의 어노테이션

runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
Expand All @@ -52,6 +55,17 @@ dependencies {
implementation 'org.springdoc:springdoc-openapi-data-rest:1.6.9'

testImplementation 'org.springframework.boot:spring-boot-starter-test'

// 소셜 로그인 관련 디펜던시
implementation 'org.springframework.boot:spring-boot-starter-security'
testImplementation 'org.springframework.security:spring-security-test'

// Thymeleaf 의존성
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6:3.1.1.RELEASE'

// Spring Security OAuth2 클라이언트 의존성 추가
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
}

sourceSets {
Expand Down
4 changes: 4 additions & 0 deletions src/main/generated/umm/spring/study/domain/QMember.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,14 @@ public class QMember extends EntityPathBase<Member> {

public final StringPath name = createString("name");

public final StringPath password = createString("password");

public final NumberPath<Integer> point = createNumber("point", Integer.class);

public final ListPath<Review, QReview> reviewList = this.<Review, QReview>createList("reviewList", Review.class, QReview.class, PathInits.DIRECT2);

public final EnumPath<umm.spring.study.domain.enums.Role> role = createEnum("role", umm.spring.study.domain.enums.Role.class);

public final EnumPath<umm.spring.study.domain.enums.SocialType> socialType = createEnum("socialType", umm.spring.study.domain.enums.SocialType.class);

public final StringPath specAddress = createString("specAddress");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.http.HttpStatus;
import umm.spring.study.apiPayload.code.BaseErrorCode;
import umm.spring.study.apiPayload.code.ErrorReasonDTO;
import umm.spring.study.domain.Store;

@Getter
@AllArgsConstructor
Expand All @@ -19,17 +20,24 @@ public enum ErrorStatus implements BaseErrorCode {
_FORBIDDEN(HttpStatus.FORBIDDEN, "COMMON403", "금지된 요청입니다."),


// 멤버 관려 에러
// 멤버 관련 에러
MEMBER_NOT_FOUND(HttpStatus.BAD_REQUEST, "MEMBER4001", "사용자가 없습니다."),
NICKNAME_NOT_EXIST(HttpStatus.BAD_REQUEST, "MEMBER4002", "닉네임은 필수 입니다."),

// 예시,,,
ARTICLE_NOT_FOUND(HttpStatus.NOT_FOUND, "ARTICLE4001", "게시글이 없습니다."),

// For test
TEMP_EXCEPTION(HttpStatus.BAD_REQUEST, "TEMP4001", "이거는 테스트");
TEMP_EXCEPTION(HttpStatus.BAD_REQUEST, "TEMP4001", "이거는 테스트"),

// 음식 카테고리 관련 에러
FOOD_CATEGORY_NOT_FOUND(HttpStatus.BAD_REQUEST, "FOODCATEGORY4001", "음식 카테고리 미설정."),

// 미션 아이디 관련 에러
MISSION_CATEGORY_NOT_FOUND(HttpStatus.BAD_REQUEST, "MISSION4001", "미션 아이디 없음."),

// 가게 없음 에러
STORE_NOT_FOUND(HttpStatus.BAD_REQUEST, "STORE4001", "가게 없음.");
private final HttpStatus httpStatus;
private final String code;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package umm.spring.study.apiPayload.exception.handler;

import umm.spring.study.apiPayload.code.BaseErrorCode;
import umm.spring.study.apiPayload.exception.GeneralException;

public class FoodCategoryHandler extends GeneralException {
public FoodCategoryHandler(BaseErrorCode errorCode) {
super(errorCode);
}
}
39 changes: 39 additions & 0 deletions src/main/java/umm/spring/study/config/SwaggerConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package umm.spring.study.config;

import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SwaggerConfig {

@Bean
public OpenAPI UMCstudyAPI() {
Info info = new Info()
.title("UMC Server WorkBook API")
.description("UMC Server WorkBook API 명세서")
.version("1.0.0");

String jwtSchemeName = "JWT TOKEN";
// API 요청헤더에 인증정보 포함
SecurityRequirement securityRequirement = new SecurityRequirement().addList(jwtSchemeName);
// SecuritySchemes 등록
Components components = new Components()
.addSecuritySchemes(jwtSchemeName, new SecurityScheme()
.name(jwtSchemeName)
.type(SecurityScheme.Type.HTTP) // HTTP 방식
.scheme("bearer")
.bearerFormat("JWT"));

return new OpenAPI()
.addServersItem(new Server().url("/"))
.info(info)
.addSecurityItem(securityRequirement)
.components(components);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package umm.spring.study.config.security;

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 umm.spring.study.domain.Member;
import umm.spring.study.domain.enums.Gender;
import umm.spring.study.domain.enums.Role;
import umm.spring.study.repository.MemberRepository;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@Service
@RequiredArgsConstructor
public class CustomOAuth2UserService extends DefaultOAuth2UserService {

private final MemberRepository memberRepository;
private final PasswordEncoder passwordEncoder;

@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);

Map<String, Object> attributes = oAuth2User.getAttributes();
Map<String, Object> properties = (Map<String, Object>) attributes.get("properties");

String nickname = (String) properties.get("nickname");
String email = nickname + "@kakao.com"; // 임시 이메일 생성

// 사용자 정보 저장 또는 업데이트
Member member = saveOrUpdateUser(email, nickname);

// 이메일을 Principal로 사용하기 위해 attributes 수정
Map<String, Object> 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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package umm.spring.study.config.security;

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 umm.spring.study.domain.Member;
import umm.spring.study.repository.MemberRepository;

@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();
}
}
46 changes: 46 additions & 0 deletions src/main/java/umm/spring/study/config/security/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package umm.spring.study.config.security;

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", "/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();
}
}
62 changes: 62 additions & 0 deletions src/main/java/umm/spring/study/converter/MemberConverter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// DTO와 엔티티 객체 간 변환을 담당
// 즉, DTO 변수를 기반으로 객체로 만드는 메서드들의 집합

package umm.spring.study.converter;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import umm.spring.study.domain.Member;
import umm.spring.study.domain.enums.Gender;
import umm.spring.study.repository.MemberRepository;
import umm.spring.study.service.MemberService.MemberCommandService;
import umm.spring.study.web.dto.MemberRequestDTO;
import umm.spring.study.web.dto.MemberResponseDTO;

import java.time.LocalDateTime;
import java.util.ArrayList;

@Service
@RequiredArgsConstructor
public class MemberConverter {

// Member 엔티티를 MemberResponseDTO.JoinResultDTO로 변환
// API 응답에 필요한 데이터만 담는 DTO로 변환하는 역할을 함
// 클라이언트 요청 바탕으로 데이터베이스에 저장된 것을 클라이언트에 응답으로 보내기 위한 작업
public static MemberResponseDTO.JoinResultDTO toJoinResultDTO(Member member){
return MemberResponseDTO.JoinResultDTO.builder()
.memberId(member.getId())
.createdAt(LocalDateTime.now())
.build();
}

// 클라이언트 요청시 MemberCommandServiceImpl의 joinMember() 에서 여기를 호출
// 클라이언트에서 전달받은 JoinDTO 요청 데이터를 기반으로 새로운 Member 엔티티 생성
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<>())
.age(2024-request.getBirthYear())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package umm.spring.study.converter;

import umm.spring.study.domain.FoodCategory;
import umm.spring.study.domain.mapping.MemberPrefer;

import java.util.List;
import java.util.stream.Collectors;

public class MemberPreferConverter {

public static List<MemberPrefer> toMemberPreferList(List<FoodCategory> foodCategoryList){

return foodCategoryList.stream()
.map(foodCategory ->
MemberPrefer.builder()
.foodCategory(foodCategory)
.build()
).collect(Collectors.toList());
}
}

29 changes: 29 additions & 0 deletions src/main/java/umm/spring/study/converter/MissionConverter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package umm.spring.study.converter;

import umm.spring.study.domain.Member;
import umm.spring.study.domain.Mission;
import umm.spring.study.domain.mapping.MemberMission;
import umm.spring.study.web.dto.MissionResponseDTO;

import java.time.LocalDateTime;

import static umm.spring.study.domain.enums.MissionStatus.CHALLENGING;

public class MissionConverter {
public static MemberMission toMemberMission(Member member, Mission mission) {
return MemberMission.builder()
.member(member)
.mission(mission)
.status(CHALLENGING)
.build();
}

public static MissionResponseDTO.AddChallengeResultDTO toAddResultDTO(MemberMission memberMission){
return MissionResponseDTO.AddChallengeResultDTO.builder()
.missionId(memberMission.getMission().getId())
.memberId(memberMission.getMember().getId())
.addAt(LocalDateTime.now())
.build();
}

}
Loading

0 comments on commit ae9d354

Please sign in to comment.