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

[Deploy] v1.1.0 운영서버 배포 #278

Merged
merged 36 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
6f655a6
[Chore] #259 - Test Part 컨벤션 맞추기
its-sky Apr 1, 2024
4de5d80
[Feat] #259 - KeyResultService 테스트 코드 작성
its-sky Apr 1, 2024
c25eb91
[Fix] #259 - 의미 없는 코드 흐름 수정
its-sky Apr 1, 2024
3fd1492
[Del] #259 - 테스트 코드에서 불필요한 예외 체크 로직 중복 확인 제거
its-sky Apr 1, 2024
5003db9
Merge pull request #260 from MOONSHOT-Team/feature/#259
its-sky Apr 1, 2024
ef9f2b0
[Fix] #261 - History 조회 API 로직 변경
its-sky Apr 8, 2024
469a969
[Fix] #261 - History 조회 API DTO 스펙 변경
its-sky Apr 8, 2024
044f341
Merge pull request #262 from MOONSHOT-Team/feature/#261
its-sky Apr 8, 2024
90ae0a0
[Refactor] #263 - 유저 조회 Redis 캐시 적용하기
its-sky Apr 10, 2024
3ea28c3
[Del] #263 - 필요 없는 로깅 삭제
its-sky Apr 11, 2024
94e7179
[Chore] #263 - 컨벤션에 맞게 파일 수정
its-sky Apr 11, 2024
bb5d61c
[Chore] #263 - 컨벤션에 맞게 파일 수정
its-sky Apr 11, 2024
7e01150
Merge pull request #265 from MOONSHOT-Team/feature/#263
its-sky Apr 11, 2024
04e9d23
[Refactor] #264 - 소셜 로그인 전략 패턴 적용
its-sky Apr 12, 2024
13177d2
[Refactor] #264 - User Retention Period 상수 UserConstants로 분리
its-sky Apr 12, 2024
1b0e577
Merge pull request #266 from MOONSHOT-Team/feature/#264
its-sky Apr 12, 2024
e38e8ff
[Refactor] #267 - 유저 회원가입 시 발행되는 이벤트 비동기 로직으로 변경
its-sky Apr 12, 2024
70b488b
Merge pull request #268 from MOONSHOT-Team/feature/#267
its-sky Apr 16, 2024
ebb194c
[Feat] #269 - Sentry 로깅 기능 추가
its-sky Apr 19, 2024
61f69c0
Merge pull request #270 from MOONSHOT-Team/feature/#269
its-sky Apr 19, 2024
64cbf71
[Feat] #269 - Task 생성 테스트 코드 추가
0lynny May 19, 2024
1509283
[Feat] #269 - Task 생성 시 예외케이스 테스트 코드 추가
0lynny May 19, 2024
630c34b
[Feat] #269 - TaskValidator 수정
0lynny May 19, 2024
2417d9d
Merge pull request #273 from MOONSHOT-Team/feature/#272
0lynny May 19, 2024
fb15a80
[Feat] #274 - 진척상황 기록 후 체크인 로그 생성 테스트 코드 작성
0lynny May 28, 2024
3d50bc4
[Feat] #274 - 진척상황 입력 시 이전 값과 동일한 경우 예외 발생 테스트코드 작성
0lynny May 28, 2024
b5e0456
[Feat] #274 - KR 수정 후 체크인 로그 생성 테스트 코드 작성
0lynny May 28, 2024
e2c9abc
[Feat] #274 - KR 생성 후 체크인 로그 생성 테스트코드 작성
0lynny May 28, 2024
026a97b
[Feat] #274 - 진척상황 입력 후 KR 달성률 계산 테스트코드 작성
0lynny May 28, 2024
76b8616
[Feat] #274 - KR에 대한 Log 조회 테스트코드 작성
0lynny May 28, 2024
90a9178
[Chore] #274 - isKeyResultAchieved에 따른 로그 추가 테스트코드 수정
0lynny May 28, 2024
ade991d
Merge pull request #275 from MOONSHOT-Team/feature/#274
0lynny May 29, 2024
7369c9d
[Del] #276 - 사용하지 않는 코드 삭제
0lynny May 29, 2024
a93e5f3
[Feat] #276 - 디스코드 회원가입 알림에 누적회원수 추가
0lynny May 29, 2024
5993eb1
[Fix] #276 - 누적회원수 필드명 변경
0lynny May 29, 2024
c9a3c11
Merge pull request #277 from MOONSHOT-Team/feature/#276
0lynny May 29, 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
6 changes: 4 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ subprojects {
}

