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/#71] 내 프로필 조회 API 구현 #72

Merged
merged 18 commits into from
Feb 15, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -7,6 +7,7 @@
import com.acon.server.member.api.response.AcornCountResponse;
import com.acon.server.member.api.response.LoginResponse;
import com.acon.server.member.api.response.MemberAreaResponse;
import com.acon.server.member.api.response.ProfileResponse;
import com.acon.server.member.application.service.MemberService;
import com.acon.server.member.domain.enums.Cuisine;
import com.acon.server.member.domain.enums.DislikeFood;
Expand Down Expand Up @@ -109,4 +110,11 @@ public ResponseEntity<AcornCountResponse> getAcornCount() {
memberService.fetchAcornCount()
);
}
}

@GetMapping(path = "/members/me", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ProfileResponse> getProfile() {
return ResponseEntity.ok(
memberService.fetchProfile()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.acon.server.member.api.response;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import jakarta.validation.constraints.NotNull;
import java.util.List;
import lombok.Builder;
import lombok.NonNull;

@Builder
@JsonInclude(Include.NON_NULL)
public record ProfileResponse(
@NotNull
Copy link
Member

Choose a reason for hiding this comment

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

P1:

생각해 보니까 Response에서 NotNull을 검증하는 Valid는 다 빼는 게 맞는 것 같네요

이 어노테이션들을 붙였다고 해도 컨트롤러에서 해당 객체 앞에 @Valid 어노테이션이 붙어있을 때만 해당 어노테이션이 제대로 작동하기 때문입니당


또한 @NotNull 어노테이션이 붙어있는 필드들 모두 nullablefalse하도록 DB 혹은 애플리케이션 단에서 방어 로직들이 작성되어 있기도 하구요,

만일 NotNull이어야 하는 필드가 null이라고 하더라도 READ를 요청하는 해당 API를 호출한 클라이언트의 귀책이 아니라 서버에 귀책 사유가 있기 때문에 공통 에러를 반환하는 Valid 처리를 하는 건 잘못된 방식인 것 같아요 !!


동일하게 Response에서 Valid를 진행하고 있는 다른 DTO들에 대해서는 제가 수정해 놓을 테니 해당 DTO에서만 수정해 주시면 감사하겠습니당

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

3949c1c
좋은 의견 감사합니다!

String image,
@NotNull
String nickname,
int leftAcornCount,
String birthDate,
@NotNull
List<VerifiedArea> verifiedArea
) {

public record VerifiedArea(
@NonNull
Long id,
@NonNull
String name
) {

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.acon.server.global.external.NaverMapsAdapter;
import com.acon.server.member.api.response.AcornCountResponse;
import com.acon.server.member.api.response.LoginResponse;
import com.acon.server.member.api.response.ProfileResponse;
import com.acon.server.member.application.mapper.GuidedSpotMapper;
import com.acon.server.member.application.mapper.MemberMapper;
import com.acon.server.member.application.mapper.PreferenceMapper;
Expand Down Expand Up @@ -37,6 +38,8 @@
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand Down Expand Up @@ -89,31 +92,22 @@ public LoginResponse login(
return LoginResponse.of(accessToken, refreshToken);
}

private boolean isExistingMember(
final SocialType socialType,
final String socialId
) {
return memberRepository.findBySocialTypeAndSocialId(socialType, socialId).isPresent();
}

protected Long fetchMemberId(
final SocialType socialType,
final String socialId
) {
MemberEntity memberEntity;

if (isExistingMember(socialType, socialId)) {
memberEntity = memberRepository.findBySocialTypeAndSocialId(
socialType,
socialId
).orElse(null);
} else {
memberEntity = memberRepository.save(MemberEntity.builder()
.socialType(socialType)
.socialId(socialId)
.leftAcornCount(25)
.build());
}
Optional<MemberEntity> optionalMemberEntity = memberRepository.findBySocialTypeAndSocialId(socialType,
socialId);
MemberEntity memberEntity = optionalMemberEntity.orElseGet(() ->
Copy link
Member

Choose a reason for hiding this comment

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

P5:

좋은 리팩토링이네요 👍

memberRepository.save(MemberEntity.builder()
.socialType(socialType)
.socialId(socialId)
.leftAcornCount(25)
.nickname(UUID.randomUUID().toString())
Copy link
Member

@ckkim817 ckkim817 Feb 13, 2025

Choose a reason for hiding this comment

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

P1:

UUID.randomUUID().toString()는 총 36자일 뿐만 아니라 -를 사용해 저희 닉네임 규칙을 위반합니다. (1~16자 이내)

다른 방식으로 닉네임을 할당해야 할 것 같아요 ~! 해당 부분은 TODO로 남겨주시면 프로필 수정 API 구현 때 제가 수정해 보겠습니다

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

넵 감사합니다!
dfa3499

// TODO: 기본 이미지 구현 전까지 임의로 이미지 할당
.profileImage("https://avatars.githubusercontent.com/u/81469686?v=4")
.build())
);

Member member = memberMapper.toDomain(memberEntity);

Expand Down Expand Up @@ -216,5 +210,23 @@ public AcornCountResponse fetchAcornCount() {
return new AcornCountResponse(acornCount);
}

@Transactional(readOnly = true)
public ProfileResponse fetchProfile() {
MemberEntity memberEntity = memberRepository.findByIdOrElseThrow(principalHandler.getUserIdFromPrincipal());
List<VerifiedAreaEntity> verifiedAreaEntityList = verifiedAreaRepository.findAllByMemberId(
memberEntity.getId());

return ProfileResponse.builder().
image(memberEntity.getProfileImage())
.nickname(memberEntity.getNickname())
.birthDate(memberEntity.getBirthDate() != null ? memberEntity.getBirthDate().toString() : null)
.leftAcornCount(memberEntity.getLeftAcornCount())
.verifiedArea(verifiedAreaEntityList.stream()
.map(verifiedAreaEntity -> new ProfileResponse.VerifiedArea(verifiedAreaEntity.getId(),
verifiedAreaEntity.getName()))
.collect(Collectors.toList()))
Copy link
Member

Choose a reason for hiding this comment

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

P1:

JAVA 16 이상부터는 아래와 같이 써도 무방합니다.
.collect(Collectors.toList())) -> .toList())


이렇게 쓸 경우, List.of()처럼 작동하여 가변 리스트가 아닌 불변 리스트를 반환하기 때문에 데이터 구조도 안전하고(수정이 불가능), 더 간결할 뿐더러 불필요한 복사 과정이 줄어 더 빠릅니다.

(.collect(Collectors.toList())는 내부적으로 새로운 ArrayList를 생성하고 .add()를 통해 요소를 추가하므로 .toList())에 비해 상대적으로 느림.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

수정했숨다
1d3e024

.build();
}

// TODO: 최근 길 안내 장소 지우는 스케줄러 추가
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public class MemberEntity extends BaseTimeEntity {
@Column(name = "profile_image")
private String profileImage;

@Column(name = "nickname", unique = true)
@Column(name = "nickname", unique = true, nullable = false)
private String nickname;

@Column(name = "nickname_updated_at")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.acon.server.member.infra.repository;

import com.acon.server.member.infra.entity.VerifiedAreaEntity;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

Expand All @@ -9,4 +10,6 @@ public interface VerifiedAreaRepository extends JpaRepository<VerifiedAreaEntity
boolean existsByMemberIdAndName(Long memberId, String name);

Optional<VerifiedAreaEntity> findByMemberIdAndName(Long memberId, String name);
}

List<VerifiedAreaEntity> findAllByMemberId(Long memberId);
}