Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ public enum FileErrorCode implements ErrorCode {
INVALID_FILE_NAME(4003, "유효하지 않은 파일명입니다."),
INVALID_FILE_EXTENSION(4004, "지원하지 않는 파일 형식입니다. (jpg, jpeg, png, gif만 허용)"),
INVALID_FILE_TYPE(4005, "이미지 파일만 업로드 가능합니다."),
FILE_NOT_FOUND(4006, "파일을 찾을 수 없습니다.");
FILE_NOT_FOUND(4006, "파일을 찾을 수 없습니다."),
FILE_DELETE_FAILED(4007, "파일 삭제에 실패했습니다.");

private final int errorCode;
private final String errorMessage;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,10 @@ public FileNotFound() {
super(FileErrorCode.FILE_NOT_FOUND);
}
}

public static class FileDeleteFailed extends FileException {
public FileDeleteFailed() {
super(FileErrorCode.FILE_DELETE_FAILED);
}
}
}
16 changes: 16 additions & 0 deletions src/main/java/run/backend/domain/file/service/FileService.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,22 @@ public class FileService {
"gif");
private static final long MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB

public void deleteImage(String fileName) {

validateFilename(fileName);
if (fileName.equals("default-profile-image.png"))
return ;

try {
Path uploadPath = Paths.get(uploadDir, "profiles");
Path filePath = uploadPath.resolve(fileName);
Files.deleteIfExists(filePath);

} catch (IOException e) {
throw new FileException.FileDeleteFailed();
}
}