dependencies {
// Spring Security

//Spring Security
implementation 'org.springframework.boot:spring-boot-starter-security'

Expand All @@ -43,6 +41,10 @@ subprojects {
// test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.mockito:mockito-core:5.9.0'

// Sentry
implementation 'io.sentry:sentry-spring-boot-starter-jakarta:6.28.0'
implementation 'io.sentry:sentry-logback:6.28.0'
}

tasks.named('test') {
Expand Down
27 changes: 27 additions & 0 deletions moonshot-api/src/main/java/org/moonshot/config/AsyncConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.moonshot.config;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@EnableAsync
@Configuration
public class AsyncConfig implements AsyncConfigurer {

@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4);
executor.setMaxPoolSize(4);
executor.setQueueCapacity(4);
executor.setKeepAliveSeconds(60);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
executor.setThreadNamePrefix("async-executor-");
executor.initialize();
return executor;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ public Optional<AchieveResponseDto> modifyKeyResult(final KeyResultModifyRequest
throw new BadRequestException(REQUIRED_KEY_RESULT_VALUE);
}

Log updateLog = logService.createUpdateLog(request, keyResult.getId());
Log updateLog = logService.createUpdateLog(request, keyResult);
validateLogNum(request.krTarget(), updateLog.getKeyResult().getTarget());

Optional<Log> prevLog = logRepository.findLatestLogByKeyResultId(LogState.RECORD, request.keyResultId());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,7 @@ public Optional<AchieveResponseDto> createRecordLog(final Long userId, final Log
return Optional.empty();
}

public Log createUpdateLog(final KeyResultModifyRequestDto request, final Long keyResultId) {
KeyResult keyResult = keyResultRepository.findById(keyResultId)
.orElseThrow(() -> new NotFoundException(NOT_FOUND_KEY_RESULT));
public Log createUpdateLog(final KeyResultModifyRequestDto request, final KeyResult keyResult) {
return logRepository.save(Log.builder()
.date(LocalDateTime.now())
.state(LogState.UPDATE)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,12 @@
package org.moonshot.objective.dto.response;

import java.util.List;
import java.util.Map;
import org.moonshot.objective.model.Criteria;

public record HistoryResponseDto(
List<ObjectiveGroupByYearDto> groups,
List<YearDto> years,
List<String> categories
) {
public static HistoryResponseDto of(List<ObjectiveGroupByYearDto> groups, Map<Integer, Integer> years,
List<String> categories, Criteria criteria) {
return new HistoryResponseDto(
groups,
YearDto.of(years),
categories.stream().distinct().toList()
);
public static HistoryResponseDto of(List<ObjectiveGroupByYearDto> groups, List<String> categories) {
return new HistoryResponseDto(groups, categories.stream().distinct().toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.moonshot.common.model.Period;
Expand Down Expand Up @@ -47,7 +46,7 @@ public class ObjectiveService implements IndexService {
private final ObjectiveRepository objectiveRepository;

public void createObjective(final Long userId, final OKRCreateRequestDto request) {
User user = userRepository.findById(userId)
User user = userRepository.findByIdWithCache(userId)
.orElseThrow(() -> new NotFoundException(NOT_FOUND_USER));

List<Objective> objectives = objectiveRepository.findAllByUserId(userId);
Expand Down Expand Up @@ -120,11 +119,6 @@ public HistoryResponseDto getObjectiveHistory(final Long userId, final Integer y
List<Objective> objectives = objectiveRepository.findObjectives(userId, year, category, criteria);
Map<Integer, List<Objective>> groups = objectives.stream()
.collect(Collectors.groupingBy(objective -> objective.getPeriod().getStartAt().getYear()));
Map<Integer, Integer> years = groups.entrySet().stream()
.collect(Collectors.toMap(
Entry::getKey,
entry -> entry.getValue().size()
));
List<String> categories = objectives.stream().map(objective -> objective.getCategory().getValue()).toList();

List<ObjectiveGroupByYearDto> groupList = groups.entrySet().stream()
Expand All @@ -142,7 +136,7 @@ public HistoryResponseDto getObjectiveHistory(final Long userId, final Integer y
.sorted(Comparator.comparingInt(ObjectiveGroupByYearDto::year).reversed()).toList();
}

return HistoryResponseDto.of(groupsSortedByCriteria, years, categories, criteria);
return HistoryResponseDto.of(groupsSortedByCriteria, categories);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public static void validateActiveTaskSizeExceeded(final int taskListSize) {
}

public static void validateIndexUnderMaximum(final int requestIndex, final int totalTaskListSize) {
if (requestIndex > totalTaskListSize) {
if (requestIndex > totalTaskListSize || requestIndex < 0) {
throw new BadRequestException(INVALID_TASK_INDEX);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package org.moonshot.user.controller;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import java.io.IOException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.moonshot.jwt.TokenResponse;
Expand All @@ -16,7 +13,6 @@
import org.moonshot.user.dto.response.UserInfoResponse;
import org.moonshot.user.model.LoginUser;
import org.moonshot.user.service.UserService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
Expand All @@ -38,7 +34,7 @@ public class UserController implements UserApi {
@PostMapping("/login")
@Logging(item = "User", action = "Post")
public ResponseEntity<MoonshotResponse<SocialLoginResponse>> login(@RequestHeader("Authorization") final String authorization,
@RequestBody final SocialLoginRequest socialLoginRequest) throws IOException {
@RequestBody final SocialLoginRequest socialLoginRequest) {
return ResponseEntity.ok(MoonshotResponse.success(SuccessType.POST_LOGIN_SUCCESS, userService.login(SocialLoginRequest.of(socialLoginRequest.socialPlatform(), authorization))));
}

Expand Down
117 changes: 7 additions & 110 deletions moonshot-api/src/main/java/org/moonshot/user/service/UserService.java
Original file line number Diff line number Diff line change
@@ -1,40 +1,27 @@
package org.moonshot.user.service;

import static org.moonshot.response.ErrorType.NOT_FOUND_USER;
import static org.moonshot.response.ErrorType.NOT_SUPPORTED_LOGIN_PLATFORM;
import static org.moonshot.user.service.validator.UserValidator.hasChange;
import static org.moonshot.user.service.validator.UserValidator.isNewUser;
import static org.moonshot.user.service.validator.UserValidator.validateUserAuthorization;
import static org.moonshot.util.MDCUtil.USER_REQUEST_ORIGIN;
import static org.moonshot.util.MDCUtil.get;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.moonshot.discord.SignUpEvent;
import org.moonshot.exception.BadRequestException;
import org.moonshot.exception.NotFoundException;
import org.moonshot.jwt.JwtTokenProvider;
import org.moonshot.jwt.TokenResponse;
import org.moonshot.objective.service.ObjectiveService;
import org.moonshot.openfeign.dto.response.google.GoogleInfoResponse;
import org.moonshot.openfeign.dto.response.google.GoogleTokenResponse;
import org.moonshot.openfeign.dto.response.kakao.KakaoTokenResponse;
import org.moonshot.openfeign.dto.response.kakao.KakaoUserResponse;
import org.moonshot.openfeign.google.GoogleApiClient;
import org.moonshot.openfeign.google.GoogleAuthApiClient;
import org.moonshot.openfeign.kakao.KakaoApiClient;
import org.moonshot.openfeign.kakao.KakaoAuthApiClient;
import org.moonshot.user.dto.request.SocialLoginRequest;
import org.moonshot.user.dto.request.UserInfoRequest;
import org.moonshot.user.dto.response.SocialLoginResponse;
import org.moonshot.user.dto.response.UserInfoResponse;
import org.moonshot.user.model.User;
import org.moonshot.user.repository.UserRepository;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.moonshot.user.service.social.SocialLoginContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
Expand All @@ -43,95 +30,16 @@
@RequiredArgsConstructor
public class UserService {

@Value("${google.client-id}")
private String googleClientId;

@Value("${google.client-secret}")
private String googleClientSecret;

@Value("${google.redirect-url}")
private String googleRedirectUrl;

@Value("${kakao.client-id}")
private String kakaoClientId;

@Value("${kakao.redirect-uri}")
private String kakaoRedirectUri;

private final UserRepository userRepository;
private final ObjectiveService objectiveService;
private final ApplicationEventPublisher eventPublisher;

private final GoogleAuthApiClient googleAuthApiClient;
private final GoogleApiClient googleApiClient;
private final KakaoAuthApiClient kakaoAuthApiClient;
private final KakaoApiClient kakaoApiClient;
private final JwtTokenProvider jwtTokenProvider;
private final SocialLoginContext socialLoginContext;

public SocialLoginResponse login(final SocialLoginRequest request) {
return switch (request.socialPlatform().getValue()) {
case "google" -> googleLogin(request);
case "kakao" -> kakaoLogin(request);
default -> null;
};
}

public SocialLoginResponse googleLogin(final SocialLoginRequest request) {
GoogleTokenResponse tokenResponse = googleAuthApiClient.googleAuth(
request.code(),
googleClientId,
googleClientSecret,
googleRedirectUrl,
"authorization_code"
);
GoogleInfoResponse userResponse = googleApiClient.googleInfo("Bearer " + tokenResponse.accessToken());
Optional<User> findUser = userRepository.findUserBySocialId(userResponse.sub());
User user;
if (isNewUser(findUser)) {
User newUser = userRepository.save(User.builderWithSignIn()
.socialId(userResponse.sub())
.socialPlatform(request.socialPlatform())
.name(userResponse.name())
.imageUrl(userResponse.picture())
.email(userResponse.email())
.build());
user = newUser;
publishSignUpEvent(newUser);
} else {
user = findUser.get();
user.resetDeleteAt();
}
TokenResponse token = new TokenResponse(jwtTokenProvider.generateAccessToken(user.getId()), jwtTokenProvider.generateRefreshToken(user.getId()));
return SocialLoginResponse.of(user.getId(), user.getName(), token);
}

public SocialLoginResponse kakaoLogin(final SocialLoginRequest request) {
KakaoTokenResponse tokenResponse = kakaoAuthApiClient.getOAuth2AccessToken(
"authorization_code",
kakaoClientId,
(String)get(USER_REQUEST_ORIGIN) + kakaoRedirectUri,
request.code()
);
KakaoUserResponse userResponse = kakaoApiClient.getUserInformation(
"Bearer " + tokenResponse.accessToken());
Optional<User> findUser = userRepository.findUserBySocialId(userResponse.id());
User user;
if (isNewUser(findUser)) {
User newUser = userRepository.save(User.builderWithSignIn()
.socialId(userResponse.id())
.socialPlatform(request.socialPlatform())
.name(userResponse.kakaoAccount().profile().nickname())
.imageUrl(userResponse.kakaoAccount().profile().profileImageUrl())
.email(null)
.build());
user = newUser;
publishSignUpEvent(newUser);
} else {
user = findUser.get();
user.resetDeleteAt();
if (socialLoginContext.support(request.socialPlatform())) {
return socialLoginContext.doLogin(request);
}
TokenResponse token = new TokenResponse(jwtTokenProvider.generateAccessToken(user.getId()), jwtTokenProvider.generateRefreshToken(user.getId()));
return SocialLoginResponse.of(user.getId(), user.getName(), token);
throw new BadRequestException(NOT_SUPPORTED_LOGIN_PLATFORM);
}

public TokenResponse reissue(final String refreshToken) {
Expand Down Expand Up @@ -181,17 +89,6 @@ public void updateUserProfileImage(final Long userId, final String imageUrl) {
user.modifyProfileImage(imageUrl);
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void publishSignUpEvent(final User user) {
eventPublisher.publishEvent(SignUpEvent.of(
user.getName(),
user.getEmail() == null ? "" : user.getEmail(),
user.getSocialPlatform().toString(),
LocalDateTime.now(),
user.getImageUrl()
));
}

public void softDeleteUser(LocalDateTime currentDate) {
List<User> expiredUserList = userRepository.findIdByDeletedAtBefore(currentDate);
if(!expiredUserList.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.moonshot.user.service;

import java.time.LocalDateTime;
import lombok.RequiredArgsConstructor;
import org.moonshot.discord.SignUpEvent;
import org.moonshot.user.model.User;
import org.moonshot.user.repository.UserRepository;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class UserSignUpService {

private final UserRepository userRepository;
private final ApplicationEventPublisher eventPublisher;

@Async
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void publishSignUpEvent(User user) {
Long totalUserCount = userRepository.count();
eventPublisher.publishEvent(SignUpEvent.of(
totalUserCount,
user.getName(),
user.getEmail() == null ? "" : user.getEmail(),
user.getSocialPlatform().toString(),
LocalDateTime.now(),
user.getImageUrl()
));
}

}
Loading
Loading