Skip to content
Merged
15 changes: 15 additions & 0 deletions src/main/java/ssu/eatssu/domain/auth/dto/AppleLoginRequestV2.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package ssu.eatssu.domain.auth.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import ssu.eatssu.domain.user.entity.DeviceType;
import ssu.eatssu.global.log.annotation.LogMask;

// 애플 계정을 통해서 갤럭시 기기에도 접속을 할 수도 있다고 생각해서, DeviceType을 받도록 설계
@Schema(title = "애플 로그인 및 회원가입 V2")
public record AppleLoginRequestV2(
@LogMask
@Schema(description = "identityToken", example = "eyJraWQiOiJXNldjT0tCIiwiYWxnIjoi...")
String identityToken,
@Schema(description = "deviceType", example = "IOS")
DeviceType deviceType
) {}
21 changes: 21 additions & 0 deletions src/main/java/ssu/eatssu/domain/auth/dto/KakaoLoginRequestV2.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package ssu.eatssu.domain.auth.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import ssu.eatssu.domain.user.entity.DeviceType;
import ssu.eatssu.global.log.annotation.LogMask;

@Schema(title = "카카오 로그인 및 회원가입 V2")
public record KakaoLoginRequestV2(
@LogMask
@NotBlank(message = "이메일을 입력해주세요.")
@Email(message = "올바른 이메일 주소를 입력해주세요.")
@Schema(description = "이메일", example = "test@email.com")
String email,
@LogMask
@Schema(description = "providerId", example = "10378247832195")
String providerId,
@Schema(description = "deviceType", example = "IOS")
DeviceType deviceType
) {}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class SecurityConfig {
};

