Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ dependencies {
// Spring Security
implementation 'org.springframework.boot:spring-boot-starter-security'
testImplementation 'org.springframework.security:spring-security-test'

// Jwt
implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
implementation 'io.jsonwebtoken:jjwt-impl:0.12.3'
implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3'
implementation 'org.springframework.boot:spring-boot-configuration-processor'

// OAuth
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
}

tasks.named('test') {
Expand Down
14 changes: 10 additions & 4 deletions src/main/java/umc/domain/auth/controller/AuthController.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@
import org.springframework.web.bind.annotation.RestController;
import umc.domain.auth.dto.AuthReqDTO;
import umc.domain.auth.dto.AuthResDTO;
import umc.domain.auth.exception.code.AuthSuccessCode;
import umc.domain.auth.service.AuthService;
import umc.domain.member.dto.MemberReqDTO;
import umc.domain.member.dto.MemberResDTO;
import umc.domain.member.exception.code.MemberSuccessCode;
import umc.global.apiPayload.ApiResponse;

@RestController
Expand All @@ -26,6 +24,14 @@ public ApiResponse<AuthResDTO.SignUpDTO> signUp(
@RequestBody @Valid AuthReqDTO.SignUpDTO reqDto
){
AuthResDTO.SignUpDTO resDto = authService.signUp(reqDto);
return ApiResponse.onSuccess(MemberSuccessCode.CREATED, resDto);
return ApiResponse.onSuccess(AuthSuccessCode.SIGN_UP, resDto);
}

@PostMapping("/login")
public ApiResponse<AuthResDTO.LoginDTO> login(
@RequestBody @Valid AuthReqDTO.LoginDTO reqDto
) {
AuthResDTO.LoginDTO resDto = authService.login(reqDto);
return ApiResponse.onSuccess(AuthSuccessCode.LOGIN_SUCCESS, resDto);
}
}
14 changes: 10 additions & 4 deletions src/main/java/umc/domain/auth/dto/AuthReqDTO.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package umc.domain.auth.dto;

import jakarta.validation.Valid;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.*;
import umc.domain.member.enums.Gender;

import java.util.List;
Expand All @@ -27,6 +24,7 @@ public record SignUpDTO(
@NotBlank
String address,
@Valid
@NotEmpty
List<TermDTO> terms,
@Valid
List<FoodPreferenceDTO> foodPreferences
Expand All @@ -43,4 +41,12 @@ public record FoodPreferenceDTO(
Long foodId
) {}
}

public record LoginDTO(
@NotBlank
@Email
String email,
@NotBlank
String password
) {}
}
5 changes: 5 additions & 0 deletions src/main/java/umc/domain/auth/dto/AuthResDTO.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,9 @@ public record SignUpDTO(
LocalDate birth,
String address
) {}

