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

Fix/#35 #36

Merged
merged 3 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
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
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ public LoginResult getKaKaoId(String accessToken) {
responseData = restTemplate.postForEntity(KAKAO_URL,httpEntity,Object.class);
ObjectMapper objectMapper = new ObjectMapper();
HashMap profileResponse = (HashMap)objectMapper.convertValue( responseData.getBody(),Map.class).get("properties");
System.out.println(profileResponse.get("profile_image").toString());
return LoginResult.of(objectMapper.convertValue(responseData.getBody(), Map.class).get("id").toString(), profileResponse.get("profile_image").toString()); //์†Œ์…œ id๋งŒ ๊ฐ€์ ธ์˜ค๋Š”๋“ฏ.
return LoginResult.of(objectMapper.convertValue(responseData.getBody(), Map.class).get("id").toString(), profileResponse==null?null:profileResponse.get("profile_image").toString()); //ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ํ—ˆ์šฉ x์‹œ null๋กœ ๋„˜๊ธฐ๊ธฐ.
}
}
Loading