private static final String[] AUTH_WHITELIST = {
"/", "/oauths/kakao", "/oauths/apple", "/menus/**", "/meals/**", "/admin/login",
"/", "/oauths/kakao", "/oauths/apple", "/menus/**", "/meals/**", "/admin/login", "/oauths/v2/kakao","/oauths/v2/apple",
"/reviews", "/reviews/menus/**", "/reviews/meals/**", "/v2/reviews/statistics/**",
"/v2/reviews/menus/**", "/v2/reviews/meals/**", "/actuator/**", "/error-test/**"
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import ssu.eatssu.domain.auth.dto.AppleLoginRequest;
import ssu.eatssu.domain.auth.dto.KakaoLoginRequest;
import ssu.eatssu.domain.auth.dto.ValidRequest;
import ssu.eatssu.domain.auth.dto.*;

Choose a reason for hiding this comment

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

medium

리포지토리 스타일 가이드 3번 규칙에 따라 와일드카드(*)를 사용한 임포트는 지양해야 합니다. 필요한 클래스를 명시적으로 임포트하여 코드의 가독성과 명확성을 높이는 것이 좋습니다.

Suggested change
import ssu.eatssu.domain.auth.dto.*;
import ssu.eatssu.domain.auth.dto.AppleLoginRequest;
import ssu.eatssu.domain.auth.dto.AppleLoginRequestV2;
import ssu.eatssu.domain.auth.dto.KakaoLoginRequest;
import ssu.eatssu.domain.auth.dto.KakaoLoginRequestV2;
import ssu.eatssu.domain.auth.dto.ValidRequest;
References
  1. Do not use wildcard when importing libraries (link)

import ssu.eatssu.domain.auth.service.OAuthService;
import ssu.eatssu.domain.user.dto.Tokens;
import ssu.eatssu.global.handler.response.BaseResponse;
Expand All @@ -31,6 +29,7 @@ public class OAuthController {

private final OAuthService oauthService;

// TODO : 로그인 & 회원 가입 마이그레이션 이후에 지울 것.

Choose a reason for hiding this comment

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

medium

리포지토리 스타일 가이드 7, 8, 9번 규칙에 따라 TODO 주석을 작성해야 합니다. 주석은 한 줄로 작성하고, 작성자 이름과 날짜를 포함하며, FIX ME 형식을 사용하는 것을 권장합니다. 예를 들어, // FIXME(author, yyyy-MM-dd): 로그인 & 회원 가입 마이그레이션 이후에 지울 것.과 같이 수정할 수 있습니다. 69번 라인의 TODO 주석도 동일하게 수정이 필요합니다.

References
  1. Comments should be written in a single line whenever possible. (link)
  2. Each comment must include the author's name and the date. (link)
  3. Use IntelliJ's default FIX ME comment format. (link)

@Operation(summary = "카카오 회원가입, 로그인 [인증 토큰 필요 X]", description = """
카카오 회원가입, 로그인 API 입니다.<br><br>
가입된 회원일 경우 카카오 로그인, 미가입 회원일 경우 회원가입 후 자동 로그인됩니다.
Expand All @@ -49,6 +48,25 @@ public BaseResponse<Tokens> kakaoLogin(@Valid @RequestBody KakaoLoginRequest req
return BaseResponse.success(tokens);
}

@Operation(summary = "카카오 회원가입, 로그인 V2 [인증 토큰 필요 X]", description = """
카카오 회원가입, 로그인 V2 API 입니다.<br><br>
가입된 회원일 경우 카카오 로그인, 미가입 회원일 경우 회원가입 후 자동 로그인됩니다.
""")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "카카오 회원가입/로그인 성공")
})
@PostMapping("/v2/kakao")
public BaseResponse<Tokens> kakaoLoginV2(@Valid @RequestBody KakaoLoginRequestV2 request) {
long startTime = System.currentTimeMillis();
Tokens tokens = oauthService.kakaoLoginV2(request);
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
log.info("OAuthWarmupRunner 완료 - 소요 시간: {} ms", duration);
Comment on lines +60 to +64

Choose a reason for hiding this comment

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

medium

System.currentTimeMillis()를 사용한 성능 측정 코드가 포함되어 있습니다. 이러한 코드는 보통 개발 및 디버깅 단계에서 임시로 사용되며, 프로덕션 코드에 포함되는 것은 바람직하지 않습니다. 만약 성능 측정이 필요하다면 AOP 등을 활용하여 공통 로직으로 분리하는 것을 고려해볼 수 있습니다. appleLoginV2 메소드에는 해당 코드가 없는 것으로 보아 일관성도 부족해 보입니다. 이 코드를 제거하는 것을 제안합니다.

Suggested change
long startTime = System.currentTimeMillis();
Tokens tokens = oauthService.kakaoLoginV2(request);
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
log.info("OAuthWarmupRunner 완료 - 소요 시간: {} ms", duration);
Tokens tokens = oauthService.kakaoLoginV2(request);


return BaseResponse.success(tokens);
}

// TODO : 로그인 & 회원 가입 마이그레이션 이후에 지울 것.
@Operation(summary = "애플 회원가입, 로그인 [인증 토큰 필요 X]", description = """
애플 로그인, 회원가입 API 입니다.<br><br>
가입된 회원일 경우 카카오 로그인, 미가입 회원일 경우 회원가입 후 자동 로그인됩니다.
Expand All @@ -62,6 +80,19 @@ public BaseResponse<Tokens> appleLogin(@Valid @RequestBody AppleLoginRequest req
return BaseResponse.success(tokens);
}

@Operation(summary = "애플 회원가입, 로그인 V2 [인증 토큰 필요 X]", description = """
애플 로그인, 회원가입 API V2 입니다.<br><br>
가입된 회원일 경우 카카오 로그인, 미가입 회원일 경우 회원가입 후 자동 로그인됩니다.
""")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "애플 회원가입/로그인 성공")
})
@PostMapping("/v2/apple")
public BaseResponse<Tokens> appleLoginV2(@Valid @RequestBody AppleLoginRequestV2 request) {
Tokens tokens = oauthService.appleLoginV2(request);
return BaseResponse.success(tokens);
}