@Builder
public record LoginDTO(
String accessToken
) {}
}
17 changes: 12 additions & 5 deletions src/main/java/umc/domain/auth/exception/code/AuthErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,18 @@
@AllArgsConstructor
public enum AuthErrorCode implements BaseErrorCode {

DUPLICATED_EMAIL(HttpStatus.NOT_FOUND, "AUTH400_1", "이미 가입되어있는 이메일입니다."),
REQUIRED_TERM_NOT_AGREED(HttpStatus.BAD_REQUEST, "AUTH404_1", "필수 약관은 동의해야 합니다."),
INVALID_TERM(HttpStatus.BAD_REQUEST, "AUTH404_2", "유효하지 않은 약관입니다."),
INVALID_FOOD(HttpStatus.BAD_REQUEST, "AUTH404_3", "유효하지 않은 음식입니다."),
TERMS_MISMATCH(HttpStatus.BAD_REQUEST, "AUTH404_4", "모든 약관에 대한 동의 여부가 필요합니다."),
// 400
REQUIRED_TERM_NOT_AGREED(HttpStatus.BAD_REQUEST, "AUTH400_1", "필수 약관은 동의해야 합니다."),
INVALID_TERM(HttpStatus.BAD_REQUEST, "AUTH400_2", "유효하지 않은 약관입니다."),
INVALID_FOOD(HttpStatus.BAD_REQUEST, "AUTH400_3", "유효하지 않은 음식입니다."),
TERMS_MISMATCH(HttpStatus.BAD_REQUEST, "AUTH400_4", "모든 약관에 대한 동의 여부가 필요합니다."),
NOT_SUPPORT_SOCIAL_PROVIDER(HttpStatus.BAD_REQUEST, "AUTH400_5", "지원하지 않는 소셜 로그인입니다."),

// 401
INVALID_LOGIN_FORM(HttpStatus.UNAUTHORIZED, "AUTH401_2", "아이디나 비밀번호가 틀렸습니다."),

// 409
DUPLICATED_EMAIL(HttpStatus.CONFLICT, "AUTH409_1", "이미 가입되어있는 이메일입니다."),
;

private final HttpStatus status;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,22 @@
package umc.domain.auth.exception.code;

public enum AuthSuccessCode {
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;
import umc.global.apiPayload.code.BaseSuccessCode;

@Getter
@AllArgsConstructor
public enum AuthSuccessCode implements BaseSuccessCode {

// 200
LOGIN_SUCCESS(HttpStatus.OK, "AUTH200_1", "로그인이 성공적으로 완료되었습니다."),

// 201
SIGN_UP(HttpStatus.CREATED, "AUTH201_1", "회원가입이 완료되었습니다."),
;

private final HttpStatus status;
private final String code;
private final String message;
}
41 changes: 35 additions & 6 deletions src/main/java/umc/domain/auth/service/AuthService.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package umc.domain.auth.service;

import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -15,6 +19,8 @@
import umc.domain.member.repository.FoodRepository;
import umc.domain.member.repository.MemberRepository;
import umc.domain.member.repository.TermRepository;
import umc.global.security.entity.AuthMember;
import umc.global.security.util.JwtUtil;

import java.util.List;
import java.util.Map;
Expand All @@ -30,6 +36,8 @@ public class AuthService {
private final PasswordEncoder passwordEncoder;
private final TermRepository termRepository;
private final FoodRepository foodRepository;
private final AuthenticationManager authenticationManager;
private final JwtUtil jwtUtil;

@Transactional
public AuthResDTO.SignUpDTO signUp(AuthReqDTO.SignUpDTO reqDto) {
Expand All @@ -47,6 +55,22 @@ public AuthResDTO.SignUpDTO signUp(AuthReqDTO.SignUpDTO reqDto) {
return AuthConverter.toSignUpDTO(member);
}

public AuthResDTO.LoginDTO login(AuthReqDTO.LoginDTO reqDto) {
Authentication authentication;

try {
authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(reqDto.email(), reqDto.password())
);
} catch (AuthenticationException e) {
throw new AuthException(AuthErrorCode.INVALID_LOGIN_FORM);
}

String accessToken = jwtUtil.createAccessToken((AuthMember) authentication.getPrincipal());

return new AuthResDTO.LoginDTO(accessToken);
}

private void addTermsToMember(Member member, List<AuthReqDTO.SignUpDTO.TermDTO> termDTOs) {
List<Term> allTerms = termRepository.findAll();

Expand All @@ -58,7 +82,7 @@ private void addTermsToMember(Member member, List<AuthReqDTO.SignUpDTO.TermDTO>
.map(AuthReqDTO.SignUpDTO.TermDTO::termId)
.collect(Collectors.toSet());

if (!allTermIds.containsAll(requestedTermIds)) {
if (termDTOs.size() != requestedTermIds.size() || !allTermIds.equals(requestedTermIds)) {
throw new AuthException(AuthErrorCode.TERMS_MISMATCH);
}

Expand All @@ -75,10 +99,15 @@ private void addTermsToMember(Member member, List<AuthReqDTO.SignUpDTO.TermDTO>
}

private void addFoodsToMember(Member member, List<AuthReqDTO.SignUpDTO.FoodPreferenceDTO> foodDTOs) {
foodDTOs.forEach(foodDTO -> {
Food food = foodRepository.findById(foodDTO.foodId())
.orElseThrow(() -> new AuthException(AuthErrorCode.INVALID_FOOD));
member.addPreferenceFood(food);
});
List<Long> foodIds = foodDTOs.stream()
.map(AuthReqDTO.SignUpDTO.FoodPreferenceDTO::foodId)
.toList();

List<Food> foods = foodRepository.findAllById(foodIds);
if (foodIds.size() != foods.size()) {
throw new AuthException(AuthErrorCode.INVALID_FOOD);
}

foods.forEach(member::addPreferenceFood);
}
}
15 changes: 10 additions & 5 deletions src/main/java/umc/domain/member/controller/MemberController.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
package umc.domain.member.controller;

import jakarta.validation.Valid;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import umc.domain.member.dto.MemberReqDTO;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import umc.domain.member.dto.MemberResDTO;
import umc.domain.member.exception.code.MemberSuccessCode;
import umc.domain.member.service.MemberService;
import umc.global.apiPayload.ApiResponse;
import umc.global.security.entity.AuthMember;

import java.time.LocalDate;

Expand Down Expand Up @@ -40,8 +43,10 @@ public ApiResponse<MemberResDTO.HomeViewDTO> getHome(
}

@GetMapping("/me")
public ApiResponse<MemberResDTO.MyPageViewDTO> getMyPage(){
MemberResDTO.MyPageViewDTO resDto = memberService.getMyPage(1L);
public ApiResponse<MemberResDTO.MyPageViewDTO> getMyPage(
@AuthenticationPrincipal AuthMember authMember
){
MemberResDTO.MyPageViewDTO resDto = memberService.getMyPage(authMember.getMember().getId());
return ApiResponse.onSuccess(MemberSuccessCode.MY_PAGE_VIEW, resDto);
}
}
12 changes: 12 additions & 0 deletions src/main/java/umc/domain/member/converter/MemberConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import umc.domain.member.dto.MemberResDTO;
import umc.domain.member.entity.Member;
import umc.domain.member.enums.Gender;
import umc.domain.mission.entity.Mission;
import umc.domain.region.entity.Region;
import umc.global.security.dto.OAuthDTO;

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
Expand Down Expand Up @@ -61,4 +63,14 @@ public static MemberResDTO.HomeViewDTO toHomeViewDTO(
.hasNext(hasNextPage)
.build();
}

public static Member toMember(OAuthDTO oAuthDTO) {
return Member.builder()
.name(oAuthDTO.getName())
.email(oAuthDTO.getEmail())
.socialUid(oAuthDTO.getSocialUid())
.socialType(oAuthDTO.getSocialType())
.gender(Gender.NONE)
.build();
}
}
10 changes: 7 additions & 3 deletions src/main/java/umc/domain/member/entity/Member.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package umc.domain.member.entity;

import jakarta.persistence.*;
import lombok.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import umc.domain.member.entity.mapping.MemberFood;
import umc.domain.member.entity.mapping.MemberTerm;
import umc.domain.member.enums.Gender;
import umc.domain.member.enums.SocialType;
import umc.domain.mission.entity.mapping.MemberMission;
import umc.domain.review.entity.Review;
import umc.global.entity.BaseEntity;

import java.time.LocalDate;
Expand Down Expand Up @@ -61,6 +62,9 @@ public class Member extends BaseEntity {
@Column(name = "social_type")
private SocialType socialType;

@Column(name = "social_uid")
private String socialUid;

@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<MemberTerm> memberTerms = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import umc.domain.member.entity.Member;
import umc.domain.member.enums.SocialType;

import java.util.Optional;

@Repository
public interface MemberRepository extends JpaRepository<Member,Long> {

Optional<Member> findByEmail(String email);

Optional<Member> findBySocialTypeAndSocialUid(SocialType providerId, String socialUid);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ public enum GeneralSuccessCode implements BaseSuccessCode {
OK(HttpStatus.OK, "SUCCESS200_1", "요청이 성공적으로 처리되었습니다."),
CREATED(HttpStatus.CREATED, "SUCCESS201_1", "리소스가 성공적으로 생성되었습니다."),
ACCEPTED(HttpStatus.ACCEPTED, "SUCCESS202_1", "요청이 수락되었으며, 처리가 진행 중입니다."),
NO_CONTENT(HttpStatus.NO_CONTENT, "SUCCESS203_1", "요청은 성공했으나, 반환할 콘텐츠가 없습니다.");
NO_CONTENT(HttpStatus.NO_CONTENT, "SUCCESS204_1", "요청은 성공했으나, 반환할 콘텐츠가 없습니다.");

private final HttpStatus status;
private final String code;
private final String message;
}
}
14 changes: 14 additions & 0 deletions src/main/java/umc/global/config/AppConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package umc.global.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
}
Loading