Skip to content
Open
3 changes: 1 addition & 2 deletions bean/umc10th/.gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
HELP.md
.gradle
.gradle-user-home/
.gradle-user-home-2/
.gradle-user-home*/
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
Expand Down
1 change: 1 addition & 0 deletions bean/umc10th/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ repositories {

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
compileOnly 'org.projectlombok:lombok'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,68 @@

import com.example.umc10th.domain.member.dto.MemberReqDto;
import com.example.umc10th.domain.member.dto.MemberResDto;
import com.example.umc10th.domain.member.exception.code.MemberSuccessCode;
import com.example.umc10th.domain.member.service.MemberService;
import com.example.umc10th.global.apiPayload.ApiResponse;
import com.example.umc10th.global.security.AuthMember;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/members")
@RequiredArgsConstructor
@Tag(name = "Member", description = "회원 API")
public class MemberController {

private final MemberService memberService;

@GetMapping("/{memberId}")
public ResponseEntity<MemberResDto.MemberInfo> getMember(@PathVariable Long memberId) {
return ResponseEntity.ok(memberService.getMember(memberId));
@Operation(summary = "회원가입", description = "회원 정보를 저장하고 JWT access token을 발급합니다.")
@PostMapping("/api/v1/users/signup")
public ResponseEntity<ApiResponse<MemberResDto.SignUpResponse>> signUp(
@Valid @RequestBody MemberReqDto.SignUpRequest request
) {
MemberResDto.SignUpResponse result = memberService.signUp(request);
return ResponseEntity.ok(ApiResponse.of(MemberSuccessCode.SIGN_UP_SUCCESS, result));
}

@PostMapping
public ResponseEntity<MemberResDto.MemberInfo> createMember(@Valid @RequestBody MemberReqDto.Create request) {
return ResponseEntity.ok(memberService.createMember(request));
@Operation(summary = "로그인", description = "이메일과 비밀번호를 검증하고 JWT access token을 발급합니다.")
@PostMapping("/api/v1/users/login")
public ResponseEntity<ApiResponse<MemberResDto.LoginResponse>> login(
@Valid @RequestBody MemberReqDto.LoginRequest request
) {
MemberResDto.LoginResponse result = memberService.login(request);
return ResponseEntity.ok(ApiResponse.of(MemberSuccessCode.LOGIN_SUCCESS, result));
}
}

@Operation(summary = "마이페이지 조회(v1)", description = "요청 body의 회원 ID로 마이페이지 정보를 조회합니다.")
@PostMapping("/api/v1/users/me")
public ResponseEntity<ApiResponse<MemberResDto.GetMyPageResponse>> getMyPage(
@Valid @RequestBody MemberReqDto.GetMyPageRequest request
) {
MemberResDto.GetMyPageResponse result = memberService.getMyPage(request.id());

return ResponseEntity.ok(ApiResponse.of(MemberSuccessCode.GET_MY_PAGE_SUCCESS, result));
}

@Operation(
summary = "마이페이지 조회(v2)",
description = "Authorization 헤더의 Bearer 토큰으로 인증된 회원의 마이페이지 정보를 조회합니다.",
security = @SecurityRequirement(name = "bearerAuth")
)
@GetMapping("/api/v2/users/me")
public ResponseEntity<ApiResponse<MemberResDto.GetMyPageResponse>> getMyPageV2(
@AuthenticationPrincipal AuthMember authMember
) {
MemberResDto.GetMyPageResponse result = memberService.getMyPage(authMember.getMember());

return ResponseEntity.ok(ApiResponse.of(MemberSuccessCode.GET_MY_PAGE_SUCCESS, result));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import com.example.umc10th.domain.member.dto.MemberReqDto;
import com.example.umc10th.domain.member.dto.MemberResDto;
import com.example.umc10th.domain.member.entity.Member;
import com.example.umc10th.domain.store.entity.FoodCategory;
import com.example.umc10th.global.security.jwt.JwtTokenProvider;
import java.util.List;

public final class MemberConverter {

Expand All @@ -19,6 +22,23 @@ public static Member toEntity(MemberReqDto.Create request) {
.build();
}

public static Member toEntity(MemberReqDto.SignUpRequest request, String encodedPassword) {
return Member.builder()
.name(request.name())
.gender(request.gender())
.birthDate(request.birthDate())
.address(request.address())
.nickname(request.nickname())
.email(request.email())
.password(encodedPassword)
.ageOver14Agreed(Boolean.TRUE.equals(request.termsAgreement().ageOver14Agreed()))
.serviceTermsAgreed(Boolean.TRUE.equals(request.termsAgreement().serviceTermsAgreed()))
.privacyPolicyAgreed(Boolean.TRUE.equals(request.termsAgreement().privacyPolicyAgreed()))
.locationAgreed(Boolean.TRUE.equals(request.termsAgreement().locationAgreed()))
.marketingAgreed(Boolean.TRUE.equals(request.termsAgreement().marketingAgreed()))
.build();
}

public static MemberResDto.MemberInfo toMemberInfo(Member member) {
return new MemberResDto.MemberInfo(
member.getId(),
Expand All @@ -29,4 +49,48 @@ public static MemberResDto.MemberInfo toMemberInfo(Member member) {
member.getNickname()
);
}
}

public static MemberResDto.GetMyPageResponse toMyPageResponse(Member member, Integer point) {
return new MemberResDto.GetMyPageResponse(
member.getName(),
"",
member.getEmail(),
null,
point == null ? 0 : point
);
}

public static MemberResDto.SignUpResponse toSignUpResponse(
Member member,
List<FoodCategory> categories,
JwtTokenProvider.TokenInfo tokenInfo
) {
List<Long> preferCategoryIds = categories.stream()
.map(FoodCategory::getId)
.toList();

return new MemberResDto.SignUpResponse(
member.getId(),
member.getEmail(),
member.getNickname(),
preferCategoryIds,
tokenInfo.accessToken(),
tokenInfo.tokenType(),
tokenInfo.expiresIn()
);
}

public static MemberResDto.LoginResponse toLoginResponse(
Member member,
JwtTokenProvider.TokenInfo tokenInfo
) {
return new MemberResDto.LoginResponse(
member.getId(),
member.getEmail(),
member.getNickname(),
tokenInfo.accessToken(),
tokenInfo.tokenType(),
tokenInfo.expiresIn()
);
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,104 @@
package com.example.umc10th.domain.member.dto;

import com.example.umc10th.domain.member.enums.Gender;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;
import java.time.LocalDate;
import java.util.List;

public class MemberReqDto {

public record Create(
@Schema(description = "회원 이름", example = "김철수")
@NotBlank @Size(max = 20) String name,

@Schema(description = "성별", example = "MALE", allowableValues = {"MALE", "FEMALE", "NONE"})
@NotNull Gender gender,

@Schema(description = "생년월일", example = "2000-03-24")
@NotNull LocalDate birthDate,

@Schema(description = "주소", example = "서울특별시 성동구 왕십리로 222")
@NotBlank @Size(max = 255) String address,

@Schema(description = "닉네임", example = "bean")
@NotBlank @Size(max = 20) String nickname
) {
}
}

public record GetMyPageRequest(
@Schema(description = "회원 ID", example = "1")
@NotNull @Positive Long id
) {
}

public record SignUpRequest(
@Schema(description = "회원 이름", example = "김철수")
@NotBlank @Size(max = 20) String name,

@Schema(description = "성별", example = "MALE", allowableValues = {"MALE", "FEMALE", "NONE"})
@NotNull Gender gender,

@Schema(description = "생년월일", example = "2000-03-24")
@NotNull LocalDate birthDate,

@Schema(description = "주소", example = "서울특별시 성동구 왕십리로 222")
@NotBlank @Size(max = 255) String address,

@Schema(description = "닉네임", example = "bean")
@NotBlank @Size(max = 20) String nickname,

@Schema(description = "이메일", example = "bean@example.com")
@NotBlank @Email @Size(max = 100) String email,

@Schema(description = "비밀번호", example = "Password1!")
@NotBlank
@Size(min = 8, max = 30)
@Pattern(
regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[!@#$%^&*()_+=\\-{}\\[\\]:;\"'<>,.?/]).+$",
message = "비밀번호는 영문, 숫자, 특수문자를 각각 1개 이상 포함해야 합니다."
)
String password,

@Schema(description = "선호 음식 카테고리 ID 목록", example = "[1, 2]")
@NotEmpty List<@NotNull @Positive Long> preferCategoryIds,

@Schema(description = "약관 동의 정보")
@Valid @NotNull TermsAgreement termsAgreement
) {
}

public record LoginRequest(
@Schema(description = "이메일", example = "bean@example.com")
@NotBlank @Email @Size(max = 100) String email,

@Schema(description = "비밀번호", example = "Password1!")
@NotBlank String password
) {
}

public record TermsAgreement(
@Schema(description = "만 14세 이상 동의 여부", example = "true")
@NotNull Boolean ageOver14Agreed,

@Schema(description = "서비스 이용약관 필수 동의 여부", example = "true")
@NotNull Boolean serviceTermsAgreed,

@Schema(description = "개인정보 처리방침 필수 동의 여부", example = "true")
@NotNull Boolean privacyPolicyAgreed,

@Schema(description = "위치정보 제공 선택 동의 여부", example = "false")
@NotNull Boolean locationAgreed,

@Schema(description = "마케팅 수신 선택 동의 여부", example = "false")
@NotNull Boolean marketingAgreed
) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.example.umc10th.domain.member.enums.Gender;
import java.time.LocalDate;
import java.util.List;

public class MemberResDto {

Expand All @@ -14,4 +15,34 @@ public record MemberInfo(
String nickname
) {
}
}

public record GetMyPageResponse(
String name,
String profileUrl,
String email,
String phoneNumber,
Integer point
) {
}

public record SignUpResponse(
Long memberId,
String email,
String nickname,
List<Long> preferCategoryIds,
String accessToken,
String tokenType,
Long expiresIn
) {
}

public record LoginResponse(
Long memberId,
String email,
String nickname,
String accessToken,
String tokenType,
Long expiresIn
) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,27 @@ public class Member {
@Column(name = "nickname", nullable = false, length = 20)
private String nickname;

@Column(name = "email", unique = true, length = 100)
private String email;

@Column(name = "password", length = 255)
private String password;

@Column(name = "age_over_14_agreed", nullable = false)
private boolean ageOver14Agreed;

@Column(name = "service_terms_agreed", nullable = false)
private boolean serviceTermsAgreed;

@Column(name = "privacy_policy_agreed", nullable = false)
private boolean privacyPolicyAgreed;

@Column(name = "location_agreed", nullable = false)
private boolean locationAgreed;

@Column(name = "marketing_agreed", nullable = false)
private boolean marketingAgreed;

@Column(name = "created_at", nullable = false)
private LocalDateTime createdAt;

Expand Down Expand Up @@ -85,4 +106,4 @@ protected void onCreate() {
protected void onUpdate() {
this.updatedAt = LocalDateTime.now();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
package com.example.umc10th.domain.member.exception;

import com.example.umc10th.domain.member.exception.code.MemberErrorCode;
import lombok.Getter;

@Getter
public class MemberException extends RuntimeException {

private final MemberErrorCode errorCode;
import com.example.umc10th.global.apiPayload.exception.GeneralException;

public class MemberException extends GeneralException {
public MemberException(MemberErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
super(errorCode);
}
}
Loading