@Operation(summary = "토큰 재발급", description = "accessToken, refreshToken 재발급 API 입니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "토큰 재발급 성공")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import ssu.eatssu.domain.user.entity.DeviceType;
import ssu.eatssu.domain.user.entity.User;

import java.util.ArrayList;
Expand All @@ -17,12 +18,14 @@ public class CustomUserDetails implements UserDetails {
private final String email;
private final String credentials;
private final GrantedAuthority role;
private final DeviceType deviceType;

public CustomUserDetails(User user) {
this.id = user.getId();
this.email = user.getEmail();
this.credentials = user.getCredentials();
this.role = user.getRole();
this.deviceType = user.getDeviceType();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ public class JwtAuthenticationFilter extends GenericFilterBean {
"/v2/reviews/menus/**", "/v2/reviews/meals/**", "/actuator/**", "/error-test/**",
"/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**", "/swagger-resources/**",
"/oauths/valid/token", "/admin/img/**", "/css/**", "/js/**",
"/favicon.ico", "/error/**", "/webjars/**", "/h2-console/**"
);
"/favicon.ico", "/error/**", "/webjars/**", "/h2-console/**",
"/oauths/v2/kakao", "/oauths/v2/apple");
private final JwtTokenProvider jwtTokenProvider;

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ public Authentication getAuthentication(String token) throws JsonProcessingExcep
UserPrincipalDto userPrincipalDto = objectMapper.readValue(claims.getSubject(), UserPrincipalDto.class);

CustomUserDetails principal = new CustomUserDetails(userPrincipalDto.getId(), userPrincipalDto.getEmail(), "",
authorities.get(0));
authorities.get(0),userPrincipalDto.getDeviceType());

return new UsernamePasswordAuthenticationToken(principal, token, authorities);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import ssu.eatssu.domain.user.entity.DeviceType;

@Getter
@Builder
Expand All @@ -12,13 +13,15 @@
public class UserPrincipalDto {
private Long id;
private String email;
private DeviceType deviceType;
private String role;

public static UserPrincipalDto from(CustomUserDetails userDetails) {
return UserPrincipalDto.builder()
.id(userDetails.getId())
.email(userDetails.getEmail())
.deviceType(userDetails.getDeviceType())
.role(userDetails.getRole().getAuthority())
.build();
}
}
}
40 changes: 35 additions & 5 deletions src/main/java/ssu/eatssu/domain/auth/service/OAuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ssu.eatssu.domain.auth.dto.AppleLoginRequest;
import ssu.eatssu.domain.auth.dto.KakaoLoginRequest;
import ssu.eatssu.domain.auth.dto.OAuthInfo;
import ssu.eatssu.domain.auth.dto.ValidRequest;
import ssu.eatssu.domain.auth.dto.*;

Choose a reason for hiding this comment

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

medium

리포지토리 스타일 가이드 3번 규칙에 따라 와일드카드(*)를 사용한 임포트는 지양해야 합니다. 필요한 클래스를 명시적으로 임포트하여 코드의 가독성과 명확성을 높이는 것이 좋습니다.

Suggested change
import ssu.eatssu.domain.auth.dto.*;
import ssu.eatssu.domain.auth.dto.AppleLoginRequest;
import ssu.eatssu.domain.auth.dto.AppleLoginRequestV2;
import ssu.eatssu.domain.auth.dto.KakaoLoginRequest;
import ssu.eatssu.domain.auth.dto.KakaoLoginRequestV2;
import ssu.eatssu.domain.auth.dto.OAuthInfo;
import ssu.eatssu.domain.auth.dto.ValidRequest;
References
  1. Do not use wildcard when importing libraries (link)

import ssu.eatssu.domain.auth.entity.AppleAuthenticator;
import ssu.eatssu.domain.auth.entity.OAuthProvider;
import ssu.eatssu.domain.auth.security.JwtTokenProvider;
import ssu.eatssu.domain.auth.util.RandomNicknameUtil;
import ssu.eatssu.domain.user.dto.Tokens;
import ssu.eatssu.domain.user.entity.DeviceType;
import ssu.eatssu.domain.user.entity.User;
import ssu.eatssu.domain.user.repository.UserRepository;
import ssu.eatssu.domain.user.service.UserService;
Expand All @@ -41,6 +39,21 @@ public Tokens kakaoLogin(KakaoLoginRequest request) {
return generateOauthJwtTokens(user.getEmail(), KAKAO, request.providerId());
}

