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 @@ -46,6 +46,15 @@ dependencies {

// Validation
implementation 'org.springframework.boot:spring-boot-starter-validation'

// 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 @@ -38,6 +38,14 @@ public ApiResponse<MemberResDTO.JoinDTO> signUp(
){
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));
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.example.umc_9th.domain.member.dto.MemberResDTO;
import com.example.umc_9th.domain.member.dto.MyPageDto;
import com.example.umc_9th.domain.member.entity.Member;
import com.example.umc_9th.global.auth.enums.Role;

public class MemberConverter {

Expand All @@ -28,14 +29,26 @@ public static MemberResDTO.JoinDTO toJoinDTO(

// DTO -> Entity
public static Member toMember(
MemberReqDTO.JoinDTO dto
MemberReqDTO.JoinDTO dto,
String password,
Role role
){
return Member.builder()
.name(dto.name())
.email(dto.email())
.password(password)
.role(role)
.birth(dto.birth())
.address(dto.address())
.gender(dto.gender())
.build();
}

public static MemberResDTO.LoginDTO toLoginDTO(Member member, String accessToken) {
return MemberResDTO.LoginDTO.builder()
.memberId(member.getId())
.accessToken(accessToken)
.build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

import com.example.umc_9th.domain.member.enums.Gender;
import com.example.umc_9th.global.annotation.ExistFoods;
import jakarta.validation.constraints.NotBlank;

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

public class MemberReqDTO {
public record JoinDTO(
String name,
String email,
String password,
Gender gender,
LocalDate birth,
String address,
Expand All @@ -17,4 +20,11 @@ public record JoinDTO(
List<Long> preferCategory
){}

public record LoginDTO(
@NotBlank
String email,
@NotBlank
String password
){}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,10 @@ public record JoinDTO(
Long memberId,
LocalDateTime 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.umc_9th.domain.member.entity;

import com.example.umc_9th.domain.member.enums.Gender;
import com.example.umc_9th.global.auth.enums.Role;
import com.example.umc_9th.global.entity.BaseEntity;
import jakarta.persistence.*;
import lombok.*;
Expand Down Expand Up @@ -42,4 +43,10 @@ public class Member extends BaseEntity {

@Column(length = 40, unique = true)
private String email;

@Column(nullable = false)
private String password;

@Enumerated(EnumType.STRING)
private Role role;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public enum MemberErrorCode implements BaseErrorCode {
NOT_FOUND(HttpStatus.NOT_FOUND,
"MEMBER404_1",
"해당 사용자를 찾지 못했습니다."),
;
INVALID(HttpStatus.UNAUTHORIZED,"MEMBER403_1" , "존재하지 않는 사용자입니다");

// 1. 필드명을 httpStatus로 변경 (Lombok @Getter와의 통일성 권장)
private final HttpStatus httpStatus;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@

public interface MemberRepository extends JpaRepository<Member, Long> {

Optional<Member> findByEmail(String email);
Optional<MyPageDto> findMyPageDtoById(Long id);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
import com.example.umc_9th.domain.member.repository.FoodRepository;
import com.example.umc_9th.domain.member.repository.MemberFoodRepository;
import com.example.umc_9th.domain.member.repository.MemberRepository;
import com.example.umc_9th.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 @@ -26,13 +28,16 @@ public class MemberCommandServiceImpl implements MemberCommandService {
private final MemberRepository memberRepository;
private final MemberFoodRepository memberFoodRepository;
private final FoodRepository foodRepository;
private final PasswordEncoder passwordEncoder;

@Override
public MemberResDTO.JoinDTO signup(
MemberReqDTO.JoinDTO dto
){

Member member = MemberConverter.toMember(dto);
String salt = passwordEncoder.encode(dto.password());

Member member = MemberConverter.toMember(dto, salt, Role.ROLE_USER);
memberRepository.save(member);

// 선호 음식 존재 여부 확인
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
package com.example.umc_9th.domain.member.service.query;

import com.example.umc_9th.domain.member.converter.MemberConverter;
import com.example.umc_9th.domain.member.dto.MemberReqDTO;
import com.example.umc_9th.domain.member.dto.MemberResDTO;
import com.example.umc_9th.domain.member.dto.MyPageDto;
import com.example.umc_9th.domain.member.entity.Member;
import com.example.umc_9th.domain.member.repository.MemberRepository;
import com.example.umc_9th.global.apiPayload.code.status.MemberErrorCode;


import com.example.umc_9th.global.apiPayload.handler.MemberHandler;
import com.example.umc_9th.global.auth.entity.CustomUserDetails;
import com.example.umc_9th.global.auth.jwt.JwtUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -14,10 +23,34 @@
public class MemberQueryService {

private final MemberRepository memberRepository;
private final JwtUtil jwtUtil; // 로그인 기능을 위해 추가
private final PasswordEncoder encoder; // 로그인 기능을 위해 추가

// 1. 마이페이지 조회
public MyPageDto getMyPageInfo(Long memberId) {

return memberRepository.findMyPageDtoById(memberId)
.orElseThrow(() -> new MemberHandler(MemberErrorCode.MEMBER_NOT_FOUND));
}
}

// 2. 로그인 (Impl에 있던 로직을 여기로 통합)
// 서비스 계층에서는 @Valid를 쓰지 않습니다. Controller에서 이미 검증된 데이터가 넘어옵니다.
public MemberResDTO.LoginDTO login(MemberReqDTO.LoginDTO dto) {

// 이메일로 회원 조회
Member member = memberRepository.findByEmail(dto.email())
.orElseThrow(() -> new MemberHandler(MemberErrorCode.MEMBER_NOT_FOUND));

// 비밀번호 검증
if (!encoder.matches(dto.password(), member.getPassword())){
// 비밀번호 틀림 -> 보안상 NOT_FOUND와 동일하게 처리하거나 별도 에러코드 사용
throw new MemberHandler(MemberErrorCode.MEMBER_NOT_FOUND);
}

// 토큰 발급
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
Expand Up @@ -5,12 +5,17 @@
import com.example.umc_9th.domain.member.dto.MemberMissionResponseDTO;
import com.example.umc_9th.domain.mission.converter.MemberMissionConverter;

import com.example.umc_9th.domain.mission.converter.MissionConverter;
import com.example.umc_9th.domain.mission.dto.MissionDTO;
import com.example.umc_9th.domain.mission.entity.MemberMission;
import com.example.umc_9th.domain.mission.service.MemberMissionCommandService;
import com.example.umc_9th.domain.mission.service.MemberMissionQueryService;
import com.example.umc_9th.global.apiPayload.ApiResponse;
import com.example.umc_9th.global.apiPayload.code.GeneralSuccessCode;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.*;

@RestController
Expand All @@ -19,6 +24,7 @@
public class MemberMissionController {

private final MemberMissionCommandService memberMissionCommandService;
private final MemberMissionQueryService memberMissionQueryService;

@PostMapping("/challenge")
public ApiResponse<MemberMissionResponseDTO.JoinResultDto> challengeMission(
Expand All @@ -32,4 +38,14 @@ public ApiResponse<MemberMissionResponseDTO.JoinResultDto> challengeMission(
MemberMissionConverter.toJoinResultDTO(memberMission)
);
}

@GetMapping("/members/my")
@Operation(summary = "내가 진행중인 미션 목록 조회 API", description = "내가 현재 도전 중인 미션 목록을 조회합니다.")
public ApiResponse<MissionDTO.ChallengingMissionListDTO> getMyChallengingMissions(
@RequestParam(name = "memberId") Long memberId,
@RequestParam(name = "page") Integer page
) {
Page<MemberMission> missionPage = memberMissionQueryService.getMyChallengingMissions(memberId, page - 1);
return ApiResponse.onSuccess(GeneralSuccessCode.OK, MissionConverter.toChallengingMissionListDTO(missionPage));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
import com.example.umc_9th.domain.mission.dto.AvailableMissionDto;
import com.example.umc_9th.domain.mission.dto.MissionDTO;
import com.example.umc_9th.domain.mission.dto.MissionStatusDto;
import com.example.umc_9th.domain.mission.entity.MemberMission;
import org.springframework.data.domain.Page;

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

public class MissionConverter {

public static MissionDTO.AvailableMissionListDTO toAvailableMissionListDTO(Page<AvailableMissionDto> page) {
Expand All @@ -26,4 +30,31 @@ public static MissionDTO.MyMissionListDTO toMyMissionListDTO(Page<MissionStatusD
.isLast(page.isLast())
.build();
}

// MemberMission -> DTO 변환
public static MissionDTO.ChallengingMissionResponseDTO toChallengingMissionDTO(MemberMission memberMission) {
return MissionDTO.ChallengingMissionResponseDTO.builder()
.memberMissionId(memberMission.getId())
.storeName(memberMission.getMission().getStore().getName()) // 연관관계 탐색
.missionSpec(memberMission.getMission().getMissionSpec())
.reward(memberMission.getMission().getReward().intValue())
.deadline(memberMission.getMission().getDeadline())
.build();
}

// Page -> List DTO 변환
public static MissionDTO.ChallengingMissionListDTO toChallengingMissionListDTO(Page<MemberMission> page) {
List<MissionDTO.ChallengingMissionResponseDTO> dtoList = page.stream()
.map(MissionConverter::toChallengingMissionDTO)
.collect(Collectors.toList());

return MissionDTO.ChallengingMissionListDTO.builder()
.missionList(dtoList)
.listSize(dtoList.size())
.totalPage(page.getTotalPages())
.totalElements(page.getTotalElements())
.isFirst(page.isFirst())
.isLast(page.isLast())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package com.example.umc_9th.domain.mission.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

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

public class MissionDTO {
Expand All @@ -27,4 +31,29 @@ public static class MyMissionListDTO {
private Boolean isFirst;
private Boolean isLast;
}

@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class ChallengingMissionResponseDTO {
private Long memberMissionId;
private String storeName;
private String missionSpec;
private Integer reward;
private LocalDate deadline;
}

@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class ChallengingMissionListDTO {
private List<ChallengingMissionResponseDTO> missionList;
private Integer listSize;
private Integer totalPage;
private Long totalElements;
private Boolean isFirst;
private Boolean isLast;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@



import com.example.umc_9th.domain.member.entity.Member;
import com.example.umc_9th.domain.mission.dto.MissionStatusDto;
import com.example.umc_9th.domain.mission.entity.MemberMission;
import com.example.umc_9th.domain.mission.enums.MissionStatus;
Expand All @@ -15,6 +16,8 @@

public interface MemberMissionRepository extends JpaRepository<MemberMission, Long> {

Page<MemberMission> findAllByMemberAndStatus(Member member, MissionStatus status, Pageable pageable);


@Query("select new com.example.umc_9th.domain.mission.dto.MissionStatusDto(s.name, m.reward, m.missionSpec, mm.status) " +
"from MemberMission mm " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.example.umc_9th.domain.mission.dto.AvailableMissionDto;
import com.example.umc_9th.domain.mission.entity.Mission;
import com.example.umc_9th.domain.store.entity.Store;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
Expand All @@ -10,6 +11,7 @@

public interface MissionRepository extends JpaRepository<Mission, Long> {

Page<Mission> findAllByStore(Store store, Pageable pageable);
/**
* 쿼리 3: 홈 화면 - 현재 지역에서 도전이 가능한 미션 목록 (페이징 포함)
*/
Expand Down
Loading