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 cookie/chapter4/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ dependencies {
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.13'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-api:2.8.13'

// 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'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import com.example.chapter4.domain.member.dto.MemberResDTO;
import com.example.chapter4.domain.member.exception.code.MemberSuccessCode;
import com.example.chapter4.domain.member.service.command.MemberCommandService;
import com.example.chapter4.domain.member.service.query.MemberQueryService;
import com.example.chapter4.global.apiPayload.ApiResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
Expand All @@ -13,14 +15,24 @@
@RestController
@RequiredArgsConstructor
public class MemberController {

private MemberQueryService memberQueryService;
private final MemberCommandService memberCommandService;


// 회원가입
@PostMapping("/sign-up")
public ApiResponse<MemberResDTO.JoinDTO> signUp(
@RequestBody MemberReqDTO.JoinDTO dto
){
return ApiResponse.onSuccess(MemberSuccessCode.FOUND, memberCommandService.signup(dto));
}
}

// 로그인
@PostMapping("/login")
public ApiResponse<MemberResDTO.LoginDTO> login(
@RequestBody @Valid MemberReqDTO.LoginDTO dto
){
return ApiResponse.onSuccess(MemberSuccessCode.FOUND, memberQueryService.login(dto));
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
import com.example.chapter4.domain.member.entity.Member;
import com.example.chapter4.domain.member.dto.MemberReqDTO;
import com.example.chapter4.domain.member.dto.MemberResDTO;
import com.example.chapter4.global.auth.enums.Role;


public class MemberConverter {

private static String password;
private static Role role;

// Entity -> DTO
public static MemberResDTO.JoinDTO toJoinDTO(
Member member
Expand All @@ -19,10 +23,14 @@ public static MemberResDTO.JoinDTO toJoinDTO(

// DTO -> Entity
public static Member toMember(
MemberReqDTO.JoinDTO dto
){
MemberReqDTO.JoinDTO dto,
String salt, Role roleUser){

return Member.builder()
.name(dto.name())
.email(dto.email())
.password(password)
.role(role)
.birth(dto.birth())
.address(dto.address())
.address(dto.specAddress())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
package com.example.chapter4.domain.member.dto;

import lombok.Builder;
import lombok.Getter;
import jakarta.validation.constraints.NotBlank;
import lombok.*;

import java.time.LocalDate;
import java.util.List;
import java.util.Optional;

@Getter
public class MemberReqDTO {

@Builder
public static class JoinDTO {

private final String name;
private final LocalDate birth; // LocalDate 또는 String 등 타입 맞춰서
private final String address;
private final String specAddress; // 상세주소 등
private final String gender;
private final List<Long> preferredFoodIds;
private final String email;
public final String password;

// Getter 또는 record 문법 등 사용 가능
public String name() { return name; }
Expand All @@ -24,5 +29,28 @@ public static class JoinDTO {
public String specAddress() { return specAddress; }
public String gender() { return gender; }
public List<Long> preferredFoodIds() { return preferredFoodIds; }

public String email() {
return email;
}

public CharSequence password() {
return password;
}

// 로그인

}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class LoginDTO {
private String username;
private String password;

public Optional<Object> email() {
return Optional.empty();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,9 @@ public static class JoinDTO {
public Long getMemberId() { return memberId; }
public String getCreateAt() { return createAt; }
}
@Builder
public record LoginDTO(
Long memberId,
String accessToken
){}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.example.chapter4.domain.member.entity;

import com.example.chapter4.domain.Alarm.entity.Alarm;
import com.example.chapter4.global.auth.enums.Role;
import com.example.chapter4.global.entity.BaseEntity;
import com.example.chapter4.domain.mission.entity.ReceivedMission;
import com.example.chapter4.domain.review.entity.Review;
Expand All @@ -19,14 +20,20 @@
@AllArgsConstructor // Lombok: 모든 필드를 인자로 받는 생성자를 생성합니다.

public class Member extends BaseEntity {
@Column(nullable = false, unique = true)
private String email;

@Column(nullable = false)
private String password;

@Enumerated(EnumType.STRING)
private Role role;


@Id // 이 필드가 테이블의 기본 키(PK)임을 나타냅니다.
@GeneratedValue(strategy = GenerationType.IDENTITY) // 기본 키 생성을 데이터베이스에 위임합니다. (e.g., MySQL의 AUTO_INCREMENT)
private Long id;

@Column(nullable = false, length = 20) // 데이터베이스 컬럼에 매핑됩니다. nullable=false는 NOT NULL 제약조건을 의미합니다.
private String email;

@Column(length = 10)
private String provider; // 소셜 로그인 제공자 (e.g., "kakao", "google")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.springframework.data.repository.query.Param;

import java.util.List;
import java.util.Optional;

public interface MemberRepository extends JpaRepository<Member, Long> {

Expand All @@ -21,4 +22,6 @@ public interface MemberRepository extends JpaRepository<Member, Long> {
@Query("SELECT r FROM Review r WHERE r.member.id = :memberId AND r.status = 'ACTIVE' ORDER BY r.createdAt DESC")
List<Review> findReviewsByMemberId(@Param("memberId") Long memberId);


Optional<Object> findByEmail(String email);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
import com.example.chapter4.domain.member.repository.FoodRepository;
import com.example.chapter4.domain.member.repository.MemberRepository;
import com.example.chapter4.domain.member.converter.MemberConverter;
import com.example.chapter4.global.auth.enums.Role;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -21,15 +23,24 @@ public class MemberCommandServiceImpl implements MemberCommandService {

private final MemberRepository memberRepository;
private final FoodRepository foodRepository;
private final PasswordEncoder passwordEncoder;

@Override
@Transactional
public MemberResDTO.JoinDTO signup(MemberReqDTO.JoinDTO dto) {
public MemberResDTO.JoinDTO signup(
MemberReqDTO.JoinDTO dto
){
// 솔트된 비밀번호 생성
String salt = passwordEncoder.encode(dto.password());

// 사용자 생성: 유저 / 관리자는 따로 API 만들어서 관리
Member member = MemberConverter.toMember(dto, salt, Role.ROLE_USER);

// 음식 엔티티 목록 조회
List<Food> foods = foodRepository.findAllById(dto.preferredFoodIds());

// DTO를 Member 엔티티로 변환
Member member = MemberConverter.toMember(dto);
member = MemberConverter.toMember(dto, salt, Role.ROLE_USER);

// 선호 Preference 리스트 생성
List<Preference> preferenceList = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.example.chapter4.domain.member.service.query;

import com.example.chapter4.domain.member.dto.MemberReqDTO;
import com.example.chapter4.domain.member.dto.MemberResDTO;
import jakarta.validation.Valid;

public class MemberQueryService {

public MemberResDTO.LoginDTO login(MemberReqDTO.@Valid LoginDTO dto) {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.example.chapter4.domain.member.service.query;

import com.example.chapter4.domain.member.converter.MemberConverter;
import com.example.chapter4.domain.member.dto.MemberReqDTO;
import com.example.chapter4.domain.member.dto.MemberResDTO;
import com.example.chapter4.domain.member.entity.Member;
import com.example.chapter4.domain.member.exception.code.MemberErrorCode;
import com.example.chapter4.domain.member.repository.MemberRepository;
import com.example.chapter4.global.auth.jwt.CustomUserDetails;
import com.example.chapter4.global.auth.jwt.JwtUtil;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class MemberQueryServiceImpl implements MemberQueryService{

private final MemberRepository memberRepository;
private final JwtUtil jwtUtil;
private final PasswordEncoder encoder;

@Override
public MemberResDTO.LoginDTO login(
MemberReqDTO.@Valid LoginDTO dto
) {

// Member 조회
Member member = memberRepository.findByEmail((dto.email())
.orElseThrow(() -> new MemberException(MemberErrorCode.NOT_FOUND));

// 비밀번호 검증
if (!encoder.matches(dto.password(), member.getPassword())){
throw new MemberException(MemberErrorCode.INVALID);
}

// JWT 토큰 발급용 UserDetails
CustomUserDetails userDetails = new CustomUserDetails(member);

// 엑세스 토큰 발급
String accessToken = jwtUtil.createAccessToken(userDetails);

// DTO 조립
return MemberConverter.toLoginDTO(member, accessToken);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.example.chapter4.domain.review.controller;

import com.example.chapter4.annotation.ValidPage;
import com.example.chapter4.domain.review.dto.MyReviewDto;
import com.example.chapter4.domain.review.dto.ReviewReqDTO;
import com.example.chapter4.domain.review.dto.ReviewResDTO;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.example.chapter4.global.auth.enums;

public enum Role {
ROLE_ADMIN, ROLE_USER
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.example.chapter4.global.auth.jwt;

import com.example.chapter4.domain.member.entity.Member;

import java.util.Collection;

public class CustomUserDetails {
public CustomUserDetails(Member member) {

}

public String getUsername() {
return null;
}

public Collection<Object> getAuthorities() {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example.chapter4.global.auth.jwt;

import org.springframework.security.core.userdetails.UserDetails;

public class CustomUserDetailsService {
public UserDetails loadUserByUsername(String email) {
return null;
}
}
Loading