/**
* V1 -> V2로 넘어가면서 DeviceType(IOS,ANDROID) 정보를 추가로 받게 되었고, 기존에 가입한 유저들은 추가로 기입해 주게 됩니다.
*/
public Tokens kakaoLoginV2(KakaoLoginRequestV2 request) {
User user = userRepository.findByProviderId(request.providerId())
.orElseGet(() -> userService.joinV2(request.email(), KAKAO, request.providerId(),request.deviceType()));

if (user.getDeviceType() == null) {
user.updateDeviceType(request.deviceType());
}
Comment on lines +49 to +51

Choose a reason for hiding this comment

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

medium

kakaoLoginV2appleLoginV2 메소드에 deviceType을 업데이트하는 로직이 중복됩니다. 중복을 제거하고 코드 재사용성을 높이기 위해 이 로직을 별도의 private 메소드로 추출하는 것을 고려해 보세요. 예를 들어, updateDeviceTypeIfNull(User user, DeviceType deviceType)와 같은 메소드를 만들어 사용할 수 있습니다. 79-81 라인에도 동일한 코드가 있습니다.


return generateOauthJwtTokens(user.getEmail(), KAKAO, request.providerId());
}


public Tokens appleLogin(AppleLoginRequest request) {
OAuthInfo oAuthInfo = appleAuthenticator.getOAuthInfoByIdentityToken(request.identityToken());

Expand All @@ -52,6 +65,24 @@ public Tokens appleLogin(AppleLoginRequest request) {
return generateOauthJwtTokens(user.getEmail(), APPLE, oAuthInfo.providerId());
}

/**
* V1 -> V2로 넘어가면서 DeviceType(IOS,ANDROID) 정보를 추가로 받게 되었고, 기존에 가입한 유저들은 추가로 기입해 주게 됩니다.
*/
public Tokens appleLoginV2(AppleLoginRequestV2 request) {
OAuthInfo oAuthInfo = appleAuthenticator.getOAuthInfoByIdentityToken(request.identityToken());

User user = userRepository.findByProviderId(oAuthInfo.providerId())
.orElseGet(() -> userService.joinV2(oAuthInfo.email(), APPLE, oAuthInfo.providerId(),request.deviceType()));

updateAppleUserEmail(user, oAuthInfo.email());

if (user.getDeviceType() == null) {
user.updateDeviceType(request.deviceType());
}

return generateOauthJwtTokens(user.getEmail(), APPLE, oAuthInfo.providerId());
}

public Tokens refreshTokens(Authentication authentication) {
return jwtTokenProvider.generateTokens(authentication);
}
Expand Down Expand Up @@ -97,5 +128,4 @@ private Tokens generateOauthJwtTokens(String email, OAuthProvider provider, Stri
private String makeOauthCredentials(OAuthProvider provider, String providerId) {
return provider + providerId;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
import ssu.eatssu.domain.slack.service.SlackService;
import ssu.eatssu.global.handler.response.BaseResponse;

/**
* 문의하기는 카카오톡으로 이동되어 사용되지 않고 있습니다.
*/
@Deprecated
@RestController
@RequiredArgsConstructor
@RequestMapping("/inquiries")
Expand Down
58 changes: 36 additions & 22 deletions src/main/java/ssu/eatssu/domain/review/dto/MealReviewResponse.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package ssu.eatssu.domain.review.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import ssu.eatssu.domain.review.entity.Review;
import ssu.eatssu.domain.review.entity.ReviewMenuLike;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import ssu.eatssu.domain.review.entity.Review;
import ssu.eatssu.domain.review.entity.ReviewMenuLike;

@AllArgsConstructor
@Builder
Expand All @@ -31,7 +31,7 @@ public class MealReviewResponse {
private String writerNickname;

@Schema(description = "평점", example = "4")
private Double rating;
private Integer rating;

@Schema(description = "리뷰 작성 날짜(format = yyyy-MM-dd)", example = "2023-04-07")
private LocalDate writtenAt;
Expand Down Expand Up @@ -60,26 +60,40 @@ public class MealReviewResponse {

public static MealReviewResponse from(Review review,
Long userId,
List<ValidMenuForViewResponse.MenuDto> validMenus,Double rating) {
List<ValidMenuForViewResponse.MenuDto> validMenus, Integer rating) {
Comment on lines 61 to +63

Choose a reason for hiding this comment

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

high

from 메소드의 rating 파라미터가 사용되지 않고 있습니다. 메소드 내부에서 resolvedRating이라는 변수를 새로 계산하여 사용하고 있어, rating 파라미터는 혼란을 야기할 수 있습니다. 이 파라미터를 메소드 시그니처에서 제거하고, 호출하는 부분(ReviewServiceV2:216)도 함께 수정하는 것을 권장합니다.

Suggested change
public static MealReviewResponse from(Review review,
Long userId,
List<ValidMenuForViewResponse.MenuDto> validMenus,Double rating) {
List<ValidMenuForViewResponse.MenuDto> validMenus, Integer rating) {
public static MealReviewResponse from(Review review,
Long userId,
List<ValidMenuForViewResponse.MenuDto> validMenus) {

List<String> imageUrls = new ArrayList<>();
review.getReviewImages().forEach(i -> imageUrls.add(i.getImageUrl()));

// 좋아요한 메뉴 ID 모음
Set<Long> likedMenuIds = review.getMenuLikes().stream()
.filter(ReviewMenuLike::getIsLike)
.map(like -> like.getMenu().getId())
.collect(Collectors.toSet());

List<MenuIdNameLikeDto> menuNames = validMenus.stream()
.map(valid -> new MenuIdNameLikeDto(
valid.getMenuId(),
valid.getName(),
likedMenuIds.contains(valid.getMenuId())
))
.toList();
List<MenuIdNameLikeDto> menuNames;
if (review.getMeal() != null) {
Set<Long> likedMenuIds = review.getMenuLikes().stream()
.filter(ReviewMenuLike::getIsLike)
.map(like -> like.getMenu().getId())
.collect(Collectors.toSet());

menuNames = validMenus.stream()
.map(valid -> new MenuIdNameLikeDto(
valid.getMenuId(),
valid.getName(),
likedMenuIds.contains(valid.getMenuId())
))
.toList();
} else if (review.getMenu() != null) {
menuNames = Collections.singletonList(
new MenuIdNameLikeDto(review.getMenu().getId(),
review.getMenu().getName(),
false)
);
} else {
menuNames = Collections.emptyList();
}
Integer resolvedRating = (review.getRating() != null)
? review.getRating()
: (review.getRatings() != null ? review.getRatings().getMainRating() : null);

MealReviewResponseBuilder builder = MealReviewResponse.builder()
.reviewId(review.getId())
.rating(rating)
.rating(resolvedRating)
.writtenAt(review.getCreatedDate().toLocalDate())
.content(review.getContent())
.imageUrls(imageUrls)
Expand Down
17 changes: 10 additions & 7 deletions src/main/java/ssu/eatssu/domain/review/dto/ReviewDetail.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
package ssu.eatssu.domain.review.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import ssu.eatssu.domain.menu.entity.Menu;
import ssu.eatssu.domain.review.entity.Review;
import ssu.eatssu.domain.review.entity.ReviewMenuLike;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@AllArgsConstructor
@Builder
@Schema(title = "리뷰 상세")
Expand Down Expand Up @@ -58,9 +57,13 @@ public static ReviewDetail from(Review review, Long userId) {
.map(like -> like.getMenu().getId())
.collect(Collectors.toSet());

Integer rating = (review.getRating() != null)
? review.getRating()
: review.getRatings().getMainRating();

ReviewDetailBuilder builder = ReviewDetail.builder()
.reviewId(review.getId())
.rating(review.getRatings().getMainRating())
.rating(rating)
.writtenAt(review.getCreatedDate().toLocalDate())
.content(review.getContent())
.imageUrls(imageUrls)
Expand Down
Loading