Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat #39] 사용자 프로필 조회/수정 API #40

Merged
merged 19 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
bdf76b9
[rename] 인가 관련 API 컨트롤러 위치 이동(MemberController -> AuthController)
dudxo Aug 13, 2024
cb5f18d
[test] : 인가 관련 API 컨트롤러 위치 이동으로 인한 테스트 코드 위치 변경
dudxo Aug 13, 2024
8586449
[feat] : 임시 로그인 API 이메일 Valid 추가
dudxo Aug 13, 2024
00db350
[test] : 사용자 프로필 조회 TDD
dudxo Aug 13, 2024
29b4f1d
[rename] : 패키지 위치 변경
dudxo Aug 13, 2024
401df18
[fix] : ResponseDTO 필드명 오탈자 수정으로 인한 테스트 코드 수정
dudxo Aug 13, 2024
ae2b32a
[feat] : 회원 프로필 조회 응답 DTO 추가
dudxo Aug 13, 2024
dbfd50b
[feat] : MemberMapper(DTO <-> Entity) 추가
dudxo Aug 13, 2024
5e152a1
[feat] : 회원 프로필 조회 API 추가
dudxo Aug 13, 2024
eb1568f
[rename] : 정적 메서드 네이밍 변경(of -> from)
dudxo Aug 13, 2024
1c7e946
[feat] : MemberErrorCode 프로필 수정 에러코드 추가
dudxo Aug 13, 2024
0f426b0
[feat] : 공무원 이메일이 일치하는 회원 반환 쿼리 메서드 추가
dudxo Aug 13, 2024
fd301be
[feat] : 사용자 프로필 수정 API 로직 추가
dudxo Aug 13, 2024
f862cbf
[test] : 사용자 프로필 수정 단위 테스트 추가
dudxo Aug 13, 2024
163beb5
[test] : 사용자 프로필 조회/수정 컨트롤러 통합 테스트 추가
dudxo Aug 13, 2024
f5798f4
[feat] : 프로필 조회 시 트랜잭션 읽기 전용 설정 추가
dudxo Aug 13, 2024
457d80e
[rename] : 프로필 수정 API 파라티터 변수 네이밍 변경
dudxo Aug 13, 2024
1f384c3
[test] : 프로필 조회/수정 API 테스트 성공 확인 데이터 정확도를 위한 수정
dudxo Aug 13, 2024
15d36bd
[rename] : 리뷰에 따른 네이밍 수정
dudxo Aug 13, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
Expand All @@ -13,10 +14,21 @@

import com.dnd.gongmuin.auth.dto.LoginRequest;
import com.dnd.gongmuin.auth.service.AuthService;
import com.dnd.gongmuin.member.domain.Member;
import com.dnd.gongmuin.member.dto.request.AdditionalInfoRequest;
import com.dnd.gongmuin.member.dto.request.LogoutRequest;
import com.dnd.gongmuin.member.dto.request.ReissueRequest;
import com.dnd.gongmuin.member.dto.request.ValidateNickNameRequest;
import com.dnd.gongmuin.member.dto.response.LogoutResponse;
import com.dnd.gongmuin.member.dto.response.ReissueResponse;
import com.dnd.gongmuin.member.dto.response.SignUpResponse;
import com.dnd.gongmuin.member.dto.response.ValidateNickNameResponse;
import com.dnd.gongmuin.member.service.MemberService;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

