Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
12 changes: 11 additions & 1 deletion Sangwan/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,16 @@ repositories {

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-webmvc'
implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
implementation 'io.jsonwebtoken:jjwt-impl:0.12.3'
implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
testImplementation 'org.springframework.boot:spring-boot-starter-data-jpa-test'
testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
Expand All @@ -51,7 +56,12 @@ tasks.named('bootRun') {
.findAll { line -> line.trim() && !line.trim().startsWith('#') && line.contains('=') }
.each { line ->
def (key, value) = line.split('=', 2)
environment key.trim(), value.trim()
def cleanedValue = value.trim()
if ((cleanedValue.startsWith('"') && cleanedValue.endsWith('"')) ||
(cleanedValue.startsWith("'") && cleanedValue.endsWith("'"))) {
cleanedValue = cleanedValue.substring(1, cleanedValue.length() - 1)
}
environment key.trim(), cleanedValue
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
import com.example.umc10th.global.apiPayload.ApiResponse;
import com.example.umc10th.global.dto.CursorPageRes;
import com.example.umc10th.global.dto.OffsetPageRes;
import com.example.umc10th.global.security.entity.AuthMember;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.PositiveOrZero;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

Expand All @@ -30,25 +32,33 @@ public ApiResponse<MemberResDTO.SignupRes> signup(
return ApiResponse.onSuccess(MemberSuccessCode.SIGNUP, memberService.signup(request));
}

// 로그인
@PostMapping("/login")
public ApiResponse<MemberResDTO.Login> login(
@Valid @RequestBody MemberReqDTO.LoginReq request
) {
return ApiResponse.onSuccess(MemberSuccessCode.OK, memberService.login(request));
}

// 홈화면 조회
@GetMapping("/me/home")
public ApiResponse<MemberResDTO.HomeRes> getHome(
@RequestParam Long memberId // TODO: 인증 구현 후 SecurityContext로 대체
@AuthenticationPrincipal AuthMember member
) {
return ApiResponse.onSuccess(MemberSuccessCode.HOME, memberService.getHome(memberId));
return ApiResponse.onSuccess(MemberSuccessCode.HOME, memberService.getHome(member.getId()));
}

// 미션 목록 조회 (진행중 / 진행완료)
@GetMapping("/me/missions")
public ApiResponse<CursorPageRes<MemberResDTO.MissionItem>> getMissions(
@RequestParam Long memberId, // TODO: 인증 구현 후 SecurityContext로 대체
@AuthenticationPrincipal AuthMember member,
@RequestParam String status,
@RequestParam(required = false) Long cursor,
@RequestParam(defaultValue = "10")
@Positive(message = "페이지 크기는 1 이상이어야 합니다.") int size
) {
return ApiResponse.onSuccess(MemberSuccessCode.MISSION_LIST,
memberService.getMissions(memberId, status, cursor, size));
memberService.getMissions(member.getId(), status, cursor, size));
}

// 미션 성공 누르기
Expand All @@ -63,21 +73,21 @@ public ApiResponse<MemberResDTO.MissionSuccessRes> requestMissionSuccess(
// 진행중인 미션 조회 (오프셋 기반 페이지네이션)
@PostMapping("/me/missions/inprogress")
public ApiResponse<OffsetPageRes<MemberResDTO.MissionItem>> getInProgressMissions(
@Valid @RequestBody MemberReqDTO.GetInProgressMissionsReq request,
@AuthenticationPrincipal AuthMember member,
@RequestParam(defaultValue = "0")
@PositiveOrZero(message = "페이지 번호는 0 이상이어야 합니다.") int page,
@RequestParam(defaultValue = "10")
@Positive(message = "페이지 크기는 1 이상이어야 합니다.") int size
) {
return ApiResponse.onSuccess(MemberSuccessCode.INPROGRESS_MISSIONS,
memberService.getInProgressMissions(request, page, size));
memberService.getInProgressMissions(member.getId(), page, size));
}

// 내 정보 가져오기
@PostMapping("/me")
@GetMapping("/me")
public ApiResponse<MemberResDTO.MyInfoRes> getMyInfo(
@Valid @RequestBody MemberReqDTO.MyInfoReq myInfoReq
@AuthenticationPrincipal AuthMember member
) {
return ApiResponse.onSuccess(MemberSuccessCode.MYINFO, memberService.requestMyInfo(myInfoReq));
return ApiResponse.onSuccess(MemberSuccessCode.MYINFO, memberService.requestMyInfo(member));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.example.umc10th.domain.member.entity.mapping.MemberAgreement;
import com.example.umc10th.domain.member.entity.mapping.MemberFoodCategory;
import com.example.umc10th.domain.member.entity.mapping.RegionProgress;
import com.example.umc10th.domain.member.enums.Gender;
import com.example.umc10th.domain.member.enums.MemberStatus;
import com.example.umc10th.domain.member.enums.Role;
import com.example.umc10th.domain.mission.entity.Mission;
Expand All @@ -14,6 +15,7 @@
import com.example.umc10th.domain.term.entity.Term;
import com.example.umc10th.global.dto.CursorPageRes;
import com.example.umc10th.global.dto.OffsetPageRes;
import com.example.umc10th.global.security.dto.OAuthDTO;
import org.springframework.data.domain.Page;

import java.time.LocalDate;
Expand All @@ -40,6 +42,23 @@ public static Member toMember(MemberReqDTO.SignupReq request, String encodedPass
.build();
}

public static Member toMember(OAuthDTO dto) {
return Member.builder()
.email(dto.getSocialEmail())
.password("")
.socialType(dto.getSocialType())
.socialId(dto.getSocialUid())
.name(dto.getName())
.role(Role.USER)
.status(MemberStatus.ACTIVE)
.step(0)
.totalPoint(0)
.gender(Gender.NONE)
.birth(LocalDate.of(1900, 1, 1))
.isVerified(false)
.build();
}

public static MemberAgreement toMemberAgreement(Member member, Term term, Boolean isAgreed) {
return MemberAgreement.builder()
.member(member)
Expand Down Expand Up @@ -73,6 +92,12 @@ public static MemberResDTO.MyInfoRes toGetInfo(Member member) {
.build();
}

public static MemberResDTO.Login toLogin(String accessToken) {
return MemberResDTO.Login.builder()
.accessToken(accessToken)
.build();
}

public static MemberResDTO.HomeMissionItem toHomeMissionItem(Mission mission) {
return MemberResDTO.HomeMissionItem.builder()
.missionId(mission.getId())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@ public record SignupReq(
List<@NotNull @Positive Long> foodCategoryIds
) {}

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

public record TermAgreementReq(
@NotNull(message = "약관 ID는 필수입니다.")
@Positive(message = "약관 ID는 양수여야 합니다.") Long termId,
@NotNull(message = "약관 동의 여부는 필수입니다.") Boolean isAgreed
) {}

public record GetInProgressMissionsReq(
@NotNull(message = "사용자 ID는 필수입니다.") Long memberId
) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,9 @@ public record MyInfoRes(
Integer point
){}

@Builder
public record Login(
String accessToken
) {}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public enum MemberErrorCode implements BaseErrorCode {
MEMBER_ALREADY_EXISTS(HttpStatus.CONFLICT, "MEMBER_4001", "이미 가입된 회원 정보가 존재합니다."),
INVALID_BIRTHDAY_FORMAT(HttpStatus.BAD_REQUEST, "MEMBER_4002", "생년월일 형식이 올바르지 않습니다. (YYYY-MM-DD)"),
REQUIRED_TERM_NOT_AGREED(HttpStatus.BAD_REQUEST, "MEMBER_4003", "필수 약관에 동의해야 합니다."),
NOT_SUPPORT_SOCIAL_PROVIDER(HttpStatus.BAD_REQUEST, "MEMBER_4004", "지원하지 않는 소셜 로그인 제공자입니다."),
INVALID_LOGIN(HttpStatus.UNAUTHORIZED, "MEMBER_401", "이메일 또는 비밀번호가 올바르지 않습니다."),
TERM_NOT_FOUND(HttpStatus.NOT_FOUND, "MEMBER_4041", "해당 약관을 찾을 수 없습니다."),
FOOD_CATEGORY_NOT_FOUND(HttpStatus.NOT_FOUND, "MEMBER_4042", "해당 음식 카테고리를 찾을 수 없습니다.");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
@RequiredArgsConstructor
public enum MemberSuccessCode implements BaseSuccessCode {

OK(HttpStatus.OK, "MEMBER_200", "성공적으로 요청을 처리했습니다."),
SIGNUP(HttpStatus.CREATED, "MEMBER_201", "반가워요! 회원 가입이 완료되었습니다."),
HOME(HttpStatus.OK, "HOME_200", "홈 화면 조회가 완료되었습니다."),
MISSION_LIST(HttpStatus.OK, "MISSION_200", "미션 목록 조회가 완료되었습니다."),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.example.umc10th.domain.member.repository;

import com.example.umc10th.domain.member.entity.Member;
import com.example.umc10th.domain.member.enums.SocialType;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;
Expand All @@ -10,4 +11,6 @@ public interface MemberRepository extends JpaRepository<Member,Long> {
boolean existsByEmail(String email);

Optional<Member> findByEmail(String email);

Optional<Member> findBySocialTypeAndSocialId(SocialType socialType, String socialId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,15 @@
import com.example.umc10th.domain.term.repository.TermRepository;
import com.example.umc10th.global.dto.CursorPageRes;
import com.example.umc10th.global.dto.OffsetPageRes;
import com.example.umc10th.global.security.entity.AuthMember;
import com.example.umc10th.global.security.util.JwtUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand Down Expand Up @@ -55,6 +61,8 @@ public class MemberService {
private final MemberAgreementRepository memberAgreementRepository;
private final MemberFoodCategoryRepository memberFoodCategoryRepository;
private final PasswordEncoder passwordEncoder;
private final AuthenticationManager authenticationManager;
private final JwtUtil jwtUtil;

@Transactional
public MemberResDTO.SignupRes signup(MemberReqDTO.SignupReq request) {
Expand All @@ -78,6 +86,19 @@ public MemberResDTO.SignupRes signup(MemberReqDTO.SignupReq request) {
return MemberConverter.toSignupRes(savedMember);
}

public MemberResDTO.Login login(MemberReqDTO.LoginReq request) {
try {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(request.email(), request.password())
);
AuthMember member = (AuthMember) authentication.getPrincipal();

return MemberConverter.toLogin(jwtUtil.createAccessToken(member));
} catch (AuthenticationException e) {
throw new MemberException(MemberErrorCode.INVALID_LOGIN);
}
}

public MemberResDTO.HomeRes getHome(Long memberId) {
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new MemberException(MemberErrorCode.MEMBER_NOT_FOUND));
Expand Down Expand Up @@ -118,13 +139,12 @@ public CursorPageRes<MemberResDTO.MissionItem> getMissions(Long memberId, String
return MemberConverter.toMissionListRes(missions, hasNext, nextCursor, cursor == null, !hasNext);
}

public OffsetPageRes<MemberResDTO.MissionItem> getInProgressMissions(
MemberReqDTO.GetInProgressMissionsReq request, int page, int size) {
memberRepository.findById(request.memberId())
public OffsetPageRes<MemberResDTO.MissionItem> getInProgressMissions(Long memberId, int page, int size) {
memberRepository.findById(memberId)
.orElseThrow(() -> new MemberException(MemberErrorCode.MEMBER_NOT_FOUND));

Page<MemberMission> memberMissionPage = memberMissionRepository.findPageByMemberIdAndStatus(
request.memberId(), MemberMissionStatus.CHALLENGING, PageRequest.of(page, size));
memberId, MemberMissionStatus.CHALLENGING, PageRequest.of(page, size));

return MemberConverter.toInProgressMissionPageRes(memberMissionPage, page);
}
Expand All @@ -134,10 +154,8 @@ public MemberResDTO.MissionSuccessRes requestMissionSuccess(Long missionId) {
return null;
}

public MemberResDTO.MyInfoRes requestMyInfo(MemberReqDTO.MyInfoReq myInfoReq) {
Member member = memberRepository.findById(myInfoReq.id())
.orElseThrow(() -> new MemberException(MemberErrorCode.MEMBER_NOT_FOUND));
return MemberConverter.toGetInfo(member);
public MemberResDTO.MyInfoRes requestMyInfo(AuthMember authMember) {
return MemberConverter.toGetInfo(authMember.getMember());
}

private LocalDate parseBirthday(String birthday) {
Expand Down
Loading