Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
ee8c7ff
[TNT-258] feat: 회원 정보 수정 구현 중
fakerdeft Mar 5, 2025
1f93763
Merge branch 'develop' of https://github.com/YAPP-Github/25th-App-Tea…
fakerdeft Mar 5, 2025
498c560
[TNT-258] feat: 회원 정보 수정 구현
fakerdeft Mar 5, 2025
f7058c1
[TNT-258] feat: else if 수정
fakerdeft Mar 5, 2025
cb566a0
[TNT-258] feat: 회원 정보 수정 기능 구현 중
fakerdeft Mar 14, 2025
16f0228
[TNT-258] feat: 프로필 사진 수정, 회원 정보 수정 기능 구현
fakerdeft Mar 21, 2025
0dfb655
Merge branch 'develop' of https://github.com/YAPP-Github/25th-App-Tea…
fakerdeft Mar 21, 2025
0fca4fe
[TNT-258] feat: 프로필 사진 수정 테스트 케이스 추가
fakerdeft Mar 21, 2025
35128d3
[TNT-258] feat: 이미지 s3 삭제 및 업로드 로직 수정
fakerdeft Mar 27, 2025
5bc49b4
[TNT-258] feat: 변수명 member로 수정
fakerdeft Mar 29, 2025
42f16d1
[TNT-258] refactor: 도메인 기반 구조에 맞게 프로필/회원 정보 수정 기능 리팩토링
fakerdeft May 1, 2025
432d291
[TNT-258] refactor: 도메인 기반 구조에 맞게 프로필/회원 정보 수정 기능 리팩토링
fakerdeft May 6, 2025
0fca7b6
[TNT-258] refactor: 프로필/회원 정보 수정 기능 리팩토링
fakerdeft May 7, 2025
96c3e28
[TNT-258] refactor: 프로필/회원 정보 수정 기능 리팩토링
fakerdeft May 7, 2025
05eb11e
Merge branch 'develop' of https://github.com/YAPP-Github/25th-App-Tea…
fakerdeft May 7, 2025
2a668ca
[TNT-258]feat: 회원 정보 업데이트 기능 수정
fakerdeft May 29, 2025
320f015
[TNT-258] refactor: 프로필/회원 정보 수정 기능 리팩토링
fakerdeft May 29, 2025
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
3 changes: 1 addition & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ sonar {
property "sonar.host.url", "https://sonarcloud.io"
property "sonar.coverage.jacoco.xmlReportPaths", "build/reports/jacoco/test/jacocoTestReport.xml"
property "sonar.exclusions", "**/*Application*.java, **/*Config*.java, **/*GlobalExceptionHandler.java, **/Q*.java, **/DynamicQuery.java, " +
"**/*Exception.java, **/*Adapter.java, **/CustomOAuth2UserService.java, **/*SearchRepository.java, **/*Filter.java"
"**/*Exception.java, **/*Adapter.java, **/CustomOAuth2UserService.java, **/*Filter.java"
property "sonar.java.coveragePlugin", "jacoco"
}
}
Expand Down Expand Up @@ -123,7 +123,6 @@ jacocoTestCoverageVerification {
'*.Q*',
'*.DynamicQuery',
'*.*Adapter',
'*.*SearchRepository',
'*.*.CustomUserDetails',
'scouter.*',
'reactor.*',
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/tnt/common/jpa/DynamicQuery.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public static <T extends SimpleExpression> BooleanExpression generateIsNull(Bool
return null;
}

if (Boolean.TRUE.equals(value)) {
if (value) {
return field.isNull();
}

Expand Down
25 changes: 24 additions & 1 deletion src/main/java/com/tnt/image/application/S3Service.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@
import com.drew.metadata.MetadataException;
import com.drew.metadata.exif.ExifIFD0Directory;
import com.tnt.common.error.exception.ImageException;
import com.tnt.image.S3Adapter;
import com.tnt.image.infrastructure.S3Adapter;
import com.tnt.member.domain.MemberType;
import com.tnt.member.dto.ProfileUpdate;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -179,4 +180,26 @@ private BufferedImage rotateImageIfRequired(BufferedImage image, MultipartFile m

return image;
}

public String handleProfileImage(ProfileUpdate profileUpdate, @Nullable MultipartFile profileImage,
MemberType memberType) {
// 새 이미지 없음
if (isNull(profileImage)) {
// 이미지 삭제 요청 - 현재 이미지가 기본 이미지가 아닌 경우
if (profileUpdate.removeCurrentImage() && !profileUpdate.isCurrentImageDefault()) {
deleteProfileImage(profileUpdate.currentImageUrl());

return profileUpdate.changeImageUrl();
} else { // 이미지 유지 요청
return profileUpdate.currentImageUrl();
}
} else { // 새 이미지 있음
// 이미지 수정 요청 - 현재 이미지가 기본 이미지가 아닌 경우
if (profileUpdate.removeCurrentImage() && !profileUpdate.isCurrentImageDefault()) {
deleteProfileImage(profileUpdate.currentImageUrl());
}

return uploadProfileImage(profileImage, memberType);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.tnt.image;
package com.tnt.image.infrastructure;

import static com.tnt.common.error.model.ErrorMessage.S3_DELETE_ERROR;
import static com.tnt.common.error.model.ErrorMessage.S3_UPLOAD_ERROR;
Expand Down
130 changes: 116 additions & 14 deletions src/main/java/com/tnt/member/application/MemberService.java
Original file line number Diff line number Diff line change
@@ -1,27 +1,37 @@
package com.tnt.member.application;

import static com.tnt.common.constant.ImageConstant.TRAINEE_DEFAULT_IMAGE;
import static com.tnt.common.constant.ImageConstant.TRAINER_DEFAULT_IMAGE;
import static com.tnt.common.error.model.ErrorMessage.MEMBER_CONFLICT;
import static com.tnt.member.domain.MemberType.TRAINEE;
import static com.tnt.member.domain.MemberType.TRAINER;
import static com.tnt.member.dto.MemberProjection.MemberTypeDto;
import static java.util.Objects.isNull;
import static java.util.stream.Collectors.toSet;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import com.tnt.common.error.exception.ConflictException;
import com.tnt.gateway.dto.response.CheckSessionResponse;
import com.tnt.member.application.repository.MemberRepository;
import com.tnt.member.domain.Member;
import com.tnt.member.domain.SocialType;
import com.tnt.member.dto.response.GetMemberInfoResponse;
import com.tnt.member.dto.response.GetMemberInfoResponse.TraineeInfo;
import com.tnt.member.dto.response.GetMemberInfoResponse.TrainerInfo;
import com.tnt.member.dto.ProfileUpdate;
import com.tnt.member.dto.request.UpdateMemberInfoRequest;
import com.tnt.member.dto.response.MemberInfoResponse;
import com.tnt.pt.application.PtService;
import com.tnt.pt.domain.PtTrainerTrainee;
import com.tnt.trainee.application.PtGoalService;
import com.tnt.trainee.application.TraineeService;
import com.tnt.trainee.application.repository.PtGoalRepository;
import com.tnt.trainee.application.repository.TraineeRepository;
import com.tnt.trainee.domain.PtGoal;
import com.tnt.trainee.domain.Trainee;
import com.tnt.trainer.application.TrainerService;
Expand All @@ -39,11 +49,13 @@ public class MemberService {
private final PtService ptService;

private final MemberRepository memberRepository;
private final TraineeRepository traineeRepository;
private final PtGoalRepository ptGoalRepository;

@Transactional(readOnly = true)
public GetMemberInfoResponse getMemberInfo(Long memberId) {
public MemberInfoResponse getMemberInfo(Long memberId) {
Member member = getByMemberId(memberId);
GetMemberInfoResponse memberInfo = null;
MemberInfoResponse memberInfoResponse = null;

if (member.getMemberType() == TRAINER) {
Trainer trainer = trainerService.getByMemberId(memberId);
Expand All @@ -56,24 +68,33 @@ public GetMemberInfoResponse getMemberInfo(Long memberId) {

int totalTraineeCount = ptTrainerTrainees.size();

TrainerInfo trainerInfo = new TrainerInfo(activeTraineeCount, totalTraineeCount);
MemberInfoResponse.TrainerInfo trainerInfo = new MemberInfoResponse.TrainerInfo(activeTraineeCount,
totalTraineeCount);

memberInfo = new GetMemberInfoResponse(member.getName(), member.getEmail(), member.getProfileImageUrl(),
memberInfoResponse = new MemberInfoResponse(member.getName(), member.getEmail(),
member.getProfileImageUrl(),
member.getMemberType(), member.getSocialType(), trainerInfo, null);
} else if (member.getMemberType() == TRAINEE) {
}

if (member.getMemberType() == TRAINEE) {
Trainee trainee = traineeService.getByMemberId(memberId);
List<String> ptGoals = ptGoalService.getAllByTraineeId(trainee.getId()).stream().map(
PtGoal::getContent).toList();
List<String> ptGoals = ptGoalService.getAllByTraineeId(trainee.getId())
.stream()
.map(PtGoal::getContent)
.toList();
boolean isConnected = ptService.isPtTrainerTraineeExistWithTraineeId(trainee.getId());

TraineeInfo traineeInfo = new TraineeInfo(isConnected, member.getBirthday(),
member.getAge(), trainee.getHeight(), trainee.getWeight(), trainee.getCautionNote(), ptGoals);
MemberInfoResponse.TraineeInfo traineeInfo = new MemberInfoResponse.TraineeInfo(isConnected,
member.getBirthday(),
member.getAge(),
trainee.getHeight(), trainee.getWeight(), trainee.getCautionNote(), ptGoals);

memberInfo = new GetMemberInfoResponse(member.getName(), member.getEmail(), member.getProfileImageUrl(),
memberInfoResponse = new MemberInfoResponse(member.getName(), member.getEmail(),
member.getProfileImageUrl(),
member.getMemberType(), member.getSocialType(), null, traineeInfo);
}

return memberInfo;
return memberInfoResponse;
}

@Transactional(readOnly = true)
Expand All @@ -92,6 +113,59 @@ public CheckSessionResponse getMemberType(Long memberId) {
return new CheckSessionResponse(memberTypeDto.memberType(), isConnected);
}

@Transactional
public ProfileUpdate checkMemberProfileImage(Long memberId, boolean removeImage,
@Nullable MultipartFile profileImage) {
Member member = getByMemberId(memberId);
String currentImageUrl = member.getProfileImageUrl();
String changeImageUrl = "";
boolean removeCurrentImage = true;
boolean isCurrentImageDefault =
currentImageUrl.equals(TRAINER_DEFAULT_IMAGE) || currentImageUrl.equals(TRAINEE_DEFAULT_IMAGE);

// 새 이미지 없음
if (isNull(profileImage)) {
// 이미지 삭제 요청 - 현재 이미지가 기본 이미지가 아닌 경우
if (removeImage && !isCurrentImageDefault) {
changeImageUrl =
member.getMemberType() == TRAINER ? TRAINER_DEFAULT_IMAGE : TRAINEE_DEFAULT_IMAGE;
} else if (!removeImage && isCurrentImageDefault) { // 이미지 유지 요청 - 현재 이미지가 기본 이미지인 경우
changeImageUrl = currentImageUrl;
removeCurrentImage = false;
} else { // 이미지 유지 요청 - 현재 이미지가 기본 이미지가 아닌 경우
removeCurrentImage = false;
}
} else { // 새 이미지 있음
// 이미지 수정 요청 - 현재 이미지가 기본 이미지인 경우
if (isCurrentImageDefault) {
removeCurrentImage = false;
}
}

return new ProfileUpdate(currentImageUrl, changeImageUrl, removeCurrentImage,
isCurrentImageDefault);
}

@Transactional
public void updateMemberInfo(Long memberId, UpdateMemberInfoRequest request, String profileImageUrl) {
Member member = getByMemberId(memberId);

if (member.getMemberType() == TRAINEE) {
Trainee trainee = traineeService.getByMemberId(memberId);

member.updateBirthday(request.birthday());
trainee.updateTraineeInfo(request.height(), request.weight(), request.cautionNote());
updatePtGoals(trainee, new HashSet<>(request.goalContents()));

traineeRepository.save(trainee);
}

member.updateName(request.name());
member.updateProfileImageUrl(profileImageUrl);

memberRepository.save(member);
}

public void validateMemberNotExists(String socialId, SocialType socialType) {
if (memberRepository.existsBySocialIdAndSocialType(socialId, socialType)) {
throw new ConflictException(MEMBER_CONFLICT);
Expand All @@ -101,4 +175,32 @@ public void validateMemberNotExists(String socialId, SocialType socialType) {
public Member getByMemberId(Long memberId) {
return memberRepository.findById(memberId);
}

private void updatePtGoals(Trainee trainee, HashSet<String> newGoalContents) {
// 기존 PT 목표들 조회
List<PtGoal> currentPtGoals = ptGoalService.getAllByTraineeId(trainee.getId());

// 기존 목표 중 더 이상 필요없는 목표 삭제
List<PtGoal> goalsToDelete = currentPtGoals.stream()
.filter(goal -> !newGoalContents.contains(goal.getContent()))
.toList();

if (!goalsToDelete.isEmpty()) {
ptGoalRepository.deleteAll(goalsToDelete);
}

// 새로운 목표 추가 (기존에 없는 것만)
Set<String> existingContents = currentPtGoals.stream()
.map(PtGoal::getContent)
.collect(toSet());

List<PtGoal> newPtGoals = newGoalContents.stream()
.filter(content -> !existingContents.contains(content))
.map(content -> PtGoal.builder().traineeId(trainee.getId()).content(content).build())
.toList();

if (!newPtGoals.isEmpty()) {
ptGoalRepository.saveAll(newPtGoals);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import com.tnt.trainee.application.DietService;
import com.tnt.trainee.application.PtGoalService;
import com.tnt.trainee.application.TraineeService;
import com.tnt.trainee.application.repository.DietRepository;
import com.tnt.trainee.application.repository.PtGoalRepository;
import com.tnt.trainee.application.repository.TraineeRepository;
import com.tnt.trainee.domain.Diet;
import com.tnt.trainee.domain.PtGoal;
Expand All @@ -43,11 +45,13 @@ public class WithdrawService {
private final DietService dietService;
private final PtService ptService;

private final PtGoalRepository ptGoalRepository;
private final MemberRepository memberRepository;
private final TrainerRepository trainerRepository;
private final TraineeRepository traineeRepository;
private final PtLessonRepository ptLessonRepository;
private final PtTrainerTraineeRepository ptTrainerTraineeRepository;
private final DietRepository dietRepository;

@Transactional
public WithdrawDto withdraw(Long memberId) {
Expand Down Expand Up @@ -104,9 +108,10 @@ private void deleteMemberData(Member member) {
}
}

ptGoals.forEach(PtGoal::softDelete);
ptGoalRepository.deleteAll(ptGoals);

diets.forEach(Diet::softDelete);
dietRepository.saveAll(diets);

trainee.softDelete();
traineeRepository.save(trainee);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.tnt.member.application.repository;

import java.util.List;

import com.tnt.member.domain.Member;
import com.tnt.member.domain.SocialType;
import com.tnt.member.dto.MemberProjection;
Expand All @@ -15,4 +17,6 @@ public interface MemberRepository {
Member findById(Long memberId);

MemberProjection.MemberTypeDto findMemberType(Long memberId);

List<Member> findAll();
}
22 changes: 14 additions & 8 deletions src/main/java/com/tnt/member/domain/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ public class Member {

private final Long id;
private final String email;
private final String name;
private final LocalDate birthday;
private final Boolean serviceAgreement;
private final Boolean collectionAgreement;
private final Boolean advertisementAgreement;
private final SocialType socialType;
private final MemberType memberType;
private String name;
private LocalDate birthday;
private String socialId;
private String fcmToken;
private String profileImageUrl;
Expand Down Expand Up @@ -60,15 +60,21 @@ public Member(Long id, String socialId, String fcmToken, String email, String na
this.deletedAt = deletedAt;
}

public void updateFcmTokenIfExpired(String fcmToken) {
if (!isBlank(fcmToken) && !this.fcmToken.equals(fcmToken)) {
this.fcmToken = fcmToken;
}
public void updateName(String name) {
this.name = validateName(name);
}

public void updateProfileImageUrl(String profileImageUrl) {
if (!isBlank(profileImageUrl) && !this.profileImageUrl.equals(profileImageUrl)) {
this.profileImageUrl = profileImageUrl;
this.profileImageUrl = validateProfileImageUrl(profileImageUrl);
}

public void updateBirthday(LocalDate birthday) {
this.birthday = birthday;
}

public void updateFcmTokenIfExpired(String fcmToken) {
if (!isBlank(fcmToken) && !this.fcmToken.equals(fcmToken)) {
this.fcmToken = fcmToken;
}
}

Expand Down
10 changes: 10 additions & 0 deletions src/main/java/com/tnt/member/dto/ProfileUpdate.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.tnt.member.dto;

public record ProfileUpdate(
String currentImageUrl,
String changeImageUrl,
boolean removeCurrentImage,
boolean isCurrentImageDefault
) {

}
Loading