@Tag(name = "Social Login API", description = "소셜 로그인 요청 API")
dudxo marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -26,6 +38,7 @@
public class AuthController {

private final AuthService authService;
private final MemberService memberService;

@Operation(summary = "카카오 로그인 API", description = "카카오 로그인 페이지로 이동 요청한다.(사용불가!!)")
@ApiResponse(useReturnTypeSchema = true)
Expand All @@ -40,10 +53,45 @@ public ResponseEntity<?> kakaoLoginRedirect() {
@Operation(summary = "임시 로그인/회원가입(토큰 발급) API", description = "로그인 또는 회원가입 후 토큰을 발급한다.")
@ApiResponse(useReturnTypeSchema = true)
@PostMapping("/token")
public ResponseEntity<String> swaggerToken(@RequestBody LoginRequest loginRequest) {
public ResponseEntity<String> swaggerToken(@RequestBody @Valid LoginRequest loginRequest) {
dudxo marked this conversation as resolved.
Show resolved Hide resolved
String accessToken = authService.swaggerToken(loginRequest);
return ResponseEntity.ok(accessToken);

}

@Operation(summary = "닉네임 중복 검증 API", description = "닉네임 중복을 검증한다.")
@ApiResponse(useReturnTypeSchema = true)
@PostMapping("/check-nickname")
public ResponseEntity<ValidateNickNameResponse> checkNickName(
@RequestBody @Valid ValidateNickNameRequest validateNickNameRequest) {
return ResponseEntity.ok(memberService.isDuplicatedNickname(validateNickNameRequest));
}

@Operation(summary = "추가정보 API", description = "추가 정보를 저장한다.")
@ApiResponse(useReturnTypeSchema = true)
@PostMapping("/member")
public ResponseEntity<SignUpResponse> signUp(
@RequestBody @Valid AdditionalInfoRequest request,
dudxo marked this conversation as resolved.
Show resolved Hide resolved
@AuthenticationPrincipal Member loginMember) {
SignUpResponse response = memberService.signUp(request, loginMember.getSocialEmail());

return ResponseEntity.ok(response);
}

@Operation(summary = "로그아웃 API", description = "로그아웃한다.")
@ApiResponse(useReturnTypeSchema = true)
@PostMapping("/logout")
public ResponseEntity<LogoutResponse> logout(@RequestBody @Valid LogoutRequest request) {
LogoutResponse response = memberService.logout(request);
return ResponseEntity.ok(response);
}

@Operation(summary = "토큰 재발급 API", description = "토큰을 재발급한다.")
@ApiResponse(useReturnTypeSchema = true)
@PostMapping("/reissue/token")
public ResponseEntity<ReissueResponse> reissue(@RequestBody @Valid ReissueRequest request) {
ReissueResponse response = memberService.reissue(request);
return ResponseEntity.ok(response);
}
}

4 changes: 4 additions & 0 deletions src/main/java/com/dnd/gongmuin/auth/dto/LoginRequest.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package com.dnd.gongmuin.auth.dto;

import jakarta.validation.constraints.Email;

public record LoginRequest(

String socialName,

@Email
String socialEmail

) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,68 +2,46 @@

import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.dnd.gongmuin.member.domain.Member;
import com.dnd.gongmuin.member.dto.request.AdditionalInfoRequest;
import com.dnd.gongmuin.member.dto.request.LogoutRequest;
import com.dnd.gongmuin.member.dto.request.ReissueRequest;
import com.dnd.gongmuin.member.dto.request.ValidateNickNameRequest;
import com.dnd.gongmuin.member.dto.response.LogoutResponse;
import com.dnd.gongmuin.member.dto.response.ReissueResponse;
import com.dnd.gongmuin.member.dto.response.SignUpResponse;
import com.dnd.gongmuin.member.dto.response.ValidateNickNameResponse;
import com.dnd.gongmuin.member.dto.request.UpdateMemberProfileRequest;
import com.dnd.gongmuin.member.dto.response.MemberProfileResponse;
import com.dnd.gongmuin.member.service.MemberService;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

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

private final MemberService memberService;

@Operation(summary = "닉네임 중복 검증 API", description = "닉네임 중복을 검증한다.")
@Operation(summary = "프로필 조회 API", description = "로그인 된 사용자 프로필 정보를 조회한다.")
@ApiResponse(useReturnTypeSchema = true)
@PostMapping("/check-nickname")
public ResponseEntity<ValidateNickNameResponse> checkNickName(
@RequestBody @Valid ValidateNickNameRequest validateNickNameRequest) {
return ResponseEntity.ok(memberService.isDuplicatedNickname(validateNickNameRequest));
}

@Operation(summary = "추가정보 API", description = "추가 정보를 저장한다.")
@ApiResponse(useReturnTypeSchema = true)
@PostMapping("/member")
public ResponseEntity<SignUpResponse> signUp(
@RequestBody @Valid AdditionalInfoRequest request,
@AuthenticationPrincipal Member loginMember) {
SignUpResponse response = memberService.signUp(request, loginMember.getSocialEmail());

@GetMapping("/profile")
public ResponseEntity<MemberProfileResponse> getMemberProfile(@AuthenticationPrincipal Member member) {
MemberProfileResponse response = memberService.getMemberProfile(member);
return ResponseEntity.ok(response);
}

@Operation(summary = "로그아웃 API", description = "로그아웃한다.")
@Operation(summary = "프로필 수정 API", description = "로그인 된 사용자 프로필 정보를 수정한다.")
@ApiResponse(useReturnTypeSchema = true)
@PostMapping("/logout")
public ResponseEntity<LogoutResponse> logout(@RequestBody @Valid LogoutRequest request) {
LogoutResponse response = memberService.logout(request);
@PatchMapping("/profile/edit")
public ResponseEntity<MemberProfileResponse> updateMemberProfile(
@RequestBody UpdateMemberProfileRequest updateMemberProfileRequest,
@AuthenticationPrincipal Member member) {
MemberProfileResponse response = memberService.updateMemberProfile(updateMemberProfileRequest, member);
dudxo marked this conversation as resolved.
Show resolved Hide resolved
return ResponseEntity.ok(response);
}

@Operation(summary = "토큰 재발급 API", description = "토큰을 재발급한다.")
@ApiResponse(useReturnTypeSchema = true)
@PostMapping("/reissue/token")
public ResponseEntity<ReissueResponse> reissue(@RequestBody @Valid ReissueRequest request) {
ReissueResponse response = memberService.reissue(request);
return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public enum JobCategory {

private final String label;

public static JobCategory of(String input) {
public static JobCategory from(String input) {
return Arrays.stream(values())
.filter(category -> category.isEqual(input))
.findAny()
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/com/dnd/gongmuin/member/domain/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,10 @@ public void increaseCredit(int credit) {
this.credit += credit;
}

public void updateProfile(String nickname, JobGroup jobGroup, JobCategory jobCategory) {
this.nickname = nickname;
this.jobGroup = jobGroup;
this.jobCategory = jobCategory;
}

}
20 changes: 20 additions & 0 deletions src/main/java/com/dnd/gongmuin/member/dto/MemberMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.dnd.gongmuin.member.dto;

import com.dnd.gongmuin.member.domain.Member;
import com.dnd.gongmuin.member.dto.response.MemberProfileResponse;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class MemberMapper {

public static MemberProfileResponse toMemberProfileResponse(Member member) {
return new MemberProfileResponse(
member.getNickname(),
member.getJobGroup().getLabel(),
member.getJobCategory().getLabel(),
member.getCredit()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.dnd.gongmuin.member.dto.request;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

public record UpdateMemberProfileRequest(

@NotBlank(message = "닉네임은 필수 입력 항목입니다.")
@Size(min = 2, max = 12, message = "닉네임은 최소 2자리 이상 최대 12자 이하입니다.")
String nickname,

@NotBlank(message = "직군은 필수 입력 항목입니다.")
String jobGroup,

@NotBlank(message = "직렬은 필수 입력 항목입니다.")
String jobCategory
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.dnd.gongmuin.member.dto.response;

public record MemberProfileResponse(
String nickname,
String jobGroup,
String jobCategory,
int credit
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ public enum MemberErrorCode implements ErrorCode {
NOT_FOUND_MEMBER("특정 회원을 찾을 수 없습니다.", "MEMBER_001"),
NOT_FOUND_NEW_MEMBER("신규 회원이 아닙니다.", "MEMBER_002"),
LOGOUT_FAILED("로그아웃을 실패했습니다.", "MEMBER_003"),
NOT_ENOUGH_CREDIT("보유한 크레딧이 부족합니다.", "MEMBER_004");
NOT_ENOUGH_CREDIT("보유한 크레딧이 부족합니다.", "MEMBER_004"),
UPDATE_PROFILE_FAILED("프로필 수정에 실패했습니다.", "MEMBER_005");

private final String message;
private final String code;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ public interface MemberRepository extends JpaRepository<Member, Long> {
boolean existsByOfficialEmail(String officialEmail);

boolean existsBySocialEmail(String socialEmail);

Member findByOfficialEmail(String officialEmail);
}
34 changes: 33 additions & 1 deletion src/main/java/com/dnd/gongmuin/member/service/MemberService.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@
import com.dnd.gongmuin.member.domain.JobCategory;
import com.dnd.gongmuin.member.domain.JobGroup;
import com.dnd.gongmuin.member.domain.Member;
import com.dnd.gongmuin.member.dto.MemberMapper;
import com.dnd.gongmuin.member.dto.request.AdditionalInfoRequest;
import com.dnd.gongmuin.member.dto.request.LogoutRequest;
import com.dnd.gongmuin.member.dto.request.ReissueRequest;
import com.dnd.gongmuin.member.dto.request.UpdateMemberProfileRequest;
import com.dnd.gongmuin.member.dto.request.ValidateNickNameRequest;
import com.dnd.gongmuin.member.dto.response.LogoutResponse;
import com.dnd.gongmuin.member.dto.response.MemberProfileResponse;
import com.dnd.gongmuin.member.dto.response.ReissueResponse;
import com.dnd.gongmuin.member.dto.response.SignUpResponse;
import com.dnd.gongmuin.member.dto.response.ValidateNickNameResponse;
Expand Down Expand Up @@ -98,7 +101,7 @@ private void updateAdditionalInfo(AdditionalInfoRequest request, Member findMemb
request.nickname(),
request.officialEmail(),
JobGroup.from(request.jobGroup()),
JobCategory.of(request.jobCategory())
JobCategory.from(request.jobCategory())
);
}

Expand Down Expand Up @@ -157,4 +160,33 @@ public ReissueResponse reissue(ReissueRequest request) {

return new ReissueResponse(reissuedAccessToken);
}

@Transactional(readOnly = true)
public MemberProfileResponse getMemberProfile(Member member) {
try {
Member findMember = memberRepository.findByOfficialEmail(member.getOfficialEmail());
return MemberMapper.toMemberProfileResponse(findMember);
} catch (Exception e) {
throw new NotFoundException(MemberErrorCode.NOT_FOUND_MEMBER);
}
}

@Transactional
public MemberProfileResponse updateMemberProfile(UpdateMemberProfileRequest request, Member member) {
try {
Member findMember = memberRepository.findByOfficialEmail(member.getOfficialEmail());
JobGroup jobGroup = JobGroup.from(request.jobGroup());
JobCategory jobCategory = JobCategory.from(request.jobCategory());

findMember.updateProfile(
request.nickname(),
jobGroup,
jobCategory
);

return MemberMapper.toMemberProfileResponse(findMember);
} catch (Exception e) {
throw new ValidationException(MemberErrorCode.UPDATE_PROFILE_FAILED);
}
}
}
Loading
Loading