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
10 changes: 10 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ dependencies {
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.13'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-api:2.8.13'

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

// Security
implementation 'org.springframework.boot:spring-boot-starter-security'
testImplementation 'org.springframework.security:spring-security-test'

// Validation
implementation 'org.springframework.boot:spring-boot-starter-validation'
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.example.umc9th2.domain.User.exception.code.UserSuccessCode;
import com.example.umc9th2.domain.User.repository.UserRepository;
import com.example.umc9th2.domain.User.service.command.UserCommandService;
import com.example.umc9th2.domain.User.service.query.UserQueryService;
import com.example.umc9th2.global.apiPayload.ApiResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
Expand All @@ -17,6 +18,7 @@
public class UserController {

private final UserCommandService userCommandService;
private final UserQueryService userQueryService;

// 회원가입
@PostMapping("/sign-up")
Expand All @@ -26,4 +28,15 @@ public ApiResponse<UserResDTO.JoinDTO> signUp(
return ApiResponse.onSuccess(UserSuccessCode.FOUND, userCommandService.signUp(dto));
}

// 로그인
@PostMapping("/login")
public ApiResponse<UserResDTO.LoginDTO> login(
@RequestBody @Valid UserReqDTO.LoginDTO dto
){
return ApiResponse.onSuccess(
UserSuccessCode.FOUND,
userQueryService.login(dto)
);

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import com.example.umc9th2.domain.User.dto.UserReqDTO;
import com.example.umc9th2.domain.User.dto.UserResDTO;
import com.example.umc9th2.domain.User.entity.User;
import com.example.umc9th2.domain.User.enums.OauthProvider;
import com.example.umc9th2.global.auth.enums.Role;

public class UserConverter {

Expand All @@ -15,19 +17,33 @@ public static UserResDTO.JoinDTO toJoinDTO(User user) {
}

// DTO -> Entity
public static User toUser(UserReqDTO.JoinDTO dto) {

public static User toUser(
UserReqDTO.JoinDTO dto,
String password,
Role role
) {
return User.builder()
.name(dto.name())
.email(dto.email()) // 추가
.password(password) // 추가 (암호화된 비밀번호)
.role(role) // 추가
.birth(dto.birth())
.gender(dto.gender())

// RabbitConnectionDetails.Address → String 변환
.address(dto.address() != null
? dto.address().toString()
: null)
.address(
dto.address() != null
? dto.address().toString()
: null
)
.specAddress(dto.specAddress())
.oauthProvider(OauthProvider.LOCAL)
.build();
}

public static UserResDTO.LoginDTO LoginDTO(User user, String accessToken) {
return UserResDTO.LoginDTO.builder()
.userId(user.getUserId())
.accessToken(accessToken)
.build();
}

}
25 changes: 14 additions & 11 deletions src/main/java/com/example/umc9th2/domain/User/dto/UserReqDTO.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,23 @@

import com.example.umc9th2.domain.User.enums.Gender;
import com.example.umc9th2.global.annotation.ExistFoods;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import org.springframework.boot.autoconfigure.amqp.RabbitConnectionDetails;

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

//public class UserReqDTO {
//
// public record JoinDTO(
// String name,
// Gender gender,
// LocalDate birth,
// RabbitConnectionDetails.Address address,
// String specAddress,
// List<Long> preferCategory
// ){}
//}
public class UserReqDTO {

public record JoinDTO(
@NotBlank
String name,
@Email
String email, // 추가된 속성
@NotBlank
String password, // 추가된 속성
@NotNull
Gender gender,
@NotNull
Expand All @@ -36,4 +30,13 @@ public record JoinDTO(
@ExistFoods
List<Long> preferCategory
){}

// 로그인
public record LoginDTO(
@NotBlank
String email,
@NotBlank
String password
){}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,11 @@ public record JoinDTO(
Long memberId,
LocalDateTime createAt
){}

// 로그인
@Builder
public record LoginDTO(
Long userId,
String accessToken
){}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.example.umc9th2.domain.User.entity.mapping.UserTerm;
import com.example.umc9th2.domain.User.enums.Gender;
import com.example.umc9th2.domain.User.enums.OauthProvider;
import com.example.umc9th2.global.auth.enums.Role;
import jakarta.persistence.*;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
Expand Down Expand Up @@ -44,6 +45,12 @@ public class User {
@Column(name = "email", nullable = false, length = 100, unique = true)
private String email;//이메일

@Column(nullable = false)
private String password;

@Enumerated(EnumType.STRING)
private Role role;

@Enumerated(EnumType.STRING)//소셜로그인
@Column(name = "oauthprovider", nullable = false, length = 20)
private OauthProvider oauthProvider; // KAKAO, NAVER, GOOGLE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
@Builder
public class UserMission {

//미션 진행중, 완료 상태, 진행전인 것도 함께 넘겨줘야 함

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long memberMissionId;//PK
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package com.example.umc9th2.domain.User.enums;

public enum OauthProvider {
KAKAO, NAVER, GOOGLE, APPLE
LOCAL, KAKAO, NAVER, GOOGLE, APPLE
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
package com.example.umc9th2.domain.User.exception;

import com.example.umc9th2.domain.User.exception.code.UserErrorCode;

public class UserException extends RuntimeException {
public UserException(String message) {
super(message);

private final UserErrorCode errorCode;

public UserException(UserErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}

public UserErrorCode getErrorCode() {
return errorCode;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
@AllArgsConstructor
public enum UserErrorCode implements BaseErrorCode {

INVALID_PASSWORD(
HttpStatus.UNAUTHORIZED,
"USER401_1",
"비밀번호가 올바르지 않습니다."
),

NOT_FOUND(HttpStatus.NOT_FOUND,
"MEMBER404_1",
"해당 사용자를 찾지 못했습니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,53 +5,63 @@
import com.example.umc9th2.domain.User.dto.UserResDTO;
import com.example.umc9th2.domain.User.entity.User;
import com.example.umc9th2.domain.User.entity.mapping.UserFood;
import com.example.umc9th2.global.auth.enums.Role;
import com.example.umc9th2.domain.Food.entity.Food;
import com.example.umc9th2.domain.Food.exception.FoodException;
import com.example.umc9th2.domain.Food.exception.code.FoodErrorCode;

import com.example.umc9th2.domain.User.repository.UserRepository;
import com.example.umc9th2.domain.User.repository.UserFoodRepository;
import com.example.umc9th2.domain.Food.repository.FoodRepository;

import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

@Service
@RequiredArgsConstructor
public class UserCommandServiceImpl implements UserCommandService {

private final UserRepository userRepository;
private final UserFoodRepository userFoodRepository;

// FoodRepository는 Food 도메인에 있어야만 정상 import 됨!!
private final FoodRepository foodRepository;

// Password Encoder 추가
private final PasswordEncoder passwordEncoder;

// 회원가입
@Override
@Transactional
public UserResDTO.JoinDTO signUp(UserReqDTO.JoinDTO dto) {

User user = UserConverter.toUser(dto);
// 1. 비밀번호 암호화 (Salted Password)
String encodedPassword = passwordEncoder.encode(dto.password());

// 2. 사용자 생성 (기본 권한: ROLE_USER)
User user = UserConverter.toUser(dto, encodedPassword, Role.ROLE_USER);
userRepository.save(user);

// 3. 선호 음식 카테고리 매핑
if (dto.preferCategory() != null && !dto.preferCategory().isEmpty()) {

List<UserFood> userFoodList = dto.preferCategory().stream()
.map(id -> UserFood.builder()
.user(user)
.food(
foodRepository.findById(id)
.orElseThrow(() -> new FoodException(FoodErrorCode.NOT_FOUND))
.orElseThrow(() ->
new FoodException(FoodErrorCode.NOT_FOUND)
)
)
.build())
.toList();

userFoodRepository.saveAll(userFoodList);
}

// 4. 응답 DTO 반환
return UserConverter.toJoinDTO(user);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
package com.example.umc9th2.domain.User.service.query;

import com.example.umc9th2.domain.User.dto.UserReqDTO;
import com.example.umc9th2.domain.User.dto.UserResDTO;
import jakarta.validation.Valid;

public interface UserQueryService {
UserResDTO.LoginDTO login(UserReqDTO.LoginDTO dto);
}

Original file line number Diff line number Diff line change
@@ -1,4 +1,41 @@
package com.example.umc9th2.domain.User.service.query;

public class UserQueryServiceImpl {
import com.example.umc9th2.domain.User.dto.UserReqDTO;
import com.example.umc9th2.domain.User.dto.UserResDTO;
import com.example.umc9th2.domain.User.entity.User;
import com.example.umc9th2.domain.User.exception.UserException;
import com.example.umc9th2.domain.User.exception.code.UserErrorCode;
import com.example.umc9th2.domain.User.repository.UserRepository;
import com.example.umc9th2.global.auth.jwt.JwtUtil;
import com.example.umc9th2.global.auth.principal.CustomUserDetails;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import com.example.umc9th2.domain.User.converter.UserConverter;
//로그인 성공시 jwt 발급, 클라이언트는 이후 요청에 토큰 사용
@Service
@RequiredArgsConstructor
public class UserQueryServiceImpl implements UserQueryService {

private final UserRepository userRepository;
private final JwtUtil jwtUtil;
private final PasswordEncoder encoder;

@Override
public UserResDTO.LoginDTO login(UserReqDTO.LoginDTO dto) {
//이메일로 사용자 조회
User user = userRepository.findByEmail(dto.email())
.orElseThrow(() -> new UserException(UserErrorCode.NOT_FOUND));
//비밀번호 검증
if (!encoder.matches(dto.password(), user.getPassword())) {
throw new UserException(UserErrorCode.INVALID_PASSWORD);
}
//jwt 발급용 userdetails 생성
CustomUserDetails userDetails = new CustomUserDetails(user);
//accress token 생성
String accessToken = jwtUtil.createAccessToken(userDetails);
//토큰을 포함한 응답 반환
return UserConverter.LoginDTO(user, accessToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public class MissionController implements MissionControllerDocs {
private final MissionQueryService missionQueryService;

//특정 가게 미션 목록 조회
//api/missions에 특정 가게 미션 목록 조회가 맞는 url인지 다시 생각해보기 직관적이지 않은듯
@GetMapping
@Override
public ApiResponse<MissionResponseDto.MissionListDTO> getStoreMissions(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public static ReviewResponseDto.ReviewPreViewListDTO toReviewPreviewListDTO(Page
ReviewResponseDto.ReviewPreViewDTO previewDTO = null;

if (!result.getContent().isEmpty()) {
// 첫 번째 요소만 DTO로 변환
// 첫 번째 요소만 DTO로 변환(전체 반환하게 다시)
previewDTO = toReviewPreviewDTO(result.getContent().get(0));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class ReviewQueryServiceImpl implements ReviewQueryService {

private static final int PAGE_SIZE = 10;

@Override
@Override//(워크북-실습예제 페이징 -1 안 해줌 수정)
public ReviewResponseDto.ReviewPreViewListDTO findReview(String storeName, Integer page) {

// 가게 검색 + 예외 발생시 처리
Expand All @@ -50,6 +50,7 @@ public List<ReviewResponseDto> getFilteredReviews(Long userId, String storeName,
//내가 작성한 리뷰 조회
//page는 1이상 0, 음수 고려 x
//사용자의 모든 리뷰를 가져오고 컨버터로 dto변환
//findbyuser_userid->findByAllByUser로 이름 수정
@Override
public ReviewResponseDto.ReviewPreViewListDTO getMyReviews(Long userId, Integer page) {

Expand Down
Loading