Skip to content

Commit

Permalink
Merge pull request #34 from Link-MIND/feature/#31
Browse files Browse the repository at this point in the history
Feature/#31
  • Loading branch information
sss4920 authored Jan 9, 2024
2 parents c1a4ae6 + ed8a3d6 commit e8013cb
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 4 deletions.
18 changes: 18 additions & 0 deletions linkmind/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ plugins {
id 'java'
id 'org.springframework.boot' version '3.2.0'
id 'io.spring.dependency-management' version '1.1.4'
id "io.sentry.jvm.gradle" version "4.1.1"
}

group = 'com.app'
Expand Down Expand Up @@ -64,8 +65,25 @@ dependencies {
// JSoup
implementation 'org.jsoup:jsoup:1.15.3'

// slack
implementation 'com.slack.api:slack-api-client:1.28.0'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.slack.api:slack-app-backend:1.28.0'
implementation 'com.slack.api:slack-api-model:1.28.0'

}

tasks.named('test') {
useJUnitPlatform()
}

sentry {
// Generates a JVM (Java, Kotlin, etc.) source bundle and uploads your source code to Sentry.
// This enables source context, allowing you to see your source
// code as part of your stack traces in Sentry.
includeSourceContext = true

org = "linkmind"
projectName = "java-spring-boot"
authToken = System.getenv("SENTRY_AUTH_TOKEN")
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.app.toaster.common.advice;

import java.io.IOException;
import java.net.MalformedURLException;

import org.springframework.http.HttpStatus;
Expand All @@ -15,19 +16,26 @@
import com.app.toaster.common.dto.ApiResponse;
import com.app.toaster.exception.Error;
import com.app.toaster.exception.model.CustomException;
import com.app.toaster.external.client.slack.SlackApi;

import io.sentry.Sentry;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintDefinitionException;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;

@RestControllerAdvice
@Component
@NoArgsConstructor
@RequiredArgsConstructor
public class ControllerExceptionAdvice {
private final SlackApi slackApi;

/**
* custom error
*/
@ExceptionHandler(CustomException.class)
protected ResponseEntity<ApiResponse> handleCustomException(CustomException e) {
Sentry.captureException(e);
return ResponseEntity.status(e.getHttpStatus())
.body(ApiResponse.error(e.getError(), e.getMessage()));
}
Expand All @@ -40,12 +48,26 @@ protected ResponseEntity<ApiResponse> handleCustomException(CustomException e) {
@ExceptionHandler(MethodArgumentNotValidException.class)
protected ResponseEntity<ApiResponse> handleConstraintDefinitionException(final MethodArgumentNotValidException e) {
FieldError fieldError = e.getBindingResult().getFieldError();
Sentry.captureException(e);
return ResponseEntity.status(e.getStatusCode())
.body(ApiResponse.error(Error.BAD_REQUEST_VALIDATION, fieldError.getDefaultMessage()));
}
@ExceptionHandler(MalformedURLException.class)
protected ApiResponse handleConstraintDefinitionException(final MalformedURLException e) {
Sentry.captureException(e);
return ApiResponse.error(Error.MALFORMED_URL_EXEPTION, Error.MALFORMED_URL_EXEPTION.getMessage());
}

/**
* 500 Internal Server Error
*/
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Exception.class)
protected ApiResponse<Object> handleException(final Exception error, final HttpServletRequest request) throws
IOException {
slackApi.sendAlert(error, request);
Sentry.captureException(error);
return ApiResponse.error(Error.INTERNAL_SERVER_ERROR);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.app.toaster.controller;

import java.io.IOException;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PostMapping;
Expand Down Expand Up @@ -31,7 +33,7 @@ public class AuthController {
public ApiResponse<SignInResponseDto> signIn(
@RequestHeader("Authorization") String socialAccessToken,
@RequestBody SignInRequestDto requestDto
) {
) throws IOException {
return ApiResponse.success(Success.LOGIN_SUCCESS, authService.signIn(socialAccessToken, requestDto));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package com.app.toaster.external.client.slack;

import static com.slack.api.model.block.composition.BlockCompositions.*;

import java.io.IOException;
import java.util.Date;
import java.util.List;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import com.app.toaster.exception.Success;
import com.app.toaster.infrastructure.UserRepository;
import com.slack.api.Slack;
import com.slack.api.model.block.Blocks;
import com.slack.api.model.block.LayoutBlock;
import com.slack.api.model.block.composition.BlockCompositions;
import com.slack.api.webhook.WebhookPayloads;

import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Component
@RequiredArgsConstructor
@Slf4j
public class SlackApi {

@Value("${slack.webhook.error}")
private String errorUrl;
@Value("${slack.webhook.success}")
private String successUrl;
private final static String NEW_LINE = "\n";
private final static String DOUBLE_NEW_LINE = "\n\n";

private final UserRepository userRepository;

private StringBuilder sb = new StringBuilder();

public void sendAlert(Exception error, HttpServletRequest request) throws IOException {

List<LayoutBlock> layoutBlocks = generateLayoutBlock(error, request);

Slack.getInstance().send(errorUrl, WebhookPayloads
.payload(p ->
p.username("Exception is detected 🚨")
.iconUrl("https://yt3.googleusercontent.com/ytc/AGIKgqMVUzRrhoo1gDQcqvPo0PxaJz7e0gqDXT0D78R5VQ=s900-c-k-c0x00ffffff-no-rj")
.blocks(layoutBlocks)));
}

private List<LayoutBlock> generateLayoutBlock(Exception error, HttpServletRequest request) {
return Blocks.asBlocks(
getHeader("서버 측 오류로 예상되는 예외 상황이 발생하였습니다."),
Blocks.divider(),
getSection(generateErrorMessage(error)),
Blocks.divider(),
getSection(generateErrorPointMessage(request)),
Blocks.divider(),
getSection("<https://github.com/team-winey/Winey-Server/issues|이슈 생성하러 가기>")
);
}

private List<LayoutBlock> generateSuccessBlock(Success success) {
return Blocks.asBlocks(
getHeader("😍회원가입 이벤트가 발생했습니다."),
Blocks.divider(),
getSection(generateSuccessMessage(success)),
Blocks.divider(),
getSection(generateSignUpMessage()),
Blocks.divider()
);
}

private String generateErrorMessage(Exception error) {
sb.setLength(0);
sb.append("*[🔥 Exception]*" + NEW_LINE + error.toString() + DOUBLE_NEW_LINE);
sb.append("*[📩 From]*" + NEW_LINE + readRootStackTrace(error) + DOUBLE_NEW_LINE);

return sb.toString();
}

private String generateSuccessMessage(Success success) {
sb.setLength(0);
sb.append("*[🔥 축하합니다!]*" + NEW_LINE + "토스트 굽는 소리가 들려요~!" + DOUBLE_NEW_LINE);

return sb.toString();
}

private String generateErrorPointMessage(HttpServletRequest request) {
sb.setLength(0);
sb.append("*[🧾세부정보]*" + NEW_LINE);
sb.append("Request URL : " + request.getRequestURL().toString() + NEW_LINE);
sb.append("Request Method : " + request.getMethod() + NEW_LINE);
sb.append("Request Time : " + new Date() + NEW_LINE);

return sb.toString();
}
private String generateSignUpMessage() {
sb.setLength(0);
sb.append("*[🧾유저 가입 정보]*" + NEW_LINE);
sb.append("토스터의 " + userRepository.count() + "번째 유저가 생성되었습니다!!❤️");
return sb.toString();
}

private String readRootStackTrace(Exception error) {
return error.getStackTrace()[0].toString();
}

private LayoutBlock getHeader(String text) {
return Blocks.header(h -> h.text(
plainText(pt -> pt.emoji(true)
.text(text))));
}

private LayoutBlock getSection(String message) {
return Blocks.section(s ->
s.text(BlockCompositions.markdownText(message)));
}

public void sendSuccess(Success success) throws IOException {

List<LayoutBlock> layoutBlocks = generateSuccessBlock(success);

Slack.getInstance().send(successUrl, WebhookPayloads
.payload(p ->
p.username("Exception is detected 🚨")
.iconUrl("https://yt3.googleusercontent.com/ytc/AGIKgqMVUzRrhoo1gDQcqvPo0PxaJz7e0gqDXT0D78R5VQ=s900-c-k-c0x00ffffff-no-rj")
.blocks(layoutBlocks)));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.app.toaster.service.auth;

import java.io.IOException;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -11,9 +13,11 @@
import com.app.toaster.domain.SocialType;
import com.app.toaster.domain.User;
import com.app.toaster.exception.Error;
import com.app.toaster.exception.Success;
import com.app.toaster.exception.model.BadRequestException;
import com.app.toaster.exception.model.NotFoundException;
import com.app.toaster.exception.model.UnprocessableEntityException;
import com.app.toaster.external.client.slack.SlackApi;
import com.app.toaster.infrastructure.UserRepository;
import com.app.toaster.service.auth.apple.AppleSignInService;
import com.app.toaster.service.auth.kakao.KakaoSignInService;
Expand All @@ -30,6 +34,8 @@ public class AuthService {

private final UserRepository userRepository;

private final SlackApi slackApi;


private final Long TOKEN_EXPIRATION_TIME_ACCESS = 24 * 60 * 60 * 1000L; //1일
private final Long TOKEN_EXPIRATION_TIME_REFRESH = 3 * 24 * 60 * 60 * 1000L; //3일
Expand All @@ -41,12 +47,11 @@ public class AuthService {


@Transactional
public SignInResponseDto signIn(String socialAccessToken, SignInRequestDto requestDto) {
public SignInResponseDto signIn(String socialAccessToken, SignInRequestDto requestDto) throws IOException {
SocialType socialType = SocialType.valueOf(requestDto.socialType());
LoginResult loginResult = login(socialType, socialAccessToken);
String socialId = loginResult.id();
String profileImage = loginResult.profile();
System.out.println(profileImage);
Boolean isRegistered = userRepository.existsBySocialIdAndSocialType(socialId, socialType);

if (!isRegistered) {
Expand All @@ -56,6 +61,7 @@ public SignInResponseDto signIn(String socialAccessToken, SignInRequestDto reque
.socialType(socialType).build();
newUser.updateFcmIsAllowed(true); //신규 유저면 true박고
userRepository.save(newUser);
slackApi.sendSuccess(Success.LOGIN_SUCCESS);
}

User user = userRepository.findBySocialIdAndSocialType(socialId, socialType)
Expand Down

0 comments on commit e8013cb

Please sign in to comment.