From 0903faa20aa15700c597564933ae545b0ce038f0 Mon Sep 17 00:00:00 2001 From: rowan Date: Sun, 30 Apr 2023 23:16:45 +0900 Subject: [PATCH 1/5] =?UTF-8?q?Update=20QueryDSL=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/build.gradle b/build.gradle index 2a6266d..2a3f5bf 100644 --- a/build.gradle +++ b/build.gradle @@ -18,11 +18,15 @@ repositories { } ext { - snippetsDir = file("$buildDir/generated-snippets") - javadocJsonDir = file("$buildDir/generated-javadoc-json") queryDslVersion = "5.0.0" } +configurations { + compileOnly { + extendsFrom annotationProcessor + } +} + dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-validation' @@ -43,6 +47,7 @@ dependencies { // QueryDSL implementation "com.querydsl:querydsl-jpa:${queryDslVersion}" + implementation "com.querydsl:querydsl-core:${queryDslVersion}" annotationProcessor( "javax.persistence:javax.persistence-api", "javax.annotation:javax.annotation-api", @@ -87,7 +92,7 @@ test { useJUnitPlatform() } -task testCoverage(type: Test) { +tasks.register('testCoverage', Test) { group 'verification' description 'Runs the unit tests with coverage' @@ -105,15 +110,11 @@ jar { } sourceSets { - main { - java { - srcDirs = ["$projectDir/src/main/java", "$projectDir/build/generated"] - } - } + main.java.srcDirs += ["$projectDir/build/generated"] } -tasks.withType(JavaCompile) { - options.annotationProcessorGeneratedSourcesDirectory = file("$projectDir/build/generated") +tasks.withType(JavaCompile).configureEach { + options.getGeneratedSourceOutputDirectory().set(file("$projectDir/build/generated")) } jacoco { From e8087c92568bc60feedd40004867571898dddb13 Mon Sep 17 00:00:00 2001 From: rowan Date: Mon, 1 May 2023 08:08:53 +0900 Subject: [PATCH 2/5] =?UTF-8?q?Add=20Pinpoint=EC=9D=98=20=EC=95=8C?= =?UTF-8?q?=EB=9E=8C=EC=9D=84=20=EC=8A=AC=EB=9E=99=EC=97=90=20=EC=A0=84?= =?UTF-8?q?=EC=86=A1=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../homework/api/config/AppEnvironment.java | 40 +++-- .../api/domain/user/api/UserController.java | 13 +- ...va => UserWithHomeAndMembersResponse.java} | 19 +- .../user/oauth/api/dto/TokenResponse.java | 13 +- .../homework/api/domain/webhook/BlockKit.java | 167 ++++++++++++++++++ .../DefaultStyleSlackPayloadConvertor.java | 39 ++++ .../webhook/PinpointMessageBrokerService.java | 19 ++ .../homework/api/domain/webhook/Slack.java | 30 ++++ .../api/domain/webhook/SlackFinder.java | 30 ++++ .../domain/webhook/SlackPayloadConvertor.java | 7 + .../api/domain/webhook/SlackResources.java | 10 ++ .../api/domain/webhook/SlackService.java | 27 +++ .../domain/webhook/api/PinpointResources.java | 44 +++++ .../domain/webhook/api/WebhookController.java | 29 +++ .../api/WebhookMaintenanceController.java | 43 +++++ .../api/WebhookMaintenanceResources.java | 36 ++++ .../webhook/client/DummySlackClient.java | 14 ++ .../domain/webhook/client/SlackClient.java | 7 + .../client/SlackClientConfiguration.java | 18 ++ .../webhook/client/StableSlackClient.java | 29 +++ .../webhook/repository/SlackRepository.java | 8 + .../api/CategoryMaintenanceResources.java | 4 +- .../domain/work/api/CategoryResources.java | 27 ++- .../simleetag/homework/utils/JsonMapper.java | 7 +- src/test/http/http-client.env.json | 8 + src/test/http/pinpoint-to-slack.http | 37 ++++ .../domain/user/api/UserControllerFlow.java | 83 +++++---- .../domain/user/api/UserControllerTest.java | 8 +- 28 files changed, 715 insertions(+), 101 deletions(-) rename src/main/java/com/simleetag/homework/api/domain/user/api/dto/{findUserWithHomeAndMembersResponse.java => UserWithHomeAndMembersResponse.java} (75%) create mode 100644 src/main/java/com/simleetag/homework/api/domain/webhook/BlockKit.java create mode 100644 src/main/java/com/simleetag/homework/api/domain/webhook/DefaultStyleSlackPayloadConvertor.java create mode 100644 src/main/java/com/simleetag/homework/api/domain/webhook/PinpointMessageBrokerService.java create mode 100644 src/main/java/com/simleetag/homework/api/domain/webhook/Slack.java create mode 100644 src/main/java/com/simleetag/homework/api/domain/webhook/SlackFinder.java create mode 100644 src/main/java/com/simleetag/homework/api/domain/webhook/SlackPayloadConvertor.java create mode 100644 src/main/java/com/simleetag/homework/api/domain/webhook/SlackResources.java create mode 100644 src/main/java/com/simleetag/homework/api/domain/webhook/SlackService.java create mode 100644 src/main/java/com/simleetag/homework/api/domain/webhook/api/PinpointResources.java create mode 100644 src/main/java/com/simleetag/homework/api/domain/webhook/api/WebhookController.java create mode 100644 src/main/java/com/simleetag/homework/api/domain/webhook/api/WebhookMaintenanceController.java create mode 100644 src/main/java/com/simleetag/homework/api/domain/webhook/api/WebhookMaintenanceResources.java create mode 100644 src/main/java/com/simleetag/homework/api/domain/webhook/client/DummySlackClient.java create mode 100644 src/main/java/com/simleetag/homework/api/domain/webhook/client/SlackClient.java create mode 100644 src/main/java/com/simleetag/homework/api/domain/webhook/client/SlackClientConfiguration.java create mode 100644 src/main/java/com/simleetag/homework/api/domain/webhook/client/StableSlackClient.java create mode 100644 src/main/java/com/simleetag/homework/api/domain/webhook/repository/SlackRepository.java create mode 100644 src/test/http/http-client.env.json create mode 100644 src/test/http/pinpoint-to-slack.http diff --git a/src/main/java/com/simleetag/homework/api/config/AppEnvironment.java b/src/main/java/com/simleetag/homework/api/config/AppEnvironment.java index c9474c4..e29f6fe 100644 --- a/src/main/java/com/simleetag/homework/api/config/AppEnvironment.java +++ b/src/main/java/com/simleetag/homework/api/config/AppEnvironment.java @@ -1,27 +1,34 @@ package com.simleetag.homework.api.config; -import org.springframework.boot.context.properties.ConfigurationProperties; - import lombok.Getter; import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; @Getter @Setter @ConfigurationProperties(prefix = "app") public class AppEnvironment { + // OAuth private Client client; - private Jwt jwt; - private Oauth oauth; + // Messenger + public Messenger messenger = new Messenger(); + + public static class ConnInfo { + public String host = "localhost"; + public boolean useDummy = false; + } + public record Oauth(KakaoAttributes kakaoAttributes, AppleAttributes appleAttributes) { public record KakaoAttributes( String clientId, String redirectUri, String tokenUri, String userInformationUri - ) {} + ) { + } public record AppleAttributes( String clientId, @@ -31,18 +38,31 @@ public record AppleAttributes( String keyPath, String uri, String clientSecretExpiration - ) {} + ) { + } } public record Jwt(OauthAttributes oauthAttributes, HomeAttributes homeAttributes) { - public record OauthAttributes(long accessTokenExpiration, String secret) {} + public record OauthAttributes(long accessTokenExpiration, String secret) { + } - public record HomeAttributes(long accessTokenExpiration, String secret) {} + public record HomeAttributes(long accessTokenExpiration, String secret) { + } } public record Client(KakaoOAuth kakaoOAuth, AppleOAuth appleOAuth) { - public record KakaoOAuth(boolean useDummy) {} + public record KakaoOAuth(boolean useDummy) { + } + + public record AppleOAuth(boolean useDummy) { + } + } + + public static class Messenger { + public Slack slack = new Slack(); - public record AppleOAuth(boolean useDummy) {} + public static class Slack extends ConnInfo { + public String path = "test/slack/path"; + } } } diff --git a/src/main/java/com/simleetag/homework/api/domain/user/api/UserController.java b/src/main/java/com/simleetag/homework/api/domain/user/api/UserController.java index 92dd2d9..ad83106 100644 --- a/src/main/java/com/simleetag/homework/api/domain/user/api/UserController.java +++ b/src/main/java/com/simleetag/homework/api/domain/user/api/UserController.java @@ -5,15 +5,12 @@ import com.simleetag.homework.api.domain.user.UserService; import com.simleetag.homework.api.domain.user.api.dto.UserProfileRequest; import com.simleetag.homework.api.domain.user.api.dto.UserProfileResponse; -import com.simleetag.homework.api.domain.user.api.dto.findUserWithHomeAndMembersResponse; - -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - +import com.simleetag.homework.api.domain.user.api.dto.UserWithHomeAndMembersResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; - import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; @Tag(name = "사용자 계정") @RequiredArgsConstructor @@ -27,9 +24,9 @@ public class UserController { description = "Homework 토큰으로 사용자 정보를 조회합니다." ) @GetMapping("/me") - public ResponseEntity findUserByAccessToken(@Login Long userId) { + public ResponseEntity findUserByAccessToken(@Login Long userId) { final User user = userService.findUserWithHomeAndMembers(userId); - return ResponseEntity.ok(findUserWithHomeAndMembersResponse.from(user)); + return ResponseEntity.ok(UserWithHomeAndMembersResponse.from(user)); } @Operation( diff --git a/src/main/java/com/simleetag/homework/api/domain/user/api/dto/findUserWithHomeAndMembersResponse.java b/src/main/java/com/simleetag/homework/api/domain/user/api/dto/UserWithHomeAndMembersResponse.java similarity index 75% rename from src/main/java/com/simleetag/homework/api/domain/user/api/dto/findUserWithHomeAndMembersResponse.java rename to src/main/java/com/simleetag/homework/api/domain/user/api/dto/UserWithHomeAndMembersResponse.java index 360812b..5f4e8f8 100644 --- a/src/main/java/com/simleetag/homework/api/domain/user/api/dto/findUserWithHomeAndMembersResponse.java +++ b/src/main/java/com/simleetag/homework/api/domain/user/api/dto/UserWithHomeAndMembersResponse.java @@ -1,16 +1,15 @@ package com.simleetag.homework.api.domain.user.api.dto; -import java.util.List; -import javax.validation.constraints.NotBlank; - import com.simleetag.homework.api.domain.home.Home; import com.simleetag.homework.api.domain.home.api.dto.HomeWithMembersResponse; import com.simleetag.homework.api.domain.home.member.Member; import com.simleetag.homework.api.domain.user.User; - import io.swagger.v3.oas.annotations.media.Schema; -public record findUserWithHomeAndMembersResponse( +import javax.validation.constraints.NotBlank; +import java.util.List; + +public record UserWithHomeAndMembersResponse( @Schema(description = "유저 ID") @NotBlank Long userId, @@ -27,13 +26,13 @@ public record findUserWithHomeAndMembersResponse( @NotBlank List homes ) { - public static findUserWithHomeAndMembersResponse from(User user) { + public static UserWithHomeAndMembersResponse from(User user) { final List userHomes = user.getMembers() - .stream() - .map(Member::getHome) - .toList(); + .stream() + .map(Member::getHome) + .toList(); - return new findUserWithHomeAndMembersResponse( + return new UserWithHomeAndMembersResponse( user.getId(), user.getUserName(), user.getProfileImage(), diff --git a/src/main/java/com/simleetag/homework/api/domain/user/oauth/api/dto/TokenResponse.java b/src/main/java/com/simleetag/homework/api/domain/user/oauth/api/dto/TokenResponse.java index f2889a5..7e1d45d 100644 --- a/src/main/java/com/simleetag/homework/api/domain/user/oauth/api/dto/TokenResponse.java +++ b/src/main/java/com/simleetag/homework/api/domain/user/oauth/api/dto/TokenResponse.java @@ -1,25 +1,24 @@ package com.simleetag.homework.api.domain.user.oauth.api.dto; -import javax.validation.constraints.NotBlank; - import com.simleetag.homework.api.domain.user.User; -import com.simleetag.homework.api.domain.user.api.dto.findUserWithHomeAndMembersResponse; - +import com.simleetag.homework.api.domain.user.api.dto.UserWithHomeAndMembersResponse; import io.swagger.v3.oas.annotations.media.Schema; +import javax.validation.constraints.NotBlank; + public record TokenResponse( @Schema(description = "Homework 서비스를 사용하기 위한 JWT") @NotBlank String homeworkToken, @Schema(description = "Homework에 가입된 유저의 정보") - @NotBlank findUserWithHomeAndMembersResponse user + @NotBlank UserWithHomeAndMembersResponse user ) { - public static TokenResponse from(String homeworkToken, findUserWithHomeAndMembersResponse user) { + public static TokenResponse from(String homeworkToken, UserWithHomeAndMembersResponse user) { return new TokenResponse(homeworkToken, user); } public static TokenResponse from(String homeworkToken, User user) { - return from(homeworkToken, findUserWithHomeAndMembersResponse.from(user)); + return from(homeworkToken, UserWithHomeAndMembersResponse.from(user)); } } diff --git a/src/main/java/com/simleetag/homework/api/domain/webhook/BlockKit.java b/src/main/java/com/simleetag/homework/api/domain/webhook/BlockKit.java new file mode 100644 index 0000000..0dba5ab --- /dev/null +++ b/src/main/java/com/simleetag/homework/api/domain/webhook/BlockKit.java @@ -0,0 +1,167 @@ +package com.simleetag.homework.api.domain.webhook; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import lombok.AllArgsConstructor; + +import java.util.List; + +/** + * https://api.slack.com/reference/block-kit + */ +public class BlockKit { + + @JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXISTING_PROPERTY, + property = "type", + visible = true + ) + @JsonSubTypes({ + @JsonSubTypes.Type(value = Context.class, name = "context"), + @JsonSubTypes.Type(value = Header.class, name = "header"), + @JsonSubTypes.Type(value = Section.class, name = "section"), + @JsonSubTypes.Type(value = Divider.class, name = "divider"), + @JsonSubTypes.Type(value = Image.class, name = "checkboxes"), + @JsonSubTypes.Type(value = Text.class, name = "plain_text"), + @JsonSubTypes.Type(value = Text.class, name = "mrkdwn"), + @JsonSubTypes.Type(value = Button.class, name = "button"), + @JsonSubTypes.Type(value = Checkbox.class, name = "checkboxes"), + @JsonSubTypes.Type(value = Image.class, name = "image"), + }) + public interface Block { + + } + + public interface BlockKitElement extends Block { + + } + + /** + * https://api.slack.com/reference/block-kit#objects + */ + public interface BlockKitObject extends Block { + + } + + /** + * https://api.slack.com/reference/block-kit#blocks + */ + public interface BlockKitBlock extends Block { + + } + + @AllArgsConstructor + public static class Context implements BlockKitBlock { + /** + * Only images BlockElement and text CompositionObject are allowed. + */ + public String type; + public List elements; + public String blockId; + + public Context(List elements) { + this("context", elements, null); + } + } + + public record Header( + Text text + ) implements BlockKitBlock { + public static String type = "header"; + } + + @AllArgsConstructor + public static class Section implements BlockKitBlock { + public String type; + public Text text; + public String blockId; + public List fields; + public BlockKitElement accessor; + + public Section(Text text) { + this("section", text, null, null, null); + } + } + + public class Divider implements BlockKitBlock { + public static String type = "divider"; + } + + public record Button( + Text text, + String actionId, + String url, + String value, + Style style, + ConfirmationDialog confirm, + Text accessibilityLabel + ) implements BlockKitElement { + public static String type = "button"; + + public enum Style { + primary, + danger + } + } + + public record Checkbox( + String actionId, + List