public String saveProfileImage(MultipartFile file) {
if (file == null || file.isEmpty()) {
return "default-profile-image.png";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import run.backend.domain.member.dto.request.MemberInfoRequest;
import run.backend.domain.member.dto.response.MemberInfoResponse;
import run.backend.domain.member.entity.Member;
import run.backend.domain.member.service.MemberServiceImpl;
Expand All @@ -25,4 +27,16 @@ public CommonResponse<MemberInfoResponse> getMemberInfo(@Login Member member) {
MemberInfoResponse response = memberService.getMemberInfo(member);
return new CommonResponse<>("유저 정보 조회 성공", response);
}

@Operation(summary = "유저 정보 수정", description = "마이페이지에서 유저 정보를 수정하는 API 입니다.")
@PostMapping
public CommonResponse<Void> updateMemberInfo(
@Login Member member,
@RequestParam String imageStatus,
@RequestPart(value = "data") MemberInfoRequest data,
@RequestPart(value = "image", required = false) MultipartFile image) {

memberService.updateMemberInfo(member, imageStatus, image, data);
return new CommonResponse<>("유저 정보 수정 성공");
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
package run.backend.domain.member.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.lang.Nullable;
import run.backend.domain.member.enums.Gender;

public record MemberInfoRequest(
@Nullable
@Schema(description = "성별", example = "FEMALE / MALE", nullable = true)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

성별은 변경 안되는거 어떤가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 피그마보고 한건데 그럼 디자이너 분들께 성별은 빼달라고 말할게요..!

Gender gender,
int age,
@Nullable
@Schema(description = "나이", example = "24", nullable = true)
Integer age,
@Nullable
@Schema(description = "닉네임", example = "러너스", nullable = true)
String nickname
) {
}
12 changes: 11 additions & 1 deletion src/main/java/run/backend/domain/member/entity/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,22 @@ public class Member extends BaseEntity {

private boolean pushEnabled;

public void setMemberDefaultInfo(Gender gender, int age, String nickname) {
public void updateGender(Gender gender) {
this.gender = gender;
}

public void updateAge(int age) {
this.age = age;
}

public void updateNickname(String nickname) {
this.nickname = nickname;
}

public void updateImage(String imageName) {
this.profileImage = imageName;
}

@Builder
public Member(String username, String nickname, Gender gender, int age, String oauthId, OAuthType oauthType, String profileImage) {
this.username = username;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package run.backend.domain.member.service;

import org.springframework.web.multipart.MultipartFile;
import run.backend.domain.member.dto.request.MemberInfoRequest;
import run.backend.domain.member.dto.response.MemberInfoResponse;
import run.backend.domain.member.entity.Member;

public interface MemberService {

MemberInfoResponse getMemberInfo(Member member);

void updateMemberInfo(Member member, String imageStatus, MultipartFile image, MemberInfoRequest data);

// void updateMember();

// void deleteMember(Member member);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import run.backend.domain.crew.entity.Crew;
import run.backend.domain.crew.enums.JoinStatus;
import run.backend.domain.file.service.FileService;
import run.backend.domain.member.dto.request.MemberInfoRequest;
import run.backend.domain.member.dto.response.MemberInfoResponse;
import run.backend.domain.member.entity.Member;
import run.backend.domain.member.repository.MemberRepository;
Expand All @@ -15,14 +18,41 @@
public class MemberServiceImpl implements MemberService {

private final MemberRepository memberRepository;
private final FileService fileService;

@Override
public MemberInfoResponse getMemberInfo(Member member) {

String crewName = memberRepository.findCrewByMemberIdAndStatus(member.getId(), JoinStatus.APPROVED)
.map(Crew::getName)
.orElse("N/A");

return new MemberInfoResponse(member.getProfileImage(), member.getNickname(), crewName);
}

@Override
@Transactional
public void updateMemberInfo(Member member, String imageStatus, MultipartFile image, MemberInfoRequest data) {

switch (imageStatus) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

깔꼼하다👍


case "updated" :
fileService.deleteImage(member.getProfileImage()); // 기존 이미지 지우기
String newImageName = fileService.saveProfileImage(image); // 새로운 이미지 저장
member.updateImage(newImageName);
break ;
case "removed" :
fileService.deleteImage(member.getProfileImage());
member.updateImage("default-profile-image.png");
break ;
}

if (data.gender() != null)
member.updateGender(data.gender());
if (data.age() != null)
member.updateAge(data.age());
if (data.nickname() != null)
member.updateNickname(data.nickname());

memberRepository.save(member);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import run.backend.domain.member.entity.Member;
import run.backend.domain.member.repository.MemberRepository;
import run.backend.global.security.CustomUserDetails;

@Component
@RequiredArgsConstructor
public class LoginArgumentResolver implements HandlerMethodArgumentResolver {

private final MemberRepository memberRepository;

@Override
public boolean supportsParameter(MethodParameter parameter) {

Expand All @@ -28,8 +31,11 @@ public boolean supportsParameter(MethodParameter parameter) {
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
CustomUserDetails principal = (CustomUserDetails) authentication.getPrincipal();
return principal.getMember();
// Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// CustomUserDetails principal = (CustomUserDetails) authentication.getPrincipal();
// return principal.getMember();

return memberRepository.findById(1L)
.orElseThrow(() -> new IllegalArgumentException("해당 유저가 없습니다"));
}
}
25 changes: 25 additions & 0 deletions src/main/java/run/backend/global/config/WebConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package run.backend.global.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import run.backend.global.annotation.member.LoginArgumentResolver;

import java.util.List;

@Configuration
public class WebConfig implements WebMvcConfigurer {

private final LoginArgumentResolver loginArgumentResolver;

@Autowired
public WebConfig(LoginArgumentResolver loginArgumentResolver) {
this.loginArgumentResolver = loginArgumentResolver;
}

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(loginArgumentResolver);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public class SecurityConfig {
};

private final String[] PermitAllPatterns = {
"/api/v1/members/**",
"/api/v1/auth/**"
};

Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ spring:
jpa:
database: mysql
hibernate:
ddl-auto: create
ddl-auto: update
properties:
hibernate:
dialect: org.hibernate.dialect.MySQLDialect
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,51 +7,67 @@
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.multipart.MultipartFile;
import run.backend.domain.crew.entity.Crew;
import run.backend.domain.crew.enums.JoinStatus;
import run.backend.domain.file.service.FileService;
import run.backend.domain.member.dto.request.MemberInfoRequest;
import run.backend.domain.member.dto.response.MemberInfoResponse;
import run.backend.domain.member.entity.Member;
import run.backend.domain.member.enums.Gender;
import run.backend.domain.member.enums.OAuthType;
import run.backend.domain.member.repository.MemberRepository;

import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
public class MemberServiceTest {

@Mock
private MemberRepository memberRepository;

@Mock
private FileService fileService;

@InjectMocks
private MemberServiceImpl memberService;

private Member testMember;
private Crew testCrew;

private MemberInfoRequest data;

@BeforeEach
public void setUp() {

// member
testMember = Member.builder()
testMember = spy(Member.builder()
.username("test username")
.oauthId("test id")
.oauthType(OAuthType.GOOGLE)
.profileImage("test image")
.build();
.build());

// crew
testCrew = Crew.builder()
testCrew = spy(Crew.builder()
.name("test crew name")
.description("크루 소개 테스트")
.image("test image url")
.build();
.build());

// memberInfoRequest
data = new MemberInfoRequest(
Gender.FEMALE,
20,
"newNickname");
}

@Test
@DisplayName("회원 정보가 올바르게 조회 되는지 확인")
@DisplayName("회원 정보 조회 테스트")
public void getMemberInfoTest() {

// given
Expand All @@ -65,4 +81,45 @@ public void getMemberInfoTest() {
assertEquals(testMember.getNickname(), response.nickName());
assertEquals(testCrew.getName(), response.crewName());
}

@Test
@DisplayName("회원 정보 수정 - 이미지 업로드")
public void updateMemberInfo_whenImageUpdated() {

// given
String imageStatus = "updated";
String newImagename = "newImage";
MultipartFile image = new MockMultipartFile(
"newImage",
"newImage.png",
"image/png",
"dummy image content".getBytes());

when(fileService.saveProfileImage(image)).thenReturn(newImagename);

// when
memberService.updateMemberInfo(testMember, imageStatus, image, data);

// then
verify(fileService).deleteImage("test image");
verify(fileService).saveProfileImage(image);
verify(testMember).updateImage(newImagename);
verify(memberRepository).save(testMember);
}

@Test
@DisplayName("회원 정보 수정 - 이미지 삭제")
public void updateMemberInfo_whenImageRemoved() {

// given
String imageStatus = "removed";

// when
memberService.updateMemberInfo(testMember, imageStatus, null, data);

// then
verify(fileService).deleteImage("test image");
verify(testMember).updateImage("default-profile-image.png");
verify(memberRepository).save(testMember);
}
}