From 9eef7b096d31f7d5adc29637250f0af5ec3b76c5 Mon Sep 17 00:00:00 2001 From: Daniel Pelaez Date: Sun, 14 Apr 2024 13:25:04 -0500 Subject: [PATCH 01/17] Moving classes to root --- pom.xml | 198 ++++++++++++- .../.mvn/wrapper/MavenWrapperDownloader.java | 140 --------- .../.mvn/wrapper/maven-wrapper.jar | Bin 50710 -> 0 bytes spring-publisher/HELP.md | 8 - spring-publisher/mvnw | 0 spring-publisher/mvnw.cmd | 182 ------------ .../abstraction/post/PostPublisher.java | 2 +- src/assembly/bin.xml | 27 ++ .../abstraction/post/PostPublisher.java | 64 ++++ .../abstraction/post/repository/Post.java | 71 +++++ .../post/repository/PostRepository.java | 8 + .../post/socialmedia/Acknowledge.java | 17 ++ .../post/socialmedia/Publication.java | 20 ++ .../socialmedia/SocialMediaPublisher.java | 11 + ...essTokenResponseConverterWithDefaults.java | 71 +++++ .../security/OAuth2CredentialsManager.java | 47 +++ .../security/OAuth2CredentialsUpdated.java | 11 + .../security/UnauthorizedException.java | 10 + .../repository/OAuth1Credentials.java | 14 + .../OAuth1CredentialsRepository.java | 7 + .../repository/OAuth2Credentials.java | 24 ++ .../OAuth2CredentialsRepository.java | 13 + .../main/SpringPublisherApplication.java | 15 + .../aws/AWSSpringPublisherApplication.java | 60 ++++ ...ingPublisherDynamoDBRepositoryFactory.java | 64 ++++ .../main/aws/StreamLambdaHandler.java | 33 +++ .../repository/LocalDateTimeConverter.java | 17 ++ .../main/aws/repository/URLConverter.java | 23 ++ .../oauth1/OAuth1CredentialAWSRepository.java | 29 ++ .../oauth1/OAuth1CredentialDynamo.java | 27 ++ .../OAuth1CredentialDynamoRepository.java | 8 + .../oauth2/OAuth2CredentialAWSRepository.java | 57 ++++ .../oauth2/OAuth2CredentialDynamo.java | 31 ++ .../OAuth2CredentialDynamoRepository.java | 8 + .../repository/post/PostAWSRepository.java | 52 ++++ .../main/aws/repository/post/PostDynamo.java | 38 +++ .../repository/post/PostDynamoRepository.java | 9 + .../main/controller/ErrorHandler.java | 14 + .../OAuth2CredentialsController.java | 33 +++ .../main/controller/PostsController.java | 42 +++ .../main/factory/CredentialsProperties.java | 13 + .../main/factory/SecurityFactory.java | 83 ++++++ .../SocialMediaPublisherProperties.java | 17 ++ .../main/factory/SpringPublisherFactory.java | 62 ++++ .../socialmedia/linkedin/ArticleContent.java | 12 + .../main/socialmedia/linkedin/Content.java | 10 + .../socialmedia/linkedin/Distribution.java | 10 + .../linkedin/LinkedInPublisher.java | 172 +++++++++++ .../socialmedia/linkedin/LinkedInShare.java | 15 + .../main/socialmedia/linkedin/Media.java | 13 + .../main/socialmedia/linkedin/Profile.java | 14 + .../main/socialmedia/linkedin/Text.java | 10 + .../socialmedia/twitter/TwitterPublisher.java | 120 ++++++++ src/main/resources/application-local.yml | 15 + src/main/resources/application-secure.yml | 32 ++ src/main/resources/application.yml | 14 + .../abstraction/post/PostPublisherTest.java | 195 ++++++++++++ .../OAuth2CredentialsManagerTest.java | 107 +++++++ ...ckSocialMediaCredentialsHandlingTests.java | 180 +++++++++++ .../aws/MockSocialMediaSuccessfulTests.java | 196 ++++++++++++ .../main/aws/StreamLambdaHandlerTest.java | 73 +++++ .../LocalDateTimeConverterTest.java | 26 ++ .../main/aws/repository/URLConverterTest.java | 28 ++ .../OAuth1CredentialAWSRepositoryTest.java | 43 +++ .../OAuth2CredentialAWSRepositoryTest.java | 65 ++++ .../post/PostAWSRepositoryTest.java | 97 ++++++ .../main/controller/PostsControllerTest.java | 72 +++++ .../linkedin/LinkedInPublisherTest.java | 280 ++++++++++++++++++ .../twitter/TwitterPublisherTest.java | 217 ++++++++++++++ 69 files changed, 3359 insertions(+), 337 deletions(-) mode change 100755 => 100644 spring-publisher/mvnw create mode 100644 src/assembly/bin.xml create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/PostPublisher.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/repository/Post.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/repository/PostRepository.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/Acknowledge.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/Publication.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/SocialMediaPublisher.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2AccessTokenResponseConverterWithDefaults.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsManager.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsUpdated.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/UnauthorizedException.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth1Credentials.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth1CredentialsRepository.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth2Credentials.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth2CredentialsRepository.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/SpringPublisherApplication.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/AWSSpringPublisherApplication.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/SpringPublisherDynamoDBRepositoryFactory.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/StreamLambdaHandler.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/LocalDateTimeConverter.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/URLConverter.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepository.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialDynamo.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialDynamoRepository.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepository.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialDynamo.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialDynamoRepository.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostAWSRepository.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostDynamo.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostDynamoRepository.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/ErrorHandler.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/OAuth2CredentialsController.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/PostsController.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/CredentialsProperties.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SecurityFactory.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SocialMediaPublisherProperties.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SpringPublisherFactory.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/ArticleContent.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Content.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Distribution.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInPublisher.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInShare.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Media.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Profile.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Text.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/twitter/TwitterPublisher.java create mode 100644 src/main/resources/application-local.yml create mode 100644 src/main/resources/application-secure.yml create mode 100644 src/main/resources/application.yml create mode 100644 src/test/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/PostPublisherTest.java create mode 100644 src/test/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsManagerTest.java create mode 100644 src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/MockSocialMediaCredentialsHandlingTests.java create mode 100644 src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/MockSocialMediaSuccessfulTests.java create mode 100644 src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/StreamLambdaHandlerTest.java create mode 100644 src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/LocalDateTimeConverterTest.java create mode 100644 src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/URLConverterTest.java create mode 100644 src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepositoryTest.java create mode 100644 src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepositoryTest.java create mode 100644 src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostAWSRepositoryTest.java create mode 100644 src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/PostsControllerTest.java create mode 100644 src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInPublisherTest.java create mode 100644 src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/twitter/TwitterPublisherTest.java diff --git a/pom.xml b/pom.xml index 8988a79..5c71305 100644 --- a/pom.xml +++ b/pom.xml @@ -14,18 +14,30 @@ 0.0.1-SNAPSHOT social-media-publisher Demo project for Spring Boot - pom + jar 11 - - spring-publisher - aws-publisher - - + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-oauth2-client + + + org.twitter4j + twitter4j-core + 4.0.7 + org.projectlombok lombok @@ -43,5 +55,179 @@ + + + com.amazonaws.serverless + aws-serverless-java-container-springboot2 + 1.5 + + + + io.github.boostchicken + spring-data-dynamodb + 5.2.3 + + + + org.springframework.boot + spring-boot-starter-web + + + + com.amazonaws + DynamoDBLocal + 1.13.0 + test + + + com.amazonaws + aws-java-sdk-dynamodb + + + com.amazonaws + aws-java-sdk-core + + + + + + org.junit-pioneer + junit-pioneer + 0.5.1 + test + + + + org.springframework.security + spring-security-test + test + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy + test-compile + + copy-dependencies + + + test + so,dll,dylib + ${project.build.directory}/native-libs + + + + + + + + + + shaded-jar + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + false + + + + package + + shade + + + + + org.apache.tomcat.embed:* + + + + + + + + + + + assembly-zip + + true + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.1.1 + + + default-jar + none + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.1.1 + + + copy-dependencies + package + + copy-dependencies + + + ${project.build.directory}${file.separator}lib + runtime + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.1.0 + + + zip-assembly + package + + single + + + ${project.artifactId}-${project.version} + + src${file.separator}assembly${file.separator}bin.xml + + false + + + + + + + + + + + + dynamodb-local-oregon + DynamoDB Local Release Repository + https://s3-us-west-2.amazonaws.com/dynamodb-local/release + + diff --git a/spring-publisher/.mvn/wrapper/MavenWrapperDownloader.java b/spring-publisher/.mvn/wrapper/MavenWrapperDownloader.java index f97cd5a..e69de29 100644 --- a/spring-publisher/.mvn/wrapper/MavenWrapperDownloader.java +++ b/spring-publisher/.mvn/wrapper/MavenWrapperDownloader.java @@ -1,140 +0,0 @@ -/* - * Copyright 2007-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import java.net.*; -import java.io.*; -import java.nio.channels.*; -import java.util.Properties; - -public class MavenWrapperDownloader { - - private static final String WRAPPER_VERSION = "0.5.6"; - /** - * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. - */ - private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" - + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; - - /** - * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to - * use instead of the default one. - */ - private static final String MAVEN_WRAPPER_PROPERTIES_PATH = - ".mvn/wrapper/maven-wrapper.properties"; - - /** - * Path where the maven-wrapper.jar will be saved to. - */ - private static final String MAVEN_WRAPPER_JAR_PATH = - ".mvn/wrapper/maven-wrapper.jar"; - - /** - * Name of the property which should be used to override the default download url for the wrapper. - */ - private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; - - public static void main(String args[]) { - System.out.println("- Downloader started"); - File baseDirectory = new File(args[0]); - System.out.println( - "- Using base directory: " + baseDirectory - .getAbsolutePath()); - // If the maven-wrapper.properties exists, read it and check if it contains a custom - // wrapperUrl parameter. - File mavenWrapperPropertyFile = new File( - baseDirectory, - MAVEN_WRAPPER_PROPERTIES_PATH); - String url = DEFAULT_DOWNLOAD_URL; - if (mavenWrapperPropertyFile.exists()) { - FileInputStream mavenWrapperPropertyFileInputStream = null; - try { - mavenWrapperPropertyFileInputStream = new FileInputStream( - mavenWrapperPropertyFile); - Properties mavenWrapperProperties = new Properties(); - mavenWrapperProperties - .load(mavenWrapperPropertyFileInputStream); - url = mavenWrapperProperties - .getProperty(PROPERTY_NAME_WRAPPER_URL, - url); - } catch (IOException e) { - System.out.println( - "- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); - } finally { - try { - if (mavenWrapperPropertyFileInputStream != null) { - mavenWrapperPropertyFileInputStream - .close(); - } - } catch (IOException e) { - // Ignore ... - } - } - } - System.out.println("- Downloading from: " + url); - File outputFile = new File( - baseDirectory.getAbsolutePath(), - MAVEN_WRAPPER_JAR_PATH); - if (!outputFile.getParentFile().exists()) { - if (!outputFile.getParentFile().mkdirs()) { - System.out.println( - "- ERROR creating output directory '" + outputFile - .getParentFile() - .getAbsolutePath() + "'"); - } - } - System.out.println( - "- Downloading to: " + outputFile - .getAbsolutePath()); - try { - downloadFileFromURL(url, outputFile); - System.out.println("Done"); - System.exit(0); - } catch (Throwable e) { - System.out.println("- Error downloading"); - e.printStackTrace(); - System.exit(1); - } - } - - private static void downloadFileFromURL( - String urlString, File destination) - throws Exception { - if (System - .getenv("MVNW_USERNAME") != null && System - .getenv("MVNW_PASSWORD") != null) { - String username = System.getenv("MVNW_USERNAME"); - char[] password = System.getenv("MVNW_PASSWORD") - .toCharArray(); - Authenticator.setDefault(new Authenticator() { - @Override - protected PasswordAuthentication getPasswordAuthentication() { - return new PasswordAuthentication(username, - password); - } - }); - } - URL website = new URL(urlString); - ReadableByteChannel rbc; - rbc = Channels.newChannel(website.openStream()); - FileOutputStream fos = new FileOutputStream( - destination); - fos.getChannel() - .transferFrom(rbc, 0, Long.MAX_VALUE); - fos.close(); - rbc.close(); - } - -} diff --git a/spring-publisher/.mvn/wrapper/maven-wrapper.jar b/spring-publisher/.mvn/wrapper/maven-wrapper.jar index 2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 GIT binary patch literal 0 HcmV?d00001 literal 50710 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 EfAhh=DF6Tf diff --git a/spring-publisher/HELP.md b/spring-publisher/HELP.md index 97150fe..e69de29 100644 --- a/spring-publisher/HELP.md +++ b/spring-publisher/HELP.md @@ -1,8 +0,0 @@ -# Getting Started - -### Reference Documentation -For further reference, please consider the following sections: - -* [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) -* [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/maven-plugin/) - diff --git a/spring-publisher/mvnw b/spring-publisher/mvnw old mode 100755 new mode 100644 diff --git a/spring-publisher/mvnw.cmd b/spring-publisher/mvnw.cmd index c8d4337..e69de29 100644 --- a/spring-publisher/mvnw.cmd +++ b/spring-publisher/mvnw.cmd @@ -1,182 +0,0 @@ -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM https://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Maven Start Up Batch script -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@REM -@REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files -@REM ---------------------------------------------------------------------------- - -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM set title of command window -title %0 -@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" -if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - -FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B -) - -@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -@REM This allows using the maven wrapper in projects that prohibit checking in binary data. -if exist %WRAPPER_JAR% ( - if "%MVNW_VERBOSE%" == "true" ( - echo Found %WRAPPER_JAR% - ) -) else ( - if not "%MVNW_REPOURL%" == "" ( - SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - ) - if "%MVNW_VERBOSE%" == "true" ( - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %DOWNLOAD_URL% - ) - - powershell -Command "&{"^ - "$webclient = new-object System.Net.WebClient;"^ - "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ - "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ - "}"^ - "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ - "}" - if "%MVNW_VERBOSE%" == "true" ( - echo Finished downloading %WRAPPER_JAR% - ) -) -@REM End of extension - -@REM Provide a "standardized" way to retrieve the CLI args that will -@REM work with both Windows and non-Windows executions. -set MAVEN_CMD_LINE_ARGS=%* - -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" -if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%" == "on" pause - -if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% - -exit /B %ERROR_CODE% diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/PostPublisher.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/PostPublisher.java index 2fab901..484cc18 100644 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/PostPublisher.java +++ b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/PostPublisher.java @@ -48,7 +48,7 @@ private boolean publishedWellOK(List publishedPosts) { private List publish(List socialMediaPublishers, Post nextPost) { return socialMediaPublishers.stream() - .flatMap(socialMediaPublisher -> socialMediaPublisher.publish(nextPost).stream()) + .map(socialMediaPublisher -> socialMediaPublisher.publish(nextPost)) .collect(Collectors.toList()); } diff --git a/src/assembly/bin.xml b/src/assembly/bin.xml new file mode 100644 index 0000000..1e08505 --- /dev/null +++ b/src/assembly/bin.xml @@ -0,0 +1,27 @@ + + lambda-package + + zip + + false + + + + ${project.build.directory}${file.separator}lib + lib + + tomcat-embed* + + + + + ${project.build.directory}${file.separator}classes + + ** + + ${file.separator} + + + \ No newline at end of file diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/PostPublisher.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/PostPublisher.java new file mode 100644 index 0000000..2fab901 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/PostPublisher.java @@ -0,0 +1,64 @@ +package com.coderstower.socialmediapubisher.springpublisher.abstraction.post; + +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.PostRepository; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Acknowledge; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Publication; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.SocialMediaPublisher; + +import java.time.Clock; +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +public class PostPublisher { + private final List socialMediaPublishers; + private final PostRepository postRepository; + private final Clock clock; + + public PostPublisher(List socialMediaPublishers, PostRepository postRepository, Clock clock) { + this.socialMediaPublishers = socialMediaPublishers; + this.postRepository = postRepository; + this.clock = clock; + } + + public Post publishNext(String group) { + ping(socialMediaPublishers); + + Post nextPost = postRepository.getNextToPublish(group) + .orElseThrow(() -> new IllegalStateException("There is not next post to publish")); + + List publishedPosts = publish(socialMediaPublishers, nextPost); + + if (publishedWellOK(publishedPosts)) { + Post toUpdate = nextPost.updateLastDatePublished(LocalDateTime.now(clock)); + + return postRepository.update(toUpdate) + .updatePublications(publishedPosts); + } else { + throw new IllegalStateException("Error publishing the post: " + nextPost + .updatePublications(publishedPosts)); + } + } + + private boolean publishedWellOK(List publishedPosts) { + return publishedPosts.stream() + .allMatch(publishedPost -> publishedPost.getStatus().equals(Publication.Status.SUCCESS)); + } + + private List publish(List socialMediaPublishers, Post nextPost) { + return socialMediaPublishers.stream() + .flatMap(socialMediaPublisher -> socialMediaPublisher.publish(nextPost).stream()) + .collect(Collectors.toList()); + } + + private void ping(List socialMediaPublishers) { + for (SocialMediaPublisher socialMediaPublisher : socialMediaPublishers) { + Acknowledge ping = socialMediaPublisher.ping(); + + if (!Acknowledge.Status.SUCCESS.equals(ping.getStatus())) { + throw new IllegalStateException("Ping to " + socialMediaPublisher.getName() + " failed: " + ping); + } + } + } +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/repository/Post.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/repository/Post.java new file mode 100644 index 0000000..3c5b945 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/repository/Post.java @@ -0,0 +1,71 @@ +package com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository; + +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Publication; +import lombok.Builder; +import lombok.Data; + +import java.net.URL; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Data +@Builder +public class Post { + private static final String BASIC_FORMAT = "%s\n\n%s\n\n%s"; + private static final String BASIC_FORMAT_WITHOUT_URL = "%s\n\n%s"; + private final String id; + private final String name; + private final String description; + private final List tags; + private final URL url; + private final LocalDateTime publishedDate; + private final List publications; + private final String group; + + public String basicFormat() { + return String.format(BASIC_FORMAT, + description, + Optional.ofNullable(tags) + .orElse(List.of()) + .stream() + .map(tag -> "#" + tag) + .collect(Collectors.joining(" ")) + , url); + } + + public String basicFormatWithoutURL() { + return String.format(BASIC_FORMAT_WITHOUT_URL, + description, + Optional.ofNullable(tags) + .orElse(List.of()) + .stream() + .map(tag -> "#" + tag) + .collect(Collectors.joining(" "))); + } + + public Post updateLastDatePublished(LocalDateTime publishedDate) { + return Post.builder() + .id(id) + .name(name) + .description(description) + .tags(tags) + .url(url) + .publications(publications) + .group(group) + .publishedDate(publishedDate).build(); + } + + public Post updatePublications(List publications) { + return Post.builder() + .id(id) + .name(name) + .description(description) + .tags(tags) + .url(url) + .publications(publications) + .group(group) + .publishedDate(publishedDate).build(); + } +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/repository/PostRepository.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/repository/PostRepository.java new file mode 100644 index 0000000..19ad73f --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/repository/PostRepository.java @@ -0,0 +1,8 @@ +package com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository; + +import java.util.Optional; + +public interface PostRepository { + Optional getNextToPublish(String group); + Post update(Post post); +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/Acknowledge.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/Acknowledge.java new file mode 100644 index 0000000..3500256 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/Acknowledge.java @@ -0,0 +1,17 @@ +package com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class Acknowledge { + private final Status status; + private final String description; + private final Exception exception; + + public enum Status{ + SUCCESS, + FAILURE + } +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/Publication.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/Publication.java new file mode 100644 index 0000000..2b80d75 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/Publication.java @@ -0,0 +1,20 @@ +package com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia; + +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@Builder +public class Publication { + private final String id; + private final Status status; + private final String publisher; + private final String credentialId; + private final LocalDateTime publishedDate; + + public enum Status{ + SUCCESS, FAILURE + } +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/SocialMediaPublisher.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/SocialMediaPublisher.java new file mode 100644 index 0000000..a555fc8 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/SocialMediaPublisher.java @@ -0,0 +1,11 @@ +package com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia; + +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; + +import java.util.List; + +public interface SocialMediaPublisher { + String getName(); + Acknowledge ping(); + List publish(Post post); +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2AccessTokenResponseConverterWithDefaults.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2AccessTokenResponseConverterWithDefaults.java new file mode 100644 index 0000000..826dea4 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2AccessTokenResponseConverterWithDefaults.java @@ -0,0 +1,71 @@ +package com.coderstower.socialmediapubisher.springpublisher.abstraction.security; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Copy from https://github.com/jgrandja/oauth2login-demo/blob/linkedin/src/main/java/sample/web/OAuth2AccessTokenResponseConverterWithDefaults.java + * + * Linkedin token response doesn't have the tokenType property. That property is required by the specification. + * + * This Converter uses Baerer token by default + */ +public class OAuth2AccessTokenResponseConverterWithDefaults implements Converter, OAuth2AccessTokenResponse> { + private static final Set TOKEN_RESPONSE_PARAMETER_NAMES = Stream.of( + OAuth2ParameterNames.ACCESS_TOKEN, + OAuth2ParameterNames.TOKEN_TYPE, + OAuth2ParameterNames.EXPIRES_IN, + OAuth2ParameterNames.REFRESH_TOKEN, + OAuth2ParameterNames.SCOPE).collect(Collectors.toSet()); + + private OAuth2AccessToken.TokenType defaultAccessTokenType = OAuth2AccessToken.TokenType.BEARER; + + @Override + public OAuth2AccessTokenResponse convert(Map tokenResponseParameters) { + String accessToken = tokenResponseParameters.get(OAuth2ParameterNames.ACCESS_TOKEN); + + OAuth2AccessToken.TokenType accessTokenType = this.defaultAccessTokenType; + if (OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase( + tokenResponseParameters.get(OAuth2ParameterNames.TOKEN_TYPE))) { + accessTokenType = OAuth2AccessToken.TokenType.BEARER; + } + + long expiresIn = 0; + if (tokenResponseParameters.containsKey(OAuth2ParameterNames.EXPIRES_IN)) { + try { + expiresIn = Long.valueOf(tokenResponseParameters.get(OAuth2ParameterNames.EXPIRES_IN)); + } catch (NumberFormatException ex) { } + } + + Set scopes = Collections.emptySet(); + if (tokenResponseParameters.containsKey(OAuth2ParameterNames.SCOPE)) { + String scope = tokenResponseParameters.get(OAuth2ParameterNames.SCOPE); + scopes = Arrays.stream(StringUtils.delimitedListToStringArray(scope, " ")).collect(Collectors.toSet()); + } + + Map additionalParameters = new LinkedHashMap<>(); + tokenResponseParameters.entrySet().stream() + .filter(e -> !TOKEN_RESPONSE_PARAMETER_NAMES.contains(e.getKey())) + .forEach(e -> additionalParameters.put(e.getKey(), e.getValue())); + + return OAuth2AccessTokenResponse.withToken(accessToken) + .tokenType(accessTokenType) + .expiresIn(expiresIn) + .scopes(scopes) + .additionalParameters(additionalParameters) + .build(); + } + + public final void setDefaultAccessTokenType(OAuth2AccessToken.TokenType defaultAccessTokenType) { + Assert.notNull(defaultAccessTokenType, "defaultAccessTokenType cannot be null"); + this.defaultAccessTokenType = defaultAccessTokenType; + } +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsManager.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsManager.java new file mode 100644 index 0000000..f578999 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsManager.java @@ -0,0 +1,47 @@ +package com.coderstower.socialmediapubisher.springpublisher.abstraction.security; + +import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2Credentials; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2CredentialsRepository; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; + +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.Map; +import java.util.Optional; + +public class OAuth2CredentialsManager { + private final OAuth2CredentialsRepository oAuth2CredentialsRepository; + private final Map principalNamesAllowed; + + public OAuth2CredentialsManager(OAuth2CredentialsRepository oAuth2CredentialsRepository, Map principalNamesAllowed) { + this.oAuth2CredentialsRepository = oAuth2CredentialsRepository; + this.principalNamesAllowed = principalNamesAllowed; + } + + public OAuth2Credentials update(OAuth2AuthorizedClient authorizedClient, String socialAccount) { + if (isNotAllowed(authorizedClient, socialAccount)) { + throw new UnauthorizedException(); + } + + OAuth2Credentials credentials = oAuth2CredentialsRepository.getCredentials(socialAccount) + .orElseGet(() -> oAuth2CredentialsRepository.save(OAuth2Credentials.builder() + .id(socialAccount) + .build())); + + OAuth2Credentials updatedCredentials = credentials.update( + authorizedClient.getAccessToken().getTokenValue(), + LocalDateTime.ofInstant( + authorizedClient.getAccessToken().getExpiresAt(), + ZoneOffset.UTC) + ); + + return oAuth2CredentialsRepository.update(updatedCredentials); + } + + private boolean isNotAllowed(OAuth2AuthorizedClient authorizedClient, String socialAccount) { + return Optional.ofNullable(authorizedClient) + .map(OAuth2AuthorizedClient::getPrincipalName) + .filter(principalName -> principalName.equals(principalNamesAllowed.get(socialAccount))) + .isEmpty(); + } +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsUpdated.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsUpdated.java new file mode 100644 index 0000000..e977097 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsUpdated.java @@ -0,0 +1,11 @@ +package com.coderstower.socialmediapubisher.springpublisher.abstraction.security; + +import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2Credentials; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class OAuth2CredentialsUpdated { + private final OAuth2Credentials oAuth2Credentials; +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/UnauthorizedException.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/UnauthorizedException.java new file mode 100644 index 0000000..840d1ef --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/UnauthorizedException.java @@ -0,0 +1,10 @@ +package com.coderstower.socialmediapubisher.springpublisher.abstraction.security; + +public class UnauthorizedException extends RuntimeException { + public UnauthorizedException() { + } + + public UnauthorizedException(String message) { + super(message); + } +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth1Credentials.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth1Credentials.java new file mode 100644 index 0000000..44fb499 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth1Credentials.java @@ -0,0 +1,14 @@ +package com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class OAuth1Credentials { + private final String id; + private final String consumerKey; + private final String consumerSecret; + private final String accessToken; + private final String tokenSecret; +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth1CredentialsRepository.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth1CredentialsRepository.java new file mode 100644 index 0000000..1a9cee7 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth1CredentialsRepository.java @@ -0,0 +1,7 @@ +package com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository; + +import java.util.Optional; + +public interface OAuth1CredentialsRepository { + Optional getCredentials(String id); +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth2Credentials.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth2Credentials.java new file mode 100644 index 0000000..3362ff5 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth2Credentials.java @@ -0,0 +1,24 @@ +package com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository; + +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Data +@Builder +public class OAuth2Credentials { + private final String id; + private final String accessToken; + private final LocalDateTime expirationDate; + private final List allowedGroups; + + public OAuth2Credentials update(String accessToken, LocalDateTime expirationDate){ + return OAuth2Credentials.builder() + .id(id) + .accessToken(accessToken) + .expirationDate(expirationDate) + .build(); + } +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth2CredentialsRepository.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth2CredentialsRepository.java new file mode 100644 index 0000000..be73c19 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth2CredentialsRepository.java @@ -0,0 +1,13 @@ +package com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository; + +import java.util.List; +import java.util.Optional; + +public interface OAuth2CredentialsRepository { + OAuth2Credentials save(OAuth2Credentials oAuth2Credentials); + + OAuth2Credentials update(OAuth2Credentials oAuth2Credentials); + + Optional getCredentials(String id); + List findAll(); +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/SpringPublisherApplication.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/SpringPublisherApplication.java new file mode 100644 index 0000000..822ed8a --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/SpringPublisherApplication.java @@ -0,0 +1,15 @@ +package com.coderstower.socialmediapubisher.springpublisher.main; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringPublisherApplication { + + public static void main(String[] args) { + SpringApplication + .run(SpringPublisherApplication.class, + args); + } + +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/AWSSpringPublisherApplication.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/AWSSpringPublisherApplication.java new file mode 100644 index 0000000..f90a8fe --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/AWSSpringPublisherApplication.java @@ -0,0 +1,60 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.aws; + +import com.coderstower.socialmediapubisher.springpublisher.main.controller.ErrorHandler; +import com.coderstower.socialmediapubisher.springpublisher.main.controller.OAuth2CredentialsController; +import com.coderstower.socialmediapubisher.springpublisher.main.controller.PostsController; +import com.coderstower.socialmediapubisher.springpublisher.main.factory.SecurityFactory; +import com.coderstower.socialmediapubisher.springpublisher.main.factory.SpringPublisherFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; +import org.springframework.context.annotation.Import; + + +@SpringBootApplication +// We use direct @Import instead of @ComponentScan to speed up cold starts +// @ComponentScan(basePackages = "my.service.controller") +@Import({PostsController.class, OAuth2CredentialsController.class, SecurityFactory.class, + SpringPublisherFactory.class, SpringPublisherDynamoDBRepositoryFactory.class, ErrorHandler.class}) +public class AWSSpringPublisherApplication extends SpringBootServletInitializer { + + /* + * Create required HandlerMapping, to avoid several default HandlerMapping instances being created + + @Bean + public HandlerMapping handlerMapping() { + return new RequestMappingHandlerMapping(); + }*/ + + /* + * Create required HandlerAdapter, to avoid several default HandlerAdapter instances being created + + @Bean + public HandlerAdapter handlerAdapter() { + return new RequestMappingHandlerAdapter(); + }*/ + + /* + * optimization - avoids creating default exception resolvers; not required as the serverless container handles + * all exceptions + * + * By default, an ExceptionHandlerExceptionResolver is created which creates many dependent object, including + * an expensive ObjectMapper instance. + * + * To enable custom @ControllerAdvice classes remove this bean. + */ + /*@Bean + public HandlerExceptionResolver handlerExceptionResolver() { + return new HandlerExceptionResolver() { + + @Override + public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { + return null; + } + }; + }*/ + + public static void main(String[] args) { + SpringApplication.run(AWSSpringPublisherApplication.class, args); + } +} \ No newline at end of file diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/SpringPublisherDynamoDBRepositoryFactory.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/SpringPublisherDynamoDBRepositoryFactory.java new file mode 100644 index 0000000..fc378ec --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/SpringPublisherDynamoDBRepositoryFactory.java @@ -0,0 +1,64 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.aws; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.client.builder.AwsClientBuilder; +import com.amazonaws.regions.Regions; +import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; +import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder; +import com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth1.OAuth1CredentialAWSRepository; +import com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth1.OAuth1CredentialDynamoRepository; +import com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth2.OAuth2CredentialAWSRepository; +import com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth2.OAuth2CredentialDynamoRepository; +import com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.post.PostAWSRepository; +import com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.post.PostDynamoRepository; +import org.socialsignin.spring.data.dynamodb.repository.config.EnableDynamoDBRepositories; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +@Configuration +@EnableDynamoDBRepositories + (basePackages = "com.coderstower.socialmediapubisher.springpublisher.main.aws.repository") +public class SpringPublisherDynamoDBRepositoryFactory { + @Bean("amazonDynamoDB") + @Profile("!local") + public AmazonDynamoDB amazonDynamoDB() { + return AmazonDynamoDBClientBuilder + .standard() + .withRegion(Regions.US_EAST_1) + .build(); + } + + @Bean("amazonDynamoDB") + @Profile("local") + public AmazonDynamoDB amazonDynamoDBLocal(@Value("${amazon.dynamodb.secretkey}") String amazonAWSSecretKey, + @Value("${amazon.dynamodb.accesskey}") String amazonAWSAccessKey, + @Value("${amazon.dynamodb.endpoint}") String amazonAWSEndpoint) { + return AmazonDynamoDBClientBuilder.standard() + .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(amazonAWSEndpoint, Regions.US_EAST_1.getName())) + .withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(amazonAWSAccessKey, amazonAWSSecretKey))) + .build(); + } + + @Bean + public OAuth1CredentialAWSRepository oauth1CredentialAWSRepository( + OAuth1CredentialDynamoRepository oauth1CredentialDynamoRepository) { + return new OAuth1CredentialAWSRepository( + oauth1CredentialDynamoRepository); + } + + @Bean + public OAuth2CredentialAWSRepository oauth2CredentialAWSRepository( + OAuth2CredentialDynamoRepository oauth2CredentialDynamoRepository) { + return new OAuth2CredentialAWSRepository( + oauth2CredentialDynamoRepository); + } + + @Bean + public PostAWSRepository postAWSRepository( + PostDynamoRepository postDynamoRepository) { + return new PostAWSRepository(postDynamoRepository); + } +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/StreamLambdaHandler.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/StreamLambdaHandler.java new file mode 100644 index 0000000..bbf1798 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/StreamLambdaHandler.java @@ -0,0 +1,33 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.aws; + + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + + +public class StreamLambdaHandler implements RequestStreamHandler { + private static SpringBootLambdaContainerHandler handler; + static { + try { + handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(AWSSpringPublisherApplication.class); + } catch (ContainerInitializationException e) { + // if we fail here. We re-throw the exception to force another cold start + e.printStackTrace(); + throw new RuntimeException("Could not initialize Spring Boot application", e); + } + } + + @Override + public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) + throws IOException { + handler.proxyStream(inputStream, outputStream, context); + } +} \ No newline at end of file diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/LocalDateTimeConverter.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/LocalDateTimeConverter.java new file mode 100644 index 0000000..8b22cf5 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/LocalDateTimeConverter.java @@ -0,0 +1,17 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository; + +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverter; + +import java.time.LocalDateTime; + +public class LocalDateTimeConverter implements DynamoDBTypeConverter { + @Override + public String convert(final LocalDateTime time) { + return time.toString(); + } + + @Override + public LocalDateTime unconvert(final String stringValue) { + return LocalDateTime.parse(stringValue); + } +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/URLConverter.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/URLConverter.java new file mode 100644 index 0000000..2bcb44b --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/URLConverter.java @@ -0,0 +1,23 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository; + +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverter; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; + +public class URLConverter implements DynamoDBTypeConverter { + @Override + public String convert(final URL url) { + return url.toString(); + } + + @Override + public URL unconvert(final String stringValue) { + try { + return URI.create(stringValue).toURL(); + } catch (MalformedURLException e) { + throw new IllegalArgumentException(e); + } + } +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepository.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepository.java new file mode 100644 index 0000000..b0d28c3 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepository.java @@ -0,0 +1,29 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth1; + +import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth1Credentials; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth1CredentialsRepository; + +import java.util.Optional; + +public class OAuth1CredentialAWSRepository implements OAuth1CredentialsRepository { + private final OAuth1CredentialDynamoRepository oauth1CredentialDynamoRepository; + + public OAuth1CredentialAWSRepository(OAuth1CredentialDynamoRepository oauth1CredentialDynamoRepository) { + this.oauth1CredentialDynamoRepository = oauth1CredentialDynamoRepository; + } + + @Override + public Optional getCredentials(String id) { + return oauth1CredentialDynamoRepository.findById(id).map(this::convert); + } + + private OAuth1Credentials convert(OAuth1CredentialDynamo oauth1CredentialDynamo) { + return OAuth1Credentials.builder() + .accessToken(oauth1CredentialDynamo.getAccessToken()) + .consumerKey(oauth1CredentialDynamo.getConsumerKey()) + .consumerSecret(oauth1CredentialDynamo.getConsumerSecret()) + .id(oauth1CredentialDynamo.getId()) + .tokenSecret(oauth1CredentialDynamo.getTokenSecret()) + .build(); + } +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialDynamo.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialDynamo.java new file mode 100644 index 0000000..d870fc8 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialDynamo.java @@ -0,0 +1,27 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth1; + +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute; +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey; +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@DynamoDBTable(tableName = "Oauth1Credentials") +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class OAuth1CredentialDynamo { + @DynamoDBHashKey + private String id; + @DynamoDBAttribute + private String consumerKey; + @DynamoDBAttribute + private String consumerSecret; + @DynamoDBAttribute + private String accessToken; + @DynamoDBAttribute + private String tokenSecret; +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialDynamoRepository.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialDynamoRepository.java new file mode 100644 index 0000000..c9723ee --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialDynamoRepository.java @@ -0,0 +1,8 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth1; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface OAuth1CredentialDynamoRepository extends CrudRepository { +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepository.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepository.java new file mode 100644 index 0000000..4cc4340 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepository.java @@ -0,0 +1,57 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth2; + +import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2Credentials; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2CredentialsRepository; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +public class OAuth2CredentialAWSRepository implements OAuth2CredentialsRepository { + private final OAuth2CredentialDynamoRepository oauth2CredentialDynamoRepository; + + public OAuth2CredentialAWSRepository(OAuth2CredentialDynamoRepository oauth2CredentialDynamoRepository) { + this.oauth2CredentialDynamoRepository = oauth2CredentialDynamoRepository; + } + + @Override + public OAuth2Credentials save(OAuth2Credentials oAuth2Credentials) { + return convert(oauth2CredentialDynamoRepository.save(convert(oAuth2Credentials))); + } + + @Override + public OAuth2Credentials update(OAuth2Credentials oAuth2Credentials) { + return convert(oauth2CredentialDynamoRepository.save(convert(oAuth2Credentials))); + } + + @Override + public Optional getCredentials(String id) { + return oauth2CredentialDynamoRepository.findById(id).map(this::convert); + } + + @Override + public List findAll() { + return StreamSupport.stream(oauth2CredentialDynamoRepository.findAll().spliterator(), false) + .map(this::convert) + .collect(Collectors.toList()); + } + + private OAuth2CredentialDynamo convert(OAuth2Credentials oAuth2Credentials) { + return OAuth2CredentialDynamo.builder() + .accessToken(oAuth2Credentials.getAccessToken()) + .id(oAuth2Credentials.getId()) + .expirationDate(oAuth2Credentials.getExpirationDate()) + .allowedGroups(oAuth2Credentials.getAllowedGroups()) + .build(); + } + + private OAuth2Credentials convert(OAuth2CredentialDynamo oauth2CredentialDynamo) { + return OAuth2Credentials.builder() + .accessToken(oauth2CredentialDynamo.getAccessToken()) + .id(oauth2CredentialDynamo.getId()) + .expirationDate(oauth2CredentialDynamo.getExpirationDate()) + .allowedGroups(oauth2CredentialDynamo.getAllowedGroups()) + .build(); + } +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialDynamo.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialDynamo.java new file mode 100644 index 0000000..9932991 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialDynamo.java @@ -0,0 +1,31 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth2; + +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute; +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey; +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable; +import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverted; +import com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.LocalDateTimeConverter; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.List; + +@Data +@DynamoDBTable(tableName = "Oauth2Credentials") +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class OAuth2CredentialDynamo { + @DynamoDBHashKey + private String id; + @DynamoDBAttribute + private String accessToken; + @DynamoDBAttribute + @DynamoDBTypeConverted(converter = LocalDateTimeConverter.class) + private LocalDateTime expirationDate; + @DynamoDBAttribute + private List allowedGroups; +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialDynamoRepository.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialDynamoRepository.java new file mode 100644 index 0000000..84acf5a --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialDynamoRepository.java @@ -0,0 +1,8 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth2; + +import org.socialsignin.spring.data.dynamodb.repository.EnableScan; +import org.springframework.data.repository.CrudRepository; + +@EnableScan +public interface OAuth2CredentialDynamoRepository extends CrudRepository { +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostAWSRepository.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostAWSRepository.java new file mode 100644 index 0000000..9f0a83a --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostAWSRepository.java @@ -0,0 +1,52 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.post; + +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.PostRepository; + +import java.util.Comparator; +import java.util.Optional; +import java.util.stream.StreamSupport; + +public class PostAWSRepository implements PostRepository { + private final PostDynamoRepository postDynamoRepository; + + public PostAWSRepository(PostDynamoRepository postDynamoRepository) { + this.postDynamoRepository = postDynamoRepository; + } + + @Override + public Optional getNextToPublish(String group) { + return StreamSupport.stream(postDynamoRepository.findAll().spliterator(), false) + .filter(postDynamo -> group.equals(postDynamo.getGroup())) + .min(Comparator.comparing(PostDynamo::getPublishedDate)) + .map(this::convert); + } + + @Override + public Post update(Post post) { + return convert(postDynamoRepository.save(convert(post))); + } + + private PostDynamo convert(Post post) { + return PostDynamo.builder() + .id(post.getId()) + .description(post.getDescription()) + .publishedDate(post.getPublishedDate()) + .name(post.getName()) + .tags(post.getTags()) + .url(post.getUrl()) + .group(post.getGroup()) + .build(); + } + + private Post convert(PostDynamo postDynamo) { + return Post.builder() + .id(postDynamo.getId()) + .description(postDynamo.getDescription()) + .publishedDate(postDynamo.getPublishedDate()) + .name(postDynamo.getName()) + .tags(postDynamo.getTags()) + .group(postDynamo.getGroup()) + .url(postDynamo.getUrl()).build(); + } +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostDynamo.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostDynamo.java new file mode 100644 index 0000000..bf5c02d --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostDynamo.java @@ -0,0 +1,38 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.post; + +import com.amazonaws.services.dynamodbv2.datamodeling.*; +import com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.LocalDateTimeConverter; +import com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.URLConverter; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.net.URL; +import java.time.LocalDateTime; +import java.util.List; + +@Data +@DynamoDBTable(tableName = "Posts") +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PostDynamo { + @DynamoDBHashKey + @DynamoDBAutoGeneratedKey + private String id; + @DynamoDBAttribute + private String name; + @DynamoDBAttribute + private String description; + @DynamoDBAttribute + private List tags; + @DynamoDBAttribute + @DynamoDBTypeConverted(converter = URLConverter.class) + private URL url; + @DynamoDBAttribute + @DynamoDBTypeConverted(converter = LocalDateTimeConverter.class) + private LocalDateTime publishedDate; + @DynamoDBAttribute + private String group; +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostDynamoRepository.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostDynamoRepository.java new file mode 100644 index 0000000..88ded39 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostDynamoRepository.java @@ -0,0 +1,9 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.post; + +import org.socialsignin.spring.data.dynamodb.repository.EnableScan; +import org.springframework.data.repository.CrudRepository; + +@EnableScan +public interface PostDynamoRepository extends + CrudRepository { +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/ErrorHandler.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/ErrorHandler.java new file mode 100644 index 0000000..51d8e31 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/ErrorHandler.java @@ -0,0 +1,14 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.controller; + +import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.UnauthorizedException; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@ControllerAdvice +public class ErrorHandler { + @ExceptionHandler(UnauthorizedException.class) + public ResponseEntity unauthorized(UnauthorizedException ex) { + return ResponseEntity.status(401).body(ex.getMessage()); + } +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/OAuth2CredentialsController.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/OAuth2CredentialsController.java new file mode 100644 index 0000000..3d15ea2 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/OAuth2CredentialsController.java @@ -0,0 +1,33 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.controller; + + +import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.OAuth2CredentialsManager; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; +import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@Slf4j +public class OAuth2CredentialsController { + private final OAuth2CredentialsManager oAuth2CredentialsManager; + + public OAuth2CredentialsController(OAuth2CredentialsManager oAuth2CredentialsManager) { + this.oAuth2CredentialsManager = oAuth2CredentialsManager; + } + + /** + * Endpoint secured to redirect to the right authorization provider. + * Needs to be a GET to allow access from the browser and the whole 0Auth2 flow + */ + @RequestMapping(path = "/oauth2/{socialAccount}/credentials", method = RequestMethod.GET) + public ResponseEntity updateOAuth2Credentials(@RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient, @PathVariable String socialAccount) { + oAuth2CredentialsManager.update(authorizedClient, socialAccount); + + return ResponseEntity.ok("Credentials updated"); + } +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/PostsController.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/PostsController.java new file mode 100644 index 0000000..6201df2 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/PostsController.java @@ -0,0 +1,42 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.controller; + + +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.PostPublisher; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +@RestController +@Slf4j +public class PostsController { + private final PostPublisher postPublisher; + + public PostsController(PostPublisher postPublisher) { + this.postPublisher = postPublisher; + } + + @RequestMapping(path = "/ping", method = RequestMethod.GET) + public Map ping() { + Map pong = new HashMap<>(); + pong.put("pong", "Hello, World!"); + + log.info("Pong: {}", pong); + + return pong; + } + + @RequestMapping(path = "/posts/{group}/next", method = RequestMethod.POST) + public Post postNext(@PathVariable String group) { + Post post = postPublisher.publishNext(group); + + log.info("Published Post from group {}: {}", group, post); + + return post; + } +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/CredentialsProperties.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/CredentialsProperties.java new file mode 100644 index 0000000..767d86f --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/CredentialsProperties.java @@ -0,0 +1,13 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.factory; + +import lombok.Builder; +import lombok.Data; +import org.springframework.boot.context.properties.ConstructorBinding; +import org.springframework.web.util.UriTemplate; + +@Data +@ConstructorBinding +@Builder +public class CredentialsProperties { + private final UriTemplate loginUrl; +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SecurityFactory.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SecurityFactory.java new file mode 100644 index 0000000..852d93b --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SecurityFactory.java @@ -0,0 +1,83 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.factory; + +import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.OAuth2AccessTokenResponseConverterWithDefaults; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.core.annotation.Order; +import org.springframework.http.converter.FormHttpMessageConverter; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient; +import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; +import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; +import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; +import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; +import org.springframework.web.client.RestTemplate; + +import java.util.Arrays; + +@Configuration +@EnableWebSecurity +public class SecurityFactory { + /** + * Creating individual security by social network. If we generalize, + * Spring Security shows a list of social networks to login with. + */ + @Order(1) + @Profile("secure") + @Configuration + public static class LinkedinSecurity extends WebSecurityConfigurerAdapter { + private final ClientRegistrationRepository clientRegistrationRepository; + + public LinkedinSecurity(ClientRegistrationRepository clientRegistrationRepository) { + this.clientRegistrationRepository = clientRegistrationRepository; + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .antMatchers("/oauth2/linkedin/credentials").authenticated() + .and() + .oauth2Login() + /* + Create a new ClientRegistrationRepository with only Linkedin configuration to avoid + using other OAuth2 configuration over this endpoint + */ + .clientRegistrationRepository(new InMemoryClientRegistrationRepository(clientRegistrationRepository.findByRegistrationId("linkedin"))) + .tokenEndpoint() + .accessTokenResponseClient(authorizationCodeTokenResponseClient()); + } + + private OAuth2AccessTokenResponseClient authorizationCodeTokenResponseClient() { + OAuth2AccessTokenResponseHttpMessageConverter tokenResponseHttpMessageConverter = + new OAuth2AccessTokenResponseHttpMessageConverter(); + tokenResponseHttpMessageConverter.setTokenResponseConverter(new OAuth2AccessTokenResponseConverterWithDefaults()); + + RestTemplate restTemplate = new RestTemplate(Arrays.asList( + new FormHttpMessageConverter(), tokenResponseHttpMessageConverter)); + restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); + + DefaultAuthorizationCodeTokenResponseClient tokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient(); + tokenResponseClient.setRestOperations(restTemplate); + + return tokenResponseClient; + } + } + + @Order(2) + @Configuration + public static class NoSecurity extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .csrf().disable() + .authorizeRequests() + .antMatchers("/**") + .permitAll(); + } + } +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SocialMediaPublisherProperties.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SocialMediaPublisherProperties.java new file mode 100644 index 0000000..a47c283 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SocialMediaPublisherProperties.java @@ -0,0 +1,17 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.factory; + +import lombok.Builder; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.ConstructorBinding; + +import java.util.Map; + +@ConfigurationProperties(prefix = "social-media-publisher") +@Data +@ConstructorBinding +@Builder +public class SocialMediaPublisherProperties { + private final Map principalNamesAllowed; + private final CredentialsProperties credentials; +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SpringPublisherFactory.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SpringPublisherFactory.java new file mode 100644 index 0000000..0381f89 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SpringPublisherFactory.java @@ -0,0 +1,62 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.factory; + +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.PostPublisher; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.PostRepository; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.SocialMediaPublisher; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.OAuth2CredentialsManager; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth1CredentialsRepository; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2CredentialsRepository; +import com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin.LinkedInPublisher; +import com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.twitter.TwitterPublisher; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.web.client.RestTemplate; +import twitter4j.Twitter; +import twitter4j.TwitterFactory; + +import java.time.Clock; +import java.util.List; + + +@Configuration +@EnableConfigurationProperties({SocialMediaPublisherProperties.class}) +public class SpringPublisherFactory { + @Bean + public Clock clock() { + return Clock.systemDefaultZone(); + } + + @Bean + public PostPublisher postPublisher(List socialMediaPublishers, PostRepository postRepository, Clock clock) { + return new PostPublisher(socialMediaPublishers, postRepository, clock); + } + + @Bean + @Profile("twitter") + public TwitterPublisher twitterPublisher(OAuth1CredentialsRepository oauth1CredentialsRepository, Twitter twitter, Clock clock) { + return new TwitterPublisher("twitter", oauth1CredentialsRepository, twitter, clock); + } + + @Bean + @Profile("linkedin") + public LinkedInPublisher linkedInPublisher(OAuth2CredentialsRepository oauth2CredentialsRepository, RestTemplate restTemplate, Clock clock, SocialMediaPublisherProperties socialMediaPublisherProperties) { + return new LinkedInPublisher("linkedin", oauth2CredentialsRepository, restTemplate, clock, socialMediaPublisherProperties.getCredentials().getLoginUrl()); + } + + @Bean + public OAuth2CredentialsManager oAuth2CredentialsManager(OAuth2CredentialsRepository oAuth2CredentialsRepository, SocialMediaPublisherProperties socialMediaPublisherProperties) { + return new OAuth2CredentialsManager(oAuth2CredentialsRepository, socialMediaPublisherProperties.getPrincipalNamesAllowed()); + } + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } + + @Bean + public Twitter twitter() { + return TwitterFactory.getSingleton(); + } +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/ArticleContent.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/ArticleContent.java new file mode 100644 index 0000000..cd1d298 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/ArticleContent.java @@ -0,0 +1,12 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class ArticleContent { + private final String title; + private final String description; + private final String source; +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Content.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Content.java new file mode 100644 index 0000000..cfa8965 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Content.java @@ -0,0 +1,10 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class Content { + private final ArticleContent article; +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Distribution.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Distribution.java new file mode 100644 index 0000000..597be90 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Distribution.java @@ -0,0 +1,10 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class Distribution { + private final String feedDistribution; +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInPublisher.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInPublisher.java new file mode 100644 index 0000000..995471c --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInPublisher.java @@ -0,0 +1,172 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin; + +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Acknowledge; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Publication; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.SocialMediaPublisher; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.UnauthorizedException; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2Credentials; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2CredentialsRepository; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.*; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriTemplate; + +import java.time.Clock; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +@Slf4j +public class LinkedInPublisher implements SocialMediaPublisher { + private final String name; + private final OAuth2CredentialsRepository oauth2CredentialsRepository; + private final RestTemplate restTemplate; + private final Clock clock; + private final UriTemplate loginURL; + + public LinkedInPublisher(String name, OAuth2CredentialsRepository oauth2CredentialsRepository, RestTemplate restTemplate, Clock clock, UriTemplate loginURL) { + this.name = name; + this.oauth2CredentialsRepository = oauth2CredentialsRepository; + this.restTemplate = restTemplate; + this.clock = clock; + this.loginURL = loginURL; + } + + @Override + public String getName() { + return name; + } + + @Override + public Acknowledge ping() { + List credentials = oauth2CredentialsRepository.findAll(); + + if (credentials.isEmpty()) { + throw new IllegalArgumentException("The credentials for " + name + " doesn't exist"); + } + + for (OAuth2Credentials credential : credentials) { + if (areCredentialsExpired(credential)) { + throw new UnauthorizedException("Unauthorized for " + name + " " + credential.getId() + ". Please login again here: " + loginURL.expand(name)); + } + } + + return Acknowledge.builder() + .status(Acknowledge.Status.SUCCESS) + .build(); + } + + @Override + public List publish(Post post) { + List credentials = oauth2CredentialsRepository.findAll() + .stream() + .filter(oAuth2Credentials -> oAuth2Credentials.getAllowedGroups().contains(post.getGroup())) + .collect(Collectors.toList()); + + List publications = new ArrayList<>(); + + for (OAuth2Credentials credential : credentials) { + try { + Profile profile = getProfile(credential); + + LinkedInShare linkedInShare = LinkedInShare.builder() + .author("urn:li:person:" + profile.getSub()) + .commentary(post.basicFormatWithoutURL()) + .distribution(Distribution.builder().feedDistribution("MAIN_FEED").build()) + .lifecycleState("PUBLISHED") + .content(Content.builder() + .article(ArticleContent.builder() + .description(post.getDescription()) + .title(post.getName()) + .source(post.getUrl().toString()) + .build()) + .build()) + .visibility("PUBLIC") + .build(); + + String shareId = publish(linkedInShare, credential); + + if (Objects.nonNull(shareId)) { + publications.add(Publication.builder() + .id(shareId) + .status(Publication.Status.SUCCESS) + .publisher(name) + .publishedDate(LocalDateTime.now(clock)) + .credentialId(credential.getId()) + .build()); + } else { + publications.add(Publication.builder() + .status(Publication.Status.FAILURE) + .publisher(name) + .publishedDate(LocalDateTime.now(clock)) + .credentialId(credential.getId()) + .build()); + } + } catch (HttpClientErrorException e) { + log.error("Error publishing to " + name + ", credentials " + credential.getId() + " response body: " + e.getResponseBodyAsString(), e); + + publications.add(Publication.builder() + .status(Publication.Status.FAILURE) + .publisher(name) + .publishedDate(LocalDateTime.now(clock)) + .credentialId(credential.getId()) + .build()); + + } catch (Exception e) { + log.error("Error publishing to " + name + ", credentials " + credential.getId(), e); + + publications.add(Publication.builder() + .status(Publication.Status.FAILURE) + .publisher(name) + .publishedDate(LocalDateTime.now(clock)) + .credentialId(credential.getId()) + .build()); + } + } + return publications; + } + + private String publish(LinkedInShare linkedInShare, OAuth2Credentials credentials) { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_JSON); + httpHeaders.add("X-Restli-Protocol-Version", "2.0.0"); + httpHeaders.add("LinkedIn-Version", "202304"); + httpHeaders.setBearerAuth(credentials.getAccessToken()); + + HttpEntity requestEntity = new HttpEntity<>(linkedInShare, httpHeaders); + + ResponseEntity response = restTemplate.exchange("https://api.linkedin.com/rest/posts", HttpMethod.POST, requestEntity, Void.class); + + if (!response.getStatusCode().is2xxSuccessful()) { + throw new IllegalStateException("Problem trying to share a linkedin post: " + response.getStatusCode()); + } + + return response.getHeaders().getFirst("X-RestLi-Id"); + } + + private Profile getProfile(OAuth2Credentials credentials) { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_JSON); + httpHeaders.add("X-Restli-Protocol-Version", "2.0.0"); + httpHeaders.add("LinkedIn-Version", "202304"); + httpHeaders.setBearerAuth(credentials.getAccessToken()); + + HttpEntity requestEntity = new HttpEntity<>(httpHeaders); + + ResponseEntity response = restTemplate.exchange("https://api.linkedin.com/v2/userinfo", HttpMethod.GET, requestEntity, Profile.class); + + if (!response.getStatusCode().is2xxSuccessful()) { + throw new IllegalStateException("Problem trying to get the profile: " + response.getStatusCode()); + } + + return response.getBody(); + } + + private boolean areCredentialsExpired(OAuth2Credentials credentials) { + return credentials.getExpirationDate().isBefore(LocalDateTime.now(clock)); + } +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInShare.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInShare.java new file mode 100644 index 0000000..4cbd39a --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInShare.java @@ -0,0 +1,15 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class LinkedInShare { + private final String commentary; + private final Distribution distribution; + private final String author; + private final String lifecycleState; + private final Content content; + private final String visibility; +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Media.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Media.java new file mode 100644 index 0000000..71f30fd --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Media.java @@ -0,0 +1,13 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class Media { + private final String status; + private final Text description; + private final Text title; + private final String originalUrl; +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Profile.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Profile.java new file mode 100644 index 0000000..9282df0 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Profile.java @@ -0,0 +1,14 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Profile { + private String sub; +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Text.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Text.java new file mode 100644 index 0000000..7d48e12 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Text.java @@ -0,0 +1,10 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class Text { + private final String text; +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/twitter/TwitterPublisher.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/twitter/TwitterPublisher.java new file mode 100644 index 0000000..c039bcb --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/twitter/TwitterPublisher.java @@ -0,0 +1,120 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.twitter; + +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Acknowledge; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Publication; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.SocialMediaPublisher; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth1Credentials; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth1CredentialsRepository; +import lombok.extern.slf4j.Slf4j; +import twitter4j.Paging; +import twitter4j.Status; +import twitter4j.Twitter; +import twitter4j.TwitterException; +import twitter4j.auth.AccessToken; +import twitter4j.auth.NullAuthorization; + +import java.time.Clock; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Objects; + +@Slf4j +public class TwitterPublisher implements SocialMediaPublisher { + private final String name; + private final OAuth1CredentialsRepository oauth1CredentialsRepository; + private final Twitter twitter; + private final Clock clock; + + public TwitterPublisher(String name, OAuth1CredentialsRepository oauth1CredentialsRepository, Twitter twitter, Clock clock) { + this.name = name; + this.oauth1CredentialsRepository = oauth1CredentialsRepository; + this.twitter = twitter; + this.clock = clock; + } + + @Override + public String getName() { + return name; + } + + @Override + public Acknowledge ping() { + OAuth1Credentials credentials = oauth1CredentialsRepository.getCredentials(name) + .orElseThrow(() -> new IllegalArgumentException("The credentials for " + name + " doesn't exist")); + + try { + if (areNotCredentialsReady(twitter)) { + setCredentials(twitter, credentials); + } + + Paging paging = new Paging(1, 1); + List statuses = twitter.getHomeTimeline(paging); + + if (Objects.nonNull(statuses)) { + return Acknowledge.builder() + .status(Acknowledge.Status.SUCCESS) + .build(); + } else { + return Acknowledge.builder() + .status(Acknowledge.Status.FAILURE) + .build(); + } + } catch (TwitterException e) { + log.error("Error ping to " + name, e); + + return Acknowledge.builder() + .description("Ping error") + .status(Acknowledge.Status.FAILURE) + .exception(e) + .build(); + } + } + + @Override + public List publish(Post post) { + OAuth1Credentials credentials = oauth1CredentialsRepository.getCredentials(name) + .orElseThrow(() -> new IllegalArgumentException("The credentials for " + name + " doesn't exist")); + + try { + if (areNotCredentialsReady(twitter)) { + setCredentials(twitter, credentials); + } + + Status statuses = twitter.updateStatus(post.basicFormat()); + + if (Objects.nonNull(statuses)) { + return List.of(Publication.builder() + .id(String.valueOf(statuses.getId())) + .status(Publication.Status.SUCCESS) + .publisher(name) + .publishedDate(LocalDateTime.now(clock)) + .build()); + } else { + return List.of(Publication.builder() + .status(Publication.Status.FAILURE) + .publisher(name) + .publishedDate(LocalDateTime.now(clock)) + .build()); + } + } catch (TwitterException e) { + log.error("Error publishing to " + name, e); + + return List.of(Publication.builder() + .status(Publication.Status.FAILURE) + .publisher(name) + .publishedDate(LocalDateTime.now(clock)) + .build()); + } + } + + private void setCredentials(Twitter twitter, OAuth1Credentials credentials) { + AccessToken accessToken = new AccessToken(credentials.getAccessToken(), credentials.getTokenSecret()); + twitter.setOAuthConsumer(credentials.getConsumerKey(), credentials.getConsumerSecret()); + twitter.setOAuthAccessToken(accessToken); + } + + private boolean areNotCredentialsReady(Twitter twitter) throws TwitterException { + return twitter.getAuthorization() == NullAuthorization.getInstance(); + } +} diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml new file mode 100644 index 0000000..ea1e022 --- /dev/null +++ b/src/main/resources/application-local.yml @@ -0,0 +1,15 @@ +# Reduce logging level to make sure the application works with SAM local +# https://github.com/awslabs/aws-serverless-java-container/issues/134 +#logging: +# level: +# root: WARN + +amazon: + dynamodb: + endpoint: http://localhost:8000/ + accesskey: key + secretkey: key2 + +spring: + main: + allow-bean-definition-overriding: true \ No newline at end of file diff --git a/src/main/resources/application-secure.yml b/src/main/resources/application-secure.yml new file mode 100644 index 0000000..148018a --- /dev/null +++ b/src/main/resources/application-secure.yml @@ -0,0 +1,32 @@ +social-media-publisher: + principal-names-allowed: + linkedin: xxx + +amazon: + dynamodb: + accesskey: xxx + secretkey: xxx + +spring: + security: + oauth2: + client: + registration: + linkedin: + client-id: xxx + client-secret: xxx + client-authentication-method: post + authorization-grant-type: authorization_code + redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}' + scope: + - openid + - profile + - email + - w_member_social + provider: + linkedin: + authorization-uri: https://www.linkedin.com/oauth/v2/authorization + token-uri: https://www.linkedin.com/oauth/v2/accessToken + user-info-uri: https://api.linkedin.com/v2/userinfo + user-name-attribute: id + jwk-set-uri: https://www.linkedin.com/oauth/openid/jwks \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..2f70249 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,14 @@ +# Reduce logging level to make sure the application works with SAM local +# https://github.com/awslabs/aws-serverless-java-container/issues/134 +#logging: +# level: +# root: WARN + +social-media-publisher: + credentials: + login-host: 'http://localhost:8080' + login-url: '${social-media-publisher.credentials.login-host}/oauth2/{social-media-id}/credentials' + +spring: + main: + allow-bean-definition-overriding: true \ No newline at end of file diff --git a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/PostPublisherTest.java b/src/test/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/PostPublisherTest.java new file mode 100644 index 0000000..01e1c46 --- /dev/null +++ b/src/test/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/PostPublisherTest.java @@ -0,0 +1,195 @@ +package com.coderstower.socialmediapubisher.springpublisher.abstraction.post; + +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.PostRepository; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Acknowledge; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Publication; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.SocialMediaPublisher; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.net.MalformedURLException; +import java.net.URI; +import java.time.Clock; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class PostPublisherTest { + @Mock + private SocialMediaPublisher socialMediaPublisher1; + @Mock + private SocialMediaPublisher socialMediaPublisher2; + @Mock + private PostRepository postRepository; + private PostPublisher postPublisher; + + @BeforeEach + public void before() { + this.postPublisher = new PostPublisher(List.of(socialMediaPublisher1, socialMediaPublisher2), postRepository, Clock + .fixed(ZonedDateTime.of(2020, 3, 3, 5, 6, 8, 1, ZoneId.of("UTC")).toInstant(), ZoneId.of("UTC"))); + } + + @Test + public void publishNext_pingFailed_exception() { + when(socialMediaPublisher1.ping()).thenReturn(Acknowledge.builder() + .status(Acknowledge.Status.SUCCESS).build()); + when(socialMediaPublisher2.ping()).thenReturn(Acknowledge.builder() + .status(Acknowledge.Status.FAILURE).build()); + when(socialMediaPublisher2.getName()).thenReturn("LINKEDIN"); + + IllegalStateException exception = assertThrows(IllegalStateException.class, () -> postPublisher.publishNext("group1")); + + assertThat(exception.getMessage()).isEqualTo("Ping to LINKEDIN failed: " + Acknowledge.builder() + .status(Acknowledge.Status.FAILURE).build()); + } + + @Test + public void publishNext_noPost_exception() { + when(postRepository.getNextToPublish("group1")).thenReturn(Optional.empty()); + when(socialMediaPublisher1.ping()).thenReturn(Acknowledge.builder() + .status(Acknowledge.Status.SUCCESS).build()); + when(socialMediaPublisher2.ping()).thenReturn(Acknowledge.builder() + .status(Acknowledge.Status.SUCCESS).build()); + + IllegalStateException exception = assertThrows(IllegalStateException.class, () -> postPublisher.publishNext("group1")); + + assertThat(exception.getMessage()).isEqualTo("There is not next post to publish"); + } + + @Test + public void publishNext_publishFailed_exception() throws MalformedURLException { + Post post = Post.builder() + .id("1") + .description("Post 1") + .name("Post") + .publishedDate(LocalDateTime.of(2020, 2, 3, 5, 6, 8, 1)) + .tags(List.of("tag1", "tag2")) + .url(URI.create("https://myblog/post").toURL()) + .group("group1") + .build(); + + Post expectedPost = Post.builder() + .id("1") + .description("Post 1") + .name("Post") + .publishedDate(LocalDateTime.of(2020, 2, 3, 5, 6, 8, 1)) + .tags(List.of("tag1", "tag2")) + .url(URI.create("https://myblog/post").toURL()) + .publications(List + .of(Publication.builder() + .id("id") + .publishedDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) + .status(Publication.Status.SUCCESS) + .publisher("TWITTER") + .build(), + Publication.builder() + .publishedDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) + .status(Publication.Status.FAILURE) + .publisher("LINKEDIN") + .build())) + .group("group1") + .build(); + + when(postRepository.getNextToPublish("group1")).thenReturn(Optional.of(post)); + when(socialMediaPublisher1.ping()).thenReturn(Acknowledge.builder() + .status(Acknowledge.Status.SUCCESS).build()); + when(socialMediaPublisher1.publish(post)).thenReturn(List.of(Publication.builder() + .id("id") + .publishedDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) + .status(Publication.Status.SUCCESS) + .publisher("TWITTER") + .build())); + when(socialMediaPublisher2.ping()).thenReturn(Acknowledge.builder() + .status(Acknowledge.Status.SUCCESS).build()); + when(socialMediaPublisher2.publish(post)).thenReturn(List.of(Publication.builder() + .publishedDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) + .status(Publication.Status.FAILURE) + .publisher("LINKEDIN") + .build())); + + IllegalStateException exception = assertThrows(IllegalStateException.class, () -> postPublisher.publishNext("group1")); + + assertThat(exception.getMessage()).isEqualTo("Error publishing the post: " + expectedPost); + } + + @Test + public void publishNext_publishSuccess_publications() throws MalformedURLException { + Post post = Post.builder() + .id("1") + .description("Post 1") + .name("Post") + .publishedDate(LocalDateTime.of(2020, 2, 3, 5, 6, 8, 1)) + .tags(List.of("tag1", "tag2")) + .url(URI.create("https://myblog/post").toURL()) + .group("group1") + .build(); + + Post postUpdated = Post.builder() + .id("1") + .description("Post 1") + .name("Post") + .publishedDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) + .tags(List.of("tag1", "tag2")) + .url(URI.create("https://myblog/post").toURL()) + .group("group1") + .build(); + + Post expectedPost = Post.builder() + .id("1") + .description("Post 1") + .name("Post") + .publishedDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) + .tags(List.of("tag1", "tag2")) + .url(URI.create("https://myblog/post").toURL()) + .group("group1") + .publications(List + .of(Publication.builder() + .id("id") + .publishedDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) + .status(Publication.Status.SUCCESS) + .publisher("TWITTER") + .build(), + Publication.builder() + .id("id") + .publishedDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) + .status(Publication.Status.SUCCESS) + .publisher("LINKEDIN") + .build())) + .build(); + + when(postRepository.getNextToPublish("group1")).thenReturn(Optional.of(post)); + when(socialMediaPublisher1.ping()).thenReturn(Acknowledge.builder() + .status(Acknowledge.Status.SUCCESS).build()); + when(socialMediaPublisher1.publish(post)).thenReturn(List.of(Publication.builder() + .id("id") + .publishedDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) + .status(Publication.Status.SUCCESS) + .publisher("TWITTER") + .build())); + when(socialMediaPublisher2.ping()).thenReturn(Acknowledge.builder() + .status(Acknowledge.Status.SUCCESS).build()); + when(socialMediaPublisher2.publish(post)).thenReturn(List.of(Publication.builder() + .id("id") + .publishedDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) + .status(Publication.Status.SUCCESS) + .publisher("LINKEDIN") + .build())); + when(postRepository.update(postUpdated)).thenReturn(postUpdated); + + Post publishedPost = postPublisher.publishNext("group1"); + + assertThat(publishedPost).isEqualTo(expectedPost); + } + +} \ No newline at end of file diff --git a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsManagerTest.java b/src/test/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsManagerTest.java new file mode 100644 index 0000000..c1b728e --- /dev/null +++ b/src/test/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsManagerTest.java @@ -0,0 +1,107 @@ +package com.coderstower.socialmediapubisher.springpublisher.abstraction.security; + +import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2Credentials; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2CredentialsRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; +import org.springframework.security.oauth2.core.OAuth2AccessToken; + +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.Map; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class OAuth2CredentialsManagerTest { + @Mock + private OAuth2CredentialsRepository oAuth2CredentialsRepository; + @Mock + private OAuth2AuthorizedClient oAuth2AuthorizedClient; + + private OAuth2CredentialsManager oAuth2CredentialsManager; + + @BeforeEach + public void before() { + oAuth2CredentialsManager = new OAuth2CredentialsManager(oAuth2CredentialsRepository, Map.of("linkedin", "myuser")); + } + + @Test + public void update_notAllowed_unauthorized() { + when(oAuth2AuthorizedClient.getPrincipalName()).thenReturn("otheruser"); + + assertThrows(UnauthorizedException.class, () -> oAuth2CredentialsManager.update(oAuth2AuthorizedClient, "linkedin")); + } + + @Test + public void update_credentialsNotFound_created() { + when(oAuth2AuthorizedClient.getPrincipalName()).thenReturn("myuser"); + when(oAuth2CredentialsRepository.getCredentials("linkedin")).thenReturn(Optional.empty()); + when(oAuth2CredentialsRepository.save(OAuth2Credentials.builder() + .id("linkedin") + .build())).thenReturn(OAuth2Credentials.builder() + .id("linkedin") + .build()); + when(oAuth2AuthorizedClient.getAccessToken()).thenReturn(new OAuth2AccessToken( + OAuth2AccessToken.TokenType.BEARER, + "newAccessToken", + LocalDateTime.of(2020, 4, 3, 5, 6, 8, 1).toInstant(ZoneOffset.UTC), + LocalDateTime.of(2020, 6, 3, 5, 6, 8, 1).toInstant(ZoneOffset.UTC))); + when(oAuth2CredentialsRepository.update(OAuth2Credentials.builder() + .id("linkedin") + .accessToken("newAccessToken") + .expirationDate(LocalDateTime.of(2020, 6, 3, 5, 6, 8, 1)) + .build())).thenReturn(OAuth2Credentials.builder() + .id("linkedin") + .accessToken("newAccessToken") + .expirationDate(LocalDateTime.of(2020, 6, 3, 5, 6, 8, 1)) + .build()); + + OAuth2Credentials credentials = oAuth2CredentialsManager.update(oAuth2AuthorizedClient, "linkedin"); + + assertThat(credentials).isEqualTo(OAuth2Credentials.builder() + .id("linkedin") + .accessToken("newAccessToken") + .expirationDate(LocalDateTime.of(2020, 6, 3, 5, 6, 8, 1)) + .build()); + } + + @Test + public void update_credentialsFound_updated() { + when(oAuth2AuthorizedClient.getPrincipalName()).thenReturn("myuser"); + when(oAuth2CredentialsRepository.getCredentials("linkedin")).thenReturn(Optional.of(OAuth2Credentials.builder() + .id("linkedin") + .accessToken("previousAccessToken") + .expirationDate(LocalDateTime.of(2020, 2, 3, 5, 6, 8, 1)) + .build())); + when(oAuth2AuthorizedClient.getAccessToken()).thenReturn(new OAuth2AccessToken( + OAuth2AccessToken.TokenType.BEARER, + "newAccessToken", + LocalDateTime.of(2020, 4, 3, 5, 6, 8, 1).toInstant(ZoneOffset.UTC), + LocalDateTime.of(2020, 6, 3, 5, 6, 8, 1).toInstant(ZoneOffset.UTC))); + when(oAuth2CredentialsRepository.update(OAuth2Credentials.builder() + .id("linkedin") + .accessToken("newAccessToken") + .expirationDate(LocalDateTime.of(2020, 6, 3, 5, 6, 8, 1)) + .build())).thenReturn(OAuth2Credentials.builder() + .id("linkedin") + .accessToken("newAccessToken") + .expirationDate(LocalDateTime.of(2020, 6, 3, 5, 6, 8, 1)) + .build()); + + OAuth2Credentials credentials = oAuth2CredentialsManager.update(oAuth2AuthorizedClient, "linkedin"); + + assertThat(credentials).isEqualTo(OAuth2Credentials.builder() + .id("linkedin") + .accessToken("newAccessToken") + .expirationDate(LocalDateTime.of(2020, 6, 3, 5, 6, 8, 1)) + .build()); + } +} \ No newline at end of file diff --git a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/MockSocialMediaCredentialsHandlingTests.java b/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/MockSocialMediaCredentialsHandlingTests.java new file mode 100644 index 0000000..fecc68f --- /dev/null +++ b/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/MockSocialMediaCredentialsHandlingTests.java @@ -0,0 +1,180 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.aws; + +import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; +import com.amazonaws.services.dynamodbv2.local.embedded.DynamoDBEmbedded; +import com.amazonaws.services.dynamodbv2.model.*; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junitpioneer.jupiter.SetSystemProperty; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Bean; +import org.springframework.http.MediaType; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.core.user.DefaultOAuth2User; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.web.servlet.MockMvc; +import twitter4j.Paging; +import twitter4j.ResponseList; +import twitter4j.Twitter; +import twitter4j.TwitterException; + +import java.time.*; +import java.util.*; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@SpringBootTest +@AutoConfigureMockMvc +@SetSystemProperty(key = "sqlite4java.library.path", value = "target/native-libs") +@TestPropertySource(properties = {"social-media-publisher.principal-names-allowed.linkedin=myuser"}) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@ActiveProfiles({"linkedin", "twitter, secure"}) +class MockSocialMediaCredentialsHandlingTests { + @Autowired + private MockMvc mvc; + @MockBean + private Twitter twitter; + @MockBean + private OAuth2AuthorizedClientService authorizedClientService; + @Autowired + private ClientRegistrationRepository registrations; + + @Test + @Order(1) + void publish_credentialsExpired_unauthorizedException() throws Exception { + mockingTwitter(); + + mvc.perform(post("/posts/group1/next") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isUnauthorized()) + .andExpect(content().string("Unauthorized for linkedin credential1. Please login again here: http://localhost:8080/oauth2/linkedin/credentials")); + } + + @Test + @Order(2) + void login_noSuccessLinkedin_return302() throws Exception { + mvc.perform(get("/oauth2/linkedin/credentials") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isFound()) + .andExpect(header().string("Location", "http://localhost/oauth2/authorization/linkedin")); + } + + @Test + @Order(3) + void login_successLinkedin_updateCredentials() throws Exception { + mockingTwitter(); + + OAuth2AuthenticationToken authenticationToken = createToken(); + OAuth2AuthorizedClient authorizedClient = createAuthorizedClient(authenticationToken); + + when(this.authorizedClientService.loadAuthorizedClient(eq("linkedin"), anyString())) + .thenReturn(authorizedClient); + + mvc.perform(get("/oauth2/linkedin/credentials") + .with(authentication(authenticationToken)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + } + + private OAuth2AuthorizedClient createAuthorizedClient(OAuth2AuthenticationToken authenticationToken) { + OAuth2AccessToken accessToken = new OAuth2AccessToken( + OAuth2AccessToken.TokenType.BEARER, + "newAccessToken", + LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1).toInstant(ZoneOffset.UTC), + LocalDateTime.of(2020, 5, 3, 5, 6, 8, 1).toInstant(ZoneOffset.UTC)); + + ClientRegistration clientRegistration = this.registrations.findByRegistrationId(authenticationToken.getAuthorizedClientRegistrationId()); + return new OAuth2AuthorizedClient(clientRegistration, authenticationToken.getName(), accessToken); + } + + private OAuth2AuthenticationToken createToken() { + Set authorities = new HashSet<>(AuthorityUtils.createAuthorityList("USER")); + OAuth2User oAuth2User = new DefaultOAuth2User(authorities, Collections.singletonMap("id", "myuser"), "id"); + return new OAuth2AuthenticationToken(oAuth2User, authorities, "linkedin"); + } + + private void mockingTwitter() throws TwitterException { + Paging paging = new Paging(1, 1); + when(twitter.getHomeTimeline(paging)).thenReturn(mock(ResponseList.class)); + } + + @TestConfiguration + static class OverriddenConfiguration { + @Bean + public Clock clock() { + return Clock.fixed(ZonedDateTime.of(2020, 3, 3, 5, 6, 8, 1, ZoneId.of("UTC")).toInstant(), ZoneId.of("UTC")); + } + + @Bean + public AmazonDynamoDB amazonDynamoDB() { + AmazonDynamoDB ddb = DynamoDBEmbedded.create().amazonDynamoDB(); + + createTable(ddb, "Oauth1Credentials", "id"); + createTable(ddb, "Oauth2Credentials", "id"); + + PutItemRequest oauth2Credentials = new PutItemRequest(); + oauth2Credentials.setTableName("Oauth2Credentials"); + oauth2Credentials.addItemEntry("id", new AttributeValue("credential1")); + oauth2Credentials.addItemEntry("accessToken", new AttributeValue("access123")); + oauth2Credentials.addItemEntry("expirationDate", new AttributeValue("2020-02-01T05:05:05")); + + ddb.putItem(oauth2Credentials); + + PutItemRequest oauth1Credentials = new PutItemRequest(); + oauth1Credentials.setTableName("Oauth1Credentials"); + oauth1Credentials.addItemEntry("id", new AttributeValue("twitter")); + oauth1Credentials.addItemEntry("accessToken", new AttributeValue("access123")); + oauth1Credentials.addItemEntry("consumerKey", new AttributeValue("consumer123")); + oauth1Credentials.addItemEntry("consumerSecret", new AttributeValue("csecret123")); + oauth1Credentials.addItemEntry("tokenSecret", new AttributeValue("tsecret123")); + + ddb.putItem(oauth1Credentials); + + return ddb; + } + + private CreateTableResult createTable(AmazonDynamoDB ddb, String tableName, String hashKeyName) { + List attributeDefinitions = new ArrayList<>(); + attributeDefinitions.add(new AttributeDefinition(hashKeyName, ScalarAttributeType.S)); + + List ks = new ArrayList<>(); + ks.add(new KeySchemaElement(hashKeyName, KeyType.HASH)); + + ProvisionedThroughput provisionedthroughput = new ProvisionedThroughput(1000L, 1000L); + + CreateTableRequest request = + new CreateTableRequest() + .withTableName(tableName) + .withAttributeDefinitions(attributeDefinitions) + .withKeySchema(ks) + .withProvisionedThroughput(provisionedthroughput); + + return ddb.createTable(request); + } + } + +} diff --git a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/MockSocialMediaSuccessfulTests.java b/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/MockSocialMediaSuccessfulTests.java new file mode 100644 index 0000000..c6a8756 --- /dev/null +++ b/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/MockSocialMediaSuccessfulTests.java @@ -0,0 +1,196 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.aws; + +import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; +import com.amazonaws.services.dynamodbv2.local.embedded.DynamoDBEmbedded; +import com.amazonaws.services.dynamodbv2.model.*; +import com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin.*; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Bean; +import org.springframework.http.*; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.web.client.RestTemplate; +import twitter4j.*; + +import java.time.Clock; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@SpringBootTest +@AutoConfigureMockMvc +@SetSystemProperty(key = "sqlite4java.library.path", value = "target/native-libs") +@ActiveProfiles({"linkedin", "twitter"}) +class MockSocialMediaSuccessfulTests { + @Autowired + private MockMvc mvc; + @MockBean + private Twitter twitter; + @MockBean + private RestTemplate restTemplate; + + @Test + void ping() throws Exception { + mvc.perform(get("/ping") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.pong", is("Hello, World!"))); + } + + @Test + void publish_allSocialMedia_success() throws Exception { + mockingTwitter(); + mockingLinkedIn(); + + mvc.perform(post("/posts/group1/next") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content() + .json("{\"id\":\"2\",\"name\":\"My Post 2\",\"description\":\"My second post\",\"tags\":[\"tag1\",\"tag2\"],\"url\":\"https://coderstower.com/2020/01/13/open-close-principle-by-example/\",\"publishedDate\":\"2020-03-03T05:06:08.000000001\",\"publications\":[{\"id\":\"123\",\"status\":\"SUCCESS\",\"publisher\":\"twitter\",\"publishedDate\":\"2020-03-03T05:06:08.000000001\"},{\"id\":\"shareid\",\"status\":\"SUCCESS\",\"publisher\":\"linkedin\",\"publishedDate\":\"2020-03-03T05:06:08.000000001\"}]}")); + } + + private void mockingLinkedIn() { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_JSON); + httpHeaders.add("X-Restli-Protocol-Version", "2.0.0"); + httpHeaders.add("LinkedIn-Version", "202304"); + httpHeaders.setBearerAuth("access123"); + + HttpEntity requestMe = new HttpEntity<>(httpHeaders); + + when(restTemplate.exchange("https://api.linkedin.com/v2/userinfo", HttpMethod.GET, requestMe, Profile.class)) + .thenReturn(ResponseEntity.ok(Profile.builder() + .sub("memberid") + .build())); + + LinkedInShare linkedInShare = LinkedInShare.builder() + .author("urn:li:person:memberid") + .lifecycleState("PUBLISHED") + .commentary("My second post\n\n#tag1 #tag2") + .distribution(Distribution.builder().feedDistribution("MAIN_FEED").build()) + .lifecycleState("PUBLISHED") + .content(Content.builder() + .article(ArticleContent.builder() + .description("My second post") + .title("My Post 2") + .source("https://coderstower.com/2020/01/13/open-close-principle-by-example/") + .build()) + .build()) + .visibility("PUBLIC") + .build(); + + HttpEntity requestShare = new HttpEntity<>(linkedInShare, httpHeaders); + + when(restTemplate.exchange("https://api.linkedin.com/rest/posts", HttpMethod.POST, requestShare, Void.class)) + .thenReturn(ResponseEntity.ok() + .header("X-RestLi-Id", "shareid") + .build()); + } + + private void mockingTwitter() throws TwitterException { + Paging paging = new Paging(1, 1); + when(twitter.getHomeTimeline(paging)).thenReturn(mock(ResponseList.class)); + + Status status = mock(Status.class); + when(status.getId()).thenReturn(123L); + when(twitter.updateStatus("My second post\n\n#tag1 #tag2\n\nhttps://coderstower.com/2020/01/13/open-close-principle-by-example/")).thenReturn(status); + } + + @TestConfiguration + static class OverriddenConfiguration { + @Bean + public Clock clock() { + return Clock.fixed(ZonedDateTime.of(2020, 3, 3, 5, 6, 8, 1, ZoneId.of("UTC")).toInstant(), ZoneId.of("UTC")); + } + + @Bean + public AmazonDynamoDB amazonDynamoDB() { + AmazonDynamoDB ddb = DynamoDBEmbedded.create().amazonDynamoDB(); + + createTable(ddb, "Oauth1Credentials", "id"); + createTable(ddb, "Oauth2Credentials", "id"); + createTable(ddb, "Posts", "id"); + + PutItemRequest oauth2Credentials = new PutItemRequest(); + oauth2Credentials.setTableName("Oauth2Credentials"); + oauth2Credentials.addItemEntry("id", new AttributeValue("linkedin")); + oauth2Credentials.addItemEntry("accessToken", new AttributeValue("access123")); + oauth2Credentials.addItemEntry("allowedGroups", new AttributeValue().withL(List.of(new AttributeValue("group1")))); + oauth2Credentials.addItemEntry("expirationDate", new AttributeValue("2020-04-01T05:05:05")); + + ddb.putItem(oauth2Credentials); + + PutItemRequest oauth1Credentials = new PutItemRequest(); + oauth1Credentials.setTableName("Oauth1Credentials"); + oauth1Credentials.addItemEntry("id", new AttributeValue("twitter")); + oauth1Credentials.addItemEntry("accessToken", new AttributeValue("access123")); + oauth1Credentials.addItemEntry("consumerKey", new AttributeValue("consumer123")); + oauth1Credentials.addItemEntry("consumerSecret", new AttributeValue("csecret123")); + oauth1Credentials.addItemEntry("tokenSecret", new AttributeValue("tsecret123")); + + ddb.putItem(oauth1Credentials); + + PutItemRequest post1 = new PutItemRequest(); + post1.setTableName("Posts"); + post1.addItemEntry("id", new AttributeValue("1")); + post1.addItemEntry("name", new AttributeValue("My Post 1")); + post1.addItemEntry("description", new AttributeValue("My first post")); + post1.addItemEntry("tags", new AttributeValue().withL(new AttributeValue("tag1"), new AttributeValue("tag2"))); + post1.addItemEntry("url", new AttributeValue("https://coderstower.com/2020/02/18/unit-tests-vs-integration-tests/")); + post1.addItemEntry("publishedDate", new AttributeValue("2013-09-17T18:47:52")); + post1.addItemEntry("group", new AttributeValue("group1")); + + ddb.putItem(post1); + + PutItemRequest post2 = new PutItemRequest(); + post2.setTableName("Posts"); + post2.addItemEntry("id", new AttributeValue("2")); + post2.addItemEntry("name", new AttributeValue("My Post 2")); + post2.addItemEntry("description", new AttributeValue("My second post")); + post2.addItemEntry("tags", new AttributeValue().withL(new AttributeValue("tag1"), new AttributeValue("tag2"))); + post2.addItemEntry("url", new AttributeValue("https://coderstower.com/2020/01/13/open-close-principle-by-example/")); + post2.addItemEntry("publishedDate", new AttributeValue("2012-09-17T18:47:52")); + post2.addItemEntry("group", new AttributeValue("group1")); + + ddb.putItem(post2); + + return ddb; + } + + private CreateTableResult createTable(AmazonDynamoDB ddb, String tableName, String hashKeyName) { + List attributeDefinitions = new ArrayList<>(); + attributeDefinitions.add(new AttributeDefinition(hashKeyName, ScalarAttributeType.S)); + + List ks = new ArrayList<>(); + ks.add(new KeySchemaElement(hashKeyName, KeyType.HASH)); + + ProvisionedThroughput provisionedthroughput = new ProvisionedThroughput(1000L, 1000L); + + CreateTableRequest request = + new CreateTableRequest() + .withTableName(tableName) + .withAttributeDefinitions(attributeDefinitions) + .withKeySchema(ks) + .withProvisionedThroughput(provisionedthroughput); + + return ddb.createTable(request); + } + } + +} diff --git a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/StreamLambdaHandlerTest.java b/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/StreamLambdaHandlerTest.java new file mode 100644 index 0000000..5c92f5b --- /dev/null +++ b/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/StreamLambdaHandlerTest.java @@ -0,0 +1,73 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.aws; + + +import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; +import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; +import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.services.lambda.runtime.Context; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import javax.ws.rs.HttpMethod; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import static org.junit.jupiter.api.Assertions.*; + +public class StreamLambdaHandlerTest { + + private static StreamLambdaHandler handler; + private static Context lambdaContext; + + @BeforeAll + public static void setUp() { + handler = new StreamLambdaHandler(); + lambdaContext = new MockLambdaContext(); + } + + @Test + public void ping_streamRequest_respondsWithHello() { + InputStream requestStream = new AwsProxyRequestBuilder("/ping", HttpMethod.GET) + .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON) + .buildStream(); + ByteArrayOutputStream responseStream = new ByteArrayOutputStream(); + + handle(requestStream, responseStream); + + AwsProxyResponse response = readResponse(responseStream); + assertNotNull(response); + assertEquals(Response.Status.OK.getStatusCode(), response.getStatusCode()); + + assertFalse(response.isBase64Encoded()); + + assertTrue(response.getBody().contains("pong")); + assertTrue(response.getBody().contains("Hello, World!")); + + assertTrue(response.getMultiValueHeaders().containsKey(HttpHeaders.CONTENT_TYPE)); + assertTrue(response.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_TYPE).startsWith(MediaType.APPLICATION_JSON)); + } + + private void handle(InputStream is, ByteArrayOutputStream os) { + try { + handler.handleRequest(is, os, lambdaContext); + } catch (IOException e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + private AwsProxyResponse readResponse(ByteArrayOutputStream responseStream) { + try { + return LambdaContainerHandler.getObjectMapper().readValue(responseStream.toByteArray(), AwsProxyResponse.class); + } catch (IOException e) { + e.printStackTrace(); + fail("Error while parsing response: " + e.getMessage()); + } + return null; + } +} diff --git a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/LocalDateTimeConverterTest.java b/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/LocalDateTimeConverterTest.java new file mode 100644 index 0000000..00e8a7e --- /dev/null +++ b/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/LocalDateTimeConverterTest.java @@ -0,0 +1,26 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository; + +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.assertThat; + +class LocalDateTimeConverterTest { + private LocalDateTimeConverter localDateTimeConverter = new LocalDateTimeConverter(); + + @Test + public void convert(){ + String date = localDateTimeConverter.convert(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)); + + assertThat(date).isEqualTo("2020-03-03T05:06:08.000000001"); + } + + @Test + public void unconvert(){ + LocalDateTime date = localDateTimeConverter.unconvert("2020-03-03T05:06:08.000000001"); + + assertThat(date).isEqualTo(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)); + } + +} \ No newline at end of file diff --git a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/URLConverterTest.java b/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/URLConverterTest.java new file mode 100644 index 0000000..590c9c7 --- /dev/null +++ b/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/URLConverterTest.java @@ -0,0 +1,28 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository; + +import org.junit.jupiter.api.Test; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; + +import static org.assertj.core.api.Assertions.assertThat; + +class URLConverterTest { + private URLConverter urlConverter = new URLConverter(); + + @Test + public void convert() throws MalformedURLException { + String url = urlConverter.convert(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()); + + assertThat(url).isEqualTo("https://coderstower.com/2020/01/13/open-close-principle-by-example/"); + } + + @Test + public void unconvert() throws MalformedURLException { + URL url = urlConverter.unconvert("https://coderstower.com/2020/01/13/open-close-principle-by-example/"); + + assertThat(url).isEqualTo(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()); + } + +} \ No newline at end of file diff --git a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepositoryTest.java b/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepositoryTest.java new file mode 100644 index 0000000..5c30a0b --- /dev/null +++ b/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepositoryTest.java @@ -0,0 +1,43 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth1; + +import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth1Credentials; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class OAuth1CredentialAWSRepositoryTest { + @Mock + private OAuth1CredentialDynamoRepository oauth1CredentialDynamoRepository; + @InjectMocks + private OAuth1CredentialAWSRepository oauth1CredentialAWSRepository; + + @Test + public void getCredentials(){ + when(oauth1CredentialDynamoRepository.findById("twitter")).thenReturn(Optional.of(OAuth1CredentialDynamo.builder() + .id("twitter") + .accessToken("accessToken") + .tokenSecret("tokenSecret") + .consumerKey("consumerKey") + .consumerSecret("consumerSecret") + .build())); + + Optional credentials = oauth1CredentialAWSRepository.getCredentials("twitter"); + + assertThat(credentials).isEqualTo(Optional.of(OAuth1Credentials.builder() + .id("twitter") + .accessToken("accessToken") + .tokenSecret("tokenSecret") + .consumerKey("consumerKey") + .consumerSecret("consumerSecret") + .build())); + } + +} \ No newline at end of file diff --git a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepositoryTest.java b/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepositoryTest.java new file mode 100644 index 0000000..3914451 --- /dev/null +++ b/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepositoryTest.java @@ -0,0 +1,65 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth2; + +import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2Credentials; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDateTime; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class OAuth2CredentialAWSRepositoryTest { + @Mock + private OAuth2CredentialDynamoRepository oauth2CredentialDynamoRepository; + @InjectMocks + private OAuth2CredentialAWSRepository oauth2CredentialAWSRepository; + + @Test + public void getCredentials(){ + when(oauth2CredentialDynamoRepository.findById("linkedin")).thenReturn(Optional.of(OAuth2CredentialDynamo.builder() + .id("linkedin") + .accessToken("accessToken") + .expirationDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) + .build())); + + Optional credentials = oauth2CredentialAWSRepository.getCredentials("linkedin"); + + assertThat(credentials).isEqualTo(Optional.of(OAuth2Credentials.builder() + .id("linkedin") + .accessToken("accessToken") + .expirationDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) + .build())); + } + + @Test + public void update(){ + when(oauth2CredentialDynamoRepository.save(OAuth2CredentialDynamo.builder() + .id("linkedin") + .accessToken("accessToken") + .expirationDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) + .build())).thenReturn(OAuth2CredentialDynamo.builder() + .id("linkedin") + .accessToken("accessToken") + .expirationDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) + .build()); + + OAuth2Credentials credentials = oauth2CredentialAWSRepository.update(OAuth2Credentials.builder() + .id("linkedin") + .accessToken("accessToken") + .expirationDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) + .build()); + + assertThat(credentials).isEqualTo(OAuth2Credentials.builder() + .id("linkedin") + .accessToken("accessToken") + .expirationDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) + .build()); + } + +} \ No newline at end of file diff --git a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostAWSRepositoryTest.java b/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostAWSRepositoryTest.java new file mode 100644 index 0000000..6a33102 --- /dev/null +++ b/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostAWSRepositoryTest.java @@ -0,0 +1,97 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.post; + +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.net.MalformedURLException; +import java.net.URI; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class PostAWSRepositoryTest { + @Mock + private PostDynamoRepository postDynamoRepository; + @InjectMocks + private PostAWSRepository postAWSRepository; + + @Test + public void getNextToPublish() throws MalformedURLException { + when(postDynamoRepository.findAll()).thenReturn(List.of(PostDynamo.builder() + .id("2") + .name("My Post 2") + .description("My second post") + .tags(List.of("tag1", "tag2")) + .group("group1") + .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) + .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) + .build(), PostDynamo.builder() + .id("1") + .name("My Post 1") + .description("My first post") + .tags(List.of("tag1", "tag2")) + .group("group1") + .url(URI.create("https://coderstower.com/2020/02/18/unit-tests-vs-integration-tests/").toURL()) + .publishedDate(LocalDateTime.parse("2013-09-17T18:47:52")) + .build())); + + Optional post = postAWSRepository.getNextToPublish("group1"); + + assertThat(post).isEqualTo(Optional.of(Post.builder() + .id("2") + .name("My Post 2") + .description("My second post") + .tags(List.of("tag1", "tag2")) + .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) + .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) + .group("group1") + .build())); + } + + @Test + public void update() throws MalformedURLException { + when(postDynamoRepository.save(PostDynamo.builder() + .id("2") + .name("My Post 2") + .description("My second post") + .tags(List.of("tag1", "tag2")) + .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) + .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) + .build())) + .thenReturn(PostDynamo.builder() + .id("2") + .name("My Post 2") + .description("My second post") + .tags(List.of("tag1", "tag2")) + .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) + .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) + .build()); + + Post post = postAWSRepository.update(Post.builder() + .id("2") + .name("My Post 2") + .description("My second post") + .tags(List.of("tag1", "tag2")) + .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) + .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) + .build()); + + assertThat(post).isEqualTo(Post.builder() + .id("2") + .name("My Post 2") + .description("My second post") + .tags(List.of("tag1", "tag2")) + .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) + .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) + .build()); + } + +} \ No newline at end of file diff --git a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/PostsControllerTest.java b/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/PostsControllerTest.java new file mode 100644 index 0000000..d96ab8c --- /dev/null +++ b/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/PostsControllerTest.java @@ -0,0 +1,72 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.controller; + +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.PostPublisher; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Publication; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.net.MalformedURLException; +import java.net.URI; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class PostsControllerTest { + @Mock + private PostPublisher postPublisher; + @InjectMocks + private PostsController postsController; + + @Test + public void ping() { + Map result = postsController.ping(); + + Map pong = new HashMap<>(); + pong.put("pong", "Hello, World!"); + + assertThat(result).isEqualTo(pong); + } + + @Test + public void postNext() throws MalformedURLException { + when(postPublisher.publishNext("group1")).thenReturn(Post.builder() + .id("2") + .name("My Post 2") + .description("My second post") + .tags(List.of("tag1", "tag2")) + .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) + .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) + .publications(List.of(Publication.builder() + .status(Publication.Status.FAILURE) + .publisher("twitter") + .build())) + .group("group1") + .build()); + + Post result = postsController.postNext("group1"); + + assertThat(result).isEqualTo(Post.builder() + .id("2") + .name("My Post 2") + .description("My second post") + .tags(List.of("tag1", "tag2")) + .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) + .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) + .publications(List.of(Publication.builder() + .status(Publication.Status.FAILURE) + .publisher("twitter") + .build())) + .group("group1") + .build()); + } + +} \ No newline at end of file diff --git a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInPublisherTest.java b/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInPublisherTest.java new file mode 100644 index 0000000..9400bfb --- /dev/null +++ b/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInPublisherTest.java @@ -0,0 +1,280 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin; + +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Acknowledge; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Publication; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.UnauthorizedException; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2Credentials; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2CredentialsRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.*; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriTemplate; + +import java.net.MalformedURLException; +import java.net.URI; +import java.time.Clock; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class LinkedInPublisherTest { + @Mock + private OAuth2CredentialsRepository oauth2CredentialsRepository; + @Mock + private RestTemplate restTemplate; + + private LinkedInPublisher linkedInPublisher; + private ZonedDateTime now; + + @BeforeEach + public void before() { + this.now = ZonedDateTime.of(2020, 3, 3, 5, 6, 8, 1, ZoneId.of("UTC")); + this.linkedInPublisher = new LinkedInPublisher("linkedin", oauth2CredentialsRepository, restTemplate, Clock + .fixed(now.toInstant(), ZoneId.of("UTC")), new UriTemplate("http://localhost:8080/oauth2/{social-media}/credentials")); + } + + @Test + public void ping_noCredential_exception() { + when(oauth2CredentialsRepository.findAll()).thenReturn(List.of()); + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> linkedInPublisher.ping()); + + assertThat(exception.getMessage()).isEqualTo("The credentials for linkedin doesn't exist"); + } + + @Test + public void ping_expiredCredential_exception() { + when(oauth2CredentialsRepository.findAll()).thenReturn(List.of(OAuth2Credentials.builder() + .expirationDate(now.toLocalDateTime().minusMonths(1)) + .id("credential1") + .build())); + + UnauthorizedException exception = assertThrows(UnauthorizedException.class, () -> linkedInPublisher.ping()); + + assertThat(exception.getMessage()).isEqualTo("Unauthorized for linkedin credential1. Please login again here: http://localhost:8080/oauth2/linkedin/credentials"); + } + + @Test + public void ping_goodCredential_success() { + when(oauth2CredentialsRepository.findAll()).thenReturn(List.of(OAuth2Credentials.builder() + .expirationDate(now.toLocalDateTime().plusMonths(1)) + .build())); + + Acknowledge ack = linkedInPublisher.ping(); + + assertThat(ack).isEqualTo(Acknowledge.builder() + .status(Acknowledge.Status.SUCCESS) + .build()); + } + + @Test + public void publish_noProfile_publicationError() { + HttpHeaders httpHeadersProfile = new HttpHeaders(); + httpHeadersProfile.setContentType(MediaType.APPLICATION_JSON); + httpHeadersProfile.add("X-Restli-Protocol-Version", "2.0.0"); + httpHeadersProfile.setBearerAuth("accessToken"); + + HttpEntity requestEntityProfile = new HttpEntity<>(httpHeadersProfile); + + when(oauth2CredentialsRepository.findAll()).thenReturn(List.of(OAuth2Credentials.builder() + .accessToken("accessToken") + .allowedGroups(List.of("group1")) + .build())); + when(restTemplate.exchange("https://api.linkedin.com/v2/me", HttpMethod.GET, requestEntityProfile, Profile.class)) + .thenReturn(ResponseEntity.notFound().build()); + + List publication = linkedInPublisher.publish(Post.builder() + .group("group1") + .build()); + + assertThat(publication).isEqualTo(List.of(Publication.builder() + .status(Publication.Status.FAILURE) + .publisher("linkedin") + .publishedDate(now.toLocalDateTime()) + .build())); + } + + @Test + public void publish_noShare_publicationError() throws MalformedURLException { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_JSON); + httpHeaders.add("X-Restli-Protocol-Version", "2.0.0"); + httpHeaders.setBearerAuth("accessToken"); + + when(oauth2CredentialsRepository.findAll()).thenReturn(List.of(OAuth2Credentials.builder() + .accessToken("accessToken") + .allowedGroups(List.of("group1")) + .build())); + + HttpEntity requestEntityProfile = new HttpEntity<>(httpHeaders); + + when(restTemplate.exchange("https://api.linkedin.com/v2/me", HttpMethod.GET, requestEntityProfile, Profile.class)) + .thenReturn(ResponseEntity.ok(Profile.builder() + .sub("memberid") + .build())); + + LinkedInShare linkedInShare = LinkedInShare.builder() + .author("urn:li:person:memberid") + .lifecycleState("PUBLISHED") + .commentary("commentary") + .distribution(Distribution.builder().feedDistribution("MAIN_FEED").build()) + .lifecycleState("PUBLISHED") + .content(Content.builder() + .article(ArticleContent.builder() + .description("My second post\n\n#tag1 #tag2") + .title("My second post") + .source("https://coderstower.com/2020/01/13/open-close-principle-by-example/") + .build()) + .build()) + .visibility("PUBLIC") + .build(); + + HttpEntity requestShare = new HttpEntity<>(linkedInShare, httpHeaders); + + when(restTemplate.exchange("https://api.linkedin.com/v2/ugcPosts", HttpMethod.POST, requestShare, Void.class)) + .thenReturn(ResponseEntity.badRequest().build()); + + List publication = linkedInPublisher.publish(Post.builder() + .id("2") + .name("My Post 2") + .description("My second post") + .tags(List.of("tag1", "tag2")) + .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) + .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) + .group("group1") + .build()); + + assertThat(publication).isEqualTo(List.of(Publication.builder() + .status(Publication.Status.FAILURE) + .publisher("linkedin") + .publishedDate(now.toLocalDateTime()) + .build())); + } + + @Test + public void publish_noShareId_publicationError() throws MalformedURLException { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_JSON); + httpHeaders.add("X-Restli-Protocol-Version", "2.0.0"); + httpHeaders.setBearerAuth("accessToken"); + + when(oauth2CredentialsRepository.findAll()).thenReturn(List.of(OAuth2Credentials.builder() + .accessToken("accessToken") + .allowedGroups(List.of("group1")) + .build())); + + HttpEntity requestEntityProfile = new HttpEntity<>(httpHeaders); + + when(restTemplate.exchange("https://api.linkedin.com/v2/me", HttpMethod.GET, requestEntityProfile, Profile.class)) + .thenReturn(ResponseEntity.ok(Profile.builder() + .sub("memberid") + .build())); + + LinkedInShare linkedInShare = LinkedInShare.builder() + .author("urn:li:person:memberid") + .lifecycleState("PUBLISHED") + .commentary("commentary") + .distribution(Distribution.builder().feedDistribution("MAIN_FEED").build()) + .lifecycleState("PUBLISHED") + .content(Content.builder() + .article(ArticleContent.builder() + .description("My second post\n\n#tag1 #tag2") + .title("My second post") + .source("https://coderstower.com/2020/01/13/open-close-principle-by-example/") + .build()) + .build()) + .visibility("PUBLIC") + .build(); + + HttpEntity requestShare = new HttpEntity<>(linkedInShare, httpHeaders); + + when(restTemplate.exchange("https://api.linkedin.com/v2/ugcPosts", HttpMethod.POST, requestShare, Void.class)) + .thenReturn(ResponseEntity.ok().build()); + + List publication = linkedInPublisher.publish(Post.builder() + .id("2") + .name("My Post 2") + .description("My second post") + .tags(List.of("tag1", "tag2")) + .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) + .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) + .group("group1") + .build()); + + assertThat(publication).isEqualTo(List.of(Publication.builder() + .status(Publication.Status.FAILURE) + .publisher("linkedin") + .publishedDate(now.toLocalDateTime()) + .build())); + } + + @Test + public void publish_shareId_publicationSucced() throws MalformedURLException { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_JSON); + httpHeaders.add("X-Restli-Protocol-Version", "2.0.0"); + httpHeaders.add("LinkedIn-Version", "202304"); + httpHeaders.setBearerAuth("accessToken"); + + when(oauth2CredentialsRepository.findAll()).thenReturn(List.of(OAuth2Credentials.builder() + .accessToken("accessToken") + .allowedGroups(List.of("group1")) + .build())); + + HttpEntity requestEntityProfile = new HttpEntity<>(httpHeaders); + + when(restTemplate.exchange("https://api.linkedin.com/v2/userinfo", HttpMethod.GET, requestEntityProfile, Profile.class)) + .thenReturn(ResponseEntity.ok(Profile.builder() + .sub("memberid") + .build())); + + LinkedInShare linkedInShare = LinkedInShare.builder() + .author("urn:li:person:memberid") + .lifecycleState("PUBLISHED") + .commentary("My second post\n\n#tag1 #tag2") + .distribution(Distribution.builder().feedDistribution("MAIN_FEED").build()) + .lifecycleState("PUBLISHED") + .content(Content.builder() + .article(ArticleContent.builder() + .description("My second post") + .title("My Post 2") + .source("https://coderstower.com/2020/01/13/open-close-principle-by-example/") + .build()) + .build()) + .visibility("PUBLIC") + .build(); + + HttpEntity requestShare = new HttpEntity<>(linkedInShare, httpHeaders); + + when(restTemplate.exchange("https://api.linkedin.com/rest/posts", HttpMethod.POST, requestShare, Void.class)) + .thenReturn(ResponseEntity.ok().header("X-RestLi-Id", "shareId").build()); + + List publication = linkedInPublisher.publish(Post.builder() + .id("2") + .name("My Post 2") + .description("My second post") + .tags(List.of("tag1", "tag2")) + .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) + .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) + .group("group1") + .build()); + + assertThat(publication).isEqualTo(List.of(Publication.builder() + .id("shareId") + .status(Publication.Status.SUCCESS) + .publisher("linkedin") + .publishedDate(now.toLocalDateTime()) + .build())); + } +} \ No newline at end of file diff --git a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/twitter/TwitterPublisherTest.java b/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/twitter/TwitterPublisherTest.java new file mode 100644 index 0000000..9b08fe8 --- /dev/null +++ b/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/twitter/TwitterPublisherTest.java @@ -0,0 +1,217 @@ +package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.twitter; + +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Acknowledge; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Publication; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth1Credentials; +import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth1CredentialsRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import twitter4j.*; +import twitter4j.auth.AccessToken; +import twitter4j.auth.Authorization; +import twitter4j.auth.NullAuthorization; + +import java.net.MalformedURLException; +import java.net.URI; +import java.time.Clock; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class TwitterPublisherTest { + @Mock + private OAuth1CredentialsRepository oauth1CredentialsRepository; + @Mock + private Twitter twitter; + private TwitterPublisher twitterPublisher; + private ZonedDateTime now; + + @BeforeEach + public void before() { + this.now = ZonedDateTime.of(2020, 3, 3, 5, 6, 8, 1, ZoneId.of("UTC")); + this.twitterPublisher = new TwitterPublisher("twitter", oauth1CredentialsRepository, twitter, Clock + .fixed(now.toInstant(), ZoneId.of("UTC"))); + } + + @Test + public void ping_noCredential_exception() { + when(oauth1CredentialsRepository.getCredentials("twitter")).thenReturn(Optional.empty()); + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> twitterPublisher.ping()); + + assertThat(exception.getMessage()).isEqualTo("The credentials for twitter doesn't exist"); + } + + @Test + public void ping_credentialNoSetTwitterError_ackFailure() throws TwitterException { + when(oauth1CredentialsRepository.getCredentials("twitter")).thenReturn(Optional.of(OAuth1Credentials.builder() + .accessToken("accessToken") + .consumerKey("consumerKey") + .consumerSecret("consumerSecret") + .tokenSecret("tokenSecret") + .build())); + when(twitter.getAuthorization()).thenReturn(NullAuthorization.getInstance()); + when(twitter.getHomeTimeline(new Paging(1, 1))).thenThrow(new TwitterException("error")); + + Acknowledge ack = twitterPublisher.ping(); + + assertThat(ack).isEqualTo(Acknowledge.builder() + .status(Acknowledge.Status.FAILURE) + .description("Ping error") + .exception(new TwitterException("error")) + .build()); + verify(twitter).setOAuthConsumer("consumerKey", "consumerSecret"); + verify(twitter).setOAuthAccessToken(new AccessToken("accessToken", "tokenSecret")); + } + + @Test + public void ping_credentialSetEmptyStatus_ackFailure() throws TwitterException { + when(oauth1CredentialsRepository.getCredentials("twitter")).thenReturn(Optional.of(OAuth1Credentials.builder() + .accessToken("accessToken") + .consumerKey("consumerKey") + .consumerSecret("consumerSecret") + .tokenSecret("tokenSecret") + .build())); + when(twitter.getAuthorization()).thenReturn(mock(Authorization.class)); + when(twitter.getHomeTimeline(new Paging(1, 1))).thenReturn(null); + + Acknowledge ack = twitterPublisher.ping(); + + assertThat(ack).isEqualTo(Acknowledge.builder() + .status(Acknowledge.Status.FAILURE) + .build()); + verify(twitter, times(0)).setOAuthConsumer("consumerKey", "consumerSecret"); + verify(twitter, times(0)).setOAuthAccessToken(new AccessToken("accessToken", "tokenSecret")); + } + + @Test + public void ping_credentialSetStatus_ackSucced() throws TwitterException { + when(oauth1CredentialsRepository.getCredentials("twitter")).thenReturn(Optional.of(OAuth1Credentials.builder() + .accessToken("accessToken") + .consumerKey("consumerKey") + .consumerSecret("consumerSecret") + .tokenSecret("tokenSecret") + .build())); + when(twitter.getAuthorization()).thenReturn(mock(Authorization.class)); + when(twitter.getHomeTimeline(new Paging(1, 1))).thenReturn(mock(ResponseList.class)); + + Acknowledge ack = twitterPublisher.ping(); + + assertThat(ack).isEqualTo(Acknowledge.builder() + .status(Acknowledge.Status.SUCCESS) + .build()); + verify(twitter, times(0)).setOAuthConsumer("consumerKey", "consumerSecret"); + verify(twitter, times(0)).setOAuthAccessToken(new AccessToken("accessToken", "tokenSecret")); + } + + @Test + public void publish_noCredential_exception() { + when(oauth1CredentialsRepository.getCredentials("twitter")).thenReturn(Optional.empty()); + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> twitterPublisher.publish(Post.builder().build())); + + assertThat(exception.getMessage()).isEqualTo("The credentials for twitter doesn't exist"); + } + + @Test + public void publish_credentialNoSetTwitterError_shareFailure() throws TwitterException, MalformedURLException { + when(oauth1CredentialsRepository.getCredentials("twitter")).thenReturn(Optional.of(OAuth1Credentials.builder() + .accessToken("accessToken") + .consumerKey("consumerKey") + .consumerSecret("consumerSecret") + .tokenSecret("tokenSecret") + .build())); + when(twitter.getAuthorization()).thenReturn(NullAuthorization.getInstance()); + when(twitter.updateStatus("My second post\n\n#tag1 #tag2\n\nhttps://coderstower.com/2020/01/13/open-close-principle-by-example/")).thenThrow(new TwitterException("error")); + + List publish = twitterPublisher.publish(Post.builder() + .id("2") + .name("My Post 2") + .description("My second post") + .tags(List.of("tag1", "tag2")) + .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) + .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) + .build()); + + assertThat(publish).isEqualTo(List.of(Publication.builder() + .status(Publication.Status.FAILURE) + .publisher("twitter") + .publishedDate(now.toLocalDateTime()) + .build())); + verify(twitter).setOAuthConsumer("consumerKey", "consumerSecret"); + verify(twitter).setOAuthAccessToken(new AccessToken("accessToken", "tokenSecret")); + } + + @Test + public void publish_credentialSetEmptyStatus_shareFailure() throws TwitterException, MalformedURLException { + when(oauth1CredentialsRepository.getCredentials("twitter")).thenReturn(Optional.of(OAuth1Credentials.builder() + .accessToken("accessToken") + .consumerKey("consumerKey") + .consumerSecret("consumerSecret") + .tokenSecret("tokenSecret") + .build())); + when(twitter.getAuthorization()).thenReturn(mock(Authorization.class)); + when(twitter.updateStatus("My second post\n\n#tag1 #tag2\n\nhttps://coderstower.com/2020/01/13/open-close-principle-by-example/")).thenReturn(null); + + List publish = twitterPublisher.publish(Post.builder() + .id("2") + .name("My Post 2") + .description("My second post") + .tags(List.of("tag1", "tag2")) + .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) + .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) + .build()); + + assertThat(publish).isEqualTo(List.of(Publication.builder() + .status(Publication.Status.FAILURE) + .publisher("twitter") + .publishedDate(now.toLocalDateTime()) + .build())); + verify(twitter, times(0)).setOAuthConsumer("consumerKey", "consumerSecret"); + verify(twitter, times(0)).setOAuthAccessToken(new AccessToken("accessToken", "tokenSecret")); + } + + @Test + public void publish_credentialSetStatus_ackSucced() throws TwitterException, MalformedURLException { + when(oauth1CredentialsRepository.getCredentials("twitter")).thenReturn(Optional.of(OAuth1Credentials.builder() + .accessToken("accessToken") + .consumerKey("consumerKey") + .consumerSecret("consumerSecret") + .tokenSecret("tokenSecret") + .build())); + when(twitter.getAuthorization()).thenReturn(mock(Authorization.class)); + + Status status = mock(Status.class); + when(status.getId()).thenReturn(123L); + when(twitter.updateStatus("My second post\n\n#tag1 #tag2\n\nhttps://coderstower.com/2020/01/13/open-close-principle-by-example/")).thenReturn(status); + + List publish = twitterPublisher.publish(Post.builder() + .id("2") + .name("My Post 2") + .description("My second post") + .tags(List.of("tag1", "tag2")) + .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) + .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) + .build()); + + assertThat(publish).isEqualTo(List.of(Publication.builder() + .id("123") + .status(Publication.Status.SUCCESS) + .publisher("twitter") + .publishedDate(now.toLocalDateTime()) + .build())); + verify(twitter, times(0)).setOAuthConsumer("consumerKey", "consumerSecret"); + verify(twitter, times(0)).setOAuthAccessToken(new AccessToken("accessToken", "tokenSecret")); + } +} \ No newline at end of file From 32ad5923aeb63b2df6c2ac040a31c2247f16ff61 Mon Sep 17 00:00:00 2001 From: Daniel Pelaez Date: Sun, 14 Apr 2024 13:26:44 -0500 Subject: [PATCH 02/17] Moving classes to root --- .../.mvn/wrapper/MavenWrapperDownloader.java | 140 ++++++++++++++ .../.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 50710 bytes spring-publisher/HELP.md | 8 + spring-publisher/mvnw.cmd | 182 ++++++++++++++++++ .../abstraction/post/PostPublisher.java | 2 +- 5 files changed, 331 insertions(+), 1 deletion(-) diff --git a/spring-publisher/.mvn/wrapper/MavenWrapperDownloader.java b/spring-publisher/.mvn/wrapper/MavenWrapperDownloader.java index e69de29..f97cd5a 100644 --- a/spring-publisher/.mvn/wrapper/MavenWrapperDownloader.java +++ b/spring-publisher/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,140 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println( + "- Using base directory: " + baseDirectory + .getAbsolutePath()); + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File( + baseDirectory, + MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if (mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream( + mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties + .load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties + .getProperty(PROPERTY_NAME_WRAPPER_URL, + url); + } catch (IOException e) { + System.out.println( + "- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if (mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream + .close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + File outputFile = new File( + baseDirectory.getAbsolutePath(), + MAVEN_WRAPPER_JAR_PATH); + if (!outputFile.getParentFile().exists()) { + if (!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile + .getParentFile() + .getAbsolutePath() + "'"); + } + } + System.out.println( + "- Downloading to: " + outputFile + .getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL( + String urlString, File destination) + throws Exception { + if (System + .getenv("MVNW_USERNAME") != null && System + .getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD") + .toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, + password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream( + destination); + fos.getChannel() + .transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/spring-publisher/.mvn/wrapper/maven-wrapper.jar b/spring-publisher/.mvn/wrapper/maven-wrapper.jar index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054 100644 GIT binary patch literal 50710 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 EfAhh=DF6Tf literal 0 HcmV?d00001 diff --git a/spring-publisher/HELP.md b/spring-publisher/HELP.md index e69de29..97150fe 100644 --- a/spring-publisher/HELP.md +++ b/spring-publisher/HELP.md @@ -0,0 +1,8 @@ +# Getting Started + +### Reference Documentation +For further reference, please consider the following sections: + +* [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) +* [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/maven-plugin/) + diff --git a/spring-publisher/mvnw.cmd b/spring-publisher/mvnw.cmd index e69de29..c8d4337 100644 --- a/spring-publisher/mvnw.cmd +++ b/spring-publisher/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/PostPublisher.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/PostPublisher.java index 484cc18..2fab901 100644 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/PostPublisher.java +++ b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/PostPublisher.java @@ -48,7 +48,7 @@ private boolean publishedWellOK(List publishedPosts) { private List publish(List socialMediaPublishers, Post nextPost) { return socialMediaPublishers.stream() - .map(socialMediaPublisher -> socialMediaPublisher.publish(nextPost)) + .flatMap(socialMediaPublisher -> socialMediaPublisher.publish(nextPost).stream()) .collect(Collectors.toList()); } From 750e05c15feda8c29249763c2c263daba7f0c509 Mon Sep 17 00:00:00 2001 From: Daniel Pelaez Date: Sun, 14 Apr 2024 13:27:39 -0500 Subject: [PATCH 03/17] Moving classes to root --- spring-publisher/mvnw | 310 ------------------------------------------ 1 file changed, 310 deletions(-) delete mode 100644 spring-publisher/mvnw diff --git a/spring-publisher/mvnw b/spring-publisher/mvnw deleted file mode 100644 index a16b543..0000000 --- a/spring-publisher/mvnw +++ /dev/null @@ -1,310 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Maven Start Up Batch script -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir -# -# Optional ENV vars -# ----------------- -# M2_HOME - location of maven2's installed home dir -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files -# ---------------------------------------------------------------------------- - -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi - - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi - -fi - -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false -case "`uname`" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - export JAVA_HOME="`/usr/libexec/java_home`" - else - export JAVA_HOME="/Library/Java/Home" - fi - fi - ;; -esac - -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` - fi -fi - -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" - - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi - done - - saveddir=`pwd` - - M2_HOME=`dirname "$PRG"`/.. - - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` - - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi - -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` -fi - -# For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" -fi - -if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then - if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" - else - javaExecutable="`readlink -f \"$javaExecutable\"`" - fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi - -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - else - JAVACMD="`which java`" - fi -fi - -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi - -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." -fi - -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { - - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 - fi - - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=`cd "$wdir/.."; pwd` - fi - # end of workaround - done - echo "${basedir}" -} - -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" - fi -} - -BASE_DIR=`find_maven_basedir "$(pwd)"` -if [ -z "$BASE_DIR" ]; then - exit 1; -fi - -########################################################################################## -# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -# This allows using the maven wrapper in projects that prohibit checking in binary data. -########################################################################################## -if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found .mvn/wrapper/maven-wrapper.jar" - fi -else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." - fi - if [ -n "$MVNW_REPOURL" ]; then - jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - else - jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - fi - while IFS="=" read key value; do - case "$key" in (wrapperUrl) jarUrl="$value"; break ;; - esac - done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" - if [ "$MVNW_VERBOSE" = true ]; then - echo "Downloading from: $jarUrl" - fi - wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" - if $cygwin; then - wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` - fi - - if command -v wget > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found wget ... using wget" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget "$jarUrl" -O "$wrapperJarPath" - else - wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" - fi - elif command -v curl > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found curl ... using curl" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl -o "$wrapperJarPath" "$jarUrl" -f - else - curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f - fi - - else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Falling back to using Java to download" - fi - javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" - # For Cygwin, switch paths to Windows format before running javac - if $cygwin; then - javaClass=`cygpath --path --windows "$javaClass"` - fi - if [ -e "$javaClass" ]; then - if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Compiling MavenWrapperDownloader.java ..." - fi - # Compiling the Java class - ("$JAVA_HOME/bin/javac" "$javaClass") - fi - if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - # Running the downloader - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Running MavenWrapperDownloader.java ..." - fi - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") - fi - fi - fi -fi -########################################################################################## -# End of extension -########################################################################################## - -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -if [ "$MVNW_VERBOSE" = true ]; then - echo $MAVEN_PROJECTBASEDIR -fi -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` -fi - -# Provide a "standardized" way to retrieve the CLI args that will -# work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" -export MAVEN_CMD_LINE_ARGS - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -exec "$JAVACMD" \ - $MAVEN_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" From b5d8380ebb7610a83aa20d44385213e23dc5ecac Mon Sep 17 00:00:00 2001 From: Daniel Pelaez Date: Sun, 14 Apr 2024 13:28:46 -0500 Subject: [PATCH 04/17] Moving classes to root --- spring-publisher/mvnw | 310 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 310 insertions(+) create mode 100644 spring-publisher/mvnw diff --git a/spring-publisher/mvnw b/spring-publisher/mvnw new file mode 100644 index 0000000..a16b543 --- /dev/null +++ b/spring-publisher/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" From d8039052f8e8a15c58c856e1f714c51af384e981 Mon Sep 17 00:00:00 2001 From: Daniel Pelaez Date: Sun, 14 Apr 2024 13:31:12 -0500 Subject: [PATCH 05/17] Moving classes to root --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5c71305..1839584 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.coderstower social-media-publisher - 0.0.1-SNAPSHOT + 0.0.6-SNAPSHOT social-media-publisher Demo project for Spring Boot jar From c1ae65bfdf55ece0960a8107a9d721bf21a46987 Mon Sep 17 00:00:00 2001 From: Daniel Pelaez Date: Sun, 14 Apr 2024 13:51:35 -0500 Subject: [PATCH 06/17] Moving classes to root --- .../aws/MockSocialMediaSuccessfulTests.java | 1 - .../abstraction/post/PostPublisher.java | 12 +++++------ .../abstraction/post/repository/Post.java | 4 ++-- .../post/repository/PostRepository.java | 2 +- .../post/socialmedia/Acknowledge.java | 2 +- .../post/socialmedia/Publication.java | 2 +- .../socialmedia/SocialMediaPublisher.java | 11 ++++++++++ ...essTokenResponseConverterWithDefaults.java | 2 +- .../security/OAuth2CredentialsManager.java | 6 +++--- .../security/OAuth2CredentialsUpdated.java | 11 ++++++++++ .../security/UnauthorizedException.java | 2 +- .../repository/OAuth1Credentials.java | 2 +- .../OAuth1CredentialsRepository.java | 2 +- .../repository/OAuth2Credentials.java | 2 +- .../OAuth2CredentialsRepository.java | 2 +- .../main/SpringPublisherApplication.java | 3 ++- .../aws/AWSSpringPublisherApplication.java | 12 +++++------ ...ingPublisherDynamoDBRepositoryFactory.java | 16 +++++++-------- .../main/aws/StreamLambdaHandler.java | 2 +- .../repository/LocalDateTimeConverter.java | 2 +- .../main/aws/repository/URLConverter.java | 2 +- .../oauth1/OAuth1CredentialAWSRepository.java | 6 +++--- .../oauth1/OAuth1CredentialDynamo.java | 2 +- .../OAuth1CredentialDynamoRepository.java | 2 +- .../oauth2/OAuth2CredentialAWSRepository.java | 6 +++--- .../oauth2/OAuth2CredentialDynamo.java | 4 ++-- .../OAuth2CredentialDynamoRepository.java | 2 +- .../repository/post/PostAWSRepository.java | 6 +++--- .../main/aws/repository/post/PostDynamo.java | 6 +++--- .../repository/post/PostDynamoRepository.java | 2 +- .../main/controller/ErrorHandler.java | 4 ++-- .../OAuth2CredentialsController.java | 4 ++-- .../main/controller/PostsController.java | 6 +++--- .../main/factory/CredentialsProperties.java | 2 +- .../main/factory/SecurityFactory.java | 4 ++-- .../SocialMediaPublisherProperties.java | 2 +- .../main/factory/SpringPublisherFactory.java | 20 +++++++++---------- .../socialmedia/linkedin/ArticleContent.java | 2 +- .../main/socialmedia/linkedin/Content.java | 2 +- .../socialmedia/linkedin/Distribution.java | 2 +- .../linkedin/LinkedInPublisher.java | 18 ++++++++--------- .../socialmedia/linkedin/LinkedInShare.java | 2 +- .../main/socialmedia/linkedin/Media.java | 2 +- .../main/socialmedia/linkedin/Profile.java | 2 +- .../main/socialmedia/linkedin/Text.java | 2 +- .../socialmedia/twitter/TwitterPublisher.java | 16 +++++++-------- .../socialmedia/SocialMediaPublisher.java | 11 ---------- .../security/OAuth2CredentialsUpdated.java | 11 ---------- .../abstraction/post/PostPublisherTest.java | 15 +++++++------- .../OAuth2CredentialsManagerTest.java | 8 +++++--- ...ckSocialMediaCredentialsHandlingTests.java | 2 +- .../aws/MockSocialMediaSuccessfulTests.java | 4 ++-- .../main/aws/StreamLambdaHandlerTest.java | 3 ++- .../LocalDateTimeConverterTest.java | 3 ++- .../main/aws/repository/URLConverterTest.java | 3 ++- .../OAuth1CredentialAWSRepositoryTest.java | 7 +++++-- .../OAuth2CredentialAWSRepositoryTest.java | 7 +++++-- .../post/PostAWSRepositoryTest.java | 7 +++++-- .../main/controller/PostsControllerTest.java | 9 +++++---- .../linkedin/LinkedInPublisherTest.java | 17 ++++++++-------- .../twitter/TwitterPublisherTest.java | 15 +++++++------- 61 files changed, 183 insertions(+), 165 deletions(-) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/abstraction/post/PostPublisher.java (79%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/abstraction/post/repository/Post.java (91%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/abstraction/post/repository/PostRepository.java (61%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/abstraction/post/socialmedia/Acknowledge.java (74%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/abstraction/post/socialmedia/Publication.java (80%) create mode 100644 src/main/java/com/coderstower/socialmediapubisher/abstraction/post/socialmedia/SocialMediaPublisher.java rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/abstraction/security/OAuth2AccessTokenResponseConverterWithDefaults.java (97%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/abstraction/security/OAuth2CredentialsManager.java (85%) create mode 100644 src/main/java/com/coderstower/socialmediapubisher/abstraction/security/OAuth2CredentialsUpdated.java rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/abstraction/security/UnauthorizedException.java (69%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/abstraction/security/repository/OAuth1Credentials.java (75%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/abstraction/security/repository/OAuth1CredentialsRepository.java (59%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/abstraction/security/repository/OAuth2Credentials.java (86%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/abstraction/security/repository/OAuth2CredentialsRepository.java (78%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/SpringPublisherApplication.java (76%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/aws/AWSSpringPublisherApplication.java (78%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/aws/SpringPublisherDynamoDBRepositoryFactory.java (74%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/aws/StreamLambdaHandler.java (94%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/aws/repository/LocalDateTimeConverter.java (84%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/aws/repository/URLConverter.java (87%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/aws/repository/oauth1/OAuth1CredentialAWSRepository.java (77%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/aws/repository/oauth1/OAuth1CredentialDynamo.java (89%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/aws/repository/oauth1/OAuth1CredentialDynamoRepository.java (72%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/aws/repository/oauth2/OAuth2CredentialAWSRepository.java (87%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/aws/repository/oauth2/OAuth2CredentialDynamo.java (82%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/aws/repository/oauth2/OAuth2CredentialDynamoRepository.java (73%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/aws/repository/post/PostAWSRepository.java (85%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/aws/repository/post/PostDynamo.java (75%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/aws/repository/post/PostDynamoRepository.java (73%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/controller/ErrorHandler.java (70%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/controller/OAuth2CredentialsController.java (88%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/controller/PostsController.java (80%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/factory/CredentialsProperties.java (79%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/factory/SecurityFactory.java (95%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/factory/SocialMediaPublisherProperties.java (86%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/factory/SpringPublisherFactory.java (68%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/socialmedia/linkedin/ArticleContent.java (69%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/socialmedia/linkedin/Content.java (59%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/socialmedia/linkedin/Distribution.java (60%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/socialmedia/linkedin/LinkedInPublisher.java (89%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/socialmedia/linkedin/LinkedInShare.java (78%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/socialmedia/linkedin/Media.java (71%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/socialmedia/linkedin/Profile.java (71%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/socialmedia/linkedin/Text.java (56%) rename src/main/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/socialmedia/twitter/TwitterPublisher.java (85%) delete mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/SocialMediaPublisher.java delete mode 100644 src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsUpdated.java rename src/test/java/com/coderstower/socialmediapubisher/{springpublisher => }/abstraction/post/PostPublisherTest.java (93%) rename src/test/java/com/coderstower/socialmediapubisher/{springpublisher => }/abstraction/security/OAuth2CredentialsManagerTest.java (91%) rename src/test/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/aws/MockSocialMediaCredentialsHandlingTests.java (99%) rename src/test/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/aws/MockSocialMediaSuccessfulTests.java (98%) rename src/test/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/aws/StreamLambdaHandlerTest.java (95%) rename src/test/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/aws/repository/LocalDateTimeConverterTest.java (81%) rename src/test/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/aws/repository/URLConverterTest.java (86%) rename src/test/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/aws/repository/oauth1/OAuth1CredentialAWSRepositoryTest.java (75%) rename src/test/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/aws/repository/oauth2/OAuth2CredentialAWSRepositoryTest.java (83%) rename src/test/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/aws/repository/post/PostAWSRepositoryTest.java (90%) rename src/test/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/controller/PostsControllerTest.java (86%) rename src/test/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/socialmedia/linkedin/LinkedInPublisherTest.java (94%) rename src/test/java/com/coderstower/socialmediapubisher/{springpublisher => }/main/socialmedia/twitter/TwitterPublisherTest.java (94%) diff --git a/aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/MockSocialMediaSuccessfulTests.java b/aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/MockSocialMediaSuccessfulTests.java index 07344da..a73c03a 100644 --- a/aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/MockSocialMediaSuccessfulTests.java +++ b/aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/MockSocialMediaSuccessfulTests.java @@ -11,7 +11,6 @@ import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput; import com.amazonaws.services.dynamodbv2.model.PutItemRequest; import com.amazonaws.services.dynamodbv2.model.ScalarAttributeType; -import com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin.*; import org.junit.jupiter.api.Test; import org.junitpioneer.jupiter.SetSystemProperty; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/PostPublisher.java b/src/main/java/com/coderstower/socialmediapubisher/abstraction/post/PostPublisher.java similarity index 79% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/PostPublisher.java rename to src/main/java/com/coderstower/socialmediapubisher/abstraction/post/PostPublisher.java index 2fab901..410889d 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/PostPublisher.java +++ b/src/main/java/com/coderstower/socialmediapubisher/abstraction/post/PostPublisher.java @@ -1,10 +1,10 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.post; +package com.coderstower.socialmediapubisher.abstraction.post; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.PostRepository; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Acknowledge; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Publication; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.SocialMediaPublisher; +import com.coderstower.socialmediapubisher.abstraction.post.repository.Post; +import com.coderstower.socialmediapubisher.abstraction.post.repository.PostRepository; +import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.Acknowledge; +import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.Publication; +import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.SocialMediaPublisher; import java.time.Clock; import java.time.LocalDateTime; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/repository/Post.java b/src/main/java/com/coderstower/socialmediapubisher/abstraction/post/repository/Post.java similarity index 91% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/repository/Post.java rename to src/main/java/com/coderstower/socialmediapubisher/abstraction/post/repository/Post.java index 3c5b945..fc76c7e 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/repository/Post.java +++ b/src/main/java/com/coderstower/socialmediapubisher/abstraction/post/repository/Post.java @@ -1,6 +1,6 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository; +package com.coderstower.socialmediapubisher.abstraction.post.repository; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Publication; +import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.Publication; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/repository/PostRepository.java b/src/main/java/com/coderstower/socialmediapubisher/abstraction/post/repository/PostRepository.java similarity index 61% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/repository/PostRepository.java rename to src/main/java/com/coderstower/socialmediapubisher/abstraction/post/repository/PostRepository.java index 19ad73f..3caaeac 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/repository/PostRepository.java +++ b/src/main/java/com/coderstower/socialmediapubisher/abstraction/post/repository/PostRepository.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository; +package com.coderstower.socialmediapubisher.abstraction.post.repository; import java.util.Optional; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/Acknowledge.java b/src/main/java/com/coderstower/socialmediapubisher/abstraction/post/socialmedia/Acknowledge.java similarity index 74% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/Acknowledge.java rename to src/main/java/com/coderstower/socialmediapubisher/abstraction/post/socialmedia/Acknowledge.java index 3500256..b20be6b 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/Acknowledge.java +++ b/src/main/java/com/coderstower/socialmediapubisher/abstraction/post/socialmedia/Acknowledge.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia; +package com.coderstower.socialmediapubisher.abstraction.post.socialmedia; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/Publication.java b/src/main/java/com/coderstower/socialmediapubisher/abstraction/post/socialmedia/Publication.java similarity index 80% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/Publication.java rename to src/main/java/com/coderstower/socialmediapubisher/abstraction/post/socialmedia/Publication.java index 2b80d75..0c5548e 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/Publication.java +++ b/src/main/java/com/coderstower/socialmediapubisher/abstraction/post/socialmedia/Publication.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia; +package com.coderstower.socialmediapubisher.abstraction.post.socialmedia; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/coderstower/socialmediapubisher/abstraction/post/socialmedia/SocialMediaPublisher.java b/src/main/java/com/coderstower/socialmediapubisher/abstraction/post/socialmedia/SocialMediaPublisher.java new file mode 100644 index 0000000..2310ede --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/abstraction/post/socialmedia/SocialMediaPublisher.java @@ -0,0 +1,11 @@ +package com.coderstower.socialmediapubisher.abstraction.post.socialmedia; + +import com.coderstower.socialmediapubisher.abstraction.post.repository.Post; + +import java.util.List; + +public interface SocialMediaPublisher { + String getName(); + Acknowledge ping(); + List publish(Post post); +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2AccessTokenResponseConverterWithDefaults.java b/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/OAuth2AccessTokenResponseConverterWithDefaults.java similarity index 97% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2AccessTokenResponseConverterWithDefaults.java rename to src/main/java/com/coderstower/socialmediapubisher/abstraction/security/OAuth2AccessTokenResponseConverterWithDefaults.java index 826dea4..83eadad 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2AccessTokenResponseConverterWithDefaults.java +++ b/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/OAuth2AccessTokenResponseConverterWithDefaults.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.security; +package com.coderstower.socialmediapubisher.abstraction.security; import org.springframework.core.convert.converter.Converter; import org.springframework.security.oauth2.core.OAuth2AccessToken; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsManager.java b/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/OAuth2CredentialsManager.java similarity index 85% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsManager.java rename to src/main/java/com/coderstower/socialmediapubisher/abstraction/security/OAuth2CredentialsManager.java index f578999..df928d9 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsManager.java +++ b/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/OAuth2CredentialsManager.java @@ -1,7 +1,7 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.security; +package com.coderstower.socialmediapubisher.abstraction.security; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2Credentials; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2CredentialsRepository; +import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth2CredentialsRepository; +import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth2Credentials; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import java.time.LocalDateTime; diff --git a/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/OAuth2CredentialsUpdated.java b/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/OAuth2CredentialsUpdated.java new file mode 100644 index 0000000..db68ba2 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/OAuth2CredentialsUpdated.java @@ -0,0 +1,11 @@ +package com.coderstower.socialmediapubisher.abstraction.security; + +import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth2Credentials; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class OAuth2CredentialsUpdated { + private final OAuth2Credentials oAuth2Credentials; +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/UnauthorizedException.java b/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/UnauthorizedException.java similarity index 69% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/UnauthorizedException.java rename to src/main/java/com/coderstower/socialmediapubisher/abstraction/security/UnauthorizedException.java index 840d1ef..49cf7cb 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/UnauthorizedException.java +++ b/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/UnauthorizedException.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.security; +package com.coderstower.socialmediapubisher.abstraction.security; public class UnauthorizedException extends RuntimeException { public UnauthorizedException() { diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth1Credentials.java b/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/repository/OAuth1Credentials.java similarity index 75% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth1Credentials.java rename to src/main/java/com/coderstower/socialmediapubisher/abstraction/security/repository/OAuth1Credentials.java index 44fb499..1682e64 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth1Credentials.java +++ b/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/repository/OAuth1Credentials.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository; +package com.coderstower.socialmediapubisher.abstraction.security.repository; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth1CredentialsRepository.java b/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/repository/OAuth1CredentialsRepository.java similarity index 59% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth1CredentialsRepository.java rename to src/main/java/com/coderstower/socialmediapubisher/abstraction/security/repository/OAuth1CredentialsRepository.java index 1a9cee7..3560252 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth1CredentialsRepository.java +++ b/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/repository/OAuth1CredentialsRepository.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository; +package com.coderstower.socialmediapubisher.abstraction.security.repository; import java.util.Optional; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth2Credentials.java b/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/repository/OAuth2Credentials.java similarity index 86% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth2Credentials.java rename to src/main/java/com/coderstower/socialmediapubisher/abstraction/security/repository/OAuth2Credentials.java index 3362ff5..5a29cc9 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth2Credentials.java +++ b/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/repository/OAuth2Credentials.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository; +package com.coderstower.socialmediapubisher.abstraction.security.repository; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth2CredentialsRepository.java b/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/repository/OAuth2CredentialsRepository.java similarity index 78% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth2CredentialsRepository.java rename to src/main/java/com/coderstower/socialmediapubisher/abstraction/security/repository/OAuth2CredentialsRepository.java index be73c19..9427f38 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth2CredentialsRepository.java +++ b/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/repository/OAuth2CredentialsRepository.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository; +package com.coderstower.socialmediapubisher.abstraction.security.repository; import java.util.List; import java.util.Optional; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/SpringPublisherApplication.java b/src/main/java/com/coderstower/socialmediapubisher/main/SpringPublisherApplication.java similarity index 76% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/SpringPublisherApplication.java rename to src/main/java/com/coderstower/socialmediapubisher/main/SpringPublisherApplication.java index 822ed8a..28dd11a 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/SpringPublisherApplication.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/SpringPublisherApplication.java @@ -1,7 +1,8 @@ -package com.coderstower.socialmediapubisher.springpublisher.main; +package com.coderstower.socialmediapubisher.main; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; @SpringBootApplication public class SpringPublisherApplication { diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/AWSSpringPublisherApplication.java b/src/main/java/com/coderstower/socialmediapubisher/main/aws/AWSSpringPublisherApplication.java similarity index 78% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/AWSSpringPublisherApplication.java rename to src/main/java/com/coderstower/socialmediapubisher/main/aws/AWSSpringPublisherApplication.java index f90a8fe..355feea 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/AWSSpringPublisherApplication.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/aws/AWSSpringPublisherApplication.java @@ -1,10 +1,10 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws; +package com.coderstower.socialmediapubisher.main.aws; -import com.coderstower.socialmediapubisher.springpublisher.main.controller.ErrorHandler; -import com.coderstower.socialmediapubisher.springpublisher.main.controller.OAuth2CredentialsController; -import com.coderstower.socialmediapubisher.springpublisher.main.controller.PostsController; -import com.coderstower.socialmediapubisher.springpublisher.main.factory.SecurityFactory; -import com.coderstower.socialmediapubisher.springpublisher.main.factory.SpringPublisherFactory; +import com.coderstower.socialmediapubisher.main.controller.OAuth2CredentialsController; +import com.coderstower.socialmediapubisher.main.factory.SecurityFactory; +import com.coderstower.socialmediapubisher.main.controller.ErrorHandler; +import com.coderstower.socialmediapubisher.main.controller.PostsController; +import com.coderstower.socialmediapubisher.main.factory.SpringPublisherFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/SpringPublisherDynamoDBRepositoryFactory.java b/src/main/java/com/coderstower/socialmediapubisher/main/aws/SpringPublisherDynamoDBRepositoryFactory.java similarity index 74% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/SpringPublisherDynamoDBRepositoryFactory.java rename to src/main/java/com/coderstower/socialmediapubisher/main/aws/SpringPublisherDynamoDBRepositoryFactory.java index fc378ec..84090c6 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/SpringPublisherDynamoDBRepositoryFactory.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/aws/SpringPublisherDynamoDBRepositoryFactory.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws; +package com.coderstower.socialmediapubisher.main.aws; import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; @@ -6,12 +6,12 @@ import com.amazonaws.regions.Regions; import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder; -import com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth1.OAuth1CredentialAWSRepository; -import com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth1.OAuth1CredentialDynamoRepository; -import com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth2.OAuth2CredentialAWSRepository; -import com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth2.OAuth2CredentialDynamoRepository; -import com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.post.PostAWSRepository; -import com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.post.PostDynamoRepository; +import com.coderstower.socialmediapubisher.main.aws.repository.oauth1.OAuth1CredentialAWSRepository; +import com.coderstower.socialmediapubisher.main.aws.repository.oauth1.OAuth1CredentialDynamoRepository; +import com.coderstower.socialmediapubisher.main.aws.repository.oauth2.OAuth2CredentialAWSRepository; +import com.coderstower.socialmediapubisher.main.aws.repository.oauth2.OAuth2CredentialDynamoRepository; +import com.coderstower.socialmediapubisher.main.aws.repository.post.PostAWSRepository; +import com.coderstower.socialmediapubisher.main.aws.repository.post.PostDynamoRepository; import org.socialsignin.spring.data.dynamodb.repository.config.EnableDynamoDBRepositories; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; @@ -20,7 +20,7 @@ @Configuration @EnableDynamoDBRepositories - (basePackages = "com.coderstower.socialmediapubisher.springpublisher.main.aws.repository") + (basePackages = "com.coderstower.socialmediapubisher.main.aws.repository") public class SpringPublisherDynamoDBRepositoryFactory { @Bean("amazonDynamoDB") @Profile("!local") diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/StreamLambdaHandler.java b/src/main/java/com/coderstower/socialmediapubisher/main/aws/StreamLambdaHandler.java similarity index 94% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/StreamLambdaHandler.java rename to src/main/java/com/coderstower/socialmediapubisher/main/aws/StreamLambdaHandler.java index bbf1798..0a1bd62 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/StreamLambdaHandler.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/aws/StreamLambdaHandler.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws; +package com.coderstower.socialmediapubisher.main.aws; import com.amazonaws.serverless.exceptions.ContainerInitializationException; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/LocalDateTimeConverter.java b/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/LocalDateTimeConverter.java similarity index 84% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/LocalDateTimeConverter.java rename to src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/LocalDateTimeConverter.java index 8b22cf5..840e21b 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/LocalDateTimeConverter.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/LocalDateTimeConverter.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository; +package com.coderstower.socialmediapubisher.main.aws.repository; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverter; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/URLConverter.java b/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/URLConverter.java similarity index 87% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/URLConverter.java rename to src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/URLConverter.java index 2bcb44b..09fea12 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/URLConverter.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/URLConverter.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository; +package com.coderstower.socialmediapubisher.main.aws.repository; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverter; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepository.java b/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepository.java similarity index 77% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepository.java rename to src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepository.java index b0d28c3..745e9f0 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepository.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepository.java @@ -1,7 +1,7 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth1; +package com.coderstower.socialmediapubisher.main.aws.repository.oauth1; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth1Credentials; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth1CredentialsRepository; +import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth1Credentials; +import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth1CredentialsRepository; import java.util.Optional; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialDynamo.java b/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialDynamo.java similarity index 89% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialDynamo.java rename to src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialDynamo.java index d870fc8..3cf0b86 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialDynamo.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialDynamo.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth1; +package com.coderstower.socialmediapubisher.main.aws.repository.oauth1; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialDynamoRepository.java b/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialDynamoRepository.java similarity index 72% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialDynamoRepository.java rename to src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialDynamoRepository.java index c9723ee..a57f7cb 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialDynamoRepository.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialDynamoRepository.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth1; +package com.coderstower.socialmediapubisher.main.aws.repository.oauth1; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepository.java b/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepository.java similarity index 87% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepository.java rename to src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepository.java index 4cc4340..820e3e9 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepository.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepository.java @@ -1,7 +1,7 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth2; +package com.coderstower.socialmediapubisher.main.aws.repository.oauth2; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2Credentials; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2CredentialsRepository; +import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth2CredentialsRepository; +import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth2Credentials; import java.util.List; import java.util.Optional; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialDynamo.java b/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialDynamo.java similarity index 82% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialDynamo.java rename to src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialDynamo.java index 9932991..a582692 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialDynamo.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialDynamo.java @@ -1,10 +1,10 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth2; +package com.coderstower.socialmediapubisher.main.aws.repository.oauth2; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverted; -import com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.LocalDateTimeConverter; +import com.coderstower.socialmediapubisher.main.aws.repository.LocalDateTimeConverter; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialDynamoRepository.java b/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialDynamoRepository.java similarity index 73% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialDynamoRepository.java rename to src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialDynamoRepository.java index 84acf5a..81b4c81 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialDynamoRepository.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialDynamoRepository.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth2; +package com.coderstower.socialmediapubisher.main.aws.repository.oauth2; import org.socialsignin.spring.data.dynamodb.repository.EnableScan; import org.springframework.data.repository.CrudRepository; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostAWSRepository.java b/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostAWSRepository.java similarity index 85% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostAWSRepository.java rename to src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostAWSRepository.java index 9f0a83a..3c8ecdc 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostAWSRepository.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostAWSRepository.java @@ -1,7 +1,7 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.post; +package com.coderstower.socialmediapubisher.main.aws.repository.post; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.PostRepository; +import com.coderstower.socialmediapubisher.abstraction.post.repository.Post; +import com.coderstower.socialmediapubisher.abstraction.post.repository.PostRepository; import java.util.Comparator; import java.util.Optional; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostDynamo.java b/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostDynamo.java similarity index 75% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostDynamo.java rename to src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostDynamo.java index bf5c02d..f1fe8f9 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostDynamo.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostDynamo.java @@ -1,8 +1,8 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.post; +package com.coderstower.socialmediapubisher.main.aws.repository.post; import com.amazonaws.services.dynamodbv2.datamodeling.*; -import com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.LocalDateTimeConverter; -import com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.URLConverter; +import com.coderstower.socialmediapubisher.main.aws.repository.URLConverter; +import com.coderstower.socialmediapubisher.main.aws.repository.LocalDateTimeConverter; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostDynamoRepository.java b/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostDynamoRepository.java similarity index 73% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostDynamoRepository.java rename to src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostDynamoRepository.java index 88ded39..ddbda9e 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostDynamoRepository.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostDynamoRepository.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.post; +package com.coderstower.socialmediapubisher.main.aws.repository.post; import org.socialsignin.spring.data.dynamodb.repository.EnableScan; import org.springframework.data.repository.CrudRepository; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/ErrorHandler.java b/src/main/java/com/coderstower/socialmediapubisher/main/controller/ErrorHandler.java similarity index 70% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/ErrorHandler.java rename to src/main/java/com/coderstower/socialmediapubisher/main/controller/ErrorHandler.java index 51d8e31..6d678a1 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/ErrorHandler.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/controller/ErrorHandler.java @@ -1,6 +1,6 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.controller; +package com.coderstower.socialmediapubisher.main.controller; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.UnauthorizedException; +import com.coderstower.socialmediapubisher.abstraction.security.UnauthorizedException; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/OAuth2CredentialsController.java b/src/main/java/com/coderstower/socialmediapubisher/main/controller/OAuth2CredentialsController.java similarity index 88% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/OAuth2CredentialsController.java rename to src/main/java/com/coderstower/socialmediapubisher/main/controller/OAuth2CredentialsController.java index 3d15ea2..935ee97 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/OAuth2CredentialsController.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/controller/OAuth2CredentialsController.java @@ -1,7 +1,7 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.controller; +package com.coderstower.socialmediapubisher.main.controller; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.OAuth2CredentialsManager; +import com.coderstower.socialmediapubisher.abstraction.security.OAuth2CredentialsManager; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/PostsController.java b/src/main/java/com/coderstower/socialmediapubisher/main/controller/PostsController.java similarity index 80% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/PostsController.java rename to src/main/java/com/coderstower/socialmediapubisher/main/controller/PostsController.java index 6201df2..ce3b08f 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/PostsController.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/controller/PostsController.java @@ -1,8 +1,8 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.controller; +package com.coderstower.socialmediapubisher.main.controller; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.PostPublisher; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; +import com.coderstower.socialmediapubisher.abstraction.post.PostPublisher; +import com.coderstower.socialmediapubisher.abstraction.post.repository.Post; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/CredentialsProperties.java b/src/main/java/com/coderstower/socialmediapubisher/main/factory/CredentialsProperties.java similarity index 79% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/CredentialsProperties.java rename to src/main/java/com/coderstower/socialmediapubisher/main/factory/CredentialsProperties.java index 767d86f..c3030f1 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/CredentialsProperties.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/factory/CredentialsProperties.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.factory; +package com.coderstower.socialmediapubisher.main.factory; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SecurityFactory.java b/src/main/java/com/coderstower/socialmediapubisher/main/factory/SecurityFactory.java similarity index 95% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SecurityFactory.java rename to src/main/java/com/coderstower/socialmediapubisher/main/factory/SecurityFactory.java index 852d93b..38d7a29 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SecurityFactory.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/factory/SecurityFactory.java @@ -1,6 +1,6 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.factory; +package com.coderstower.socialmediapubisher.main.factory; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.OAuth2AccessTokenResponseConverterWithDefaults; +import com.coderstower.socialmediapubisher.abstraction.security.OAuth2AccessTokenResponseConverterWithDefaults; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import org.springframework.core.annotation.Order; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SocialMediaPublisherProperties.java b/src/main/java/com/coderstower/socialmediapubisher/main/factory/SocialMediaPublisherProperties.java similarity index 86% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SocialMediaPublisherProperties.java rename to src/main/java/com/coderstower/socialmediapubisher/main/factory/SocialMediaPublisherProperties.java index a47c283..592a85e 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SocialMediaPublisherProperties.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/factory/SocialMediaPublisherProperties.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.factory; +package com.coderstower.socialmediapubisher.main.factory; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SpringPublisherFactory.java b/src/main/java/com/coderstower/socialmediapubisher/main/factory/SpringPublisherFactory.java similarity index 68% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SpringPublisherFactory.java rename to src/main/java/com/coderstower/socialmediapubisher/main/factory/SpringPublisherFactory.java index 0381f89..93219a6 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SpringPublisherFactory.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/factory/SpringPublisherFactory.java @@ -1,13 +1,13 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.factory; - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.PostPublisher; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.PostRepository; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.SocialMediaPublisher; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.OAuth2CredentialsManager; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth1CredentialsRepository; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2CredentialsRepository; -import com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin.LinkedInPublisher; -import com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.twitter.TwitterPublisher; +package com.coderstower.socialmediapubisher.main.factory; + +import com.coderstower.socialmediapubisher.abstraction.post.PostPublisher; +import com.coderstower.socialmediapubisher.abstraction.post.repository.PostRepository; +import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.SocialMediaPublisher; +import com.coderstower.socialmediapubisher.abstraction.security.OAuth2CredentialsManager; +import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth1CredentialsRepository; +import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth2CredentialsRepository; +import com.coderstower.socialmediapubisher.main.socialmedia.linkedin.LinkedInPublisher; +import com.coderstower.socialmediapubisher.main.socialmedia.twitter.TwitterPublisher; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/ArticleContent.java b/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/ArticleContent.java similarity index 69% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/ArticleContent.java rename to src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/ArticleContent.java index cd1d298..302d0b2 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/ArticleContent.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/ArticleContent.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin; +package com.coderstower.socialmediapubisher.main.socialmedia.linkedin; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Content.java b/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/Content.java similarity index 59% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Content.java rename to src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/Content.java index cfa8965..0e11716 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Content.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/Content.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin; +package com.coderstower.socialmediapubisher.main.socialmedia.linkedin; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Distribution.java b/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/Distribution.java similarity index 60% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Distribution.java rename to src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/Distribution.java index 597be90..df6d7c4 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Distribution.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/Distribution.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin; +package com.coderstower.socialmediapubisher.main.socialmedia.linkedin; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInPublisher.java b/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/LinkedInPublisher.java similarity index 89% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInPublisher.java rename to src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/LinkedInPublisher.java index 995471c..9639e23 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInPublisher.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/LinkedInPublisher.java @@ -1,12 +1,12 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin; - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Acknowledge; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Publication; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.SocialMediaPublisher; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.UnauthorizedException; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2Credentials; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2CredentialsRepository; +package com.coderstower.socialmediapubisher.main.socialmedia.linkedin; + +import com.coderstower.socialmediapubisher.abstraction.post.repository.Post; +import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.Acknowledge; +import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.SocialMediaPublisher; +import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth2CredentialsRepository; +import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.Publication; +import com.coderstower.socialmediapubisher.abstraction.security.UnauthorizedException; +import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth2Credentials; import lombok.extern.slf4j.Slf4j; import org.springframework.http.*; import org.springframework.web.client.HttpClientErrorException; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInShare.java b/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/LinkedInShare.java similarity index 78% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInShare.java rename to src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/LinkedInShare.java index 4cbd39a..890ad7b 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInShare.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/LinkedInShare.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin; +package com.coderstower.socialmediapubisher.main.socialmedia.linkedin; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Media.java b/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/Media.java similarity index 71% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Media.java rename to src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/Media.java index 71f30fd..09e7287 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Media.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/Media.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin; +package com.coderstower.socialmediapubisher.main.socialmedia.linkedin; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Profile.java b/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/Profile.java similarity index 71% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Profile.java rename to src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/Profile.java index 9282df0..5320cba 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Profile.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/Profile.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin; +package com.coderstower.socialmediapubisher.main.socialmedia.linkedin; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Text.java b/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/Text.java similarity index 56% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Text.java rename to src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/Text.java index 7d48e12..5640743 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Text.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/Text.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin; +package com.coderstower.socialmediapubisher.main.socialmedia.linkedin; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/twitter/TwitterPublisher.java b/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/twitter/TwitterPublisher.java similarity index 85% rename from src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/twitter/TwitterPublisher.java rename to src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/twitter/TwitterPublisher.java index c039bcb..4e9e41d 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/twitter/TwitterPublisher.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/twitter/TwitterPublisher.java @@ -1,11 +1,11 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.twitter; - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Acknowledge; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Publication; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.SocialMediaPublisher; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth1Credentials; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth1CredentialsRepository; +package com.coderstower.socialmediapubisher.main.socialmedia.twitter; + +import com.coderstower.socialmediapubisher.abstraction.post.repository.Post; +import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.Acknowledge; +import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.SocialMediaPublisher; +import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth1Credentials; +import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth1CredentialsRepository; +import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.Publication; import lombok.extern.slf4j.Slf4j; import twitter4j.Paging; import twitter4j.Status; diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/SocialMediaPublisher.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/SocialMediaPublisher.java deleted file mode 100644 index a555fc8..0000000 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/SocialMediaPublisher.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia; - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; - -import java.util.List; - -public interface SocialMediaPublisher { - String getName(); - Acknowledge ping(); - List publish(Post post); -} diff --git a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsUpdated.java b/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsUpdated.java deleted file mode 100644 index e977097..0000000 --- a/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsUpdated.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.security; - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2Credentials; -import lombok.Builder; -import lombok.Data; - -@Data -@Builder -public class OAuth2CredentialsUpdated { - private final OAuth2Credentials oAuth2Credentials; -} diff --git a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/PostPublisherTest.java b/src/test/java/com/coderstower/socialmediapubisher/abstraction/post/PostPublisherTest.java similarity index 93% rename from src/test/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/PostPublisherTest.java rename to src/test/java/com/coderstower/socialmediapubisher/abstraction/post/PostPublisherTest.java index 01e1c46..1477c48 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/PostPublisherTest.java +++ b/src/test/java/com/coderstower/socialmediapubisher/abstraction/post/PostPublisherTest.java @@ -1,10 +1,11 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.post; - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.PostRepository; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Acknowledge; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Publication; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.SocialMediaPublisher; +package com.coderstower.socialmediapubisher.abstraction.post; + +import com.coderstower.socialmediapubisher.abstraction.post.PostPublisher; +import com.coderstower.socialmediapubisher.abstraction.post.repository.Post; +import com.coderstower.socialmediapubisher.abstraction.post.repository.PostRepository; +import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.Acknowledge; +import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.Publication; +import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.SocialMediaPublisher; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsManagerTest.java b/src/test/java/com/coderstower/socialmediapubisher/abstraction/security/OAuth2CredentialsManagerTest.java similarity index 91% rename from src/test/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsManagerTest.java rename to src/test/java/com/coderstower/socialmediapubisher/abstraction/security/OAuth2CredentialsManagerTest.java index c1b728e..3186af9 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsManagerTest.java +++ b/src/test/java/com/coderstower/socialmediapubisher/abstraction/security/OAuth2CredentialsManagerTest.java @@ -1,7 +1,9 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.security; +package com.coderstower.socialmediapubisher.abstraction.security; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2Credentials; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2CredentialsRepository; +import com.coderstower.socialmediapubisher.abstraction.security.OAuth2CredentialsManager; +import com.coderstower.socialmediapubisher.abstraction.security.UnauthorizedException; +import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth2Credentials; +import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth2CredentialsRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/MockSocialMediaCredentialsHandlingTests.java b/src/test/java/com/coderstower/socialmediapubisher/main/aws/MockSocialMediaCredentialsHandlingTests.java similarity index 99% rename from src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/MockSocialMediaCredentialsHandlingTests.java rename to src/test/java/com/coderstower/socialmediapubisher/main/aws/MockSocialMediaCredentialsHandlingTests.java index fecc68f..b1e7147 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/MockSocialMediaCredentialsHandlingTests.java +++ b/src/test/java/com/coderstower/socialmediapubisher/main/aws/MockSocialMediaCredentialsHandlingTests.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws; +package com.coderstower.socialmediapubisher.main.aws; import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; import com.amazonaws.services.dynamodbv2.local.embedded.DynamoDBEmbedded; diff --git a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/MockSocialMediaSuccessfulTests.java b/src/test/java/com/coderstower/socialmediapubisher/main/aws/MockSocialMediaSuccessfulTests.java similarity index 98% rename from src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/MockSocialMediaSuccessfulTests.java rename to src/test/java/com/coderstower/socialmediapubisher/main/aws/MockSocialMediaSuccessfulTests.java index c6a8756..847a533 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/MockSocialMediaSuccessfulTests.java +++ b/src/test/java/com/coderstower/socialmediapubisher/main/aws/MockSocialMediaSuccessfulTests.java @@ -1,9 +1,9 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws; +package com.coderstower.socialmediapubisher.main.aws; import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; import com.amazonaws.services.dynamodbv2.local.embedded.DynamoDBEmbedded; import com.amazonaws.services.dynamodbv2.model.*; -import com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin.*; +import com.coderstower.socialmediapubisher.main.socialmedia.linkedin.*; import org.junit.jupiter.api.Test; import org.junitpioneer.jupiter.SetSystemProperty; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/StreamLambdaHandlerTest.java b/src/test/java/com/coderstower/socialmediapubisher/main/aws/StreamLambdaHandlerTest.java similarity index 95% rename from src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/StreamLambdaHandlerTest.java rename to src/test/java/com/coderstower/socialmediapubisher/main/aws/StreamLambdaHandlerTest.java index 5c92f5b..9914610 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/StreamLambdaHandlerTest.java +++ b/src/test/java/com/coderstower/socialmediapubisher/main/aws/StreamLambdaHandlerTest.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws; +package com.coderstower.socialmediapubisher.main.aws; import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; @@ -6,6 +6,7 @@ import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import com.amazonaws.services.lambda.runtime.Context; +import com.coderstower.socialmediapubisher.main.aws.StreamLambdaHandler; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/LocalDateTimeConverterTest.java b/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/LocalDateTimeConverterTest.java similarity index 81% rename from src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/LocalDateTimeConverterTest.java rename to src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/LocalDateTimeConverterTest.java index 00e8a7e..899bc77 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/LocalDateTimeConverterTest.java +++ b/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/LocalDateTimeConverterTest.java @@ -1,5 +1,6 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository; +package com.coderstower.socialmediapubisher.main.aws.repository; +import com.coderstower.socialmediapubisher.main.aws.repository.LocalDateTimeConverter; import org.junit.jupiter.api.Test; import java.time.LocalDateTime; diff --git a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/URLConverterTest.java b/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/URLConverterTest.java similarity index 86% rename from src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/URLConverterTest.java rename to src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/URLConverterTest.java index 590c9c7..3573ae9 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/URLConverterTest.java +++ b/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/URLConverterTest.java @@ -1,5 +1,6 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository; +package com.coderstower.socialmediapubisher.main.aws.repository; +import com.coderstower.socialmediapubisher.main.aws.repository.URLConverter; import org.junit.jupiter.api.Test; import java.net.MalformedURLException; diff --git a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepositoryTest.java b/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepositoryTest.java similarity index 75% rename from src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepositoryTest.java rename to src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepositoryTest.java index 5c30a0b..627342e 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepositoryTest.java +++ b/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepositoryTest.java @@ -1,6 +1,9 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth1; +package com.coderstower.socialmediapubisher.main.aws.repository.oauth1; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth1Credentials; +import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth1Credentials; +import com.coderstower.socialmediapubisher.main.aws.repository.oauth1.OAuth1CredentialAWSRepository; +import com.coderstower.socialmediapubisher.main.aws.repository.oauth1.OAuth1CredentialDynamo; +import com.coderstower.socialmediapubisher.main.aws.repository.oauth1.OAuth1CredentialDynamoRepository; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; diff --git a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepositoryTest.java b/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepositoryTest.java similarity index 83% rename from src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepositoryTest.java rename to src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepositoryTest.java index 3914451..5da1092 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepositoryTest.java +++ b/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepositoryTest.java @@ -1,6 +1,9 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth2; +package com.coderstower.socialmediapubisher.main.aws.repository.oauth2; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2Credentials; +import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth2Credentials; +import com.coderstower.socialmediapubisher.main.aws.repository.oauth2.OAuth2CredentialAWSRepository; +import com.coderstower.socialmediapubisher.main.aws.repository.oauth2.OAuth2CredentialDynamo; +import com.coderstower.socialmediapubisher.main.aws.repository.oauth2.OAuth2CredentialDynamoRepository; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; diff --git a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostAWSRepositoryTest.java b/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostAWSRepositoryTest.java similarity index 90% rename from src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostAWSRepositoryTest.java rename to src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostAWSRepositoryTest.java index 6a33102..415bf8d 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostAWSRepositoryTest.java +++ b/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostAWSRepositoryTest.java @@ -1,6 +1,9 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.post; +package com.coderstower.socialmediapubisher.main.aws.repository.post; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; +import com.coderstower.socialmediapubisher.abstraction.post.repository.Post; +import com.coderstower.socialmediapubisher.main.aws.repository.post.PostAWSRepository; +import com.coderstower.socialmediapubisher.main.aws.repository.post.PostDynamo; +import com.coderstower.socialmediapubisher.main.aws.repository.post.PostDynamoRepository; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; diff --git a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/PostsControllerTest.java b/src/test/java/com/coderstower/socialmediapubisher/main/controller/PostsControllerTest.java similarity index 86% rename from src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/PostsControllerTest.java rename to src/test/java/com/coderstower/socialmediapubisher/main/controller/PostsControllerTest.java index d96ab8c..0f09fa9 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/PostsControllerTest.java +++ b/src/test/java/com/coderstower/socialmediapubisher/main/controller/PostsControllerTest.java @@ -1,8 +1,9 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.controller; +package com.coderstower.socialmediapubisher.main.controller; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.PostPublisher; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Publication; +import com.coderstower.socialmediapubisher.abstraction.post.PostPublisher; +import com.coderstower.socialmediapubisher.abstraction.post.repository.Post; +import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.Publication; +import com.coderstower.socialmediapubisher.main.controller.PostsController; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; diff --git a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInPublisherTest.java b/src/test/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/LinkedInPublisherTest.java similarity index 94% rename from src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInPublisherTest.java rename to src/test/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/LinkedInPublisherTest.java index 9400bfb..00e29cc 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInPublisherTest.java +++ b/src/test/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/LinkedInPublisherTest.java @@ -1,11 +1,12 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin; - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Acknowledge; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Publication; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.UnauthorizedException; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2Credentials; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2CredentialsRepository; +package com.coderstower.socialmediapubisher.main.socialmedia.linkedin; + +import com.coderstower.socialmediapubisher.abstraction.post.repository.Post; +import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.Acknowledge; +import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.Publication; +import com.coderstower.socialmediapubisher.abstraction.security.UnauthorizedException; +import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth2Credentials; +import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth2CredentialsRepository; +import com.coderstower.socialmediapubisher.main.socialmedia.linkedin.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/twitter/TwitterPublisherTest.java b/src/test/java/com/coderstower/socialmediapubisher/main/socialmedia/twitter/TwitterPublisherTest.java similarity index 94% rename from src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/twitter/TwitterPublisherTest.java rename to src/test/java/com/coderstower/socialmediapubisher/main/socialmedia/twitter/TwitterPublisherTest.java index 9b08fe8..9b37439 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/twitter/TwitterPublisherTest.java +++ b/src/test/java/com/coderstower/socialmediapubisher/main/socialmedia/twitter/TwitterPublisherTest.java @@ -1,10 +1,11 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.twitter; - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Acknowledge; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Publication; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth1Credentials; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth1CredentialsRepository; +package com.coderstower.socialmediapubisher.main.socialmedia.twitter; + +import com.coderstower.socialmediapubisher.abstraction.post.repository.Post; +import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.Acknowledge; +import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.Publication; +import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth1Credentials; +import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth1CredentialsRepository; +import com.coderstower.socialmediapubisher.main.socialmedia.twitter.TwitterPublisher; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; From a453978c038dfe9e49a4eaac7d52bdfd1a72a1d2 Mon Sep 17 00:00:00 2001 From: Daniel Pelaez Date: Sun, 14 Apr 2024 13:55:42 -0500 Subject: [PATCH 07/17] Moving classes to root --- .../security/OAuth2CredentialsUpdated.java | 11 ----------- .../post/PostPublisher.java | 12 ++++++------ .../post/repository/Post.java | 4 ++-- .../post/repository/PostRepository.java | 2 +- .../post/socialmedia/Acknowledge.java | 2 +- .../post/socialmedia/Publication.java | 2 +- .../post/socialmedia/SocialMediaPublisher.java | 4 ++-- ...2AccessTokenResponseConverterWithDefaults.java | 2 +- .../security/OAuth2CredentialsManager.java | 6 +++--- .../domain/security/OAuth2CredentialsUpdated.java | 11 +++++++++++ .../security/UnauthorizedException.java | 2 +- .../security/repository/OAuth1Credentials.java | 2 +- .../repository/OAuth1CredentialsRepository.java | 2 +- .../security/repository/OAuth2Credentials.java | 2 +- .../repository/OAuth2CredentialsRepository.java | 2 +- .../oauth1/OAuth1CredentialAWSRepository.java | 4 ++-- .../oauth2/OAuth2CredentialAWSRepository.java | 4 ++-- .../aws/repository/post/PostAWSRepository.java | 4 ++-- .../main/controller/ErrorHandler.java | 2 +- .../controller/OAuth2CredentialsController.java | 2 +- .../main/controller/PostsController.java | 4 ++-- .../main/factory/SecurityFactory.java | 2 +- .../main/factory/SpringPublisherFactory.java | 12 ++++++------ .../socialmedia/linkedin/LinkedInPublisher.java | 14 +++++++------- .../socialmedia/twitter/TwitterPublisher.java | 12 ++++++------ .../post/PostPublisherTest.java | 15 +++++++-------- .../security/OAuth2CredentialsManagerTest.java | 8 +++----- .../oauth1/OAuth1CredentialAWSRepositoryTest.java | 5 +---- .../oauth2/OAuth2CredentialAWSRepositoryTest.java | 5 +---- .../repository/post/PostAWSRepositoryTest.java | 5 +---- .../main/controller/PostsControllerTest.java | 7 +++---- .../linkedin/LinkedInPublisherTest.java | 13 ++++++------- .../socialmedia/twitter/TwitterPublisherTest.java | 11 +++++------ 33 files changed, 90 insertions(+), 105 deletions(-) delete mode 100644 src/main/java/com/coderstower/socialmediapubisher/abstraction/security/OAuth2CredentialsUpdated.java rename src/main/java/com/coderstower/socialmediapubisher/{abstraction => domain}/post/PostPublisher.java (82%) rename src/main/java/com/coderstower/socialmediapubisher/{abstraction => domain}/post/repository/Post.java (93%) rename src/main/java/com/coderstower/socialmediapubisher/{abstraction => domain}/post/repository/PostRepository.java (66%) rename src/main/java/com/coderstower/socialmediapubisher/{abstraction => domain}/post/socialmedia/Acknowledge.java (78%) rename src/main/java/com/coderstower/socialmediapubisher/{abstraction => domain}/post/socialmedia/Publication.java (83%) rename src/main/java/com/coderstower/socialmediapubisher/{abstraction => domain}/post/socialmedia/SocialMediaPublisher.java (50%) rename src/main/java/com/coderstower/socialmediapubisher/{abstraction => domain}/security/OAuth2AccessTokenResponseConverterWithDefaults.java (98%) rename src/main/java/com/coderstower/socialmediapubisher/{abstraction => domain}/security/OAuth2CredentialsManager.java (87%) create mode 100644 src/main/java/com/coderstower/socialmediapubisher/domain/security/OAuth2CredentialsUpdated.java rename src/main/java/com/coderstower/socialmediapubisher/{abstraction => domain}/security/UnauthorizedException.java (74%) rename src/main/java/com/coderstower/socialmediapubisher/{abstraction => domain}/security/repository/OAuth1Credentials.java (78%) rename src/main/java/com/coderstower/socialmediapubisher/{abstraction => domain}/security/repository/OAuth1CredentialsRepository.java (64%) rename src/main/java/com/coderstower/socialmediapubisher/{abstraction => domain}/security/repository/OAuth2Credentials.java (88%) rename src/main/java/com/coderstower/socialmediapubisher/{abstraction => domain}/security/repository/OAuth2CredentialsRepository.java (81%) rename src/test/java/com/coderstower/socialmediapubisher/{abstraction => domain}/post/PostPublisherTest.java (93%) rename src/test/java/com/coderstower/socialmediapubisher/{abstraction => domain}/security/OAuth2CredentialsManagerTest.java (91%) diff --git a/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/OAuth2CredentialsUpdated.java b/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/OAuth2CredentialsUpdated.java deleted file mode 100644 index db68ba2..0000000 --- a/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/OAuth2CredentialsUpdated.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.coderstower.socialmediapubisher.abstraction.security; - -import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth2Credentials; -import lombok.Builder; -import lombok.Data; - -@Data -@Builder -public class OAuth2CredentialsUpdated { - private final OAuth2Credentials oAuth2Credentials; -} diff --git a/src/main/java/com/coderstower/socialmediapubisher/abstraction/post/PostPublisher.java b/src/main/java/com/coderstower/socialmediapubisher/domain/post/PostPublisher.java similarity index 82% rename from src/main/java/com/coderstower/socialmediapubisher/abstraction/post/PostPublisher.java rename to src/main/java/com/coderstower/socialmediapubisher/domain/post/PostPublisher.java index 410889d..fc3213e 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/abstraction/post/PostPublisher.java +++ b/src/main/java/com/coderstower/socialmediapubisher/domain/post/PostPublisher.java @@ -1,10 +1,10 @@ -package com.coderstower.socialmediapubisher.abstraction.post; +package com.coderstower.socialmediapubisher.domain.post; -import com.coderstower.socialmediapubisher.abstraction.post.repository.Post; -import com.coderstower.socialmediapubisher.abstraction.post.repository.PostRepository; -import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.Acknowledge; -import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.Publication; -import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.SocialMediaPublisher; +import com.coderstower.socialmediapubisher.domain.post.repository.Post; +import com.coderstower.socialmediapubisher.domain.post.repository.PostRepository; +import com.coderstower.socialmediapubisher.domain.post.socialmedia.Acknowledge; +import com.coderstower.socialmediapubisher.domain.post.socialmedia.Publication; +import com.coderstower.socialmediapubisher.domain.post.socialmedia.SocialMediaPublisher; import java.time.Clock; import java.time.LocalDateTime; diff --git a/src/main/java/com/coderstower/socialmediapubisher/abstraction/post/repository/Post.java b/src/main/java/com/coderstower/socialmediapubisher/domain/post/repository/Post.java similarity index 93% rename from src/main/java/com/coderstower/socialmediapubisher/abstraction/post/repository/Post.java rename to src/main/java/com/coderstower/socialmediapubisher/domain/post/repository/Post.java index fc76c7e..81bc9ad 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/abstraction/post/repository/Post.java +++ b/src/main/java/com/coderstower/socialmediapubisher/domain/post/repository/Post.java @@ -1,6 +1,6 @@ -package com.coderstower.socialmediapubisher.abstraction.post.repository; +package com.coderstower.socialmediapubisher.domain.post.repository; -import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.Publication; +import com.coderstower.socialmediapubisher.domain.post.socialmedia.Publication; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/coderstower/socialmediapubisher/abstraction/post/repository/PostRepository.java b/src/main/java/com/coderstower/socialmediapubisher/domain/post/repository/PostRepository.java similarity index 66% rename from src/main/java/com/coderstower/socialmediapubisher/abstraction/post/repository/PostRepository.java rename to src/main/java/com/coderstower/socialmediapubisher/domain/post/repository/PostRepository.java index 3caaeac..03b5308 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/abstraction/post/repository/PostRepository.java +++ b/src/main/java/com/coderstower/socialmediapubisher/domain/post/repository/PostRepository.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.abstraction.post.repository; +package com.coderstower.socialmediapubisher.domain.post.repository; import java.util.Optional; diff --git a/src/main/java/com/coderstower/socialmediapubisher/abstraction/post/socialmedia/Acknowledge.java b/src/main/java/com/coderstower/socialmediapubisher/domain/post/socialmedia/Acknowledge.java similarity index 78% rename from src/main/java/com/coderstower/socialmediapubisher/abstraction/post/socialmedia/Acknowledge.java rename to src/main/java/com/coderstower/socialmediapubisher/domain/post/socialmedia/Acknowledge.java index b20be6b..de2399b 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/abstraction/post/socialmedia/Acknowledge.java +++ b/src/main/java/com/coderstower/socialmediapubisher/domain/post/socialmedia/Acknowledge.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.abstraction.post.socialmedia; +package com.coderstower.socialmediapubisher.domain.post.socialmedia; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/coderstower/socialmediapubisher/abstraction/post/socialmedia/Publication.java b/src/main/java/com/coderstower/socialmediapubisher/domain/post/socialmedia/Publication.java similarity index 83% rename from src/main/java/com/coderstower/socialmediapubisher/abstraction/post/socialmedia/Publication.java rename to src/main/java/com/coderstower/socialmediapubisher/domain/post/socialmedia/Publication.java index 0c5548e..ed0860f 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/abstraction/post/socialmedia/Publication.java +++ b/src/main/java/com/coderstower/socialmediapubisher/domain/post/socialmedia/Publication.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.abstraction.post.socialmedia; +package com.coderstower.socialmediapubisher.domain.post.socialmedia; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/coderstower/socialmediapubisher/abstraction/post/socialmedia/SocialMediaPublisher.java b/src/main/java/com/coderstower/socialmediapubisher/domain/post/socialmedia/SocialMediaPublisher.java similarity index 50% rename from src/main/java/com/coderstower/socialmediapubisher/abstraction/post/socialmedia/SocialMediaPublisher.java rename to src/main/java/com/coderstower/socialmediapubisher/domain/post/socialmedia/SocialMediaPublisher.java index 2310ede..2dfe29d 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/abstraction/post/socialmedia/SocialMediaPublisher.java +++ b/src/main/java/com/coderstower/socialmediapubisher/domain/post/socialmedia/SocialMediaPublisher.java @@ -1,6 +1,6 @@ -package com.coderstower.socialmediapubisher.abstraction.post.socialmedia; +package com.coderstower.socialmediapubisher.domain.post.socialmedia; -import com.coderstower.socialmediapubisher.abstraction.post.repository.Post; +import com.coderstower.socialmediapubisher.domain.post.repository.Post; import java.util.List; diff --git a/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/OAuth2AccessTokenResponseConverterWithDefaults.java b/src/main/java/com/coderstower/socialmediapubisher/domain/security/OAuth2AccessTokenResponseConverterWithDefaults.java similarity index 98% rename from src/main/java/com/coderstower/socialmediapubisher/abstraction/security/OAuth2AccessTokenResponseConverterWithDefaults.java rename to src/main/java/com/coderstower/socialmediapubisher/domain/security/OAuth2AccessTokenResponseConverterWithDefaults.java index 83eadad..f92ba75 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/OAuth2AccessTokenResponseConverterWithDefaults.java +++ b/src/main/java/com/coderstower/socialmediapubisher/domain/security/OAuth2AccessTokenResponseConverterWithDefaults.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.abstraction.security; +package com.coderstower.socialmediapubisher.domain.security; import org.springframework.core.convert.converter.Converter; import org.springframework.security.oauth2.core.OAuth2AccessToken; diff --git a/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/OAuth2CredentialsManager.java b/src/main/java/com/coderstower/socialmediapubisher/domain/security/OAuth2CredentialsManager.java similarity index 87% rename from src/main/java/com/coderstower/socialmediapubisher/abstraction/security/OAuth2CredentialsManager.java rename to src/main/java/com/coderstower/socialmediapubisher/domain/security/OAuth2CredentialsManager.java index df928d9..42d27a5 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/OAuth2CredentialsManager.java +++ b/src/main/java/com/coderstower/socialmediapubisher/domain/security/OAuth2CredentialsManager.java @@ -1,7 +1,7 @@ -package com.coderstower.socialmediapubisher.abstraction.security; +package com.coderstower.socialmediapubisher.domain.security; -import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth2CredentialsRepository; -import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth2Credentials; +import com.coderstower.socialmediapubisher.domain.security.repository.OAuth2CredentialsRepository; +import com.coderstower.socialmediapubisher.domain.security.repository.OAuth2Credentials; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import java.time.LocalDateTime; diff --git a/src/main/java/com/coderstower/socialmediapubisher/domain/security/OAuth2CredentialsUpdated.java b/src/main/java/com/coderstower/socialmediapubisher/domain/security/OAuth2CredentialsUpdated.java new file mode 100644 index 0000000..320b33f --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/domain/security/OAuth2CredentialsUpdated.java @@ -0,0 +1,11 @@ +package com.coderstower.socialmediapubisher.domain.security; + +import com.coderstower.socialmediapubisher.domain.security.repository.OAuth2Credentials; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class OAuth2CredentialsUpdated { + private final OAuth2Credentials oAuth2Credentials; +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/UnauthorizedException.java b/src/main/java/com/coderstower/socialmediapubisher/domain/security/UnauthorizedException.java similarity index 74% rename from src/main/java/com/coderstower/socialmediapubisher/abstraction/security/UnauthorizedException.java rename to src/main/java/com/coderstower/socialmediapubisher/domain/security/UnauthorizedException.java index 49cf7cb..7f37b37 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/UnauthorizedException.java +++ b/src/main/java/com/coderstower/socialmediapubisher/domain/security/UnauthorizedException.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.abstraction.security; +package com.coderstower.socialmediapubisher.domain.security; public class UnauthorizedException extends RuntimeException { public UnauthorizedException() { diff --git a/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/repository/OAuth1Credentials.java b/src/main/java/com/coderstower/socialmediapubisher/domain/security/repository/OAuth1Credentials.java similarity index 78% rename from src/main/java/com/coderstower/socialmediapubisher/abstraction/security/repository/OAuth1Credentials.java rename to src/main/java/com/coderstower/socialmediapubisher/domain/security/repository/OAuth1Credentials.java index 1682e64..f78a9a5 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/repository/OAuth1Credentials.java +++ b/src/main/java/com/coderstower/socialmediapubisher/domain/security/repository/OAuth1Credentials.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.abstraction.security.repository; +package com.coderstower.socialmediapubisher.domain.security.repository; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/repository/OAuth1CredentialsRepository.java b/src/main/java/com/coderstower/socialmediapubisher/domain/security/repository/OAuth1CredentialsRepository.java similarity index 64% rename from src/main/java/com/coderstower/socialmediapubisher/abstraction/security/repository/OAuth1CredentialsRepository.java rename to src/main/java/com/coderstower/socialmediapubisher/domain/security/repository/OAuth1CredentialsRepository.java index 3560252..21176b6 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/repository/OAuth1CredentialsRepository.java +++ b/src/main/java/com/coderstower/socialmediapubisher/domain/security/repository/OAuth1CredentialsRepository.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.abstraction.security.repository; +package com.coderstower.socialmediapubisher.domain.security.repository; import java.util.Optional; diff --git a/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/repository/OAuth2Credentials.java b/src/main/java/com/coderstower/socialmediapubisher/domain/security/repository/OAuth2Credentials.java similarity index 88% rename from src/main/java/com/coderstower/socialmediapubisher/abstraction/security/repository/OAuth2Credentials.java rename to src/main/java/com/coderstower/socialmediapubisher/domain/security/repository/OAuth2Credentials.java index 5a29cc9..ee22756 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/repository/OAuth2Credentials.java +++ b/src/main/java/com/coderstower/socialmediapubisher/domain/security/repository/OAuth2Credentials.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.abstraction.security.repository; +package com.coderstower.socialmediapubisher.domain.security.repository; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/repository/OAuth2CredentialsRepository.java b/src/main/java/com/coderstower/socialmediapubisher/domain/security/repository/OAuth2CredentialsRepository.java similarity index 81% rename from src/main/java/com/coderstower/socialmediapubisher/abstraction/security/repository/OAuth2CredentialsRepository.java rename to src/main/java/com/coderstower/socialmediapubisher/domain/security/repository/OAuth2CredentialsRepository.java index 9427f38..7239117 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/abstraction/security/repository/OAuth2CredentialsRepository.java +++ b/src/main/java/com/coderstower/socialmediapubisher/domain/security/repository/OAuth2CredentialsRepository.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.abstraction.security.repository; +package com.coderstower.socialmediapubisher.domain.security.repository; import java.util.List; import java.util.Optional; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepository.java b/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepository.java index 745e9f0..e393bb8 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepository.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepository.java @@ -1,7 +1,7 @@ package com.coderstower.socialmediapubisher.main.aws.repository.oauth1; -import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth1Credentials; -import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth1CredentialsRepository; +import com.coderstower.socialmediapubisher.domain.security.repository.OAuth1Credentials; +import com.coderstower.socialmediapubisher.domain.security.repository.OAuth1CredentialsRepository; import java.util.Optional; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepository.java b/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepository.java index 820e3e9..1cf5df1 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepository.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepository.java @@ -1,7 +1,7 @@ package com.coderstower.socialmediapubisher.main.aws.repository.oauth2; -import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth2CredentialsRepository; -import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth2Credentials; +import com.coderstower.socialmediapubisher.domain.security.repository.OAuth2CredentialsRepository; +import com.coderstower.socialmediapubisher.domain.security.repository.OAuth2Credentials; import java.util.List; import java.util.Optional; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostAWSRepository.java b/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostAWSRepository.java index 3c8ecdc..4584189 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostAWSRepository.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostAWSRepository.java @@ -1,7 +1,7 @@ package com.coderstower.socialmediapubisher.main.aws.repository.post; -import com.coderstower.socialmediapubisher.abstraction.post.repository.Post; -import com.coderstower.socialmediapubisher.abstraction.post.repository.PostRepository; +import com.coderstower.socialmediapubisher.domain.post.repository.Post; +import com.coderstower.socialmediapubisher.domain.post.repository.PostRepository; import java.util.Comparator; import java.util.Optional; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/controller/ErrorHandler.java b/src/main/java/com/coderstower/socialmediapubisher/main/controller/ErrorHandler.java index 6d678a1..6108d28 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/controller/ErrorHandler.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/controller/ErrorHandler.java @@ -1,6 +1,6 @@ package com.coderstower.socialmediapubisher.main.controller; -import com.coderstower.socialmediapubisher.abstraction.security.UnauthorizedException; +import com.coderstower.socialmediapubisher.domain.security.UnauthorizedException; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/controller/OAuth2CredentialsController.java b/src/main/java/com/coderstower/socialmediapubisher/main/controller/OAuth2CredentialsController.java index 935ee97..04801a1 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/controller/OAuth2CredentialsController.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/controller/OAuth2CredentialsController.java @@ -1,7 +1,7 @@ package com.coderstower.socialmediapubisher.main.controller; -import com.coderstower.socialmediapubisher.abstraction.security.OAuth2CredentialsManager; +import com.coderstower.socialmediapubisher.domain.security.OAuth2CredentialsManager; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/controller/PostsController.java b/src/main/java/com/coderstower/socialmediapubisher/main/controller/PostsController.java index ce3b08f..e7eb28d 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/controller/PostsController.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/controller/PostsController.java @@ -1,8 +1,8 @@ package com.coderstower.socialmediapubisher.main.controller; -import com.coderstower.socialmediapubisher.abstraction.post.PostPublisher; -import com.coderstower.socialmediapubisher.abstraction.post.repository.Post; +import com.coderstower.socialmediapubisher.domain.post.PostPublisher; +import com.coderstower.socialmediapubisher.domain.post.repository.Post; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/factory/SecurityFactory.java b/src/main/java/com/coderstower/socialmediapubisher/main/factory/SecurityFactory.java index 38d7a29..967095b 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/factory/SecurityFactory.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/factory/SecurityFactory.java @@ -1,6 +1,6 @@ package com.coderstower.socialmediapubisher.main.factory; -import com.coderstower.socialmediapubisher.abstraction.security.OAuth2AccessTokenResponseConverterWithDefaults; +import com.coderstower.socialmediapubisher.domain.security.OAuth2AccessTokenResponseConverterWithDefaults; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import org.springframework.core.annotation.Order; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/factory/SpringPublisherFactory.java b/src/main/java/com/coderstower/socialmediapubisher/main/factory/SpringPublisherFactory.java index 93219a6..5c620ca 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/factory/SpringPublisherFactory.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/factory/SpringPublisherFactory.java @@ -1,11 +1,11 @@ package com.coderstower.socialmediapubisher.main.factory; -import com.coderstower.socialmediapubisher.abstraction.post.PostPublisher; -import com.coderstower.socialmediapubisher.abstraction.post.repository.PostRepository; -import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.SocialMediaPublisher; -import com.coderstower.socialmediapubisher.abstraction.security.OAuth2CredentialsManager; -import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth1CredentialsRepository; -import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth2CredentialsRepository; +import com.coderstower.socialmediapubisher.domain.post.PostPublisher; +import com.coderstower.socialmediapubisher.domain.post.repository.PostRepository; +import com.coderstower.socialmediapubisher.domain.post.socialmedia.SocialMediaPublisher; +import com.coderstower.socialmediapubisher.domain.security.OAuth2CredentialsManager; +import com.coderstower.socialmediapubisher.domain.security.repository.OAuth1CredentialsRepository; +import com.coderstower.socialmediapubisher.domain.security.repository.OAuth2CredentialsRepository; import com.coderstower.socialmediapubisher.main.socialmedia.linkedin.LinkedInPublisher; import com.coderstower.socialmediapubisher.main.socialmedia.twitter.TwitterPublisher; import org.springframework.boot.context.properties.EnableConfigurationProperties; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/LinkedInPublisher.java b/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/LinkedInPublisher.java index 9639e23..520d83a 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/LinkedInPublisher.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/LinkedInPublisher.java @@ -1,12 +1,12 @@ package com.coderstower.socialmediapubisher.main.socialmedia.linkedin; -import com.coderstower.socialmediapubisher.abstraction.post.repository.Post; -import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.Acknowledge; -import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.SocialMediaPublisher; -import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth2CredentialsRepository; -import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.Publication; -import com.coderstower.socialmediapubisher.abstraction.security.UnauthorizedException; -import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth2Credentials; +import com.coderstower.socialmediapubisher.domain.post.repository.Post; +import com.coderstower.socialmediapubisher.domain.post.socialmedia.Acknowledge; +import com.coderstower.socialmediapubisher.domain.post.socialmedia.SocialMediaPublisher; +import com.coderstower.socialmediapubisher.domain.security.repository.OAuth2CredentialsRepository; +import com.coderstower.socialmediapubisher.domain.post.socialmedia.Publication; +import com.coderstower.socialmediapubisher.domain.security.UnauthorizedException; +import com.coderstower.socialmediapubisher.domain.security.repository.OAuth2Credentials; import lombok.extern.slf4j.Slf4j; import org.springframework.http.*; import org.springframework.web.client.HttpClientErrorException; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/twitter/TwitterPublisher.java b/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/twitter/TwitterPublisher.java index 4e9e41d..7943445 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/twitter/TwitterPublisher.java +++ b/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/twitter/TwitterPublisher.java @@ -1,11 +1,11 @@ package com.coderstower.socialmediapubisher.main.socialmedia.twitter; -import com.coderstower.socialmediapubisher.abstraction.post.repository.Post; -import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.Acknowledge; -import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.SocialMediaPublisher; -import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth1Credentials; -import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth1CredentialsRepository; -import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.Publication; +import com.coderstower.socialmediapubisher.domain.post.repository.Post; +import com.coderstower.socialmediapubisher.domain.post.socialmedia.Acknowledge; +import com.coderstower.socialmediapubisher.domain.post.socialmedia.SocialMediaPublisher; +import com.coderstower.socialmediapubisher.domain.security.repository.OAuth1Credentials; +import com.coderstower.socialmediapubisher.domain.security.repository.OAuth1CredentialsRepository; +import com.coderstower.socialmediapubisher.domain.post.socialmedia.Publication; import lombok.extern.slf4j.Slf4j; import twitter4j.Paging; import twitter4j.Status; diff --git a/src/test/java/com/coderstower/socialmediapubisher/abstraction/post/PostPublisherTest.java b/src/test/java/com/coderstower/socialmediapubisher/domain/post/PostPublisherTest.java similarity index 93% rename from src/test/java/com/coderstower/socialmediapubisher/abstraction/post/PostPublisherTest.java rename to src/test/java/com/coderstower/socialmediapubisher/domain/post/PostPublisherTest.java index 1477c48..3aab2a2 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/abstraction/post/PostPublisherTest.java +++ b/src/test/java/com/coderstower/socialmediapubisher/domain/post/PostPublisherTest.java @@ -1,11 +1,10 @@ -package com.coderstower.socialmediapubisher.abstraction.post; - -import com.coderstower.socialmediapubisher.abstraction.post.PostPublisher; -import com.coderstower.socialmediapubisher.abstraction.post.repository.Post; -import com.coderstower.socialmediapubisher.abstraction.post.repository.PostRepository; -import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.Acknowledge; -import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.Publication; -import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.SocialMediaPublisher; +package com.coderstower.socialmediapubisher.domain.post; + +import com.coderstower.socialmediapubisher.domain.post.repository.Post; +import com.coderstower.socialmediapubisher.domain.post.repository.PostRepository; +import com.coderstower.socialmediapubisher.domain.post.socialmedia.Acknowledge; +import com.coderstower.socialmediapubisher.domain.post.socialmedia.Publication; +import com.coderstower.socialmediapubisher.domain.post.socialmedia.SocialMediaPublisher; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/src/test/java/com/coderstower/socialmediapubisher/abstraction/security/OAuth2CredentialsManagerTest.java b/src/test/java/com/coderstower/socialmediapubisher/domain/security/OAuth2CredentialsManagerTest.java similarity index 91% rename from src/test/java/com/coderstower/socialmediapubisher/abstraction/security/OAuth2CredentialsManagerTest.java rename to src/test/java/com/coderstower/socialmediapubisher/domain/security/OAuth2CredentialsManagerTest.java index 3186af9..09cb133 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/abstraction/security/OAuth2CredentialsManagerTest.java +++ b/src/test/java/com/coderstower/socialmediapubisher/domain/security/OAuth2CredentialsManagerTest.java @@ -1,9 +1,7 @@ -package com.coderstower.socialmediapubisher.abstraction.security; +package com.coderstower.socialmediapubisher.domain.security; -import com.coderstower.socialmediapubisher.abstraction.security.OAuth2CredentialsManager; -import com.coderstower.socialmediapubisher.abstraction.security.UnauthorizedException; -import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth2Credentials; -import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth2CredentialsRepository; +import com.coderstower.socialmediapubisher.domain.security.repository.OAuth2Credentials; +import com.coderstower.socialmediapubisher.domain.security.repository.OAuth2CredentialsRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepositoryTest.java b/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepositoryTest.java index 627342e..6971a4f 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepositoryTest.java +++ b/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepositoryTest.java @@ -1,9 +1,6 @@ package com.coderstower.socialmediapubisher.main.aws.repository.oauth1; -import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth1Credentials; -import com.coderstower.socialmediapubisher.main.aws.repository.oauth1.OAuth1CredentialAWSRepository; -import com.coderstower.socialmediapubisher.main.aws.repository.oauth1.OAuth1CredentialDynamo; -import com.coderstower.socialmediapubisher.main.aws.repository.oauth1.OAuth1CredentialDynamoRepository; +import com.coderstower.socialmediapubisher.domain.security.repository.OAuth1Credentials; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; diff --git a/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepositoryTest.java b/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepositoryTest.java index 5da1092..a14f0b4 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepositoryTest.java +++ b/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepositoryTest.java @@ -1,9 +1,6 @@ package com.coderstower.socialmediapubisher.main.aws.repository.oauth2; -import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth2Credentials; -import com.coderstower.socialmediapubisher.main.aws.repository.oauth2.OAuth2CredentialAWSRepository; -import com.coderstower.socialmediapubisher.main.aws.repository.oauth2.OAuth2CredentialDynamo; -import com.coderstower.socialmediapubisher.main.aws.repository.oauth2.OAuth2CredentialDynamoRepository; +import com.coderstower.socialmediapubisher.domain.security.repository.OAuth2Credentials; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; diff --git a/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostAWSRepositoryTest.java b/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostAWSRepositoryTest.java index 415bf8d..eaebe5a 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostAWSRepositoryTest.java +++ b/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostAWSRepositoryTest.java @@ -1,9 +1,6 @@ package com.coderstower.socialmediapubisher.main.aws.repository.post; -import com.coderstower.socialmediapubisher.abstraction.post.repository.Post; -import com.coderstower.socialmediapubisher.main.aws.repository.post.PostAWSRepository; -import com.coderstower.socialmediapubisher.main.aws.repository.post.PostDynamo; -import com.coderstower.socialmediapubisher.main.aws.repository.post.PostDynamoRepository; +import com.coderstower.socialmediapubisher.domain.post.repository.Post; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; diff --git a/src/test/java/com/coderstower/socialmediapubisher/main/controller/PostsControllerTest.java b/src/test/java/com/coderstower/socialmediapubisher/main/controller/PostsControllerTest.java index 0f09fa9..37ea18f 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/main/controller/PostsControllerTest.java +++ b/src/test/java/com/coderstower/socialmediapubisher/main/controller/PostsControllerTest.java @@ -1,9 +1,8 @@ package com.coderstower.socialmediapubisher.main.controller; -import com.coderstower.socialmediapubisher.abstraction.post.PostPublisher; -import com.coderstower.socialmediapubisher.abstraction.post.repository.Post; -import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.Publication; -import com.coderstower.socialmediapubisher.main.controller.PostsController; +import com.coderstower.socialmediapubisher.domain.post.PostPublisher; +import com.coderstower.socialmediapubisher.domain.post.repository.Post; +import com.coderstower.socialmediapubisher.domain.post.socialmedia.Publication; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; diff --git a/src/test/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/LinkedInPublisherTest.java b/src/test/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/LinkedInPublisherTest.java index 00e29cc..ce3b696 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/LinkedInPublisherTest.java +++ b/src/test/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/LinkedInPublisherTest.java @@ -1,12 +1,11 @@ package com.coderstower.socialmediapubisher.main.socialmedia.linkedin; -import com.coderstower.socialmediapubisher.abstraction.post.repository.Post; -import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.Acknowledge; -import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.Publication; -import com.coderstower.socialmediapubisher.abstraction.security.UnauthorizedException; -import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth2Credentials; -import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth2CredentialsRepository; -import com.coderstower.socialmediapubisher.main.socialmedia.linkedin.*; +import com.coderstower.socialmediapubisher.domain.post.repository.Post; +import com.coderstower.socialmediapubisher.domain.post.socialmedia.Acknowledge; +import com.coderstower.socialmediapubisher.domain.post.socialmedia.Publication; +import com.coderstower.socialmediapubisher.domain.security.UnauthorizedException; +import com.coderstower.socialmediapubisher.domain.security.repository.OAuth2Credentials; +import com.coderstower.socialmediapubisher.domain.security.repository.OAuth2CredentialsRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/src/test/java/com/coderstower/socialmediapubisher/main/socialmedia/twitter/TwitterPublisherTest.java b/src/test/java/com/coderstower/socialmediapubisher/main/socialmedia/twitter/TwitterPublisherTest.java index 9b37439..22ed531 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/main/socialmedia/twitter/TwitterPublisherTest.java +++ b/src/test/java/com/coderstower/socialmediapubisher/main/socialmedia/twitter/TwitterPublisherTest.java @@ -1,11 +1,10 @@ package com.coderstower.socialmediapubisher.main.socialmedia.twitter; -import com.coderstower.socialmediapubisher.abstraction.post.repository.Post; -import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.Acknowledge; -import com.coderstower.socialmediapubisher.abstraction.post.socialmedia.Publication; -import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth1Credentials; -import com.coderstower.socialmediapubisher.abstraction.security.repository.OAuth1CredentialsRepository; -import com.coderstower.socialmediapubisher.main.socialmedia.twitter.TwitterPublisher; +import com.coderstower.socialmediapubisher.domain.post.repository.Post; +import com.coderstower.socialmediapubisher.domain.post.socialmedia.Acknowledge; +import com.coderstower.socialmediapubisher.domain.post.socialmedia.Publication; +import com.coderstower.socialmediapubisher.domain.security.repository.OAuth1Credentials; +import com.coderstower.socialmediapubisher.domain.security.repository.OAuth1CredentialsRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; From 403b79c149cd665019d66ed383ddde81ec7b2839 Mon Sep 17 00:00:00 2001 From: Daniel Pelaez Date: Sun, 14 Apr 2024 13:58:02 -0500 Subject: [PATCH 08/17] Moving classes to root --- .../SpringPublisherApplication.java | 3 +-- .../aws/AWSSpringPublisherApplication.java | 12 ++++++------ ...SpringPublisherDynamoDBRepositoryFactory.java | 16 ++++++++-------- .../aws/StreamLambdaHandler.java | 2 +- .../aws/repository/LocalDateTimeConverter.java | 2 +- .../aws/repository/URLConverter.java | 2 +- .../oauth1/OAuth1CredentialAWSRepository.java | 2 +- .../oauth1/OAuth1CredentialDynamo.java | 2 +- .../oauth1/OAuth1CredentialDynamoRepository.java | 2 +- .../oauth2/OAuth2CredentialAWSRepository.java | 2 +- .../oauth2/OAuth2CredentialDynamo.java | 4 ++-- .../oauth2/OAuth2CredentialDynamoRepository.java | 2 +- .../aws/repository/post/PostAWSRepository.java | 2 +- .../aws/repository/post/PostDynamo.java | 6 +++--- .../repository/post/PostDynamoRepository.java | 2 +- .../controller/ErrorHandler.java | 2 +- .../controller/OAuth2CredentialsController.java | 2 +- .../controller/PostsController.java | 2 +- .../factory/CredentialsProperties.java | 2 +- .../factory/SecurityFactory.java | 2 +- .../factory/SocialMediaPublisherProperties.java | 2 +- .../factory/SpringPublisherFactory.java | 6 +++--- .../socialmedia/linkedin/ArticleContent.java | 2 +- .../socialmedia/linkedin/Content.java | 2 +- .../socialmedia/linkedin/Distribution.java | 2 +- .../socialmedia/linkedin/LinkedInPublisher.java | 2 +- .../socialmedia/linkedin/LinkedInShare.java | 2 +- .../socialmedia/linkedin/Media.java | 2 +- .../socialmedia/linkedin/Profile.java | 2 +- .../socialmedia/linkedin/Text.java | 2 +- .../socialmedia/twitter/TwitterPublisher.java | 2 +- .../MockSocialMediaCredentialsHandlingTests.java | 2 +- .../aws/MockSocialMediaSuccessfulTests.java | 4 ++-- .../aws/StreamLambdaHandlerTest.java | 3 +-- .../repository/LocalDateTimeConverterTest.java | 3 +-- .../aws/repository/URLConverterTest.java | 3 +-- .../OAuth1CredentialAWSRepositoryTest.java | 2 +- .../OAuth2CredentialAWSRepositoryTest.java | 2 +- .../repository/post/PostAWSRepositoryTest.java | 2 +- .../controller/PostsControllerTest.java | 2 +- .../linkedin/LinkedInPublisherTest.java | 2 +- .../twitter/TwitterPublisherTest.java | 2 +- 42 files changed, 60 insertions(+), 64 deletions(-) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/SpringPublisherApplication.java (76%) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/aws/AWSSpringPublisherApplication.java (80%) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/aws/SpringPublisherDynamoDBRepositoryFactory.java (75%) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/aws/StreamLambdaHandler.java (95%) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/aws/repository/LocalDateTimeConverter.java (86%) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/aws/repository/URLConverter.java (88%) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/aws/repository/oauth1/OAuth1CredentialAWSRepository.java (94%) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/aws/repository/oauth1/OAuth1CredentialDynamo.java (90%) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/aws/repository/oauth1/OAuth1CredentialDynamoRepository.java (74%) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/aws/repository/oauth2/OAuth2CredentialAWSRepository.java (96%) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/aws/repository/oauth2/OAuth2CredentialDynamo.java (83%) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/aws/repository/oauth2/OAuth2CredentialDynamoRepository.java (75%) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/aws/repository/post/PostAWSRepository.java (95%) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/aws/repository/post/PostDynamo.java (77%) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/aws/repository/post/PostDynamoRepository.java (75%) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/controller/ErrorHandler.java (88%) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/controller/OAuth2CredentialsController.java (95%) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/controller/PostsController.java (94%) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/factory/CredentialsProperties.java (81%) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/factory/SecurityFactory.java (98%) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/factory/SocialMediaPublisherProperties.java (87%) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/factory/SpringPublisherFactory.java (91%) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/socialmedia/linkedin/ArticleContent.java (71%) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/socialmedia/linkedin/Content.java (61%) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/socialmedia/linkedin/Distribution.java (63%) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/socialmedia/linkedin/LinkedInPublisher.java (98%) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/socialmedia/linkedin/LinkedInShare.java (80%) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/socialmedia/linkedin/Media.java (73%) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/socialmedia/linkedin/Profile.java (73%) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/socialmedia/linkedin/Text.java (59%) rename src/main/java/com/coderstower/socialmediapubisher/{main => application}/socialmedia/twitter/TwitterPublisher.java (98%) rename src/test/java/com/coderstower/socialmediapubisher/{main => application}/aws/MockSocialMediaCredentialsHandlingTests.java (99%) rename src/test/java/com/coderstower/socialmediapubisher/{main => application}/aws/MockSocialMediaSuccessfulTests.java (98%) rename src/test/java/com/coderstower/socialmediapubisher/{main => application}/aws/StreamLambdaHandlerTest.java (95%) rename src/test/java/com/coderstower/socialmediapubisher/{main => application}/aws/repository/LocalDateTimeConverterTest.java (81%) rename src/test/java/com/coderstower/socialmediapubisher/{main => application}/aws/repository/URLConverterTest.java (86%) rename src/test/java/com/coderstower/socialmediapubisher/{main => application}/aws/repository/oauth1/OAuth1CredentialAWSRepositoryTest.java (95%) rename src/test/java/com/coderstower/socialmediapubisher/{main => application}/aws/repository/oauth2/OAuth2CredentialAWSRepositoryTest.java (96%) rename src/test/java/com/coderstower/socialmediapubisher/{main => application}/aws/repository/post/PostAWSRepositoryTest.java (98%) rename src/test/java/com/coderstower/socialmediapubisher/{main => application}/controller/PostsControllerTest.java (97%) rename src/test/java/com/coderstower/socialmediapubisher/{main => application}/socialmedia/linkedin/LinkedInPublisherTest.java (99%) rename src/test/java/com/coderstower/socialmediapubisher/{main => application}/socialmedia/twitter/TwitterPublisherTest.java (99%) diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/SpringPublisherApplication.java b/src/main/java/com/coderstower/socialmediapubisher/application/SpringPublisherApplication.java similarity index 76% rename from src/main/java/com/coderstower/socialmediapubisher/main/SpringPublisherApplication.java rename to src/main/java/com/coderstower/socialmediapubisher/application/SpringPublisherApplication.java index 28dd11a..88e13e9 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/SpringPublisherApplication.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/SpringPublisherApplication.java @@ -1,8 +1,7 @@ -package com.coderstower.socialmediapubisher.main; +package com.coderstower.socialmediapubisher.application; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.ComponentScan; @SpringBootApplication public class SpringPublisherApplication { diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/aws/AWSSpringPublisherApplication.java b/src/main/java/com/coderstower/socialmediapubisher/application/aws/AWSSpringPublisherApplication.java similarity index 80% rename from src/main/java/com/coderstower/socialmediapubisher/main/aws/AWSSpringPublisherApplication.java rename to src/main/java/com/coderstower/socialmediapubisher/application/aws/AWSSpringPublisherApplication.java index 355feea..89406f7 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/aws/AWSSpringPublisherApplication.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/aws/AWSSpringPublisherApplication.java @@ -1,10 +1,10 @@ -package com.coderstower.socialmediapubisher.main.aws; +package com.coderstower.socialmediapubisher.application.aws; -import com.coderstower.socialmediapubisher.main.controller.OAuth2CredentialsController; -import com.coderstower.socialmediapubisher.main.factory.SecurityFactory; -import com.coderstower.socialmediapubisher.main.controller.ErrorHandler; -import com.coderstower.socialmediapubisher.main.controller.PostsController; -import com.coderstower.socialmediapubisher.main.factory.SpringPublisherFactory; +import com.coderstower.socialmediapubisher.application.controller.OAuth2CredentialsController; +import com.coderstower.socialmediapubisher.application.factory.SecurityFactory; +import com.coderstower.socialmediapubisher.application.controller.ErrorHandler; +import com.coderstower.socialmediapubisher.application.controller.PostsController; +import com.coderstower.socialmediapubisher.application.factory.SpringPublisherFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/aws/SpringPublisherDynamoDBRepositoryFactory.java b/src/main/java/com/coderstower/socialmediapubisher/application/aws/SpringPublisherDynamoDBRepositoryFactory.java similarity index 75% rename from src/main/java/com/coderstower/socialmediapubisher/main/aws/SpringPublisherDynamoDBRepositoryFactory.java rename to src/main/java/com/coderstower/socialmediapubisher/application/aws/SpringPublisherDynamoDBRepositoryFactory.java index 84090c6..064d5b5 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/aws/SpringPublisherDynamoDBRepositoryFactory.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/aws/SpringPublisherDynamoDBRepositoryFactory.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.aws; +package com.coderstower.socialmediapubisher.application.aws; import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; @@ -6,12 +6,12 @@ import com.amazonaws.regions.Regions; import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder; -import com.coderstower.socialmediapubisher.main.aws.repository.oauth1.OAuth1CredentialAWSRepository; -import com.coderstower.socialmediapubisher.main.aws.repository.oauth1.OAuth1CredentialDynamoRepository; -import com.coderstower.socialmediapubisher.main.aws.repository.oauth2.OAuth2CredentialAWSRepository; -import com.coderstower.socialmediapubisher.main.aws.repository.oauth2.OAuth2CredentialDynamoRepository; -import com.coderstower.socialmediapubisher.main.aws.repository.post.PostAWSRepository; -import com.coderstower.socialmediapubisher.main.aws.repository.post.PostDynamoRepository; +import com.coderstower.socialmediapubisher.application.aws.repository.oauth1.OAuth1CredentialAWSRepository; +import com.coderstower.socialmediapubisher.application.aws.repository.oauth1.OAuth1CredentialDynamoRepository; +import com.coderstower.socialmediapubisher.application.aws.repository.oauth2.OAuth2CredentialAWSRepository; +import com.coderstower.socialmediapubisher.application.aws.repository.oauth2.OAuth2CredentialDynamoRepository; +import com.coderstower.socialmediapubisher.application.aws.repository.post.PostAWSRepository; +import com.coderstower.socialmediapubisher.application.aws.repository.post.PostDynamoRepository; import org.socialsignin.spring.data.dynamodb.repository.config.EnableDynamoDBRepositories; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; @@ -20,7 +20,7 @@ @Configuration @EnableDynamoDBRepositories - (basePackages = "com.coderstower.socialmediapubisher.main.aws.repository") + (basePackages = "com.coderstower.socialmediapubisher.application.aws.repository") public class SpringPublisherDynamoDBRepositoryFactory { @Bean("amazonDynamoDB") @Profile("!local") diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/aws/StreamLambdaHandler.java b/src/main/java/com/coderstower/socialmediapubisher/application/aws/StreamLambdaHandler.java similarity index 95% rename from src/main/java/com/coderstower/socialmediapubisher/main/aws/StreamLambdaHandler.java rename to src/main/java/com/coderstower/socialmediapubisher/application/aws/StreamLambdaHandler.java index 0a1bd62..58bba3a 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/aws/StreamLambdaHandler.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/aws/StreamLambdaHandler.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.aws; +package com.coderstower.socialmediapubisher.application.aws; import com.amazonaws.serverless.exceptions.ContainerInitializationException; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/LocalDateTimeConverter.java b/src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/LocalDateTimeConverter.java similarity index 86% rename from src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/LocalDateTimeConverter.java rename to src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/LocalDateTimeConverter.java index 840e21b..5ae39a2 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/LocalDateTimeConverter.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/LocalDateTimeConverter.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.aws.repository; +package com.coderstower.socialmediapubisher.application.aws.repository; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverter; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/URLConverter.java b/src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/URLConverter.java similarity index 88% rename from src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/URLConverter.java rename to src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/URLConverter.java index 09fea12..6986f3b 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/URLConverter.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/URLConverter.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.aws.repository; +package com.coderstower.socialmediapubisher.application.aws.repository; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverter; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepository.java b/src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth1/OAuth1CredentialAWSRepository.java similarity index 94% rename from src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepository.java rename to src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth1/OAuth1CredentialAWSRepository.java index e393bb8..5805354 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepository.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth1/OAuth1CredentialAWSRepository.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.aws.repository.oauth1; +package com.coderstower.socialmediapubisher.application.aws.repository.oauth1; import com.coderstower.socialmediapubisher.domain.security.repository.OAuth1Credentials; import com.coderstower.socialmediapubisher.domain.security.repository.OAuth1CredentialsRepository; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialDynamo.java b/src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth1/OAuth1CredentialDynamo.java similarity index 90% rename from src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialDynamo.java rename to src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth1/OAuth1CredentialDynamo.java index 3cf0b86..e4c8efa 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialDynamo.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth1/OAuth1CredentialDynamo.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.aws.repository.oauth1; +package com.coderstower.socialmediapubisher.application.aws.repository.oauth1; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialDynamoRepository.java b/src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth1/OAuth1CredentialDynamoRepository.java similarity index 74% rename from src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialDynamoRepository.java rename to src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth1/OAuth1CredentialDynamoRepository.java index a57f7cb..3d36dd6 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialDynamoRepository.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth1/OAuth1CredentialDynamoRepository.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.aws.repository.oauth1; +package com.coderstower.socialmediapubisher.application.aws.repository.oauth1; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepository.java b/src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth2/OAuth2CredentialAWSRepository.java similarity index 96% rename from src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepository.java rename to src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth2/OAuth2CredentialAWSRepository.java index 1cf5df1..118be4a 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepository.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth2/OAuth2CredentialAWSRepository.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.aws.repository.oauth2; +package com.coderstower.socialmediapubisher.application.aws.repository.oauth2; import com.coderstower.socialmediapubisher.domain.security.repository.OAuth2CredentialsRepository; import com.coderstower.socialmediapubisher.domain.security.repository.OAuth2Credentials; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialDynamo.java b/src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth2/OAuth2CredentialDynamo.java similarity index 83% rename from src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialDynamo.java rename to src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth2/OAuth2CredentialDynamo.java index a582692..2457596 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialDynamo.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth2/OAuth2CredentialDynamo.java @@ -1,10 +1,10 @@ -package com.coderstower.socialmediapubisher.main.aws.repository.oauth2; +package com.coderstower.socialmediapubisher.application.aws.repository.oauth2; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverted; -import com.coderstower.socialmediapubisher.main.aws.repository.LocalDateTimeConverter; +import com.coderstower.socialmediapubisher.application.aws.repository.LocalDateTimeConverter; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialDynamoRepository.java b/src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth2/OAuth2CredentialDynamoRepository.java similarity index 75% rename from src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialDynamoRepository.java rename to src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth2/OAuth2CredentialDynamoRepository.java index 81b4c81..d207eab 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialDynamoRepository.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth2/OAuth2CredentialDynamoRepository.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.aws.repository.oauth2; +package com.coderstower.socialmediapubisher.application.aws.repository.oauth2; import org.socialsignin.spring.data.dynamodb.repository.EnableScan; import org.springframework.data.repository.CrudRepository; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostAWSRepository.java b/src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/post/PostAWSRepository.java similarity index 95% rename from src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostAWSRepository.java rename to src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/post/PostAWSRepository.java index 4584189..6b1c221 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostAWSRepository.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/post/PostAWSRepository.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.aws.repository.post; +package com.coderstower.socialmediapubisher.application.aws.repository.post; import com.coderstower.socialmediapubisher.domain.post.repository.Post; import com.coderstower.socialmediapubisher.domain.post.repository.PostRepository; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostDynamo.java b/src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/post/PostDynamo.java similarity index 77% rename from src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostDynamo.java rename to src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/post/PostDynamo.java index f1fe8f9..67853aa 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostDynamo.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/post/PostDynamo.java @@ -1,8 +1,8 @@ -package com.coderstower.socialmediapubisher.main.aws.repository.post; +package com.coderstower.socialmediapubisher.application.aws.repository.post; import com.amazonaws.services.dynamodbv2.datamodeling.*; -import com.coderstower.socialmediapubisher.main.aws.repository.URLConverter; -import com.coderstower.socialmediapubisher.main.aws.repository.LocalDateTimeConverter; +import com.coderstower.socialmediapubisher.application.aws.repository.URLConverter; +import com.coderstower.socialmediapubisher.application.aws.repository.LocalDateTimeConverter; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostDynamoRepository.java b/src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/post/PostDynamoRepository.java similarity index 75% rename from src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostDynamoRepository.java rename to src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/post/PostDynamoRepository.java index ddbda9e..15ae6a0 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostDynamoRepository.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/post/PostDynamoRepository.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.aws.repository.post; +package com.coderstower.socialmediapubisher.application.aws.repository.post; import org.socialsignin.spring.data.dynamodb.repository.EnableScan; import org.springframework.data.repository.CrudRepository; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/controller/ErrorHandler.java b/src/main/java/com/coderstower/socialmediapubisher/application/controller/ErrorHandler.java similarity index 88% rename from src/main/java/com/coderstower/socialmediapubisher/main/controller/ErrorHandler.java rename to src/main/java/com/coderstower/socialmediapubisher/application/controller/ErrorHandler.java index 6108d28..131fca3 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/controller/ErrorHandler.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/controller/ErrorHandler.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.controller; +package com.coderstower.socialmediapubisher.application.controller; import com.coderstower.socialmediapubisher.domain.security.UnauthorizedException; import org.springframework.http.ResponseEntity; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/controller/OAuth2CredentialsController.java b/src/main/java/com/coderstower/socialmediapubisher/application/controller/OAuth2CredentialsController.java similarity index 95% rename from src/main/java/com/coderstower/socialmediapubisher/main/controller/OAuth2CredentialsController.java rename to src/main/java/com/coderstower/socialmediapubisher/application/controller/OAuth2CredentialsController.java index 04801a1..a5ea9c1 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/controller/OAuth2CredentialsController.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/controller/OAuth2CredentialsController.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.controller; +package com.coderstower.socialmediapubisher.application.controller; import com.coderstower.socialmediapubisher.domain.security.OAuth2CredentialsManager; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/controller/PostsController.java b/src/main/java/com/coderstower/socialmediapubisher/application/controller/PostsController.java similarity index 94% rename from src/main/java/com/coderstower/socialmediapubisher/main/controller/PostsController.java rename to src/main/java/com/coderstower/socialmediapubisher/application/controller/PostsController.java index e7eb28d..2a154d0 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/controller/PostsController.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/controller/PostsController.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.controller; +package com.coderstower.socialmediapubisher.application.controller; import com.coderstower.socialmediapubisher.domain.post.PostPublisher; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/factory/CredentialsProperties.java b/src/main/java/com/coderstower/socialmediapubisher/application/factory/CredentialsProperties.java similarity index 81% rename from src/main/java/com/coderstower/socialmediapubisher/main/factory/CredentialsProperties.java rename to src/main/java/com/coderstower/socialmediapubisher/application/factory/CredentialsProperties.java index c3030f1..bcfc572 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/factory/CredentialsProperties.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/factory/CredentialsProperties.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.factory; +package com.coderstower.socialmediapubisher.application.factory; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/factory/SecurityFactory.java b/src/main/java/com/coderstower/socialmediapubisher/application/factory/SecurityFactory.java similarity index 98% rename from src/main/java/com/coderstower/socialmediapubisher/main/factory/SecurityFactory.java rename to src/main/java/com/coderstower/socialmediapubisher/application/factory/SecurityFactory.java index 967095b..734f873 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/factory/SecurityFactory.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/factory/SecurityFactory.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.factory; +package com.coderstower.socialmediapubisher.application.factory; import com.coderstower.socialmediapubisher.domain.security.OAuth2AccessTokenResponseConverterWithDefaults; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/factory/SocialMediaPublisherProperties.java b/src/main/java/com/coderstower/socialmediapubisher/application/factory/SocialMediaPublisherProperties.java similarity index 87% rename from src/main/java/com/coderstower/socialmediapubisher/main/factory/SocialMediaPublisherProperties.java rename to src/main/java/com/coderstower/socialmediapubisher/application/factory/SocialMediaPublisherProperties.java index 592a85e..aaf49a6 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/factory/SocialMediaPublisherProperties.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/factory/SocialMediaPublisherProperties.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.factory; +package com.coderstower.socialmediapubisher.application.factory; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/factory/SpringPublisherFactory.java b/src/main/java/com/coderstower/socialmediapubisher/application/factory/SpringPublisherFactory.java similarity index 91% rename from src/main/java/com/coderstower/socialmediapubisher/main/factory/SpringPublisherFactory.java rename to src/main/java/com/coderstower/socialmediapubisher/application/factory/SpringPublisherFactory.java index 5c620ca..c3a6715 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/factory/SpringPublisherFactory.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/factory/SpringPublisherFactory.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.factory; +package com.coderstower.socialmediapubisher.application.factory; import com.coderstower.socialmediapubisher.domain.post.PostPublisher; import com.coderstower.socialmediapubisher.domain.post.repository.PostRepository; @@ -6,8 +6,8 @@ import com.coderstower.socialmediapubisher.domain.security.OAuth2CredentialsManager; import com.coderstower.socialmediapubisher.domain.security.repository.OAuth1CredentialsRepository; import com.coderstower.socialmediapubisher.domain.security.repository.OAuth2CredentialsRepository; -import com.coderstower.socialmediapubisher.main.socialmedia.linkedin.LinkedInPublisher; -import com.coderstower.socialmediapubisher.main.socialmedia.twitter.TwitterPublisher; +import com.coderstower.socialmediapubisher.application.socialmedia.linkedin.LinkedInPublisher; +import com.coderstower.socialmediapubisher.application.socialmedia.twitter.TwitterPublisher; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/ArticleContent.java b/src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/ArticleContent.java similarity index 71% rename from src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/ArticleContent.java rename to src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/ArticleContent.java index 302d0b2..82ec27d 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/ArticleContent.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/ArticleContent.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.socialmedia.linkedin; +package com.coderstower.socialmediapubisher.application.socialmedia.linkedin; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/Content.java b/src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/Content.java similarity index 61% rename from src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/Content.java rename to src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/Content.java index 0e11716..66fc5e4 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/Content.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/Content.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.socialmedia.linkedin; +package com.coderstower.socialmediapubisher.application.socialmedia.linkedin; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/Distribution.java b/src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/Distribution.java similarity index 63% rename from src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/Distribution.java rename to src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/Distribution.java index df6d7c4..db240ce 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/Distribution.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/Distribution.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.socialmedia.linkedin; +package com.coderstower.socialmediapubisher.application.socialmedia.linkedin; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/LinkedInPublisher.java b/src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/LinkedInPublisher.java similarity index 98% rename from src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/LinkedInPublisher.java rename to src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/LinkedInPublisher.java index 520d83a..acf20ec 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/LinkedInPublisher.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/LinkedInPublisher.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.socialmedia.linkedin; +package com.coderstower.socialmediapubisher.application.socialmedia.linkedin; import com.coderstower.socialmediapubisher.domain.post.repository.Post; import com.coderstower.socialmediapubisher.domain.post.socialmedia.Acknowledge; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/LinkedInShare.java b/src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/LinkedInShare.java similarity index 80% rename from src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/LinkedInShare.java rename to src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/LinkedInShare.java index 890ad7b..8db75a9 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/LinkedInShare.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/LinkedInShare.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.socialmedia.linkedin; +package com.coderstower.socialmediapubisher.application.socialmedia.linkedin; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/Media.java b/src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/Media.java similarity index 73% rename from src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/Media.java rename to src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/Media.java index 09e7287..afe0794 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/Media.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/Media.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.socialmedia.linkedin; +package com.coderstower.socialmediapubisher.application.socialmedia.linkedin; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/Profile.java b/src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/Profile.java similarity index 73% rename from src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/Profile.java rename to src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/Profile.java index 5320cba..db1a8ed 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/Profile.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/Profile.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.socialmedia.linkedin; +package com.coderstower.socialmediapubisher.application.socialmedia.linkedin; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/Text.java b/src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/Text.java similarity index 59% rename from src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/Text.java rename to src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/Text.java index 5640743..8773691 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/Text.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/Text.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.socialmedia.linkedin; +package com.coderstower.socialmediapubisher.application.socialmedia.linkedin; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/twitter/TwitterPublisher.java b/src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/twitter/TwitterPublisher.java similarity index 98% rename from src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/twitter/TwitterPublisher.java rename to src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/twitter/TwitterPublisher.java index 7943445..4a5fdc0 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/main/socialmedia/twitter/TwitterPublisher.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/twitter/TwitterPublisher.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.socialmedia.twitter; +package com.coderstower.socialmediapubisher.application.socialmedia.twitter; import com.coderstower.socialmediapubisher.domain.post.repository.Post; import com.coderstower.socialmediapubisher.domain.post.socialmedia.Acknowledge; diff --git a/src/test/java/com/coderstower/socialmediapubisher/main/aws/MockSocialMediaCredentialsHandlingTests.java b/src/test/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaCredentialsHandlingTests.java similarity index 99% rename from src/test/java/com/coderstower/socialmediapubisher/main/aws/MockSocialMediaCredentialsHandlingTests.java rename to src/test/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaCredentialsHandlingTests.java index b1e7147..90bdaf0 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/main/aws/MockSocialMediaCredentialsHandlingTests.java +++ b/src/test/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaCredentialsHandlingTests.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.aws; +package com.coderstower.socialmediapubisher.application.aws; import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; import com.amazonaws.services.dynamodbv2.local.embedded.DynamoDBEmbedded; diff --git a/src/test/java/com/coderstower/socialmediapubisher/main/aws/MockSocialMediaSuccessfulTests.java b/src/test/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaSuccessfulTests.java similarity index 98% rename from src/test/java/com/coderstower/socialmediapubisher/main/aws/MockSocialMediaSuccessfulTests.java rename to src/test/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaSuccessfulTests.java index 847a533..ccac009 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/main/aws/MockSocialMediaSuccessfulTests.java +++ b/src/test/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaSuccessfulTests.java @@ -1,9 +1,9 @@ -package com.coderstower.socialmediapubisher.main.aws; +package com.coderstower.socialmediapubisher.application.aws; import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; import com.amazonaws.services.dynamodbv2.local.embedded.DynamoDBEmbedded; import com.amazonaws.services.dynamodbv2.model.*; -import com.coderstower.socialmediapubisher.main.socialmedia.linkedin.*; +import com.coderstower.socialmediapubisher.application.socialmedia.linkedin.*; import org.junit.jupiter.api.Test; import org.junitpioneer.jupiter.SetSystemProperty; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/test/java/com/coderstower/socialmediapubisher/main/aws/StreamLambdaHandlerTest.java b/src/test/java/com/coderstower/socialmediapubisher/application/aws/StreamLambdaHandlerTest.java similarity index 95% rename from src/test/java/com/coderstower/socialmediapubisher/main/aws/StreamLambdaHandlerTest.java rename to src/test/java/com/coderstower/socialmediapubisher/application/aws/StreamLambdaHandlerTest.java index 9914610..50e24ca 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/main/aws/StreamLambdaHandlerTest.java +++ b/src/test/java/com/coderstower/socialmediapubisher/application/aws/StreamLambdaHandlerTest.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.aws; +package com.coderstower.socialmediapubisher.application.aws; import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; @@ -6,7 +6,6 @@ import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import com.amazonaws.services.lambda.runtime.Context; -import com.coderstower.socialmediapubisher.main.aws.StreamLambdaHandler; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/LocalDateTimeConverterTest.java b/src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/LocalDateTimeConverterTest.java similarity index 81% rename from src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/LocalDateTimeConverterTest.java rename to src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/LocalDateTimeConverterTest.java index 899bc77..504ae4b 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/LocalDateTimeConverterTest.java +++ b/src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/LocalDateTimeConverterTest.java @@ -1,6 +1,5 @@ -package com.coderstower.socialmediapubisher.main.aws.repository; +package com.coderstower.socialmediapubisher.application.aws.repository; -import com.coderstower.socialmediapubisher.main.aws.repository.LocalDateTimeConverter; import org.junit.jupiter.api.Test; import java.time.LocalDateTime; diff --git a/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/URLConverterTest.java b/src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/URLConverterTest.java similarity index 86% rename from src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/URLConverterTest.java rename to src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/URLConverterTest.java index 3573ae9..ee05c19 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/URLConverterTest.java +++ b/src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/URLConverterTest.java @@ -1,6 +1,5 @@ -package com.coderstower.socialmediapubisher.main.aws.repository; +package com.coderstower.socialmediapubisher.application.aws.repository; -import com.coderstower.socialmediapubisher.main.aws.repository.URLConverter; import org.junit.jupiter.api.Test; import java.net.MalformedURLException; diff --git a/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepositoryTest.java b/src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth1/OAuth1CredentialAWSRepositoryTest.java similarity index 95% rename from src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepositoryTest.java rename to src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth1/OAuth1CredentialAWSRepositoryTest.java index 6971a4f..66f7f25 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepositoryTest.java +++ b/src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth1/OAuth1CredentialAWSRepositoryTest.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.aws.repository.oauth1; +package com.coderstower.socialmediapubisher.application.aws.repository.oauth1; import com.coderstower.socialmediapubisher.domain.security.repository.OAuth1Credentials; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepositoryTest.java b/src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth2/OAuth2CredentialAWSRepositoryTest.java similarity index 96% rename from src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepositoryTest.java rename to src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth2/OAuth2CredentialAWSRepositoryTest.java index a14f0b4..b9fede6 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepositoryTest.java +++ b/src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth2/OAuth2CredentialAWSRepositoryTest.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.aws.repository.oauth2; +package com.coderstower.socialmediapubisher.application.aws.repository.oauth2; import com.coderstower.socialmediapubisher.domain.security.repository.OAuth2Credentials; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostAWSRepositoryTest.java b/src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/post/PostAWSRepositoryTest.java similarity index 98% rename from src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostAWSRepositoryTest.java rename to src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/post/PostAWSRepositoryTest.java index eaebe5a..61339be 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/main/aws/repository/post/PostAWSRepositoryTest.java +++ b/src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/post/PostAWSRepositoryTest.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.aws.repository.post; +package com.coderstower.socialmediapubisher.application.aws.repository.post; import com.coderstower.socialmediapubisher.domain.post.repository.Post; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/coderstower/socialmediapubisher/main/controller/PostsControllerTest.java b/src/test/java/com/coderstower/socialmediapubisher/application/controller/PostsControllerTest.java similarity index 97% rename from src/test/java/com/coderstower/socialmediapubisher/main/controller/PostsControllerTest.java rename to src/test/java/com/coderstower/socialmediapubisher/application/controller/PostsControllerTest.java index 37ea18f..f583390 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/main/controller/PostsControllerTest.java +++ b/src/test/java/com/coderstower/socialmediapubisher/application/controller/PostsControllerTest.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.controller; +package com.coderstower.socialmediapubisher.application.controller; import com.coderstower.socialmediapubisher.domain.post.PostPublisher; import com.coderstower.socialmediapubisher.domain.post.repository.Post; diff --git a/src/test/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/LinkedInPublisherTest.java b/src/test/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/LinkedInPublisherTest.java similarity index 99% rename from src/test/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/LinkedInPublisherTest.java rename to src/test/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/LinkedInPublisherTest.java index ce3b696..5f28558 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/main/socialmedia/linkedin/LinkedInPublisherTest.java +++ b/src/test/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/LinkedInPublisherTest.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.socialmedia.linkedin; +package com.coderstower.socialmediapubisher.application.socialmedia.linkedin; import com.coderstower.socialmediapubisher.domain.post.repository.Post; import com.coderstower.socialmediapubisher.domain.post.socialmedia.Acknowledge; diff --git a/src/test/java/com/coderstower/socialmediapubisher/main/socialmedia/twitter/TwitterPublisherTest.java b/src/test/java/com/coderstower/socialmediapubisher/application/socialmedia/twitter/TwitterPublisherTest.java similarity index 99% rename from src/test/java/com/coderstower/socialmediapubisher/main/socialmedia/twitter/TwitterPublisherTest.java rename to src/test/java/com/coderstower/socialmediapubisher/application/socialmedia/twitter/TwitterPublisherTest.java index 22ed531..4d692b4 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/main/socialmedia/twitter/TwitterPublisherTest.java +++ b/src/test/java/com/coderstower/socialmediapubisher/application/socialmedia/twitter/TwitterPublisherTest.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.main.socialmedia.twitter; +package com.coderstower.socialmediapubisher.application.socialmedia.twitter; import com.coderstower.socialmediapubisher.domain.post.repository.Post; import com.coderstower.socialmediapubisher.domain.post.socialmedia.Acknowledge; From 5a4c9e392b45532128825f53848d27cea5829fbd Mon Sep 17 00:00:00 2001 From: Daniel Pelaez Date: Thu, 25 Apr 2024 19:09:10 -0500 Subject: [PATCH 09/17] Moving to gradle --- .gitignore | 144 ++++++-- build.gradle.kts | 57 ++++ gradle/libs.versions.toml | 28 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43453 bytes gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 249 ++++++++++++++ gradlew.bat | 92 ++++++ pom.xml | 233 ------------- settings.gradle.kts | 5 + .../.mvn/wrapper/MavenWrapperDownloader.java | 140 -------- .../.mvn/wrapper/maven-wrapper.jar | Bin 50710 -> 0 bytes .../.mvn/wrapper/maven-wrapper.properties | 2 - spring-publisher/mvnw | 310 ------------------ spring-publisher/mvnw.cmd | 182 ---------- 14 files changed, 556 insertions(+), 893 deletions(-) create mode 100644 build.gradle.kts create mode 100644 gradle/libs.versions.toml create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat delete mode 100644 pom.xml create mode 100644 settings.gradle.kts delete mode 100644 spring-publisher/.mvn/wrapper/MavenWrapperDownloader.java delete mode 100644 spring-publisher/.mvn/wrapper/maven-wrapper.jar delete mode 100644 spring-publisher/.mvn/wrapper/maven-wrapper.properties delete mode 100644 spring-publisher/mvnw delete mode 100644 spring-publisher/mvnw.cmd diff --git a/.gitignore b/.gitignore index 4bb9a88..cdfe28d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,27 +1,119 @@ +# Created by https://www.toptal.com/developers/gitignore/api/gradle,intellij+all +# Edit at https://www.toptal.com/developers/gitignore?templates=gradle,intellij+all + +### Intellij+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + ### Intellij+all Patch ### -# Ignores the whole .idea folder and all .iml files -# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 - -.idea/ - -# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 - -*.iml -modules.xml -.idea/misc.xml -*.ipr - -### Maven ### -target/ -pom.xml.tag -pom.xml.releaseBackup -pom.xml.versionsBackup -pom.xml.next -release.properties -dependency-reduced-pom.xml -buildNumber.properties -.mvn/timing.properties -.mvn/wrapper/maven-wrapper.jar -.flattened-pom.xml - -# End of https://www.gitignore.io/api/maven,intellij+all \ No newline at end of file +# Ignore everything but code style settings and run configurations +# that are supposed to be shared within teams. + +.idea/* + +!.idea/codeStyles +!.idea/runConfigurations + +### Gradle ### +.gradle +**/build/ +!src/**/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Avoid ignore Gradle wrappper properties +!gradle-wrapper.properties + +# Cache of project +.gradletasknamecache + +# Eclipse Gradle plugin generated files +# Eclipse Core +.project +# JDT-specific (Eclipse Java Development Tools) +.classpath + +### Gradle Patch ### +# Java heap dump +*.hprof + +# End of https://www.toptal.com/developers/gitignore/api/gradle,intellij+all \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..eac173d --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,57 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +plugins { + id("io.freefair.lombok") version "8.6" + `java-library` + `maven-publish` +} + +repositories { + mavenLocal() + maven { + url = uri("https://s3-us-west-2.amazonaws.com/dynamodb-local/release") + } + + maven { + url = uri("https://repo.maven.apache.org/maven2/") + } +} + +dependencies { + api(libs.org.springframework.boot.spring.boot.starter.web) + api(libs.org.springframework.boot.spring.boot.starter.security) + api(libs.org.springframework.boot.spring.boot.starter.oauth2.client) + api(libs.org.twitter4j.twitter4j.core) + api(libs.com.amazonaws.serverless.aws.serverless.java.container.springboot2) + api(libs.io.github.boostchicken.spring.data.dynamodb) + testImplementation(libs.org.springframework.boot.spring.boot.starter.test) + testImplementation(libs.com.amazonaws.dynamodblocal) + testImplementation(libs.org.junit.pioneer.junit.pioneer) + testImplementation(libs.org.springframework.security.spring.security.test) +// compileOnly(libs.org.projectlombok.lombok) +} + +group = "com.coderstower" +version = "0.0.6-SNAPSHOT" +description = "social-media-publisher" +java.sourceCompatibility = JavaVersion.VERSION_1_8 + +//publishing { +// publications.create("maven") { +// from(components["java"]) +// } +//} + +tasks.withType() { + options.encoding = "UTF-8" +} + +tasks.withType() { + options.encoding = "UTF-8" +} + +tasks.withType { + useJUnitPlatform() +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..a091fdf --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,28 @@ +# This file was generated by the Gradle 'init' task. +# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format + +[versions] +com-amazonaws-dynamodblocal = "1.13.0" +com-amazonaws-serverless-aws-serverless-java-container-springboot2 = "1.5" +io-github-boostchicken-spring-data-dynamodb = "5.2.3" +org-junit-pioneer-junit-pioneer = "0.5.1" +org-projectlombok-lombok = "1.18.12" +org-springframework-boot-spring-boot-starter-oauth2-client = "2.2.1.RELEASE" +org-springframework-boot-spring-boot-starter-security = "2.2.1.RELEASE" +org-springframework-boot-spring-boot-starter-test = "2.2.1.RELEASE" +org-springframework-boot-spring-boot-starter-web = "2.2.1.RELEASE" +org-springframework-security-spring-security-test = "5.2.1.RELEASE" +org-twitter4j-twitter4j-core = "4.0.7" + +[libraries] +com-amazonaws-dynamodblocal = { module = "com.amazonaws:DynamoDBLocal", version.ref = "com-amazonaws-dynamodblocal" } +com-amazonaws-serverless-aws-serverless-java-container-springboot2 = { module = "com.amazonaws.serverless:aws-serverless-java-container-springboot2", version.ref = "com-amazonaws-serverless-aws-serverless-java-container-springboot2" } +io-github-boostchicken-spring-data-dynamodb = { module = "io.github.boostchicken:spring-data-dynamodb", version.ref = "io-github-boostchicken-spring-data-dynamodb" } +org-junit-pioneer-junit-pioneer = { module = "org.junit-pioneer:junit-pioneer", version.ref = "org-junit-pioneer-junit-pioneer" } +org-projectlombok-lombok = { module = "org.projectlombok:lombok", version.ref = "org-projectlombok-lombok" } +org-springframework-boot-spring-boot-starter-oauth2-client = { module = "org.springframework.boot:spring-boot-starter-oauth2-client", version.ref = "org-springframework-boot-spring-boot-starter-oauth2-client" } +org-springframework-boot-spring-boot-starter-security = { module = "org.springframework.boot:spring-boot-starter-security", version.ref = "org-springframework-boot-spring-boot-starter-security" } +org-springframework-boot-spring-boot-starter-test = { module = "org.springframework.boot:spring-boot-starter-test", version.ref = "org-springframework-boot-spring-boot-starter-test" } +org-springframework-boot-spring-boot-starter-web = { module = "org.springframework.boot:spring-boot-starter-web", version.ref = "org-springframework-boot-spring-boot-starter-web" } +org-springframework-security-spring-security-test = { module = "org.springframework.security:spring-security-test", version.ref = "org-springframework-security-spring-security-test" } +org-twitter4j-twitter4j-core = { module = "org.twitter4j:twitter4j-core", version.ref = "org-twitter4j-twitter4j-core" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..e6441136f3d4ba8a0da8d277868979cfbc8ad796 GIT binary patch literal 43453 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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
NnX)EH+Nua)3Y(c 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 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..25da30d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/pom.xml b/pom.xml deleted file mode 100644 index 1839584..0000000 --- a/pom.xml +++ /dev/null @@ -1,233 +0,0 @@ - - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 2.2.1.RELEASE - - - - com.coderstower - social-media-publisher - 0.0.6-SNAPSHOT - social-media-publisher - Demo project for Spring Boot - jar - - - 11 - - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-security - - - org.springframework.boot - spring-boot-starter-oauth2-client - - - org.twitter4j - twitter4j-core - 4.0.7 - - - org.projectlombok - lombok - 1.18.12 - provided - - - org.springframework.boot - spring-boot-starter-test - test - - - org.junit.vintage - junit-vintage-engine - - - - - - com.amazonaws.serverless - aws-serverless-java-container-springboot2 - 1.5 - - - - io.github.boostchicken - spring-data-dynamodb - 5.2.3 - - - - org.springframework.boot - spring-boot-starter-web - - - - com.amazonaws - DynamoDBLocal - 1.13.0 - test - - - com.amazonaws - aws-java-sdk-dynamodb - - - com.amazonaws - aws-java-sdk-core - - - - - - org.junit-pioneer - junit-pioneer - 0.5.1 - test - - - - org.springframework.security - spring-security-test - test - - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - copy - test-compile - - copy-dependencies - - - test - so,dll,dylib - ${project.build.directory}/native-libs - - - - - - - - - - shaded-jar - - - - org.apache.maven.plugins - maven-shade-plugin - 2.3 - - false - - - - package - - shade - - - - - org.apache.tomcat.embed:* - - - - - - - - - - - assembly-zip - - true - - - - - - org.apache.maven.plugins - maven-jar-plugin - 3.1.1 - - - default-jar - none - - - - - - org.apache.maven.plugins - maven-dependency-plugin - 3.1.1 - - - copy-dependencies - package - - copy-dependencies - - - ${project.build.directory}${file.separator}lib - runtime - - - - - - org.apache.maven.plugins - maven-assembly-plugin - 3.1.0 - - - zip-assembly - package - - single - - - ${project.artifactId}-${project.version} - - src${file.separator}assembly${file.separator}bin.xml - - false - - - - - - - - - - - - dynamodb-local-oregon - DynamoDB Local Release Repository - https://s3-us-west-2.amazonaws.com/dynamodb-local/release - - - diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..a2f0ece --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,5 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +rootProject.name = "social-media-publisher" diff --git a/spring-publisher/.mvn/wrapper/MavenWrapperDownloader.java b/spring-publisher/.mvn/wrapper/MavenWrapperDownloader.java deleted file mode 100644 index f97cd5a..0000000 --- a/spring-publisher/.mvn/wrapper/MavenWrapperDownloader.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2007-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import java.net.*; -import java.io.*; -import java.nio.channels.*; -import java.util.Properties; - -public class MavenWrapperDownloader { - - private static final String WRAPPER_VERSION = "0.5.6"; - /** - * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. - */ - private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" - + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; - - /** - * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to - * use instead of the default one. - */ - private static final String MAVEN_WRAPPER_PROPERTIES_PATH = - ".mvn/wrapper/maven-wrapper.properties"; - - /** - * Path where the maven-wrapper.jar will be saved to. - */ - private static final String MAVEN_WRAPPER_JAR_PATH = - ".mvn/wrapper/maven-wrapper.jar"; - - /** - * Name of the property which should be used to override the default download url for the wrapper. - */ - private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; - - public static void main(String args[]) { - System.out.println("- Downloader started"); - File baseDirectory = new File(args[0]); - System.out.println( - "- Using base directory: " + baseDirectory - .getAbsolutePath()); - // If the maven-wrapper.properties exists, read it and check if it contains a custom - // wrapperUrl parameter. - File mavenWrapperPropertyFile = new File( - baseDirectory, - MAVEN_WRAPPER_PROPERTIES_PATH); - String url = DEFAULT_DOWNLOAD_URL; - if (mavenWrapperPropertyFile.exists()) { - FileInputStream mavenWrapperPropertyFileInputStream = null; - try { - mavenWrapperPropertyFileInputStream = new FileInputStream( - mavenWrapperPropertyFile); - Properties mavenWrapperProperties = new Properties(); - mavenWrapperProperties - .load(mavenWrapperPropertyFileInputStream); - url = mavenWrapperProperties - .getProperty(PROPERTY_NAME_WRAPPER_URL, - url); - } catch (IOException e) { - System.out.println( - "- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); - } finally { - try { - if (mavenWrapperPropertyFileInputStream != null) { - mavenWrapperPropertyFileInputStream - .close(); - } - } catch (IOException e) { - // Ignore ... - } - } - } - System.out.println("- Downloading from: " + url); - File outputFile = new File( - baseDirectory.getAbsolutePath(), - MAVEN_WRAPPER_JAR_PATH); - if (!outputFile.getParentFile().exists()) { - if (!outputFile.getParentFile().mkdirs()) { - System.out.println( - "- ERROR creating output directory '" + outputFile - .getParentFile() - .getAbsolutePath() + "'"); - } - } - System.out.println( - "- Downloading to: " + outputFile - .getAbsolutePath()); - try { - downloadFileFromURL(url, outputFile); - System.out.println("Done"); - System.exit(0); - } catch (Throwable e) { - System.out.println("- Error downloading"); - e.printStackTrace(); - System.exit(1); - } - } - - private static void downloadFileFromURL( - String urlString, File destination) - throws Exception { - if (System - .getenv("MVNW_USERNAME") != null && System - .getenv("MVNW_PASSWORD") != null) { - String username = System.getenv("MVNW_USERNAME"); - char[] password = System.getenv("MVNW_PASSWORD") - .toCharArray(); - Authenticator.setDefault(new Authenticator() { - @Override - protected PasswordAuthentication getPasswordAuthentication() { - return new PasswordAuthentication(username, - password); - } - }); - } - URL website = new URL(urlString); - ReadableByteChannel rbc; - rbc = Channels.newChannel(website.openStream()); - FileOutputStream fos = new FileOutputStream( - destination); - fos.getChannel() - .transferFrom(rbc, 0, Long.MAX_VALUE); - fos.close(); - rbc.close(); - } - -} diff --git a/spring-publisher/.mvn/wrapper/maven-wrapper.jar b/spring-publisher/.mvn/wrapper/maven-wrapper.jar deleted file mode 100644 index 2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50710 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 EfAhh=DF6Tf diff --git a/spring-publisher/.mvn/wrapper/maven-wrapper.properties b/spring-publisher/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index 642d572..0000000 --- a/spring-publisher/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,2 +0,0 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/spring-publisher/mvnw b/spring-publisher/mvnw deleted file mode 100644 index a16b543..0000000 --- a/spring-publisher/mvnw +++ /dev/null @@ -1,310 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Maven Start Up Batch script -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir -# -# Optional ENV vars -# ----------------- -# M2_HOME - location of maven2's installed home dir -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files -# ---------------------------------------------------------------------------- - -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi - - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi - -fi - -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false -case "`uname`" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - export JAVA_HOME="`/usr/libexec/java_home`" - else - export JAVA_HOME="/Library/Java/Home" - fi - fi - ;; -esac - -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` - fi -fi - -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" - - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi - done - - saveddir=`pwd` - - M2_HOME=`dirname "$PRG"`/.. - - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` - - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi - -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` -fi - -# For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" -fi - -if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then - if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" - else - javaExecutable="`readlink -f \"$javaExecutable\"`" - fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi - -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - else - JAVACMD="`which java`" - fi -fi - -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi - -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." -fi - -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { - - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 - fi - - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=`cd "$wdir/.."; pwd` - fi - # end of workaround - done - echo "${basedir}" -} - -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" - fi -} - -BASE_DIR=`find_maven_basedir "$(pwd)"` -if [ -z "$BASE_DIR" ]; then - exit 1; -fi - -########################################################################################## -# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -# This allows using the maven wrapper in projects that prohibit checking in binary data. -########################################################################################## -if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found .mvn/wrapper/maven-wrapper.jar" - fi -else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." - fi - if [ -n "$MVNW_REPOURL" ]; then - jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - else - jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - fi - while IFS="=" read key value; do - case "$key" in (wrapperUrl) jarUrl="$value"; break ;; - esac - done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" - if [ "$MVNW_VERBOSE" = true ]; then - echo "Downloading from: $jarUrl" - fi - wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" - if $cygwin; then - wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` - fi - - if command -v wget > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found wget ... using wget" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget "$jarUrl" -O "$wrapperJarPath" - else - wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" - fi - elif command -v curl > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found curl ... using curl" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl -o "$wrapperJarPath" "$jarUrl" -f - else - curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f - fi - - else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Falling back to using Java to download" - fi - javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" - # For Cygwin, switch paths to Windows format before running javac - if $cygwin; then - javaClass=`cygpath --path --windows "$javaClass"` - fi - if [ -e "$javaClass" ]; then - if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Compiling MavenWrapperDownloader.java ..." - fi - # Compiling the Java class - ("$JAVA_HOME/bin/javac" "$javaClass") - fi - if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - # Running the downloader - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Running MavenWrapperDownloader.java ..." - fi - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") - fi - fi - fi -fi -########################################################################################## -# End of extension -########################################################################################## - -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -if [ "$MVNW_VERBOSE" = true ]; then - echo $MAVEN_PROJECTBASEDIR -fi -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` -fi - -# Provide a "standardized" way to retrieve the CLI args that will -# work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" -export MAVEN_CMD_LINE_ARGS - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -exec "$JAVACMD" \ - $MAVEN_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/spring-publisher/mvnw.cmd b/spring-publisher/mvnw.cmd deleted file mode 100644 index c8d4337..0000000 --- a/spring-publisher/mvnw.cmd +++ /dev/null @@ -1,182 +0,0 @@ -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM https://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Maven Start Up Batch script -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@REM -@REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files -@REM ---------------------------------------------------------------------------- - -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM set title of command window -title %0 -@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" -if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - -FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B -) - -@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -@REM This allows using the maven wrapper in projects that prohibit checking in binary data. -if exist %WRAPPER_JAR% ( - if "%MVNW_VERBOSE%" == "true" ( - echo Found %WRAPPER_JAR% - ) -) else ( - if not "%MVNW_REPOURL%" == "" ( - SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - ) - if "%MVNW_VERBOSE%" == "true" ( - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %DOWNLOAD_URL% - ) - - powershell -Command "&{"^ - "$webclient = new-object System.Net.WebClient;"^ - "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ - "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ - "}"^ - "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ - "}" - if "%MVNW_VERBOSE%" == "true" ( - echo Finished downloading %WRAPPER_JAR% - ) -) -@REM End of extension - -@REM Provide a "standardized" way to retrieve the CLI args that will -@REM work with both Windows and non-Windows executions. -set MAVEN_CMD_LINE_ARGS=%* - -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" -if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%" == "on" pause - -if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% - -exit /B %ERROR_CODE% From 7b534821780638deb079e278c38f3316aa6d3c53 Mon Sep 17 00:00:00 2001 From: Daniel Pelaez Date: Sat, 27 Apr 2024 11:05:06 -0500 Subject: [PATCH 10/17] Moving to gradle --- build.gradle.kts | 76 ++++++------ gradle/libs.versions.toml | 17 +-- ...ckSocialMediaCredentialsHandlingTests.java | 2 +- .../aws/MockSocialMediaSuccessfulTests.java | 0 .../factory/CredentialsProperties.java | 2 - .../application/factory/SecurityFactory.java | 110 +++++++++--------- .../SocialMediaPublisherProperties.java | 2 - .../aws/StreamLambdaHandlerTest.java | 73 ------------ 8 files changed, 105 insertions(+), 177 deletions(-) rename src/{test => itest}/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaCredentialsHandlingTests.java (99%) rename src/{test => itest}/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaSuccessfulTests.java (100%) delete mode 100644 src/test/java/com/coderstower/socialmediapubisher/application/aws/StreamLambdaHandlerTest.java diff --git a/build.gradle.kts b/build.gradle.kts index eac173d..7d2f946 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,55 +1,65 @@ -/* - * This file was generated by the Gradle 'init' task. - */ - plugins { + java id("io.freefair.lombok") version "8.6" - `java-library` - `maven-publish` + id("org.springframework.boot") version "3.2.5" + id("io.spring.dependency-management") version "1.1.4" } repositories { - mavenLocal() - maven { - url = uri("https://s3-us-west-2.amazonaws.com/dynamodb-local/release") + mavenCentral() +} + +sourceSets { + create("itest") { + compileClasspath += sourceSets.main.get().output + runtimeClasspath += sourceSets.main.get().output } +} + +val itestImplementation by configurations.getting { + extendsFrom(configurations.implementation.get()) +} +val itestRuntimeOnly by configurations.getting + +configurations["itestRuntimeOnly"].extendsFrom(configurations.runtimeOnly.get()) + +val itest = task("itest") { + description = "Runs integration tests." + group = "verification" - maven { - url = uri("https://repo.maven.apache.org/maven2/") + testClassesDirs = sourceSets["itest"].output.classesDirs + classpath = sourceSets["itest"].runtimeClasspath + shouldRunAfter("test") + + useJUnitPlatform() + + testLogging { + events("passed") } } +tasks.check { dependsOn(itest) } + dependencies { - api(libs.org.springframework.boot.spring.boot.starter.web) - api(libs.org.springframework.boot.spring.boot.starter.security) - api(libs.org.springframework.boot.spring.boot.starter.oauth2.client) - api(libs.org.twitter4j.twitter4j.core) - api(libs.com.amazonaws.serverless.aws.serverless.java.container.springboot2) - api(libs.io.github.boostchicken.spring.data.dynamodb) + implementation(libs.org.springframework.boot.spring.boot.starter.web) + implementation(libs.org.springframework.boot.spring.boot.starter.security) + implementation(libs.org.springframework.boot.spring.boot.starter.oauth2.client) + implementation(libs.org.twitter4j.twitter4j.core) + implementation(libs.io.github.boostchicken.spring.data.dynamodb) + implementation(libs.com.amazonaws.serverless.aws.serverless.java.container.springboot2) testImplementation(libs.org.springframework.boot.spring.boot.starter.test) - testImplementation(libs.com.amazonaws.dynamodblocal) - testImplementation(libs.org.junit.pioneer.junit.pioneer) testImplementation(libs.org.springframework.security.spring.security.test) -// compileOnly(libs.org.projectlombok.lombok) + itestImplementation(libs.org.springframework.boot.spring.boot.starter.test) + itestImplementation("org.testcontainers:localstack") + } group = "com.coderstower" version = "0.0.6-SNAPSHOT" description = "social-media-publisher" -java.sourceCompatibility = JavaVersion.VERSION_1_8 - -//publishing { -// publications.create("maven") { -// from(components["java"]) -// } -//} - -tasks.withType() { - options.encoding = "UTF-8" -} -tasks.withType() { - options.encoding = "UTF-8" +java { + sourceCompatibility = JavaVersion.VERSION_17 } tasks.withType { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a091fdf..4b3c44b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,14 +4,9 @@ [versions] com-amazonaws-dynamodblocal = "1.13.0" com-amazonaws-serverless-aws-serverless-java-container-springboot2 = "1.5" -io-github-boostchicken-spring-data-dynamodb = "5.2.3" +io-github-boostchicken-spring-data-dynamodb = "5.2.5" org-junit-pioneer-junit-pioneer = "0.5.1" org-projectlombok-lombok = "1.18.12" -org-springframework-boot-spring-boot-starter-oauth2-client = "2.2.1.RELEASE" -org-springframework-boot-spring-boot-starter-security = "2.2.1.RELEASE" -org-springframework-boot-spring-boot-starter-test = "2.2.1.RELEASE" -org-springframework-boot-spring-boot-starter-web = "2.2.1.RELEASE" -org-springframework-security-spring-security-test = "5.2.1.RELEASE" org-twitter4j-twitter4j-core = "4.0.7" [libraries] @@ -20,9 +15,9 @@ com-amazonaws-serverless-aws-serverless-java-container-springboot2 = { module = io-github-boostchicken-spring-data-dynamodb = { module = "io.github.boostchicken:spring-data-dynamodb", version.ref = "io-github-boostchicken-spring-data-dynamodb" } org-junit-pioneer-junit-pioneer = { module = "org.junit-pioneer:junit-pioneer", version.ref = "org-junit-pioneer-junit-pioneer" } org-projectlombok-lombok = { module = "org.projectlombok:lombok", version.ref = "org-projectlombok-lombok" } -org-springframework-boot-spring-boot-starter-oauth2-client = { module = "org.springframework.boot:spring-boot-starter-oauth2-client", version.ref = "org-springframework-boot-spring-boot-starter-oauth2-client" } -org-springframework-boot-spring-boot-starter-security = { module = "org.springframework.boot:spring-boot-starter-security", version.ref = "org-springframework-boot-spring-boot-starter-security" } -org-springframework-boot-spring-boot-starter-test = { module = "org.springframework.boot:spring-boot-starter-test", version.ref = "org-springframework-boot-spring-boot-starter-test" } -org-springframework-boot-spring-boot-starter-web = { module = "org.springframework.boot:spring-boot-starter-web", version.ref = "org-springframework-boot-spring-boot-starter-web" } -org-springframework-security-spring-security-test = { module = "org.springframework.security:spring-security-test", version.ref = "org-springframework-security-spring-security-test" } +org-springframework-boot-spring-boot-starter-oauth2-client = { module = "org.springframework.boot:spring-boot-starter-oauth2-client" } +org-springframework-boot-spring-boot-starter-security = { module = "org.springframework.boot:spring-boot-starter-security"} +org-springframework-boot-spring-boot-starter-test = { module = "org.springframework.boot:spring-boot-starter-test" } +org-springframework-boot-spring-boot-starter-web = { module = "org.springframework.boot:spring-boot-starter-web" } +org-springframework-security-spring-security-test = { module = "org.springframework.security:spring-security-test" } org-twitter4j-twitter4j-core = { module = "org.twitter4j:twitter4j-core", version.ref = "org-twitter4j-twitter4j-core" } diff --git a/src/test/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaCredentialsHandlingTests.java b/src/itest/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaCredentialsHandlingTests.java similarity index 99% rename from src/test/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaCredentialsHandlingTests.java rename to src/itest/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaCredentialsHandlingTests.java index 90bdaf0..38c8e77 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaCredentialsHandlingTests.java +++ b/src/itest/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaCredentialsHandlingTests.java @@ -48,7 +48,7 @@ @SpringBootTest @AutoConfigureMockMvc -@SetSystemProperty(key = "sqlite4java.library.path", value = "target/native-libs") +//@SetSystemProperty(key = "sqlite4java.library.path", value = "target/native-libs") @TestPropertySource(properties = {"social-media-publisher.principal-names-allowed.linkedin=myuser"}) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) @ActiveProfiles({"linkedin", "twitter, secure"}) diff --git a/src/test/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaSuccessfulTests.java b/src/itest/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaSuccessfulTests.java similarity index 100% rename from src/test/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaSuccessfulTests.java rename to src/itest/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaSuccessfulTests.java diff --git a/src/main/java/com/coderstower/socialmediapubisher/application/factory/CredentialsProperties.java b/src/main/java/com/coderstower/socialmediapubisher/application/factory/CredentialsProperties.java index bcfc572..28f279a 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/application/factory/CredentialsProperties.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/factory/CredentialsProperties.java @@ -2,11 +2,9 @@ import lombok.Builder; import lombok.Data; -import org.springframework.boot.context.properties.ConstructorBinding; import org.springframework.web.util.UriTemplate; @Data -@ConstructorBinding @Builder public class CredentialsProperties { private final UriTemplate loginUrl; diff --git a/src/main/java/com/coderstower/socialmediapubisher/application/factory/SecurityFactory.java b/src/main/java/com/coderstower/socialmediapubisher/application/factory/SecurityFactory.java index 734f873..18cd3a9 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/application/factory/SecurityFactory.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/factory/SecurityFactory.java @@ -7,7 +7,7 @@ import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +//import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; @@ -26,58 +26,58 @@ public class SecurityFactory { * Creating individual security by social network. If we generalize, * Spring Security shows a list of social networks to login with. */ - @Order(1) - @Profile("secure") - @Configuration - public static class LinkedinSecurity extends WebSecurityConfigurerAdapter { - private final ClientRegistrationRepository clientRegistrationRepository; - - public LinkedinSecurity(ClientRegistrationRepository clientRegistrationRepository) { - this.clientRegistrationRepository = clientRegistrationRepository; - } - - @Override - protected void configure(HttpSecurity http) throws Exception { - http - .authorizeRequests() - .antMatchers("/oauth2/linkedin/credentials").authenticated() - .and() - .oauth2Login() - /* - Create a new ClientRegistrationRepository with only Linkedin configuration to avoid - using other OAuth2 configuration over this endpoint - */ - .clientRegistrationRepository(new InMemoryClientRegistrationRepository(clientRegistrationRepository.findByRegistrationId("linkedin"))) - .tokenEndpoint() - .accessTokenResponseClient(authorizationCodeTokenResponseClient()); - } - - private OAuth2AccessTokenResponseClient authorizationCodeTokenResponseClient() { - OAuth2AccessTokenResponseHttpMessageConverter tokenResponseHttpMessageConverter = - new OAuth2AccessTokenResponseHttpMessageConverter(); - tokenResponseHttpMessageConverter.setTokenResponseConverter(new OAuth2AccessTokenResponseConverterWithDefaults()); - - RestTemplate restTemplate = new RestTemplate(Arrays.asList( - new FormHttpMessageConverter(), tokenResponseHttpMessageConverter)); - restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); - - DefaultAuthorizationCodeTokenResponseClient tokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient(); - tokenResponseClient.setRestOperations(restTemplate); - - return tokenResponseClient; - } - } - - @Order(2) - @Configuration - public static class NoSecurity extends WebSecurityConfigurerAdapter { - @Override - protected void configure(HttpSecurity http) throws Exception { - http - .csrf().disable() - .authorizeRequests() - .antMatchers("/**") - .permitAll(); - } - } +// @Order(1) +// @Profile("secure") +// @Configuration +// public static class LinkedinSecurity extends WebSecurityConfigurerAdapter { +// private final ClientRegistrationRepository clientRegistrationRepository; +// +// public LinkedinSecurity(ClientRegistrationRepository clientRegistrationRepository) { +// this.clientRegistrationRepository = clientRegistrationRepository; +// } +// +// @Override +// protected void configure(HttpSecurity http) throws Exception { +// http +// .authorizeRequests() +// .antMatchers("/oauth2/linkedin/credentials").authenticated() +// .and() +// .oauth2Login() +// /* +// Create a new ClientRegistrationRepository with only Linkedin configuration to avoid +// using other OAuth2 configuration over this endpoint +// */ +// .clientRegistrationRepository(new InMemoryClientRegistrationRepository(clientRegistrationRepository.findByRegistrationId("linkedin"))) +// .tokenEndpoint() +// .accessTokenResponseClient(authorizationCodeTokenResponseClient()); +// } +// +// private OAuth2AccessTokenResponseClient authorizationCodeTokenResponseClient() { +// OAuth2AccessTokenResponseHttpMessageConverter tokenResponseHttpMessageConverter = +// new OAuth2AccessTokenResponseHttpMessageConverter(); +// tokenResponseHttpMessageConverter.setTokenResponseConverter(new OAuth2AccessTokenResponseConverterWithDefaults()); +// +// RestTemplate restTemplate = new RestTemplate(Arrays.asList( +// new FormHttpMessageConverter(), tokenResponseHttpMessageConverter)); +// restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); +// +// DefaultAuthorizationCodeTokenResponseClient tokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient(); +// tokenResponseClient.setRestOperations(restTemplate); +// +// return tokenResponseClient; +// } +// } +// +// @Order(2) +// @Configuration +// public static class NoSecurity extends WebSecurityConfigurerAdapter { +// @Override +// protected void configure(HttpSecurity http) throws Exception { +// http +// .csrf().disable() +// .authorizeRequests() +// .antMatchers("/**") +// .permitAll(); +// } +// } } diff --git a/src/main/java/com/coderstower/socialmediapubisher/application/factory/SocialMediaPublisherProperties.java b/src/main/java/com/coderstower/socialmediapubisher/application/factory/SocialMediaPublisherProperties.java index aaf49a6..413f45f 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/application/factory/SocialMediaPublisherProperties.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/factory/SocialMediaPublisherProperties.java @@ -3,13 +3,11 @@ import lombok.Builder; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.ConstructorBinding; import java.util.Map; @ConfigurationProperties(prefix = "social-media-publisher") @Data -@ConstructorBinding @Builder public class SocialMediaPublisherProperties { private final Map principalNamesAllowed; diff --git a/src/test/java/com/coderstower/socialmediapubisher/application/aws/StreamLambdaHandlerTest.java b/src/test/java/com/coderstower/socialmediapubisher/application/aws/StreamLambdaHandlerTest.java deleted file mode 100644 index 50e24ca..0000000 --- a/src/test/java/com/coderstower/socialmediapubisher/application/aws/StreamLambdaHandlerTest.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.coderstower.socialmediapubisher.application.aws; - - -import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; -import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; -import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; -import com.amazonaws.serverless.proxy.model.AwsProxyResponse; -import com.amazonaws.services.lambda.runtime.Context; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import javax.ws.rs.HttpMethod; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; - -import static org.junit.jupiter.api.Assertions.*; - -public class StreamLambdaHandlerTest { - - private static StreamLambdaHandler handler; - private static Context lambdaContext; - - @BeforeAll - public static void setUp() { - handler = new StreamLambdaHandler(); - lambdaContext = new MockLambdaContext(); - } - - @Test - public void ping_streamRequest_respondsWithHello() { - InputStream requestStream = new AwsProxyRequestBuilder("/ping", HttpMethod.GET) - .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON) - .buildStream(); - ByteArrayOutputStream responseStream = new ByteArrayOutputStream(); - - handle(requestStream, responseStream); - - AwsProxyResponse response = readResponse(responseStream); - assertNotNull(response); - assertEquals(Response.Status.OK.getStatusCode(), response.getStatusCode()); - - assertFalse(response.isBase64Encoded()); - - assertTrue(response.getBody().contains("pong")); - assertTrue(response.getBody().contains("Hello, World!")); - - assertTrue(response.getMultiValueHeaders().containsKey(HttpHeaders.CONTENT_TYPE)); - assertTrue(response.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_TYPE).startsWith(MediaType.APPLICATION_JSON)); - } - - private void handle(InputStream is, ByteArrayOutputStream os) { - try { - handler.handleRequest(is, os, lambdaContext); - } catch (IOException e) { - e.printStackTrace(); - fail(e.getMessage()); - } - } - - private AwsProxyResponse readResponse(ByteArrayOutputStream responseStream) { - try { - return LambdaContainerHandler.getObjectMapper().readValue(responseStream.toByteArray(), AwsProxyResponse.class); - } catch (IOException e) { - e.printStackTrace(); - fail("Error while parsing response: " + e.getMessage()); - } - return null; - } -} From 61658b0ef91ec770cc25f54b27dfc87335294835 Mon Sep 17 00:00:00 2001 From: Daniel Pelaez Date: Sat, 27 Apr 2024 18:14:34 -0500 Subject: [PATCH 11/17] Moving to gradle --- build.gradle.kts | 9 ++ .../application/MockedEdgesConfig.java | 17 +++ .../application/PostNextToLinkedInTest.java | 21 ++++ ...ckSocialMediaCredentialsHandlingTests.java | 66 +++++----- .../aws/MockSocialMediaSuccessfulTests.java | 116 +++++++++--------- .../linkedin/wiremock/publishToLinkedIn.json | 0 .../factory/LinkedInProperties.java | 11 ++ .../SocialMediaPublisherProperties.java | 1 + .../factory/SpringPublisherFactory.java | 11 -- .../linkedin/LinkedInPublisher.java | 9 +- .../linkedin/LinkedInPublisherFactory.java | 27 ++++ src/main/resources/application-itest.yml | 14 +++ src/main/resources/application.yml | 11 +- .../linkedin/LinkedInPublisherTest.java | 2 +- 14 files changed, 205 insertions(+), 110 deletions(-) create mode 100644 src/itest/java/com/coderstower/socialmediapubisher/application/MockedEdgesConfig.java create mode 100644 src/itest/java/com/coderstower/socialmediapubisher/application/PostNextToLinkedInTest.java create mode 100644 src/itest/resources/testcases/post/next/linkedin/wiremock/publishToLinkedIn.json create mode 100644 src/main/java/com/coderstower/socialmediapubisher/application/factory/LinkedInProperties.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/LinkedInPublisherFactory.java create mode 100644 src/main/resources/application-itest.yml diff --git a/build.gradle.kts b/build.gradle.kts index 7d2f946..8c69ca9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,6 +5,8 @@ plugins { id("io.spring.dependency-management") version "1.1.4" } +extra["springCloudVersion"] = "2023.0.1" + repositories { mavenCentral() } @@ -40,6 +42,12 @@ val itest = task("itest") { tasks.check { dependsOn(itest) } +dependencyManagement { + imports { + mavenBom("org.springframework.cloud:spring-cloud-dependencies:${property("springCloudVersion")}") + } +} + dependencies { implementation(libs.org.springframework.boot.spring.boot.starter.web) implementation(libs.org.springframework.boot.spring.boot.starter.security) @@ -49,6 +57,7 @@ dependencies { implementation(libs.com.amazonaws.serverless.aws.serverless.java.container.springboot2) testImplementation(libs.org.springframework.boot.spring.boot.starter.test) testImplementation(libs.org.springframework.security.spring.security.test) + itestImplementation("org.springframework.cloud:spring-cloud-starter-contract-stub-runner") itestImplementation(libs.org.springframework.boot.spring.boot.starter.test) itestImplementation("org.testcontainers:localstack") diff --git a/src/itest/java/com/coderstower/socialmediapubisher/application/MockedEdgesConfig.java b/src/itest/java/com/coderstower/socialmediapubisher/application/MockedEdgesConfig.java new file mode 100644 index 0000000..388a4f3 --- /dev/null +++ b/src/itest/java/com/coderstower/socialmediapubisher/application/MockedEdgesConfig.java @@ -0,0 +1,17 @@ +package com.coderstower.socialmediapubisher.application; + +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.core.io.ResourceLoader; +import org.springframework.test.context.ActiveProfiles; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@WireMockTest(httpPort = 9090) +@ActiveProfiles("itest") +public abstract class MockedEdgesConfig { + @Autowired + protected WebTestClient webTestClient; + @Autowired + protected ResourceLoader resourceLoader; +} diff --git a/src/itest/java/com/coderstower/socialmediapubisher/application/PostNextToLinkedInTest.java b/src/itest/java/com/coderstower/socialmediapubisher/application/PostNextToLinkedInTest.java new file mode 100644 index 0000000..8463700 --- /dev/null +++ b/src/itest/java/com/coderstower/socialmediapubisher/application/PostNextToLinkedInTest.java @@ -0,0 +1,21 @@ +package com.coderstower.socialmediapubisher.application; + +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.stubbing.StubMapping; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; + + +public class PostNextToLinkedInTest extends MockedEdgesConfig{ + + @Test + public void publishNextPostSuccessful(WireMockRuntimeInfo wireMockRuntimeInfo) throws IOException { + var wireMock = wireMockRuntimeInfo.getWireMock(); + + var resource = resourceLoader.getResource("classpath:testcases/post/next/linkedin/wiremock/"); + + wireMock.loadMappingsFrom(resource.getFile()); + } +} diff --git a/src/itest/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaCredentialsHandlingTests.java b/src/itest/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaCredentialsHandlingTests.java index 38c8e77..5d74709 100644 --- a/src/itest/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaCredentialsHandlingTests.java +++ b/src/itest/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaCredentialsHandlingTests.java @@ -1,13 +1,13 @@ package com.coderstower.socialmediapubisher.application.aws; import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; -import com.amazonaws.services.dynamodbv2.local.embedded.DynamoDBEmbedded; +//import com.amazonaws.services.dynamodbv2.local.embedded.DynamoDBEmbedded; import com.amazonaws.services.dynamodbv2.model.*; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; -import org.junitpioneer.jupiter.SetSystemProperty; +//import org.junitpioneer.jupiter.SetSystemProperty; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; @@ -40,8 +40,8 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +//import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; +//import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @@ -68,7 +68,7 @@ void publish_credentialsExpired_unauthorizedException() throws Exception { mockingTwitter(); mvc.perform(post("/posts/group1/next") - .with(csrf()) +// .with(csrf()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isUnauthorized()) .andExpect(content().string("Unauthorized for linkedin credential1. Please login again here: http://localhost:8080/oauth2/linkedin/credentials")); @@ -95,7 +95,7 @@ void login_successLinkedin_updateCredentials() throws Exception { .thenReturn(authorizedClient); mvc.perform(get("/oauth2/linkedin/credentials") - .with(authentication(authenticationToken)) +// .with(authentication(authenticationToken)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()); } @@ -129,33 +129,33 @@ public Clock clock() { return Clock.fixed(ZonedDateTime.of(2020, 3, 3, 5, 6, 8, 1, ZoneId.of("UTC")).toInstant(), ZoneId.of("UTC")); } - @Bean - public AmazonDynamoDB amazonDynamoDB() { - AmazonDynamoDB ddb = DynamoDBEmbedded.create().amazonDynamoDB(); - - createTable(ddb, "Oauth1Credentials", "id"); - createTable(ddb, "Oauth2Credentials", "id"); - - PutItemRequest oauth2Credentials = new PutItemRequest(); - oauth2Credentials.setTableName("Oauth2Credentials"); - oauth2Credentials.addItemEntry("id", new AttributeValue("credential1")); - oauth2Credentials.addItemEntry("accessToken", new AttributeValue("access123")); - oauth2Credentials.addItemEntry("expirationDate", new AttributeValue("2020-02-01T05:05:05")); - - ddb.putItem(oauth2Credentials); - - PutItemRequest oauth1Credentials = new PutItemRequest(); - oauth1Credentials.setTableName("Oauth1Credentials"); - oauth1Credentials.addItemEntry("id", new AttributeValue("twitter")); - oauth1Credentials.addItemEntry("accessToken", new AttributeValue("access123")); - oauth1Credentials.addItemEntry("consumerKey", new AttributeValue("consumer123")); - oauth1Credentials.addItemEntry("consumerSecret", new AttributeValue("csecret123")); - oauth1Credentials.addItemEntry("tokenSecret", new AttributeValue("tsecret123")); - - ddb.putItem(oauth1Credentials); - - return ddb; - } +// @Bean +// public AmazonDynamoDB amazonDynamoDB() { +// AmazonDynamoDB ddb = DynamoDBEmbedded.create().amazonDynamoDB(); +// +// createTable(ddb, "Oauth1Credentials", "id"); +// createTable(ddb, "Oauth2Credentials", "id"); +// +// PutItemRequest oauth2Credentials = new PutItemRequest(); +// oauth2Credentials.setTableName("Oauth2Credentials"); +// oauth2Credentials.addItemEntry("id", new AttributeValue("credential1")); +// oauth2Credentials.addItemEntry("accessToken", new AttributeValue("access123")); +// oauth2Credentials.addItemEntry("expirationDate", new AttributeValue("2020-02-01T05:05:05")); +// +// ddb.putItem(oauth2Credentials); +// +// PutItemRequest oauth1Credentials = new PutItemRequest(); +// oauth1Credentials.setTableName("Oauth1Credentials"); +// oauth1Credentials.addItemEntry("id", new AttributeValue("twitter")); +// oauth1Credentials.addItemEntry("accessToken", new AttributeValue("access123")); +// oauth1Credentials.addItemEntry("consumerKey", new AttributeValue("consumer123")); +// oauth1Credentials.addItemEntry("consumerSecret", new AttributeValue("csecret123")); +// oauth1Credentials.addItemEntry("tokenSecret", new AttributeValue("tsecret123")); +// +// ddb.putItem(oauth1Credentials); +// +// return ddb; +// } private CreateTableResult createTable(AmazonDynamoDB ddb, String tableName, String hashKeyName) { List attributeDefinitions = new ArrayList<>(); diff --git a/src/itest/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaSuccessfulTests.java b/src/itest/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaSuccessfulTests.java index ccac009..f795759 100644 --- a/src/itest/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaSuccessfulTests.java +++ b/src/itest/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaSuccessfulTests.java @@ -1,11 +1,11 @@ package com.coderstower.socialmediapubisher.application.aws; import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; -import com.amazonaws.services.dynamodbv2.local.embedded.DynamoDBEmbedded; +//import com.amazonaws.services.dynamodbv2.local.embedded.DynamoDBEmbedded; import com.amazonaws.services.dynamodbv2.model.*; import com.coderstower.socialmediapubisher.application.socialmedia.linkedin.*; import org.junit.jupiter.api.Test; -import org.junitpioneer.jupiter.SetSystemProperty; +//import org.junitpioneer.jupiter.SetSystemProperty; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; @@ -27,14 +27,14 @@ import static org.hamcrest.Matchers.is; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +//import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @SpringBootTest @AutoConfigureMockMvc -@SetSystemProperty(key = "sqlite4java.library.path", value = "target/native-libs") +//@SetSystemProperty(key = "sqlite4java.library.path", value = "target/native-libs") @ActiveProfiles({"linkedin", "twitter"}) class MockSocialMediaSuccessfulTests { @Autowired @@ -58,7 +58,7 @@ void publish_allSocialMedia_success() throws Exception { mockingLinkedIn(); mvc.perform(post("/posts/group1/next") - .with(csrf()) +// .with(csrf()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content() @@ -119,59 +119,59 @@ public Clock clock() { return Clock.fixed(ZonedDateTime.of(2020, 3, 3, 5, 6, 8, 1, ZoneId.of("UTC")).toInstant(), ZoneId.of("UTC")); } - @Bean - public AmazonDynamoDB amazonDynamoDB() { - AmazonDynamoDB ddb = DynamoDBEmbedded.create().amazonDynamoDB(); - - createTable(ddb, "Oauth1Credentials", "id"); - createTable(ddb, "Oauth2Credentials", "id"); - createTable(ddb, "Posts", "id"); - - PutItemRequest oauth2Credentials = new PutItemRequest(); - oauth2Credentials.setTableName("Oauth2Credentials"); - oauth2Credentials.addItemEntry("id", new AttributeValue("linkedin")); - oauth2Credentials.addItemEntry("accessToken", new AttributeValue("access123")); - oauth2Credentials.addItemEntry("allowedGroups", new AttributeValue().withL(List.of(new AttributeValue("group1")))); - oauth2Credentials.addItemEntry("expirationDate", new AttributeValue("2020-04-01T05:05:05")); - - ddb.putItem(oauth2Credentials); - - PutItemRequest oauth1Credentials = new PutItemRequest(); - oauth1Credentials.setTableName("Oauth1Credentials"); - oauth1Credentials.addItemEntry("id", new AttributeValue("twitter")); - oauth1Credentials.addItemEntry("accessToken", new AttributeValue("access123")); - oauth1Credentials.addItemEntry("consumerKey", new AttributeValue("consumer123")); - oauth1Credentials.addItemEntry("consumerSecret", new AttributeValue("csecret123")); - oauth1Credentials.addItemEntry("tokenSecret", new AttributeValue("tsecret123")); - - ddb.putItem(oauth1Credentials); - - PutItemRequest post1 = new PutItemRequest(); - post1.setTableName("Posts"); - post1.addItemEntry("id", new AttributeValue("1")); - post1.addItemEntry("name", new AttributeValue("My Post 1")); - post1.addItemEntry("description", new AttributeValue("My first post")); - post1.addItemEntry("tags", new AttributeValue().withL(new AttributeValue("tag1"), new AttributeValue("tag2"))); - post1.addItemEntry("url", new AttributeValue("https://coderstower.com/2020/02/18/unit-tests-vs-integration-tests/")); - post1.addItemEntry("publishedDate", new AttributeValue("2013-09-17T18:47:52")); - post1.addItemEntry("group", new AttributeValue("group1")); - - ddb.putItem(post1); - - PutItemRequest post2 = new PutItemRequest(); - post2.setTableName("Posts"); - post2.addItemEntry("id", new AttributeValue("2")); - post2.addItemEntry("name", new AttributeValue("My Post 2")); - post2.addItemEntry("description", new AttributeValue("My second post")); - post2.addItemEntry("tags", new AttributeValue().withL(new AttributeValue("tag1"), new AttributeValue("tag2"))); - post2.addItemEntry("url", new AttributeValue("https://coderstower.com/2020/01/13/open-close-principle-by-example/")); - post2.addItemEntry("publishedDate", new AttributeValue("2012-09-17T18:47:52")); - post2.addItemEntry("group", new AttributeValue("group1")); - - ddb.putItem(post2); - - return ddb; - } +// @Bean +// public AmazonDynamoDB amazonDynamoDB() { +// AmazonDynamoDB ddb = DynamoDBEmbedded.create().amazonDynamoDB(); +// +// createTable(ddb, "Oauth1Credentials", "id"); +// createTable(ddb, "Oauth2Credentials", "id"); +// createTable(ddb, "Posts", "id"); +// +// PutItemRequest oauth2Credentials = new PutItemRequest(); +// oauth2Credentials.setTableName("Oauth2Credentials"); +// oauth2Credentials.addItemEntry("id", new AttributeValue("linkedin")); +// oauth2Credentials.addItemEntry("accessToken", new AttributeValue("access123")); +// oauth2Credentials.addItemEntry("allowedGroups", new AttributeValue().withL(List.of(new AttributeValue("group1")))); +// oauth2Credentials.addItemEntry("expirationDate", new AttributeValue("2020-04-01T05:05:05")); +// +// ddb.putItem(oauth2Credentials); +// +// PutItemRequest oauth1Credentials = new PutItemRequest(); +// oauth1Credentials.setTableName("Oauth1Credentials"); +// oauth1Credentials.addItemEntry("id", new AttributeValue("twitter")); +// oauth1Credentials.addItemEntry("accessToken", new AttributeValue("access123")); +// oauth1Credentials.addItemEntry("consumerKey", new AttributeValue("consumer123")); +// oauth1Credentials.addItemEntry("consumerSecret", new AttributeValue("csecret123")); +// oauth1Credentials.addItemEntry("tokenSecret", new AttributeValue("tsecret123")); +// +// ddb.putItem(oauth1Credentials); +// +// PutItemRequest post1 = new PutItemRequest(); +// post1.setTableName("Posts"); +// post1.addItemEntry("id", new AttributeValue("1")); +// post1.addItemEntry("name", new AttributeValue("My Post 1")); +// post1.addItemEntry("description", new AttributeValue("My first post")); +// post1.addItemEntry("tags", new AttributeValue().withL(new AttributeValue("tag1"), new AttributeValue("tag2"))); +// post1.addItemEntry("url", new AttributeValue("https://coderstower.com/2020/02/18/unit-tests-vs-integration-tests/")); +// post1.addItemEntry("publishedDate", new AttributeValue("2013-09-17T18:47:52")); +// post1.addItemEntry("group", new AttributeValue("group1")); +// +// ddb.putItem(post1); +// +// PutItemRequest post2 = new PutItemRequest(); +// post2.setTableName("Posts"); +// post2.addItemEntry("id", new AttributeValue("2")); +// post2.addItemEntry("name", new AttributeValue("My Post 2")); +// post2.addItemEntry("description", new AttributeValue("My second post")); +// post2.addItemEntry("tags", new AttributeValue().withL(new AttributeValue("tag1"), new AttributeValue("tag2"))); +// post2.addItemEntry("url", new AttributeValue("https://coderstower.com/2020/01/13/open-close-principle-by-example/")); +// post2.addItemEntry("publishedDate", new AttributeValue("2012-09-17T18:47:52")); +// post2.addItemEntry("group", new AttributeValue("group1")); +// +// ddb.putItem(post2); +// +// return ddb; +// } private CreateTableResult createTable(AmazonDynamoDB ddb, String tableName, String hashKeyName) { List attributeDefinitions = new ArrayList<>(); diff --git a/src/itest/resources/testcases/post/next/linkedin/wiremock/publishToLinkedIn.json b/src/itest/resources/testcases/post/next/linkedin/wiremock/publishToLinkedIn.json new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/com/coderstower/socialmediapubisher/application/factory/LinkedInProperties.java b/src/main/java/com/coderstower/socialmediapubisher/application/factory/LinkedInProperties.java new file mode 100644 index 0000000..f18c5bb --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/application/factory/LinkedInProperties.java @@ -0,0 +1,11 @@ +package com.coderstower.socialmediapubisher.application.factory; + +import lombok.Builder; +import lombok.Data; + +import java.net.URI; +@Data +@Builder +public class LinkedInProperties { + private final URI baseURL; +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/application/factory/SocialMediaPublisherProperties.java b/src/main/java/com/coderstower/socialmediapubisher/application/factory/SocialMediaPublisherProperties.java index 413f45f..def7c84 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/application/factory/SocialMediaPublisherProperties.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/factory/SocialMediaPublisherProperties.java @@ -12,4 +12,5 @@ public class SocialMediaPublisherProperties { private final Map principalNamesAllowed; private final CredentialsProperties credentials; + private final LinkedInProperties linkedIn; } diff --git a/src/main/java/com/coderstower/socialmediapubisher/application/factory/SpringPublisherFactory.java b/src/main/java/com/coderstower/socialmediapubisher/application/factory/SpringPublisherFactory.java index c3a6715..a36d73d 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/application/factory/SpringPublisherFactory.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/factory/SpringPublisherFactory.java @@ -39,17 +39,6 @@ public TwitterPublisher twitterPublisher(OAuth1CredentialsRepository oauth1Crede return new TwitterPublisher("twitter", oauth1CredentialsRepository, twitter, clock); } - @Bean - @Profile("linkedin") - public LinkedInPublisher linkedInPublisher(OAuth2CredentialsRepository oauth2CredentialsRepository, RestTemplate restTemplate, Clock clock, SocialMediaPublisherProperties socialMediaPublisherProperties) { - return new LinkedInPublisher("linkedin", oauth2CredentialsRepository, restTemplate, clock, socialMediaPublisherProperties.getCredentials().getLoginUrl()); - } - - @Bean - public OAuth2CredentialsManager oAuth2CredentialsManager(OAuth2CredentialsRepository oAuth2CredentialsRepository, SocialMediaPublisherProperties socialMediaPublisherProperties) { - return new OAuth2CredentialsManager(oAuth2CredentialsRepository, socialMediaPublisherProperties.getPrincipalNamesAllowed()); - } - @Bean public RestTemplate restTemplate() { return new RestTemplate(); diff --git a/src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/LinkedInPublisher.java b/src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/LinkedInPublisher.java index acf20ec..7cc5dbf 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/LinkedInPublisher.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/LinkedInPublisher.java @@ -13,6 +13,7 @@ import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriTemplate; +import java.net.URI; import java.time.Clock; import java.time.LocalDateTime; import java.util.ArrayList; @@ -27,13 +28,15 @@ public class LinkedInPublisher implements SocialMediaPublisher { private final RestTemplate restTemplate; private final Clock clock; private final UriTemplate loginURL; + private final URI baseURL; - public LinkedInPublisher(String name, OAuth2CredentialsRepository oauth2CredentialsRepository, RestTemplate restTemplate, Clock clock, UriTemplate loginURL) { + public LinkedInPublisher(String name, OAuth2CredentialsRepository oauth2CredentialsRepository, RestTemplate restTemplate, Clock clock, UriTemplate loginURL, URI baseURL) { this.name = name; this.oauth2CredentialsRepository = oauth2CredentialsRepository; this.restTemplate = restTemplate; this.clock = clock; this.loginURL = loginURL; + this.baseURL = baseURL; } @Override @@ -139,7 +142,7 @@ private String publish(LinkedInShare linkedInShare, OAuth2Credentials credential HttpEntity requestEntity = new HttpEntity<>(linkedInShare, httpHeaders); - ResponseEntity response = restTemplate.exchange("https://api.linkedin.com/rest/posts", HttpMethod.POST, requestEntity, Void.class); + ResponseEntity response = restTemplate.exchange(baseURL + "/rest/posts", HttpMethod.POST, requestEntity, Void.class); if (!response.getStatusCode().is2xxSuccessful()) { throw new IllegalStateException("Problem trying to share a linkedin post: " + response.getStatusCode()); @@ -157,7 +160,7 @@ private Profile getProfile(OAuth2Credentials credentials) { HttpEntity requestEntity = new HttpEntity<>(httpHeaders); - ResponseEntity response = restTemplate.exchange("https://api.linkedin.com/v2/userinfo", HttpMethod.GET, requestEntity, Profile.class); + ResponseEntity response = restTemplate.exchange(baseURL + "/v2/userinfo", HttpMethod.GET, requestEntity, Profile.class); if (!response.getStatusCode().is2xxSuccessful()) { throw new IllegalStateException("Problem trying to get the profile: " + response.getStatusCode()); diff --git a/src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/LinkedInPublisherFactory.java b/src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/LinkedInPublisherFactory.java new file mode 100644 index 0000000..29ad60d --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/LinkedInPublisherFactory.java @@ -0,0 +1,27 @@ +package com.coderstower.socialmediapubisher.application.socialmedia.linkedin; + +import com.coderstower.socialmediapubisher.application.factory.SocialMediaPublisherProperties; +import com.coderstower.socialmediapubisher.domain.security.OAuth2CredentialsManager; +import com.coderstower.socialmediapubisher.domain.security.repository.OAuth2CredentialsRepository; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.web.client.RestTemplate; + +import java.time.Clock; + + +@Configuration +@Profile("linkedin") +public class LinkedInPublisherFactory { + + @Bean + public LinkedInPublisher linkedInPublisher(OAuth2CredentialsRepository oauth2CredentialsRepository, RestTemplate restTemplate, Clock clock, SocialMediaPublisherProperties socialMediaPublisherProperties) { + return new LinkedInPublisher("linkedin", oauth2CredentialsRepository, restTemplate, clock, socialMediaPublisherProperties.getCredentials().getLoginUrl(), socialMediaPublisherProperties.getLinkedIn().getBaseURL()); + } + + @Bean + public OAuth2CredentialsManager oAuth2CredentialsManager(OAuth2CredentialsRepository oAuth2CredentialsRepository, SocialMediaPublisherProperties socialMediaPublisherProperties) { + return new OAuth2CredentialsManager(oAuth2CredentialsRepository, socialMediaPublisherProperties.getPrincipalNamesAllowed()); + } +} diff --git a/src/main/resources/application-itest.yml b/src/main/resources/application-itest.yml new file mode 100644 index 0000000..1c34873 --- /dev/null +++ b/src/main/resources/application-itest.yml @@ -0,0 +1,14 @@ +social-media-publisher: + principal-names-allowed: + linkedin: xxx + linked-in: + base-url: http://localhost:9090 + +amazon: + dynamodb: + accesskey: xxx + secretkey: xxx + +spring: + main: + allow-bean-definition-overriding: true \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 2f70249..6ec0ce7 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -4,11 +4,14 @@ # level: # root: WARN +spring: + profiles: + group: + itest: linkedin + social-media-publisher: + linked-in: + base-url: https://api.linkedin.com credentials: login-host: 'http://localhost:8080' login-url: '${social-media-publisher.credentials.login-host}/oauth2/{social-media-id}/credentials' - -spring: - main: - allow-bean-definition-overriding: true \ No newline at end of file diff --git a/src/test/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/LinkedInPublisherTest.java b/src/test/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/LinkedInPublisherTest.java index 5f28558..6b3769c 100644 --- a/src/test/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/LinkedInPublisherTest.java +++ b/src/test/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/LinkedInPublisherTest.java @@ -41,7 +41,7 @@ class LinkedInPublisherTest { public void before() { this.now = ZonedDateTime.of(2020, 3, 3, 5, 6, 8, 1, ZoneId.of("UTC")); this.linkedInPublisher = new LinkedInPublisher("linkedin", oauth2CredentialsRepository, restTemplate, Clock - .fixed(now.toInstant(), ZoneId.of("UTC")), new UriTemplate("http://localhost:8080/oauth2/{social-media}/credentials")); + .fixed(now.toInstant(), ZoneId.of("UTC")), new UriTemplate("http://localhost:8080/oauth2/{social-media}/credentials"), URI.create("http://localhost:8080")); } @Test From 3fe380cad590f18609f468a9e87d32fe9e95cdd8 Mon Sep 17 00:00:00 2001 From: Daniel Pelaez Date: Sun, 28 Apr 2024 11:55:32 -0500 Subject: [PATCH 12/17] Moving to gradle --- build.gradle.kts | 1 + .../application/MockedEdgesConfig.java | 62 +++- .../application/PostNextToLinkedInTest.java | 21 -- .../application/SpringSlf4jNotifier.java | 21 ++ ...ckSocialMediaCredentialsHandlingTests.java | 180 ----------- .../aws/MockSocialMediaSuccessfulTests.java | 196 ------------ .../linkedin/PostNextToLinkedInTest.java | 58 ++++ .../post/next/linkedin/response.json | 21 ++ .../mappings/getOauth2Credentials.json | 44 +++ .../linkedin/wiremock/mappings/getPosts.json | 56 ++++ .../wiremock/mappings/getUserInfo.json | 15 + .../wiremock/mappings/postOnLinkedIn.json | 32 ++ .../linkedin/wiremock/mappings/putPost.json | 51 ++++ .../wiremock/mappings/updatePost.json | 72 +++++ .../linkedin/wiremock/publishToLinkedIn.json | 0 .../application/factory/SecurityFactory.java | 29 +- src/main/resources/application-itest.yml | 11 +- .../LocalDateTimeConverterTest.java | 26 -- .../aws/repository/URLConverterTest.java | 28 -- .../OAuth1CredentialAWSRepositoryTest.java | 43 --- .../OAuth2CredentialAWSRepositoryTest.java | 65 ---- .../post/PostAWSRepositoryTest.java | 97 ------ .../controller/PostsControllerTest.java | 72 ----- .../linkedin/LinkedInPublisherTest.java | 280 ------------------ .../twitter/TwitterPublisherTest.java | 217 -------------- .../domain/post/PostPublisherTest.java | 195 ------------ .../OAuth2CredentialsManagerTest.java | 107 ------- 27 files changed, 448 insertions(+), 1552 deletions(-) delete mode 100644 src/itest/java/com/coderstower/socialmediapubisher/application/PostNextToLinkedInTest.java create mode 100644 src/itest/java/com/coderstower/socialmediapubisher/application/SpringSlf4jNotifier.java delete mode 100644 src/itest/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaCredentialsHandlingTests.java delete mode 100644 src/itest/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaSuccessfulTests.java create mode 100644 src/itest/java/com/coderstower/socialmediapubisher/application/linkedin/PostNextToLinkedInTest.java create mode 100644 src/itest/resources/testcases/post/next/linkedin/response.json create mode 100644 src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/getOauth2Credentials.json create mode 100644 src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/getPosts.json create mode 100644 src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/getUserInfo.json create mode 100644 src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/postOnLinkedIn.json create mode 100644 src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/putPost.json create mode 100644 src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/updatePost.json delete mode 100644 src/itest/resources/testcases/post/next/linkedin/wiremock/publishToLinkedIn.json delete mode 100644 src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/LocalDateTimeConverterTest.java delete mode 100644 src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/URLConverterTest.java delete mode 100644 src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth1/OAuth1CredentialAWSRepositoryTest.java delete mode 100644 src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth2/OAuth2CredentialAWSRepositoryTest.java delete mode 100644 src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/post/PostAWSRepositoryTest.java delete mode 100644 src/test/java/com/coderstower/socialmediapubisher/application/controller/PostsControllerTest.java delete mode 100644 src/test/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/LinkedInPublisherTest.java delete mode 100644 src/test/java/com/coderstower/socialmediapubisher/application/socialmedia/twitter/TwitterPublisherTest.java delete mode 100644 src/test/java/com/coderstower/socialmediapubisher/domain/post/PostPublisherTest.java delete mode 100644 src/test/java/com/coderstower/socialmediapubisher/domain/security/OAuth2CredentialsManagerTest.java diff --git a/build.gradle.kts b/build.gradle.kts index 8c69ca9..b34d055 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -60,6 +60,7 @@ dependencies { itestImplementation("org.springframework.cloud:spring-cloud-starter-contract-stub-runner") itestImplementation(libs.org.springframework.boot.spring.boot.starter.test) itestImplementation("org.testcontainers:localstack") + itestImplementation("io.rest-assured:rest-assured:5.4.0") } diff --git a/src/itest/java/com/coderstower/socialmediapubisher/application/MockedEdgesConfig.java b/src/itest/java/com/coderstower/socialmediapubisher/application/MockedEdgesConfig.java index 388a4f3..b582dff 100644 --- a/src/itest/java/com/coderstower/socialmediapubisher/application/MockedEdgesConfig.java +++ b/src/itest/java/com/coderstower/socialmediapubisher/application/MockedEdgesConfig.java @@ -1,17 +1,63 @@ package com.coderstower.socialmediapubisher.application; -import com.github.tomakehurst.wiremock.junit5.WireMockTest; -import org.springframework.beans.factory.annotation.Autowired; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.tomakehurst.wiremock.junit5.WireMockExtension; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import org.junit.jupiter.api.extension.RegisterExtension; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.core.io.ResourceLoader; +import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.test.context.ActiveProfiles; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.junit.jupiter.api.Assertions.assertEquals; + @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@WireMockTest(httpPort = 9090) @ActiveProfiles("itest") public abstract class MockedEdgesConfig { - @Autowired - protected WebTestClient webTestClient; - @Autowired - protected ResourceLoader resourceLoader; + @RegisterExtension + static WireMockExtension wm1 = WireMockExtension.newInstance() + .options( + wireMockConfig() + .port(8089) + .notifier(new SpringSlf4jNotifier()) + ) + .configureStaticDsl(true) + .failOnUnmatchedRequests(true) + .build(); + + @LocalServerPort + protected Integer port; + private ObjectMapper mapper = new ObjectMapper(); + protected Path parentPath = Path.of("src/itest/resources/"); + + protected void loadMocks(WireMockRuntimeInfo wireMockRuntimeInfo, String mocksPath) { + var wireMock = wireMockRuntimeInfo.getWireMock(); + + var mocksFolder = parentPath.resolve(mocksPath); + + wireMock.loadMappingsFrom(mocksFolder.toFile()); + } + + protected void validateResponse(String expectedResponsePath, String response) { + try { + var expectedTree = mapper.readTree(readFromFile(expectedResponsePath)); + var expectedJSONPretty = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(expectedTree); + + var currentTree = mapper.readTree(response); + var currentJSONPretty = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(currentTree); + + assertEquals(expectedJSONPretty, currentJSONPretty); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + + private String readFromFile(String filePath) throws IOException { + return Files.readString(parentPath.resolve(filePath)); + } } diff --git a/src/itest/java/com/coderstower/socialmediapubisher/application/PostNextToLinkedInTest.java b/src/itest/java/com/coderstower/socialmediapubisher/application/PostNextToLinkedInTest.java deleted file mode 100644 index 8463700..0000000 --- a/src/itest/java/com/coderstower/socialmediapubisher/application/PostNextToLinkedInTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.coderstower.socialmediapubisher.application; - -import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; -import com.github.tomakehurst.wiremock.stubbing.StubMapping; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.nio.file.Files; - - -public class PostNextToLinkedInTest extends MockedEdgesConfig{ - - @Test - public void publishNextPostSuccessful(WireMockRuntimeInfo wireMockRuntimeInfo) throws IOException { - var wireMock = wireMockRuntimeInfo.getWireMock(); - - var resource = resourceLoader.getResource("classpath:testcases/post/next/linkedin/wiremock/"); - - wireMock.loadMappingsFrom(resource.getFile()); - } -} diff --git a/src/itest/java/com/coderstower/socialmediapubisher/application/SpringSlf4jNotifier.java b/src/itest/java/com/coderstower/socialmediapubisher/application/SpringSlf4jNotifier.java new file mode 100644 index 0000000..8f05f59 --- /dev/null +++ b/src/itest/java/com/coderstower/socialmediapubisher/application/SpringSlf4jNotifier.java @@ -0,0 +1,21 @@ +package com.coderstower.socialmediapubisher.application; + +import com.github.tomakehurst.wiremock.common.Notifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class SpringSlf4jNotifier implements Notifier { + private static final Logger log = LoggerFactory.getLogger(SpringSlf4jNotifier.class); + + public void info(String message) { + log.info(message); + } + + public void error(String message) { + log.error(message); + } + + public void error(String message, Throwable t) { + log.error(message, t); + } +} diff --git a/src/itest/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaCredentialsHandlingTests.java b/src/itest/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaCredentialsHandlingTests.java deleted file mode 100644 index 5d74709..0000000 --- a/src/itest/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaCredentialsHandlingTests.java +++ /dev/null @@ -1,180 +0,0 @@ -package com.coderstower.socialmediapubisher.application.aws; - -import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; -//import com.amazonaws.services.dynamodbv2.local.embedded.DynamoDBEmbedded; -import com.amazonaws.services.dynamodbv2.model.*; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; -//import org.junitpioneer.jupiter.SetSystemProperty; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Bean; -import org.springframework.http.MediaType; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; -import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.user.DefaultOAuth2User; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.web.servlet.MockMvc; -import twitter4j.Paging; -import twitter4j.ResponseList; -import twitter4j.Twitter; -import twitter4j.TwitterException; - -import java.time.*; -import java.util.*; - -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -//import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; -//import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -@SpringBootTest -@AutoConfigureMockMvc -//@SetSystemProperty(key = "sqlite4java.library.path", value = "target/native-libs") -@TestPropertySource(properties = {"social-media-publisher.principal-names-allowed.linkedin=myuser"}) -@TestMethodOrder(MethodOrderer.OrderAnnotation.class) -@ActiveProfiles({"linkedin", "twitter, secure"}) -class MockSocialMediaCredentialsHandlingTests { - @Autowired - private MockMvc mvc; - @MockBean - private Twitter twitter; - @MockBean - private OAuth2AuthorizedClientService authorizedClientService; - @Autowired - private ClientRegistrationRepository registrations; - - @Test - @Order(1) - void publish_credentialsExpired_unauthorizedException() throws Exception { - mockingTwitter(); - - mvc.perform(post("/posts/group1/next") -// .with(csrf()) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isUnauthorized()) - .andExpect(content().string("Unauthorized for linkedin credential1. Please login again here: http://localhost:8080/oauth2/linkedin/credentials")); - } - - @Test - @Order(2) - void login_noSuccessLinkedin_return302() throws Exception { - mvc.perform(get("/oauth2/linkedin/credentials") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isFound()) - .andExpect(header().string("Location", "http://localhost/oauth2/authorization/linkedin")); - } - - @Test - @Order(3) - void login_successLinkedin_updateCredentials() throws Exception { - mockingTwitter(); - - OAuth2AuthenticationToken authenticationToken = createToken(); - OAuth2AuthorizedClient authorizedClient = createAuthorizedClient(authenticationToken); - - when(this.authorizedClientService.loadAuthorizedClient(eq("linkedin"), anyString())) - .thenReturn(authorizedClient); - - mvc.perform(get("/oauth2/linkedin/credentials") -// .with(authentication(authenticationToken)) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()); - } - - private OAuth2AuthorizedClient createAuthorizedClient(OAuth2AuthenticationToken authenticationToken) { - OAuth2AccessToken accessToken = new OAuth2AccessToken( - OAuth2AccessToken.TokenType.BEARER, - "newAccessToken", - LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1).toInstant(ZoneOffset.UTC), - LocalDateTime.of(2020, 5, 3, 5, 6, 8, 1).toInstant(ZoneOffset.UTC)); - - ClientRegistration clientRegistration = this.registrations.findByRegistrationId(authenticationToken.getAuthorizedClientRegistrationId()); - return new OAuth2AuthorizedClient(clientRegistration, authenticationToken.getName(), accessToken); - } - - private OAuth2AuthenticationToken createToken() { - Set authorities = new HashSet<>(AuthorityUtils.createAuthorityList("USER")); - OAuth2User oAuth2User = new DefaultOAuth2User(authorities, Collections.singletonMap("id", "myuser"), "id"); - return new OAuth2AuthenticationToken(oAuth2User, authorities, "linkedin"); - } - - private void mockingTwitter() throws TwitterException { - Paging paging = new Paging(1, 1); - when(twitter.getHomeTimeline(paging)).thenReturn(mock(ResponseList.class)); - } - - @TestConfiguration - static class OverriddenConfiguration { - @Bean - public Clock clock() { - return Clock.fixed(ZonedDateTime.of(2020, 3, 3, 5, 6, 8, 1, ZoneId.of("UTC")).toInstant(), ZoneId.of("UTC")); - } - -// @Bean -// public AmazonDynamoDB amazonDynamoDB() { -// AmazonDynamoDB ddb = DynamoDBEmbedded.create().amazonDynamoDB(); -// -// createTable(ddb, "Oauth1Credentials", "id"); -// createTable(ddb, "Oauth2Credentials", "id"); -// -// PutItemRequest oauth2Credentials = new PutItemRequest(); -// oauth2Credentials.setTableName("Oauth2Credentials"); -// oauth2Credentials.addItemEntry("id", new AttributeValue("credential1")); -// oauth2Credentials.addItemEntry("accessToken", new AttributeValue("access123")); -// oauth2Credentials.addItemEntry("expirationDate", new AttributeValue("2020-02-01T05:05:05")); -// -// ddb.putItem(oauth2Credentials); -// -// PutItemRequest oauth1Credentials = new PutItemRequest(); -// oauth1Credentials.setTableName("Oauth1Credentials"); -// oauth1Credentials.addItemEntry("id", new AttributeValue("twitter")); -// oauth1Credentials.addItemEntry("accessToken", new AttributeValue("access123")); -// oauth1Credentials.addItemEntry("consumerKey", new AttributeValue("consumer123")); -// oauth1Credentials.addItemEntry("consumerSecret", new AttributeValue("csecret123")); -// oauth1Credentials.addItemEntry("tokenSecret", new AttributeValue("tsecret123")); -// -// ddb.putItem(oauth1Credentials); -// -// return ddb; -// } - - private CreateTableResult createTable(AmazonDynamoDB ddb, String tableName, String hashKeyName) { - List attributeDefinitions = new ArrayList<>(); - attributeDefinitions.add(new AttributeDefinition(hashKeyName, ScalarAttributeType.S)); - - List ks = new ArrayList<>(); - ks.add(new KeySchemaElement(hashKeyName, KeyType.HASH)); - - ProvisionedThroughput provisionedthroughput = new ProvisionedThroughput(1000L, 1000L); - - CreateTableRequest request = - new CreateTableRequest() - .withTableName(tableName) - .withAttributeDefinitions(attributeDefinitions) - .withKeySchema(ks) - .withProvisionedThroughput(provisionedthroughput); - - return ddb.createTable(request); - } - } - -} diff --git a/src/itest/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaSuccessfulTests.java b/src/itest/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaSuccessfulTests.java deleted file mode 100644 index f795759..0000000 --- a/src/itest/java/com/coderstower/socialmediapubisher/application/aws/MockSocialMediaSuccessfulTests.java +++ /dev/null @@ -1,196 +0,0 @@ -package com.coderstower.socialmediapubisher.application.aws; - -import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; -//import com.amazonaws.services.dynamodbv2.local.embedded.DynamoDBEmbedded; -import com.amazonaws.services.dynamodbv2.model.*; -import com.coderstower.socialmediapubisher.application.socialmedia.linkedin.*; -import org.junit.jupiter.api.Test; -//import org.junitpioneer.jupiter.SetSystemProperty; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Bean; -import org.springframework.http.*; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.web.client.RestTemplate; -import twitter4j.*; - -import java.time.Clock; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.List; - -import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -//import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -@SpringBootTest -@AutoConfigureMockMvc -//@SetSystemProperty(key = "sqlite4java.library.path", value = "target/native-libs") -@ActiveProfiles({"linkedin", "twitter"}) -class MockSocialMediaSuccessfulTests { - @Autowired - private MockMvc mvc; - @MockBean - private Twitter twitter; - @MockBean - private RestTemplate restTemplate; - - @Test - void ping() throws Exception { - mvc.perform(get("/ping") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.pong", is("Hello, World!"))); - } - - @Test - void publish_allSocialMedia_success() throws Exception { - mockingTwitter(); - mockingLinkedIn(); - - mvc.perform(post("/posts/group1/next") -// .with(csrf()) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(content() - .json("{\"id\":\"2\",\"name\":\"My Post 2\",\"description\":\"My second post\",\"tags\":[\"tag1\",\"tag2\"],\"url\":\"https://coderstower.com/2020/01/13/open-close-principle-by-example/\",\"publishedDate\":\"2020-03-03T05:06:08.000000001\",\"publications\":[{\"id\":\"123\",\"status\":\"SUCCESS\",\"publisher\":\"twitter\",\"publishedDate\":\"2020-03-03T05:06:08.000000001\"},{\"id\":\"shareid\",\"status\":\"SUCCESS\",\"publisher\":\"linkedin\",\"publishedDate\":\"2020-03-03T05:06:08.000000001\"}]}")); - } - - private void mockingLinkedIn() { - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setContentType(MediaType.APPLICATION_JSON); - httpHeaders.add("X-Restli-Protocol-Version", "2.0.0"); - httpHeaders.add("LinkedIn-Version", "202304"); - httpHeaders.setBearerAuth("access123"); - - HttpEntity requestMe = new HttpEntity<>(httpHeaders); - - when(restTemplate.exchange("https://api.linkedin.com/v2/userinfo", HttpMethod.GET, requestMe, Profile.class)) - .thenReturn(ResponseEntity.ok(Profile.builder() - .sub("memberid") - .build())); - - LinkedInShare linkedInShare = LinkedInShare.builder() - .author("urn:li:person:memberid") - .lifecycleState("PUBLISHED") - .commentary("My second post\n\n#tag1 #tag2") - .distribution(Distribution.builder().feedDistribution("MAIN_FEED").build()) - .lifecycleState("PUBLISHED") - .content(Content.builder() - .article(ArticleContent.builder() - .description("My second post") - .title("My Post 2") - .source("https://coderstower.com/2020/01/13/open-close-principle-by-example/") - .build()) - .build()) - .visibility("PUBLIC") - .build(); - - HttpEntity requestShare = new HttpEntity<>(linkedInShare, httpHeaders); - - when(restTemplate.exchange("https://api.linkedin.com/rest/posts", HttpMethod.POST, requestShare, Void.class)) - .thenReturn(ResponseEntity.ok() - .header("X-RestLi-Id", "shareid") - .build()); - } - - private void mockingTwitter() throws TwitterException { - Paging paging = new Paging(1, 1); - when(twitter.getHomeTimeline(paging)).thenReturn(mock(ResponseList.class)); - - Status status = mock(Status.class); - when(status.getId()).thenReturn(123L); - when(twitter.updateStatus("My second post\n\n#tag1 #tag2\n\nhttps://coderstower.com/2020/01/13/open-close-principle-by-example/")).thenReturn(status); - } - - @TestConfiguration - static class OverriddenConfiguration { - @Bean - public Clock clock() { - return Clock.fixed(ZonedDateTime.of(2020, 3, 3, 5, 6, 8, 1, ZoneId.of("UTC")).toInstant(), ZoneId.of("UTC")); - } - -// @Bean -// public AmazonDynamoDB amazonDynamoDB() { -// AmazonDynamoDB ddb = DynamoDBEmbedded.create().amazonDynamoDB(); -// -// createTable(ddb, "Oauth1Credentials", "id"); -// createTable(ddb, "Oauth2Credentials", "id"); -// createTable(ddb, "Posts", "id"); -// -// PutItemRequest oauth2Credentials = new PutItemRequest(); -// oauth2Credentials.setTableName("Oauth2Credentials"); -// oauth2Credentials.addItemEntry("id", new AttributeValue("linkedin")); -// oauth2Credentials.addItemEntry("accessToken", new AttributeValue("access123")); -// oauth2Credentials.addItemEntry("allowedGroups", new AttributeValue().withL(List.of(new AttributeValue("group1")))); -// oauth2Credentials.addItemEntry("expirationDate", new AttributeValue("2020-04-01T05:05:05")); -// -// ddb.putItem(oauth2Credentials); -// -// PutItemRequest oauth1Credentials = new PutItemRequest(); -// oauth1Credentials.setTableName("Oauth1Credentials"); -// oauth1Credentials.addItemEntry("id", new AttributeValue("twitter")); -// oauth1Credentials.addItemEntry("accessToken", new AttributeValue("access123")); -// oauth1Credentials.addItemEntry("consumerKey", new AttributeValue("consumer123")); -// oauth1Credentials.addItemEntry("consumerSecret", new AttributeValue("csecret123")); -// oauth1Credentials.addItemEntry("tokenSecret", new AttributeValue("tsecret123")); -// -// ddb.putItem(oauth1Credentials); -// -// PutItemRequest post1 = new PutItemRequest(); -// post1.setTableName("Posts"); -// post1.addItemEntry("id", new AttributeValue("1")); -// post1.addItemEntry("name", new AttributeValue("My Post 1")); -// post1.addItemEntry("description", new AttributeValue("My first post")); -// post1.addItemEntry("tags", new AttributeValue().withL(new AttributeValue("tag1"), new AttributeValue("tag2"))); -// post1.addItemEntry("url", new AttributeValue("https://coderstower.com/2020/02/18/unit-tests-vs-integration-tests/")); -// post1.addItemEntry("publishedDate", new AttributeValue("2013-09-17T18:47:52")); -// post1.addItemEntry("group", new AttributeValue("group1")); -// -// ddb.putItem(post1); -// -// PutItemRequest post2 = new PutItemRequest(); -// post2.setTableName("Posts"); -// post2.addItemEntry("id", new AttributeValue("2")); -// post2.addItemEntry("name", new AttributeValue("My Post 2")); -// post2.addItemEntry("description", new AttributeValue("My second post")); -// post2.addItemEntry("tags", new AttributeValue().withL(new AttributeValue("tag1"), new AttributeValue("tag2"))); -// post2.addItemEntry("url", new AttributeValue("https://coderstower.com/2020/01/13/open-close-principle-by-example/")); -// post2.addItemEntry("publishedDate", new AttributeValue("2012-09-17T18:47:52")); -// post2.addItemEntry("group", new AttributeValue("group1")); -// -// ddb.putItem(post2); -// -// return ddb; -// } - - private CreateTableResult createTable(AmazonDynamoDB ddb, String tableName, String hashKeyName) { - List attributeDefinitions = new ArrayList<>(); - attributeDefinitions.add(new AttributeDefinition(hashKeyName, ScalarAttributeType.S)); - - List ks = new ArrayList<>(); - ks.add(new KeySchemaElement(hashKeyName, KeyType.HASH)); - - ProvisionedThroughput provisionedthroughput = new ProvisionedThroughput(1000L, 1000L); - - CreateTableRequest request = - new CreateTableRequest() - .withTableName(tableName) - .withAttributeDefinitions(attributeDefinitions) - .withKeySchema(ks) - .withProvisionedThroughput(provisionedthroughput); - - return ddb.createTable(request); - } - } - -} diff --git a/src/itest/java/com/coderstower/socialmediapubisher/application/linkedin/PostNextToLinkedInTest.java b/src/itest/java/com/coderstower/socialmediapubisher/application/linkedin/PostNextToLinkedInTest.java new file mode 100644 index 0000000..9503750 --- /dev/null +++ b/src/itest/java/com/coderstower/socialmediapubisher/application/linkedin/PostNextToLinkedInTest.java @@ -0,0 +1,58 @@ +package com.coderstower.socialmediapubisher.application.linkedin; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.client.builder.AwsClientBuilder; +import com.amazonaws.regions.Regions; +import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; +import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder; +import com.coderstower.socialmediapubisher.application.MockedEdgesConfig; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Profile; + +import java.time.Clock; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +import static io.restassured.RestAssured.given; + + +public class PostNextToLinkedInTest extends MockedEdgesConfig { + + @Test + public void publishNextPostSuccessful(WireMockRuntimeInfo wireMockRuntimeInfo) { + loadMocks(wireMockRuntimeInfo, "testcases/post/next/linkedin/wiremock/"); + + var response = given(). + port(port). + post("/posts/group1/next"). + then(). + statusCode(200). + extract() + .body() + .asString(); + + validateResponse("testcases/post/next/linkedin/response.json", response); + } + + @TestConfiguration + static class OverriddenConfiguration { + @Bean + public Clock clock() { + return Clock.fixed(ZonedDateTime.of(2020, 3, 3, 5, 6, 8, 1, ZoneId.of("UTC")).toInstant(), ZoneId.of("UTC")); + } + + + @Bean("amazonDynamoDB") + public AmazonDynamoDB amazonDynamoDBLocal() { + return AmazonDynamoDBClientBuilder.standard() + .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration("http://localhost:8089", Regions.US_EAST_1.getName())) + .withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials("132", "456"))) + .build(); + } + } +} diff --git a/src/itest/resources/testcases/post/next/linkedin/response.json b/src/itest/resources/testcases/post/next/linkedin/response.json new file mode 100644 index 0000000..ee696be --- /dev/null +++ b/src/itest/resources/testcases/post/next/linkedin/response.json @@ -0,0 +1,21 @@ +{ + "id": "post1", + "name": "Post 1", + "description": "This is a new post 1", + "tags": [ + "java", + "code" + ], + "url": "http://coders.com/post1", + "publishedDate": "2020-03-03T05:06:08.000000001", + "publications": [ + { + "id": "linkedIn123", + "status": "SUCCESS", + "publisher": "linkedin", + "credentialId": "id132", + "publishedDate": "2020-03-03T05:06:08.000000001" + } + ], + "group": "group1" +} \ No newline at end of file diff --git a/src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/getOauth2Credentials.json b/src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/getOauth2Credentials.json new file mode 100644 index 0000000..4cd6be7 --- /dev/null +++ b/src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/getOauth2Credentials.json @@ -0,0 +1,44 @@ +{ + "request": { + "method": "POST", + "url": "/", + "headers": { + "X-Amz-Target": { + "equalTo": "DynamoDB_20120810.Scan" + } + }, + "bodyPatterns": [ + { + "equalToJson": { + "TableName": "Oauth2Credentials" + } + } + ] + }, + "response": { + "status": 200, + "jsonBody": { + "Count": 1, + "Items": [ + { + "id": { + "S": "id132" + }, + "accessToken": { + "S": "accessToken132" + }, + "expirationDate": { + "S": "2024-04-28T16:24:12" + }, + "allowedGroups": { + "L": [ + { + "S": "group1" + } + ] + } + } + ] + } + } +} diff --git a/src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/getPosts.json b/src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/getPosts.json new file mode 100644 index 0000000..fdabaa6 --- /dev/null +++ b/src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/getPosts.json @@ -0,0 +1,56 @@ +{ + "request": { + "method": "POST", + "url": "/", + "headers": { + "X-Amz-Target": { + "equalTo": "DynamoDB_20120810.Scan" + } + }, + "bodyPatterns": [ + { + "equalToJson": { + "TableName": "Posts" + } + } + ] + }, + "response": { + "status": 200, + "jsonBody": { + "Count": 1, + "Items": [ + { + "id": { + "S": "post1" + }, + "name": { + "S": "Post 1" + }, + "description": { + "S": "This is a new post 1" + }, + "tags": { + "L": [ + { + "S": "java" + }, + { + "S": "code" + } + ] + }, + "url": { + "S": "http://coders.com/post1" + }, + "publishedDate": { + "S": "2024-04-28T16:24:12" + }, + "group": { + "S": "group1" + } + } + ] + } + } +} diff --git a/src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/getUserInfo.json b/src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/getUserInfo.json new file mode 100644 index 0000000..ee7e9cf --- /dev/null +++ b/src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/getUserInfo.json @@ -0,0 +1,15 @@ +{ + "request": { + "method": "GET", + "url": "/v2/userinfo" + }, + "response": { + "status": 200, + "jsonBody": { + "sub": "account123" + }, + "headers": { + "Content-Type": "application/json" + } + } +} diff --git a/src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/postOnLinkedIn.json b/src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/postOnLinkedIn.json new file mode 100644 index 0000000..5018776 --- /dev/null +++ b/src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/postOnLinkedIn.json @@ -0,0 +1,32 @@ +{ + "request": { + "method": "POST", + "url": "/rest/posts", + "bodyPatterns": [ + { + "equalToJson": { + "commentary": "This is a new post 1\n\n#java #code", + "distribution": { + "feedDistribution": "MAIN_FEED" + }, + "author": "urn:li:person:account123", + "lifecycleState": "PUBLISHED", + "content": { + "article": { + "title": "Post 1", + "description": "This is a new post 1", + "source": "http://coders.com/post1" + } + }, + "visibility": "PUBLIC" + } + } + ] + }, + "response": { + "status": 200, + "headers": { + "X-RestLi-Id": "linkedIn123" + } + } +} diff --git a/src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/putPost.json b/src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/putPost.json new file mode 100644 index 0000000..f6cc1cd --- /dev/null +++ b/src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/putPost.json @@ -0,0 +1,51 @@ +{ + "request": { + "method": "POST", + "url": "/", + "headers": { + "X-Amz-Target": { + "equalTo": "DynamoDB_20120810.PutItem" + } + }, + "bodyPatterns": [ + { + "equalToJson": { + "TableName": "Posts", + "Item": { + "name": { + "S": "Post 1" + }, + "description": { + "S": "This is a new post 1" + }, + "publishedDate": { + "S": "2020-03-03T05:06:08.000000001" + }, + "id": { + "S": "post1" + }, + "url": { + "S": "http://coders.com/post1" + }, + "tags": { + "L": [ + { + "S": "java" + }, + { + "S": "code" + } + ] + }, + "group": { + "S": "group1" + } + } + } + } + ] + }, + "response": { + "status": 200 + } +} diff --git a/src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/updatePost.json b/src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/updatePost.json new file mode 100644 index 0000000..1ced8b9 --- /dev/null +++ b/src/itest/resources/testcases/post/next/linkedin/wiremock/mappings/updatePost.json @@ -0,0 +1,72 @@ +{ + "request": { + "method": "POST", + "url": "/", + "headers": { + "X-Amz-Target": { + "equalTo": "DynamoDB_20120810.UpdateItem" + } + }, + "bodyPatterns": [ + { + "equalToJson": { + "TableName": "Posts", + "Key": { + "id": { + "S": "post1" + } + }, + "AttributeUpdates": { + "name": { + "Value": { + "S": "Post 1" + }, + "Action": "PUT" + }, + "description": { + "Value": { + "S": "This is a new post 1" + }, + "Action": "PUT" + }, + "publishedDate": { + "Value": { + "S": "2020-03-03T05:06:08.000000001" + }, + "Action": "PUT" + }, + "url": { + "Value": { + "S": "http://coders.com/post1" + }, + "Action": "PUT" + }, + "tags": { + "Value": { + "L": [ + { + "S": "java" + }, + { + "S": "code" + } + ] + }, + "Action": "PUT" + }, + "group": { + "Value": { + "S": "group1" + }, + "Action": "PUT" + } + }, + "ReturnValues": "ALL_NEW" + } + } + ] + }, + "response": { + "status": 200 + } +} diff --git a/src/itest/resources/testcases/post/next/linkedin/wiremock/publishToLinkedIn.json b/src/itest/resources/testcases/post/next/linkedin/wiremock/publishToLinkedIn.json deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/coderstower/socialmediapubisher/application/factory/SecurityFactory.java b/src/main/java/com/coderstower/socialmediapubisher/application/factory/SecurityFactory.java index 18cd3a9..6dbf751 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/application/factory/SecurityFactory.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/factory/SecurityFactory.java @@ -1,23 +1,10 @@ package com.coderstower.socialmediapubisher.application.factory; -import com.coderstower.socialmediapubisher.domain.security.OAuth2AccessTokenResponseConverterWithDefaults; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; -import org.springframework.core.annotation.Order; -import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -//import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient; -import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; -import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; -import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler; -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; -import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; -import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; -import org.springframework.web.client.RestTemplate; - -import java.util.Arrays; +import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity @@ -80,4 +67,16 @@ public class SecurityFactory { // .permitAll(); // } // } + @Bean + public SecurityFilterChain noSecurity(HttpSecurity http) throws Exception { + return http + .csrf().disable() + .securityMatcher("/**") + .authorizeHttpRequests(authorize -> + authorize + .anyRequest() + .permitAll() + ) + .build(); + } } diff --git a/src/main/resources/application-itest.yml b/src/main/resources/application-itest.yml index 1c34873..fb23ce2 100644 --- a/src/main/resources/application-itest.yml +++ b/src/main/resources/application-itest.yml @@ -2,7 +2,7 @@ social-media-publisher: principal-names-allowed: linkedin: xxx linked-in: - base-url: http://localhost:9090 + base-url: http://localhost:8089 amazon: dynamodb: @@ -11,4 +11,11 @@ amazon: spring: main: - allow-bean-definition-overriding: true \ No newline at end of file + allow-bean-definition-overriding: true + +logging: + level: + com: + github: + tomakehurst: + wiremock: debug \ No newline at end of file diff --git a/src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/LocalDateTimeConverterTest.java b/src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/LocalDateTimeConverterTest.java deleted file mode 100644 index 504ae4b..0000000 --- a/src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/LocalDateTimeConverterTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.coderstower.socialmediapubisher.application.aws.repository; - -import org.junit.jupiter.api.Test; - -import java.time.LocalDateTime; - -import static org.assertj.core.api.Assertions.assertThat; - -class LocalDateTimeConverterTest { - private LocalDateTimeConverter localDateTimeConverter = new LocalDateTimeConverter(); - - @Test - public void convert(){ - String date = localDateTimeConverter.convert(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)); - - assertThat(date).isEqualTo("2020-03-03T05:06:08.000000001"); - } - - @Test - public void unconvert(){ - LocalDateTime date = localDateTimeConverter.unconvert("2020-03-03T05:06:08.000000001"); - - assertThat(date).isEqualTo(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)); - } - -} \ No newline at end of file diff --git a/src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/URLConverterTest.java b/src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/URLConverterTest.java deleted file mode 100644 index ee05c19..0000000 --- a/src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/URLConverterTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.coderstower.socialmediapubisher.application.aws.repository; - -import org.junit.jupiter.api.Test; - -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URL; - -import static org.assertj.core.api.Assertions.assertThat; - -class URLConverterTest { - private URLConverter urlConverter = new URLConverter(); - - @Test - public void convert() throws MalformedURLException { - String url = urlConverter.convert(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()); - - assertThat(url).isEqualTo("https://coderstower.com/2020/01/13/open-close-principle-by-example/"); - } - - @Test - public void unconvert() throws MalformedURLException { - URL url = urlConverter.unconvert("https://coderstower.com/2020/01/13/open-close-principle-by-example/"); - - assertThat(url).isEqualTo(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()); - } - -} \ No newline at end of file diff --git a/src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth1/OAuth1CredentialAWSRepositoryTest.java b/src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth1/OAuth1CredentialAWSRepositoryTest.java deleted file mode 100644 index 66f7f25..0000000 --- a/src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth1/OAuth1CredentialAWSRepositoryTest.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.coderstower.socialmediapubisher.application.aws.repository.oauth1; - -import com.coderstower.socialmediapubisher.domain.security.repository.OAuth1Credentials; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class OAuth1CredentialAWSRepositoryTest { - @Mock - private OAuth1CredentialDynamoRepository oauth1CredentialDynamoRepository; - @InjectMocks - private OAuth1CredentialAWSRepository oauth1CredentialAWSRepository; - - @Test - public void getCredentials(){ - when(oauth1CredentialDynamoRepository.findById("twitter")).thenReturn(Optional.of(OAuth1CredentialDynamo.builder() - .id("twitter") - .accessToken("accessToken") - .tokenSecret("tokenSecret") - .consumerKey("consumerKey") - .consumerSecret("consumerSecret") - .build())); - - Optional credentials = oauth1CredentialAWSRepository.getCredentials("twitter"); - - assertThat(credentials).isEqualTo(Optional.of(OAuth1Credentials.builder() - .id("twitter") - .accessToken("accessToken") - .tokenSecret("tokenSecret") - .consumerKey("consumerKey") - .consumerSecret("consumerSecret") - .build())); - } - -} \ No newline at end of file diff --git a/src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth2/OAuth2CredentialAWSRepositoryTest.java b/src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth2/OAuth2CredentialAWSRepositoryTest.java deleted file mode 100644 index b9fede6..0000000 --- a/src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/oauth2/OAuth2CredentialAWSRepositoryTest.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.coderstower.socialmediapubisher.application.aws.repository.oauth2; - -import com.coderstower.socialmediapubisher.domain.security.repository.OAuth2Credentials; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.time.LocalDateTime; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class OAuth2CredentialAWSRepositoryTest { - @Mock - private OAuth2CredentialDynamoRepository oauth2CredentialDynamoRepository; - @InjectMocks - private OAuth2CredentialAWSRepository oauth2CredentialAWSRepository; - - @Test - public void getCredentials(){ - when(oauth2CredentialDynamoRepository.findById("linkedin")).thenReturn(Optional.of(OAuth2CredentialDynamo.builder() - .id("linkedin") - .accessToken("accessToken") - .expirationDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .build())); - - Optional credentials = oauth2CredentialAWSRepository.getCredentials("linkedin"); - - assertThat(credentials).isEqualTo(Optional.of(OAuth2Credentials.builder() - .id("linkedin") - .accessToken("accessToken") - .expirationDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .build())); - } - - @Test - public void update(){ - when(oauth2CredentialDynamoRepository.save(OAuth2CredentialDynamo.builder() - .id("linkedin") - .accessToken("accessToken") - .expirationDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .build())).thenReturn(OAuth2CredentialDynamo.builder() - .id("linkedin") - .accessToken("accessToken") - .expirationDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .build()); - - OAuth2Credentials credentials = oauth2CredentialAWSRepository.update(OAuth2Credentials.builder() - .id("linkedin") - .accessToken("accessToken") - .expirationDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .build()); - - assertThat(credentials).isEqualTo(OAuth2Credentials.builder() - .id("linkedin") - .accessToken("accessToken") - .expirationDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .build()); - } - -} \ No newline at end of file diff --git a/src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/post/PostAWSRepositoryTest.java b/src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/post/PostAWSRepositoryTest.java deleted file mode 100644 index 61339be..0000000 --- a/src/test/java/com/coderstower/socialmediapubisher/application/aws/repository/post/PostAWSRepositoryTest.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.coderstower.socialmediapubisher.application.aws.repository.post; - -import com.coderstower.socialmediapubisher.domain.post.repository.Post; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.net.MalformedURLException; -import java.net.URI; -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class PostAWSRepositoryTest { - @Mock - private PostDynamoRepository postDynamoRepository; - @InjectMocks - private PostAWSRepository postAWSRepository; - - @Test - public void getNextToPublish() throws MalformedURLException { - when(postDynamoRepository.findAll()).thenReturn(List.of(PostDynamo.builder() - .id("2") - .name("My Post 2") - .description("My second post") - .tags(List.of("tag1", "tag2")) - .group("group1") - .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) - .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) - .build(), PostDynamo.builder() - .id("1") - .name("My Post 1") - .description("My first post") - .tags(List.of("tag1", "tag2")) - .group("group1") - .url(URI.create("https://coderstower.com/2020/02/18/unit-tests-vs-integration-tests/").toURL()) - .publishedDate(LocalDateTime.parse("2013-09-17T18:47:52")) - .build())); - - Optional post = postAWSRepository.getNextToPublish("group1"); - - assertThat(post).isEqualTo(Optional.of(Post.builder() - .id("2") - .name("My Post 2") - .description("My second post") - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) - .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) - .group("group1") - .build())); - } - - @Test - public void update() throws MalformedURLException { - when(postDynamoRepository.save(PostDynamo.builder() - .id("2") - .name("My Post 2") - .description("My second post") - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) - .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) - .build())) - .thenReturn(PostDynamo.builder() - .id("2") - .name("My Post 2") - .description("My second post") - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) - .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) - .build()); - - Post post = postAWSRepository.update(Post.builder() - .id("2") - .name("My Post 2") - .description("My second post") - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) - .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) - .build()); - - assertThat(post).isEqualTo(Post.builder() - .id("2") - .name("My Post 2") - .description("My second post") - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) - .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) - .build()); - } - -} \ No newline at end of file diff --git a/src/test/java/com/coderstower/socialmediapubisher/application/controller/PostsControllerTest.java b/src/test/java/com/coderstower/socialmediapubisher/application/controller/PostsControllerTest.java deleted file mode 100644 index f583390..0000000 --- a/src/test/java/com/coderstower/socialmediapubisher/application/controller/PostsControllerTest.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.coderstower.socialmediapubisher.application.controller; - -import com.coderstower.socialmediapubisher.domain.post.PostPublisher; -import com.coderstower.socialmediapubisher.domain.post.repository.Post; -import com.coderstower.socialmediapubisher.domain.post.socialmedia.Publication; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.net.MalformedURLException; -import java.net.URI; -import java.time.LocalDateTime; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class PostsControllerTest { - @Mock - private PostPublisher postPublisher; - @InjectMocks - private PostsController postsController; - - @Test - public void ping() { - Map result = postsController.ping(); - - Map pong = new HashMap<>(); - pong.put("pong", "Hello, World!"); - - assertThat(result).isEqualTo(pong); - } - - @Test - public void postNext() throws MalformedURLException { - when(postPublisher.publishNext("group1")).thenReturn(Post.builder() - .id("2") - .name("My Post 2") - .description("My second post") - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) - .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) - .publications(List.of(Publication.builder() - .status(Publication.Status.FAILURE) - .publisher("twitter") - .build())) - .group("group1") - .build()); - - Post result = postsController.postNext("group1"); - - assertThat(result).isEqualTo(Post.builder() - .id("2") - .name("My Post 2") - .description("My second post") - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) - .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) - .publications(List.of(Publication.builder() - .status(Publication.Status.FAILURE) - .publisher("twitter") - .build())) - .group("group1") - .build()); - } - -} \ No newline at end of file diff --git a/src/test/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/LinkedInPublisherTest.java b/src/test/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/LinkedInPublisherTest.java deleted file mode 100644 index 6b3769c..0000000 --- a/src/test/java/com/coderstower/socialmediapubisher/application/socialmedia/linkedin/LinkedInPublisherTest.java +++ /dev/null @@ -1,280 +0,0 @@ -package com.coderstower.socialmediapubisher.application.socialmedia.linkedin; - -import com.coderstower.socialmediapubisher.domain.post.repository.Post; -import com.coderstower.socialmediapubisher.domain.post.socialmedia.Acknowledge; -import com.coderstower.socialmediapubisher.domain.post.socialmedia.Publication; -import com.coderstower.socialmediapubisher.domain.security.UnauthorizedException; -import com.coderstower.socialmediapubisher.domain.security.repository.OAuth2Credentials; -import com.coderstower.socialmediapubisher.domain.security.repository.OAuth2CredentialsRepository; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.http.*; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.UriTemplate; - -import java.net.MalformedURLException; -import java.net.URI; -import java.time.Clock; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class LinkedInPublisherTest { - @Mock - private OAuth2CredentialsRepository oauth2CredentialsRepository; - @Mock - private RestTemplate restTemplate; - - private LinkedInPublisher linkedInPublisher; - private ZonedDateTime now; - - @BeforeEach - public void before() { - this.now = ZonedDateTime.of(2020, 3, 3, 5, 6, 8, 1, ZoneId.of("UTC")); - this.linkedInPublisher = new LinkedInPublisher("linkedin", oauth2CredentialsRepository, restTemplate, Clock - .fixed(now.toInstant(), ZoneId.of("UTC")), new UriTemplate("http://localhost:8080/oauth2/{social-media}/credentials"), URI.create("http://localhost:8080")); - } - - @Test - public void ping_noCredential_exception() { - when(oauth2CredentialsRepository.findAll()).thenReturn(List.of()); - - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> linkedInPublisher.ping()); - - assertThat(exception.getMessage()).isEqualTo("The credentials for linkedin doesn't exist"); - } - - @Test - public void ping_expiredCredential_exception() { - when(oauth2CredentialsRepository.findAll()).thenReturn(List.of(OAuth2Credentials.builder() - .expirationDate(now.toLocalDateTime().minusMonths(1)) - .id("credential1") - .build())); - - UnauthorizedException exception = assertThrows(UnauthorizedException.class, () -> linkedInPublisher.ping()); - - assertThat(exception.getMessage()).isEqualTo("Unauthorized for linkedin credential1. Please login again here: http://localhost:8080/oauth2/linkedin/credentials"); - } - - @Test - public void ping_goodCredential_success() { - when(oauth2CredentialsRepository.findAll()).thenReturn(List.of(OAuth2Credentials.builder() - .expirationDate(now.toLocalDateTime().plusMonths(1)) - .build())); - - Acknowledge ack = linkedInPublisher.ping(); - - assertThat(ack).isEqualTo(Acknowledge.builder() - .status(Acknowledge.Status.SUCCESS) - .build()); - } - - @Test - public void publish_noProfile_publicationError() { - HttpHeaders httpHeadersProfile = new HttpHeaders(); - httpHeadersProfile.setContentType(MediaType.APPLICATION_JSON); - httpHeadersProfile.add("X-Restli-Protocol-Version", "2.0.0"); - httpHeadersProfile.setBearerAuth("accessToken"); - - HttpEntity requestEntityProfile = new HttpEntity<>(httpHeadersProfile); - - when(oauth2CredentialsRepository.findAll()).thenReturn(List.of(OAuth2Credentials.builder() - .accessToken("accessToken") - .allowedGroups(List.of("group1")) - .build())); - when(restTemplate.exchange("https://api.linkedin.com/v2/me", HttpMethod.GET, requestEntityProfile, Profile.class)) - .thenReturn(ResponseEntity.notFound().build()); - - List publication = linkedInPublisher.publish(Post.builder() - .group("group1") - .build()); - - assertThat(publication).isEqualTo(List.of(Publication.builder() - .status(Publication.Status.FAILURE) - .publisher("linkedin") - .publishedDate(now.toLocalDateTime()) - .build())); - } - - @Test - public void publish_noShare_publicationError() throws MalformedURLException { - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setContentType(MediaType.APPLICATION_JSON); - httpHeaders.add("X-Restli-Protocol-Version", "2.0.0"); - httpHeaders.setBearerAuth("accessToken"); - - when(oauth2CredentialsRepository.findAll()).thenReturn(List.of(OAuth2Credentials.builder() - .accessToken("accessToken") - .allowedGroups(List.of("group1")) - .build())); - - HttpEntity requestEntityProfile = new HttpEntity<>(httpHeaders); - - when(restTemplate.exchange("https://api.linkedin.com/v2/me", HttpMethod.GET, requestEntityProfile, Profile.class)) - .thenReturn(ResponseEntity.ok(Profile.builder() - .sub("memberid") - .build())); - - LinkedInShare linkedInShare = LinkedInShare.builder() - .author("urn:li:person:memberid") - .lifecycleState("PUBLISHED") - .commentary("commentary") - .distribution(Distribution.builder().feedDistribution("MAIN_FEED").build()) - .lifecycleState("PUBLISHED") - .content(Content.builder() - .article(ArticleContent.builder() - .description("My second post\n\n#tag1 #tag2") - .title("My second post") - .source("https://coderstower.com/2020/01/13/open-close-principle-by-example/") - .build()) - .build()) - .visibility("PUBLIC") - .build(); - - HttpEntity requestShare = new HttpEntity<>(linkedInShare, httpHeaders); - - when(restTemplate.exchange("https://api.linkedin.com/v2/ugcPosts", HttpMethod.POST, requestShare, Void.class)) - .thenReturn(ResponseEntity.badRequest().build()); - - List publication = linkedInPublisher.publish(Post.builder() - .id("2") - .name("My Post 2") - .description("My second post") - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) - .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) - .group("group1") - .build()); - - assertThat(publication).isEqualTo(List.of(Publication.builder() - .status(Publication.Status.FAILURE) - .publisher("linkedin") - .publishedDate(now.toLocalDateTime()) - .build())); - } - - @Test - public void publish_noShareId_publicationError() throws MalformedURLException { - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setContentType(MediaType.APPLICATION_JSON); - httpHeaders.add("X-Restli-Protocol-Version", "2.0.0"); - httpHeaders.setBearerAuth("accessToken"); - - when(oauth2CredentialsRepository.findAll()).thenReturn(List.of(OAuth2Credentials.builder() - .accessToken("accessToken") - .allowedGroups(List.of("group1")) - .build())); - - HttpEntity requestEntityProfile = new HttpEntity<>(httpHeaders); - - when(restTemplate.exchange("https://api.linkedin.com/v2/me", HttpMethod.GET, requestEntityProfile, Profile.class)) - .thenReturn(ResponseEntity.ok(Profile.builder() - .sub("memberid") - .build())); - - LinkedInShare linkedInShare = LinkedInShare.builder() - .author("urn:li:person:memberid") - .lifecycleState("PUBLISHED") - .commentary("commentary") - .distribution(Distribution.builder().feedDistribution("MAIN_FEED").build()) - .lifecycleState("PUBLISHED") - .content(Content.builder() - .article(ArticleContent.builder() - .description("My second post\n\n#tag1 #tag2") - .title("My second post") - .source("https://coderstower.com/2020/01/13/open-close-principle-by-example/") - .build()) - .build()) - .visibility("PUBLIC") - .build(); - - HttpEntity requestShare = new HttpEntity<>(linkedInShare, httpHeaders); - - when(restTemplate.exchange("https://api.linkedin.com/v2/ugcPosts", HttpMethod.POST, requestShare, Void.class)) - .thenReturn(ResponseEntity.ok().build()); - - List publication = linkedInPublisher.publish(Post.builder() - .id("2") - .name("My Post 2") - .description("My second post") - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) - .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) - .group("group1") - .build()); - - assertThat(publication).isEqualTo(List.of(Publication.builder() - .status(Publication.Status.FAILURE) - .publisher("linkedin") - .publishedDate(now.toLocalDateTime()) - .build())); - } - - @Test - public void publish_shareId_publicationSucced() throws MalformedURLException { - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setContentType(MediaType.APPLICATION_JSON); - httpHeaders.add("X-Restli-Protocol-Version", "2.0.0"); - httpHeaders.add("LinkedIn-Version", "202304"); - httpHeaders.setBearerAuth("accessToken"); - - when(oauth2CredentialsRepository.findAll()).thenReturn(List.of(OAuth2Credentials.builder() - .accessToken("accessToken") - .allowedGroups(List.of("group1")) - .build())); - - HttpEntity requestEntityProfile = new HttpEntity<>(httpHeaders); - - when(restTemplate.exchange("https://api.linkedin.com/v2/userinfo", HttpMethod.GET, requestEntityProfile, Profile.class)) - .thenReturn(ResponseEntity.ok(Profile.builder() - .sub("memberid") - .build())); - - LinkedInShare linkedInShare = LinkedInShare.builder() - .author("urn:li:person:memberid") - .lifecycleState("PUBLISHED") - .commentary("My second post\n\n#tag1 #tag2") - .distribution(Distribution.builder().feedDistribution("MAIN_FEED").build()) - .lifecycleState("PUBLISHED") - .content(Content.builder() - .article(ArticleContent.builder() - .description("My second post") - .title("My Post 2") - .source("https://coderstower.com/2020/01/13/open-close-principle-by-example/") - .build()) - .build()) - .visibility("PUBLIC") - .build(); - - HttpEntity requestShare = new HttpEntity<>(linkedInShare, httpHeaders); - - when(restTemplate.exchange("https://api.linkedin.com/rest/posts", HttpMethod.POST, requestShare, Void.class)) - .thenReturn(ResponseEntity.ok().header("X-RestLi-Id", "shareId").build()); - - List publication = linkedInPublisher.publish(Post.builder() - .id("2") - .name("My Post 2") - .description("My second post") - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) - .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) - .group("group1") - .build()); - - assertThat(publication).isEqualTo(List.of(Publication.builder() - .id("shareId") - .status(Publication.Status.SUCCESS) - .publisher("linkedin") - .publishedDate(now.toLocalDateTime()) - .build())); - } -} \ No newline at end of file diff --git a/src/test/java/com/coderstower/socialmediapubisher/application/socialmedia/twitter/TwitterPublisherTest.java b/src/test/java/com/coderstower/socialmediapubisher/application/socialmedia/twitter/TwitterPublisherTest.java deleted file mode 100644 index 4d692b4..0000000 --- a/src/test/java/com/coderstower/socialmediapubisher/application/socialmedia/twitter/TwitterPublisherTest.java +++ /dev/null @@ -1,217 +0,0 @@ -package com.coderstower.socialmediapubisher.application.socialmedia.twitter; - -import com.coderstower.socialmediapubisher.domain.post.repository.Post; -import com.coderstower.socialmediapubisher.domain.post.socialmedia.Acknowledge; -import com.coderstower.socialmediapubisher.domain.post.socialmedia.Publication; -import com.coderstower.socialmediapubisher.domain.security.repository.OAuth1Credentials; -import com.coderstower.socialmediapubisher.domain.security.repository.OAuth1CredentialsRepository; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import twitter4j.*; -import twitter4j.auth.AccessToken; -import twitter4j.auth.Authorization; -import twitter4j.auth.NullAuthorization; - -import java.net.MalformedURLException; -import java.net.URI; -import java.time.Clock; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.List; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -class TwitterPublisherTest { - @Mock - private OAuth1CredentialsRepository oauth1CredentialsRepository; - @Mock - private Twitter twitter; - private TwitterPublisher twitterPublisher; - private ZonedDateTime now; - - @BeforeEach - public void before() { - this.now = ZonedDateTime.of(2020, 3, 3, 5, 6, 8, 1, ZoneId.of("UTC")); - this.twitterPublisher = new TwitterPublisher("twitter", oauth1CredentialsRepository, twitter, Clock - .fixed(now.toInstant(), ZoneId.of("UTC"))); - } - - @Test - public void ping_noCredential_exception() { - when(oauth1CredentialsRepository.getCredentials("twitter")).thenReturn(Optional.empty()); - - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> twitterPublisher.ping()); - - assertThat(exception.getMessage()).isEqualTo("The credentials for twitter doesn't exist"); - } - - @Test - public void ping_credentialNoSetTwitterError_ackFailure() throws TwitterException { - when(oauth1CredentialsRepository.getCredentials("twitter")).thenReturn(Optional.of(OAuth1Credentials.builder() - .accessToken("accessToken") - .consumerKey("consumerKey") - .consumerSecret("consumerSecret") - .tokenSecret("tokenSecret") - .build())); - when(twitter.getAuthorization()).thenReturn(NullAuthorization.getInstance()); - when(twitter.getHomeTimeline(new Paging(1, 1))).thenThrow(new TwitterException("error")); - - Acknowledge ack = twitterPublisher.ping(); - - assertThat(ack).isEqualTo(Acknowledge.builder() - .status(Acknowledge.Status.FAILURE) - .description("Ping error") - .exception(new TwitterException("error")) - .build()); - verify(twitter).setOAuthConsumer("consumerKey", "consumerSecret"); - verify(twitter).setOAuthAccessToken(new AccessToken("accessToken", "tokenSecret")); - } - - @Test - public void ping_credentialSetEmptyStatus_ackFailure() throws TwitterException { - when(oauth1CredentialsRepository.getCredentials("twitter")).thenReturn(Optional.of(OAuth1Credentials.builder() - .accessToken("accessToken") - .consumerKey("consumerKey") - .consumerSecret("consumerSecret") - .tokenSecret("tokenSecret") - .build())); - when(twitter.getAuthorization()).thenReturn(mock(Authorization.class)); - when(twitter.getHomeTimeline(new Paging(1, 1))).thenReturn(null); - - Acknowledge ack = twitterPublisher.ping(); - - assertThat(ack).isEqualTo(Acknowledge.builder() - .status(Acknowledge.Status.FAILURE) - .build()); - verify(twitter, times(0)).setOAuthConsumer("consumerKey", "consumerSecret"); - verify(twitter, times(0)).setOAuthAccessToken(new AccessToken("accessToken", "tokenSecret")); - } - - @Test - public void ping_credentialSetStatus_ackSucced() throws TwitterException { - when(oauth1CredentialsRepository.getCredentials("twitter")).thenReturn(Optional.of(OAuth1Credentials.builder() - .accessToken("accessToken") - .consumerKey("consumerKey") - .consumerSecret("consumerSecret") - .tokenSecret("tokenSecret") - .build())); - when(twitter.getAuthorization()).thenReturn(mock(Authorization.class)); - when(twitter.getHomeTimeline(new Paging(1, 1))).thenReturn(mock(ResponseList.class)); - - Acknowledge ack = twitterPublisher.ping(); - - assertThat(ack).isEqualTo(Acknowledge.builder() - .status(Acknowledge.Status.SUCCESS) - .build()); - verify(twitter, times(0)).setOAuthConsumer("consumerKey", "consumerSecret"); - verify(twitter, times(0)).setOAuthAccessToken(new AccessToken("accessToken", "tokenSecret")); - } - - @Test - public void publish_noCredential_exception() { - when(oauth1CredentialsRepository.getCredentials("twitter")).thenReturn(Optional.empty()); - - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> twitterPublisher.publish(Post.builder().build())); - - assertThat(exception.getMessage()).isEqualTo("The credentials for twitter doesn't exist"); - } - - @Test - public void publish_credentialNoSetTwitterError_shareFailure() throws TwitterException, MalformedURLException { - when(oauth1CredentialsRepository.getCredentials("twitter")).thenReturn(Optional.of(OAuth1Credentials.builder() - .accessToken("accessToken") - .consumerKey("consumerKey") - .consumerSecret("consumerSecret") - .tokenSecret("tokenSecret") - .build())); - when(twitter.getAuthorization()).thenReturn(NullAuthorization.getInstance()); - when(twitter.updateStatus("My second post\n\n#tag1 #tag2\n\nhttps://coderstower.com/2020/01/13/open-close-principle-by-example/")).thenThrow(new TwitterException("error")); - - List publish = twitterPublisher.publish(Post.builder() - .id("2") - .name("My Post 2") - .description("My second post") - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) - .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) - .build()); - - assertThat(publish).isEqualTo(List.of(Publication.builder() - .status(Publication.Status.FAILURE) - .publisher("twitter") - .publishedDate(now.toLocalDateTime()) - .build())); - verify(twitter).setOAuthConsumer("consumerKey", "consumerSecret"); - verify(twitter).setOAuthAccessToken(new AccessToken("accessToken", "tokenSecret")); - } - - @Test - public void publish_credentialSetEmptyStatus_shareFailure() throws TwitterException, MalformedURLException { - when(oauth1CredentialsRepository.getCredentials("twitter")).thenReturn(Optional.of(OAuth1Credentials.builder() - .accessToken("accessToken") - .consumerKey("consumerKey") - .consumerSecret("consumerSecret") - .tokenSecret("tokenSecret") - .build())); - when(twitter.getAuthorization()).thenReturn(mock(Authorization.class)); - when(twitter.updateStatus("My second post\n\n#tag1 #tag2\n\nhttps://coderstower.com/2020/01/13/open-close-principle-by-example/")).thenReturn(null); - - List publish = twitterPublisher.publish(Post.builder() - .id("2") - .name("My Post 2") - .description("My second post") - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) - .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) - .build()); - - assertThat(publish).isEqualTo(List.of(Publication.builder() - .status(Publication.Status.FAILURE) - .publisher("twitter") - .publishedDate(now.toLocalDateTime()) - .build())); - verify(twitter, times(0)).setOAuthConsumer("consumerKey", "consumerSecret"); - verify(twitter, times(0)).setOAuthAccessToken(new AccessToken("accessToken", "tokenSecret")); - } - - @Test - public void publish_credentialSetStatus_ackSucced() throws TwitterException, MalformedURLException { - when(oauth1CredentialsRepository.getCredentials("twitter")).thenReturn(Optional.of(OAuth1Credentials.builder() - .accessToken("accessToken") - .consumerKey("consumerKey") - .consumerSecret("consumerSecret") - .tokenSecret("tokenSecret") - .build())); - when(twitter.getAuthorization()).thenReturn(mock(Authorization.class)); - - Status status = mock(Status.class); - when(status.getId()).thenReturn(123L); - when(twitter.updateStatus("My second post\n\n#tag1 #tag2\n\nhttps://coderstower.com/2020/01/13/open-close-principle-by-example/")).thenReturn(status); - - List publish = twitterPublisher.publish(Post.builder() - .id("2") - .name("My Post 2") - .description("My second post") - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) - .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) - .build()); - - assertThat(publish).isEqualTo(List.of(Publication.builder() - .id("123") - .status(Publication.Status.SUCCESS) - .publisher("twitter") - .publishedDate(now.toLocalDateTime()) - .build())); - verify(twitter, times(0)).setOAuthConsumer("consumerKey", "consumerSecret"); - verify(twitter, times(0)).setOAuthAccessToken(new AccessToken("accessToken", "tokenSecret")); - } -} \ No newline at end of file diff --git a/src/test/java/com/coderstower/socialmediapubisher/domain/post/PostPublisherTest.java b/src/test/java/com/coderstower/socialmediapubisher/domain/post/PostPublisherTest.java deleted file mode 100644 index 3aab2a2..0000000 --- a/src/test/java/com/coderstower/socialmediapubisher/domain/post/PostPublisherTest.java +++ /dev/null @@ -1,195 +0,0 @@ -package com.coderstower.socialmediapubisher.domain.post; - -import com.coderstower.socialmediapubisher.domain.post.repository.Post; -import com.coderstower.socialmediapubisher.domain.post.repository.PostRepository; -import com.coderstower.socialmediapubisher.domain.post.socialmedia.Acknowledge; -import com.coderstower.socialmediapubisher.domain.post.socialmedia.Publication; -import com.coderstower.socialmediapubisher.domain.post.socialmedia.SocialMediaPublisher; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.net.MalformedURLException; -import java.net.URI; -import java.time.Clock; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.List; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class PostPublisherTest { - @Mock - private SocialMediaPublisher socialMediaPublisher1; - @Mock - private SocialMediaPublisher socialMediaPublisher2; - @Mock - private PostRepository postRepository; - private PostPublisher postPublisher; - - @BeforeEach - public void before() { - this.postPublisher = new PostPublisher(List.of(socialMediaPublisher1, socialMediaPublisher2), postRepository, Clock - .fixed(ZonedDateTime.of(2020, 3, 3, 5, 6, 8, 1, ZoneId.of("UTC")).toInstant(), ZoneId.of("UTC"))); - } - - @Test - public void publishNext_pingFailed_exception() { - when(socialMediaPublisher1.ping()).thenReturn(Acknowledge.builder() - .status(Acknowledge.Status.SUCCESS).build()); - when(socialMediaPublisher2.ping()).thenReturn(Acknowledge.builder() - .status(Acknowledge.Status.FAILURE).build()); - when(socialMediaPublisher2.getName()).thenReturn("LINKEDIN"); - - IllegalStateException exception = assertThrows(IllegalStateException.class, () -> postPublisher.publishNext("group1")); - - assertThat(exception.getMessage()).isEqualTo("Ping to LINKEDIN failed: " + Acknowledge.builder() - .status(Acknowledge.Status.FAILURE).build()); - } - - @Test - public void publishNext_noPost_exception() { - when(postRepository.getNextToPublish("group1")).thenReturn(Optional.empty()); - when(socialMediaPublisher1.ping()).thenReturn(Acknowledge.builder() - .status(Acknowledge.Status.SUCCESS).build()); - when(socialMediaPublisher2.ping()).thenReturn(Acknowledge.builder() - .status(Acknowledge.Status.SUCCESS).build()); - - IllegalStateException exception = assertThrows(IllegalStateException.class, () -> postPublisher.publishNext("group1")); - - assertThat(exception.getMessage()).isEqualTo("There is not next post to publish"); - } - - @Test - public void publishNext_publishFailed_exception() throws MalformedURLException { - Post post = Post.builder() - .id("1") - .description("Post 1") - .name("Post") - .publishedDate(LocalDateTime.of(2020, 2, 3, 5, 6, 8, 1)) - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://myblog/post").toURL()) - .group("group1") - .build(); - - Post expectedPost = Post.builder() - .id("1") - .description("Post 1") - .name("Post") - .publishedDate(LocalDateTime.of(2020, 2, 3, 5, 6, 8, 1)) - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://myblog/post").toURL()) - .publications(List - .of(Publication.builder() - .id("id") - .publishedDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .status(Publication.Status.SUCCESS) - .publisher("TWITTER") - .build(), - Publication.builder() - .publishedDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .status(Publication.Status.FAILURE) - .publisher("LINKEDIN") - .build())) - .group("group1") - .build(); - - when(postRepository.getNextToPublish("group1")).thenReturn(Optional.of(post)); - when(socialMediaPublisher1.ping()).thenReturn(Acknowledge.builder() - .status(Acknowledge.Status.SUCCESS).build()); - when(socialMediaPublisher1.publish(post)).thenReturn(List.of(Publication.builder() - .id("id") - .publishedDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .status(Publication.Status.SUCCESS) - .publisher("TWITTER") - .build())); - when(socialMediaPublisher2.ping()).thenReturn(Acknowledge.builder() - .status(Acknowledge.Status.SUCCESS).build()); - when(socialMediaPublisher2.publish(post)).thenReturn(List.of(Publication.builder() - .publishedDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .status(Publication.Status.FAILURE) - .publisher("LINKEDIN") - .build())); - - IllegalStateException exception = assertThrows(IllegalStateException.class, () -> postPublisher.publishNext("group1")); - - assertThat(exception.getMessage()).isEqualTo("Error publishing the post: " + expectedPost); - } - - @Test - public void publishNext_publishSuccess_publications() throws MalformedURLException { - Post post = Post.builder() - .id("1") - .description("Post 1") - .name("Post") - .publishedDate(LocalDateTime.of(2020, 2, 3, 5, 6, 8, 1)) - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://myblog/post").toURL()) - .group("group1") - .build(); - - Post postUpdated = Post.builder() - .id("1") - .description("Post 1") - .name("Post") - .publishedDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://myblog/post").toURL()) - .group("group1") - .build(); - - Post expectedPost = Post.builder() - .id("1") - .description("Post 1") - .name("Post") - .publishedDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://myblog/post").toURL()) - .group("group1") - .publications(List - .of(Publication.builder() - .id("id") - .publishedDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .status(Publication.Status.SUCCESS) - .publisher("TWITTER") - .build(), - Publication.builder() - .id("id") - .publishedDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .status(Publication.Status.SUCCESS) - .publisher("LINKEDIN") - .build())) - .build(); - - when(postRepository.getNextToPublish("group1")).thenReturn(Optional.of(post)); - when(socialMediaPublisher1.ping()).thenReturn(Acknowledge.builder() - .status(Acknowledge.Status.SUCCESS).build()); - when(socialMediaPublisher1.publish(post)).thenReturn(List.of(Publication.builder() - .id("id") - .publishedDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .status(Publication.Status.SUCCESS) - .publisher("TWITTER") - .build())); - when(socialMediaPublisher2.ping()).thenReturn(Acknowledge.builder() - .status(Acknowledge.Status.SUCCESS).build()); - when(socialMediaPublisher2.publish(post)).thenReturn(List.of(Publication.builder() - .id("id") - .publishedDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .status(Publication.Status.SUCCESS) - .publisher("LINKEDIN") - .build())); - when(postRepository.update(postUpdated)).thenReturn(postUpdated); - - Post publishedPost = postPublisher.publishNext("group1"); - - assertThat(publishedPost).isEqualTo(expectedPost); - } - -} \ No newline at end of file diff --git a/src/test/java/com/coderstower/socialmediapubisher/domain/security/OAuth2CredentialsManagerTest.java b/src/test/java/com/coderstower/socialmediapubisher/domain/security/OAuth2CredentialsManagerTest.java deleted file mode 100644 index 09cb133..0000000 --- a/src/test/java/com/coderstower/socialmediapubisher/domain/security/OAuth2CredentialsManagerTest.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.coderstower.socialmediapubisher.domain.security; - -import com.coderstower.socialmediapubisher.domain.security.repository.OAuth2Credentials; -import com.coderstower.socialmediapubisher.domain.security.repository.OAuth2CredentialsRepository; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; -import org.springframework.security.oauth2.core.OAuth2AccessToken; - -import java.time.LocalDateTime; -import java.time.ZoneOffset; -import java.util.Map; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class OAuth2CredentialsManagerTest { - @Mock - private OAuth2CredentialsRepository oAuth2CredentialsRepository; - @Mock - private OAuth2AuthorizedClient oAuth2AuthorizedClient; - - private OAuth2CredentialsManager oAuth2CredentialsManager; - - @BeforeEach - public void before() { - oAuth2CredentialsManager = new OAuth2CredentialsManager(oAuth2CredentialsRepository, Map.of("linkedin", "myuser")); - } - - @Test - public void update_notAllowed_unauthorized() { - when(oAuth2AuthorizedClient.getPrincipalName()).thenReturn("otheruser"); - - assertThrows(UnauthorizedException.class, () -> oAuth2CredentialsManager.update(oAuth2AuthorizedClient, "linkedin")); - } - - @Test - public void update_credentialsNotFound_created() { - when(oAuth2AuthorizedClient.getPrincipalName()).thenReturn("myuser"); - when(oAuth2CredentialsRepository.getCredentials("linkedin")).thenReturn(Optional.empty()); - when(oAuth2CredentialsRepository.save(OAuth2Credentials.builder() - .id("linkedin") - .build())).thenReturn(OAuth2Credentials.builder() - .id("linkedin") - .build()); - when(oAuth2AuthorizedClient.getAccessToken()).thenReturn(new OAuth2AccessToken( - OAuth2AccessToken.TokenType.BEARER, - "newAccessToken", - LocalDateTime.of(2020, 4, 3, 5, 6, 8, 1).toInstant(ZoneOffset.UTC), - LocalDateTime.of(2020, 6, 3, 5, 6, 8, 1).toInstant(ZoneOffset.UTC))); - when(oAuth2CredentialsRepository.update(OAuth2Credentials.builder() - .id("linkedin") - .accessToken("newAccessToken") - .expirationDate(LocalDateTime.of(2020, 6, 3, 5, 6, 8, 1)) - .build())).thenReturn(OAuth2Credentials.builder() - .id("linkedin") - .accessToken("newAccessToken") - .expirationDate(LocalDateTime.of(2020, 6, 3, 5, 6, 8, 1)) - .build()); - - OAuth2Credentials credentials = oAuth2CredentialsManager.update(oAuth2AuthorizedClient, "linkedin"); - - assertThat(credentials).isEqualTo(OAuth2Credentials.builder() - .id("linkedin") - .accessToken("newAccessToken") - .expirationDate(LocalDateTime.of(2020, 6, 3, 5, 6, 8, 1)) - .build()); - } - - @Test - public void update_credentialsFound_updated() { - when(oAuth2AuthorizedClient.getPrincipalName()).thenReturn("myuser"); - when(oAuth2CredentialsRepository.getCredentials("linkedin")).thenReturn(Optional.of(OAuth2Credentials.builder() - .id("linkedin") - .accessToken("previousAccessToken") - .expirationDate(LocalDateTime.of(2020, 2, 3, 5, 6, 8, 1)) - .build())); - when(oAuth2AuthorizedClient.getAccessToken()).thenReturn(new OAuth2AccessToken( - OAuth2AccessToken.TokenType.BEARER, - "newAccessToken", - LocalDateTime.of(2020, 4, 3, 5, 6, 8, 1).toInstant(ZoneOffset.UTC), - LocalDateTime.of(2020, 6, 3, 5, 6, 8, 1).toInstant(ZoneOffset.UTC))); - when(oAuth2CredentialsRepository.update(OAuth2Credentials.builder() - .id("linkedin") - .accessToken("newAccessToken") - .expirationDate(LocalDateTime.of(2020, 6, 3, 5, 6, 8, 1)) - .build())).thenReturn(OAuth2Credentials.builder() - .id("linkedin") - .accessToken("newAccessToken") - .expirationDate(LocalDateTime.of(2020, 6, 3, 5, 6, 8, 1)) - .build()); - - OAuth2Credentials credentials = oAuth2CredentialsManager.update(oAuth2AuthorizedClient, "linkedin"); - - assertThat(credentials).isEqualTo(OAuth2Credentials.builder() - .id("linkedin") - .accessToken("newAccessToken") - .expirationDate(LocalDateTime.of(2020, 6, 3, 5, 6, 8, 1)) - .build()); - } -} \ No newline at end of file From 8572e0ba1cd18c068b87b1fa2c0e7ab302d5af32 Mon Sep 17 00:00:00 2001 From: Daniel Pelaez Date: Wed, 1 May 2024 10:57:10 -0500 Subject: [PATCH 13/17] Moving to gradle --- .../application/MockedEdgesConfig.java | 43 +++---------------- .../application/OverriddenConfiguration.java | 30 +++++++++++++ .../linkedin/PostNextToLinkedInTest.java | 38 ++-------------- .../extesion/ITestExtension.java | 20 +++++++++ .../extesion/ITestHandler.java | 42 ++++++++++++++++++ .../SpringSlf4jNotifier.java | 4 +- src/main/resources/application-local.yml | 15 ------- 7 files changed, 105 insertions(+), 87 deletions(-) create mode 100644 src/itest/java/com/coderstower/socialmediapubisher/application/OverriddenConfiguration.java create mode 100644 src/itest/java/com/coderstower/socialmediapubisher/extesion/ITestExtension.java create mode 100644 src/itest/java/com/coderstower/socialmediapubisher/extesion/ITestHandler.java rename src/itest/java/com/coderstower/socialmediapubisher/{application => extesion}/SpringSlf4jNotifier.java (80%) delete mode 100644 src/main/resources/application-local.yml diff --git a/src/itest/java/com/coderstower/socialmediapubisher/application/MockedEdgesConfig.java b/src/itest/java/com/coderstower/socialmediapubisher/application/MockedEdgesConfig.java index b582dff..435cb5f 100644 --- a/src/itest/java/com/coderstower/socialmediapubisher/application/MockedEdgesConfig.java +++ b/src/itest/java/com/coderstower/socialmediapubisher/application/MockedEdgesConfig.java @@ -1,22 +1,22 @@ package com.coderstower.socialmediapubisher.application; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.coderstower.socialmediapubisher.extesion.ITestExtension; +import com.coderstower.socialmediapubisher.extesion.SpringSlf4jNotifier; import com.github.tomakehurst.wiremock.junit5.WireMockExtension; -import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.RegisterExtension; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.context.annotation.Import; import org.springframework.test.context.ActiveProfiles; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; +import org.springframework.test.context.ContextConfiguration; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; -import static org.junit.jupiter.api.Assertions.assertEquals; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles("itest") +@ContextConfiguration(classes = OverriddenConfiguration.class) +@ExtendWith(ITestExtension.class) public abstract class MockedEdgesConfig { @RegisterExtension static WireMockExtension wm1 = WireMockExtension.newInstance() @@ -31,33 +31,4 @@ public abstract class MockedEdgesConfig { @LocalServerPort protected Integer port; - private ObjectMapper mapper = new ObjectMapper(); - protected Path parentPath = Path.of("src/itest/resources/"); - - protected void loadMocks(WireMockRuntimeInfo wireMockRuntimeInfo, String mocksPath) { - var wireMock = wireMockRuntimeInfo.getWireMock(); - - var mocksFolder = parentPath.resolve(mocksPath); - - wireMock.loadMappingsFrom(mocksFolder.toFile()); - } - - protected void validateResponse(String expectedResponsePath, String response) { - try { - var expectedTree = mapper.readTree(readFromFile(expectedResponsePath)); - var expectedJSONPretty = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(expectedTree); - - var currentTree = mapper.readTree(response); - var currentJSONPretty = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(currentTree); - - assertEquals(expectedJSONPretty, currentJSONPretty); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - - private String readFromFile(String filePath) throws IOException { - return Files.readString(parentPath.resolve(filePath)); - } } diff --git a/src/itest/java/com/coderstower/socialmediapubisher/application/OverriddenConfiguration.java b/src/itest/java/com/coderstower/socialmediapubisher/application/OverriddenConfiguration.java new file mode 100644 index 0000000..2456a26 --- /dev/null +++ b/src/itest/java/com/coderstower/socialmediapubisher/application/OverriddenConfiguration.java @@ -0,0 +1,30 @@ +package com.coderstower.socialmediapubisher.application; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.client.builder.AwsClientBuilder; +import com.amazonaws.regions.Regions; +import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; +import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; + +import java.time.Clock; +import java.time.ZoneId; +import java.time.ZonedDateTime; +@TestConfiguration +public class OverriddenConfiguration { + @Bean + public Clock clock() { + return Clock.fixed(ZonedDateTime.of(2020, 3, 3, 5, 6, 8, 1, ZoneId.of("UTC")).toInstant(), ZoneId.of("UTC")); + } + + + @Bean("amazonDynamoDB") + public AmazonDynamoDB amazonDynamoDBLocal() { + return AmazonDynamoDBClientBuilder.standard() + .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration("http://localhost:8089", Regions.US_EAST_1.getName())) + .withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials("132", "456"))) + .build(); + } +} diff --git a/src/itest/java/com/coderstower/socialmediapubisher/application/linkedin/PostNextToLinkedInTest.java b/src/itest/java/com/coderstower/socialmediapubisher/application/linkedin/PostNextToLinkedInTest.java index 9503750..3c0cb47 100644 --- a/src/itest/java/com/coderstower/socialmediapubisher/application/linkedin/PostNextToLinkedInTest.java +++ b/src/itest/java/com/coderstower/socialmediapubisher/application/linkedin/PostNextToLinkedInTest.java @@ -1,22 +1,9 @@ package com.coderstower.socialmediapubisher.application.linkedin; -import com.amazonaws.auth.AWSStaticCredentialsProvider; -import com.amazonaws.auth.BasicAWSCredentials; -import com.amazonaws.client.builder.AwsClientBuilder; -import com.amazonaws.regions.Regions; -import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; -import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder; import com.coderstower.socialmediapubisher.application.MockedEdgesConfig; +import com.coderstower.socialmediapubisher.extesion.ITestHandler; import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Profile; - -import java.time.Clock; -import java.time.ZoneId; -import java.time.ZonedDateTime; import static io.restassured.RestAssured.given; @@ -24,8 +11,8 @@ public class PostNextToLinkedInTest extends MockedEdgesConfig { @Test - public void publishNextPostSuccessful(WireMockRuntimeInfo wireMockRuntimeInfo) { - loadMocks(wireMockRuntimeInfo, "testcases/post/next/linkedin/wiremock/"); + public void publishNextPostSuccessful(WireMockRuntimeInfo wireMockRuntimeInfo, ITestHandler iTestHandler) { + iTestHandler.loadWiremockMocks(wireMockRuntimeInfo, "testcases/post/next/linkedin/wiremock/"); var response = given(). port(port). @@ -36,23 +23,6 @@ public void publishNextPostSuccessful(WireMockRuntimeInfo wireMockRuntimeInfo) { .body() .asString(); - validateResponse("testcases/post/next/linkedin/response.json", response); - } - - @TestConfiguration - static class OverriddenConfiguration { - @Bean - public Clock clock() { - return Clock.fixed(ZonedDateTime.of(2020, 3, 3, 5, 6, 8, 1, ZoneId.of("UTC")).toInstant(), ZoneId.of("UTC")); - } - - - @Bean("amazonDynamoDB") - public AmazonDynamoDB amazonDynamoDBLocal() { - return AmazonDynamoDBClientBuilder.standard() - .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration("http://localhost:8089", Regions.US_EAST_1.getName())) - .withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials("132", "456"))) - .build(); - } + iTestHandler.validateJSONResponse("testcases/post/next/linkedin/response.json", response); } } diff --git a/src/itest/java/com/coderstower/socialmediapubisher/extesion/ITestExtension.java b/src/itest/java/com/coderstower/socialmediapubisher/extesion/ITestExtension.java new file mode 100644 index 0000000..19876e6 --- /dev/null +++ b/src/itest/java/com/coderstower/socialmediapubisher/extesion/ITestExtension.java @@ -0,0 +1,20 @@ +package com.coderstower.socialmediapubisher.extesion; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; + +public class ITestExtension implements ParameterResolver { + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { + return parameterContext.getParameter().getType() == ITestHandler.class; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { + var store = extensionContext.getRoot().getStore(ExtensionContext.Namespace.GLOBAL); + + return store.getOrComputeIfAbsent(ITestHandler.class, input -> new ITestHandler(), ITestHandler.class); + } +} diff --git a/src/itest/java/com/coderstower/socialmediapubisher/extesion/ITestHandler.java b/src/itest/java/com/coderstower/socialmediapubisher/extesion/ITestHandler.java new file mode 100644 index 0000000..f43bfa1 --- /dev/null +++ b/src/itest/java/com/coderstower/socialmediapubisher/extesion/ITestHandler.java @@ -0,0 +1,42 @@ +package com.coderstower.socialmediapubisher.extesion; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ITestHandler { + private ObjectMapper mapper = new ObjectMapper(); + private Path parentPath = Path.of("src/itest/resources/"); + + public void loadWiremockMocks(WireMockRuntimeInfo wireMockRuntimeInfo, String mocksPath) { + var wireMock = wireMockRuntimeInfo.getWireMock(); + + var mocksFolder = parentPath.resolve(mocksPath); + + wireMock.loadMappingsFrom(mocksFolder.toFile()); + } + + public void validateJSONResponse(String expectedResponsePath, String response) { + try { + var expectedTree = mapper.readTree(readFromFile(expectedResponsePath)); + var expectedJSONPretty = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(expectedTree); + + var currentTree = mapper.readTree(response); + var currentJSONPretty = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(currentTree); + + assertEquals(expectedJSONPretty, currentJSONPretty); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + + private String readFromFile(String filePath) throws IOException { + return Files.readString(parentPath.resolve(filePath)); + } +} diff --git a/src/itest/java/com/coderstower/socialmediapubisher/application/SpringSlf4jNotifier.java b/src/itest/java/com/coderstower/socialmediapubisher/extesion/SpringSlf4jNotifier.java similarity index 80% rename from src/itest/java/com/coderstower/socialmediapubisher/application/SpringSlf4jNotifier.java rename to src/itest/java/com/coderstower/socialmediapubisher/extesion/SpringSlf4jNotifier.java index 8f05f59..0fbede0 100644 --- a/src/itest/java/com/coderstower/socialmediapubisher/application/SpringSlf4jNotifier.java +++ b/src/itest/java/com/coderstower/socialmediapubisher/extesion/SpringSlf4jNotifier.java @@ -1,10 +1,10 @@ -package com.coderstower.socialmediapubisher.application; +package com.coderstower.socialmediapubisher.extesion; import com.github.tomakehurst.wiremock.common.Notifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -class SpringSlf4jNotifier implements Notifier { +public class SpringSlf4jNotifier implements Notifier { private static final Logger log = LoggerFactory.getLogger(SpringSlf4jNotifier.class); public void info(String message) { diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml deleted file mode 100644 index ea1e022..0000000 --- a/src/main/resources/application-local.yml +++ /dev/null @@ -1,15 +0,0 @@ -# Reduce logging level to make sure the application works with SAM local -# https://github.com/awslabs/aws-serverless-java-container/issues/134 -#logging: -# level: -# root: WARN - -amazon: - dynamodb: - endpoint: http://localhost:8000/ - accesskey: key - secretkey: key2 - -spring: - main: - allow-bean-definition-overriding: true \ No newline at end of file From 4fafa864046b96209e40a8f16ee4d1731a263f61 Mon Sep 17 00:00:00 2001 From: Daniel Pelaez Date: Mon, 6 May 2024 11:32:10 -0500 Subject: [PATCH 14/17] Removing subprojects --- build.gradle.kts | 7 +++---- gradle/libs.versions.toml | 13 +++++-------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index b34d055..befc95c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -54,13 +54,12 @@ dependencies { implementation(libs.org.springframework.boot.spring.boot.starter.oauth2.client) implementation(libs.org.twitter4j.twitter4j.core) implementation(libs.io.github.boostchicken.spring.data.dynamodb) - implementation(libs.com.amazonaws.serverless.aws.serverless.java.container.springboot2) + implementation(libs.com.amazonaws.serverless.aws.serverless.java.container.springboot3) testImplementation(libs.org.springframework.boot.spring.boot.starter.test) testImplementation(libs.org.springframework.security.spring.security.test) - itestImplementation("org.springframework.cloud:spring-cloud-starter-contract-stub-runner") + itestImplementation(libs.org.springframework.cloud.spring.cloud.starter.contract.stub.runner) itestImplementation(libs.org.springframework.boot.spring.boot.starter.test) - itestImplementation("org.testcontainers:localstack") - itestImplementation("io.rest-assured:rest-assured:5.4.0") + itestImplementation(libs.io.rest.assured.rest.assured) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4b3c44b..a77c207 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,22 +2,19 @@ # https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format [versions] -com-amazonaws-dynamodblocal = "1.13.0" -com-amazonaws-serverless-aws-serverless-java-container-springboot2 = "1.5" +com-amazonaws-serverless-aws-serverless-java-container-springboot3 = "2.0.1" io-github-boostchicken-spring-data-dynamodb = "5.2.5" -org-junit-pioneer-junit-pioneer = "0.5.1" -org-projectlombok-lombok = "1.18.12" org-twitter4j-twitter4j-core = "4.0.7" +io-rest-assured-rest-assured = "5.4.0" [libraries] -com-amazonaws-dynamodblocal = { module = "com.amazonaws:DynamoDBLocal", version.ref = "com-amazonaws-dynamodblocal" } -com-amazonaws-serverless-aws-serverless-java-container-springboot2 = { module = "com.amazonaws.serverless:aws-serverless-java-container-springboot2", version.ref = "com-amazonaws-serverless-aws-serverless-java-container-springboot2" } +com-amazonaws-serverless-aws-serverless-java-container-springboot3 = { module = "com.amazonaws.serverless:aws-serverless-java-container-springboot3", version.ref = "com-amazonaws-serverless-aws-serverless-java-container-springboot3" } io-github-boostchicken-spring-data-dynamodb = { module = "io.github.boostchicken:spring-data-dynamodb", version.ref = "io-github-boostchicken-spring-data-dynamodb" } -org-junit-pioneer-junit-pioneer = { module = "org.junit-pioneer:junit-pioneer", version.ref = "org-junit-pioneer-junit-pioneer" } -org-projectlombok-lombok = { module = "org.projectlombok:lombok", version.ref = "org-projectlombok-lombok" } org-springframework-boot-spring-boot-starter-oauth2-client = { module = "org.springframework.boot:spring-boot-starter-oauth2-client" } org-springframework-boot-spring-boot-starter-security = { module = "org.springframework.boot:spring-boot-starter-security"} org-springframework-boot-spring-boot-starter-test = { module = "org.springframework.boot:spring-boot-starter-test" } org-springframework-boot-spring-boot-starter-web = { module = "org.springframework.boot:spring-boot-starter-web" } org-springframework-security-spring-security-test = { module = "org.springframework.security:spring-security-test" } +org-springframework-cloud-spring-cloud-starter-contract-stub-runner = { module = "org.springframework.cloud:spring-cloud-starter-contract-stub-runner" } +io-rest-assured-rest-assured = { module = "io.rest-assured:rest-assured", version.ref = "io-rest-assured-rest-assured"} org-twitter4j-twitter4j-core = { module = "org.twitter4j:twitter4j-core", version.ref = "org-twitter4j-twitter4j-core" } From 1fd66c99d27acf7e493dcdefc3495a64a8ffecae Mon Sep 17 00:00:00 2001 From: Daniel Pelaez Date: Mon, 6 May 2024 11:33:58 -0500 Subject: [PATCH 15/17] Removing subprojects --- aws-publisher/README.md | 0 aws-publisher/pom.xml | 201 ------------ aws-publisher/sam.yaml | 30 -- aws-publisher/src/assembly/bin.xml | 27 -- .../aws/AWSSpringPublisherApplication.java | 60 ---- ...ingPublisherDynamoDBRepositoryFactory.java | 64 ---- .../main/aws/StreamLambdaHandler.java | 33 -- .../repository/LocalDateTimeConverter.java | 17 -- .../main/aws/repository/URLConverter.java | 24 -- .../oauth1/OAuth1CredentialAWSRepository.java | 29 -- .../oauth1/OAuth1CredentialDynamo.java | 27 -- .../OAuth1CredentialDynamoRepository.java | 8 - .../oauth2/OAuth2CredentialAWSRepository.java | 57 ---- .../oauth2/OAuth2CredentialDynamo.java | 31 -- .../OAuth2CredentialDynamoRepository.java | 9 - .../repository/post/PostAWSRepository.java | 53 ---- .../main/aws/repository/post/PostDynamo.java | 42 --- .../repository/post/PostDynamoRepository.java | 11 - .../src/main/resources/application-local.yml | 15 - .../src/main/resources/application-secure.yml | 32 -- .../src/main/resources/application.yml | 14 - ...ckSocialMediaCredentialsHandlingTests.java | 196 ------------ .../aws/MockSocialMediaSuccessfulTests.java | 210 ------------- .../main/aws/StreamLambdaHandlerTest.java | 74 ----- .../LocalDateTimeConverterTest.java | 26 -- .../main/aws/repository/URLConverterTest.java | 28 -- .../OAuth1CredentialAWSRepositoryTest.java | 43 --- .../OAuth2CredentialAWSRepositoryTest.java | 65 ---- .../post/PostAWSRepositoryTest.java | 97 ------ spring-publisher/HELP.md | 8 - spring-publisher/pom.xml | 37 --- .../abstraction/post/PostPublisher.java | 64 ---- .../abstraction/post/repository/Post.java | 71 ----- .../post/repository/PostRepository.java | 8 - .../post/socialmedia/Acknowledge.java | 17 -- .../post/socialmedia/Publication.java | 21 -- .../socialmedia/SocialMediaPublisher.java | 11 - ...essTokenResponseConverterWithDefaults.java | 75 ----- .../security/OAuth2CredentialsManager.java | 47 --- .../security/OAuth2CredentialsUpdated.java | 11 - .../security/UnauthorizedException.java | 10 - .../repository/OAuth1Credentials.java | 14 - .../OAuth1CredentialsRepository.java | 7 - .../repository/OAuth2Credentials.java | 24 -- .../OAuth2CredentialsRepository.java | 13 - .../main/SpringPublisherApplication.java | 15 - .../main/controller/ErrorHandler.java | 14 - .../OAuth2CredentialsController.java | 36 --- .../main/controller/PostsController.java | 42 --- .../main/factory/CredentialsProperties.java | 13 - .../main/factory/SecurityFactory.java | 83 ----- .../SocialMediaPublisherProperties.java | 17 -- .../main/factory/SpringPublisherFactory.java | 62 ---- .../socialmedia/linkedin/ArticleContent.java | 15 - .../main/socialmedia/linkedin/Content.java | 10 - .../socialmedia/linkedin/Distribution.java | 10 - .../linkedin/LinkedInPublisher.java | 176 ----------- .../socialmedia/linkedin/LinkedInShare.java | 15 - .../main/socialmedia/linkedin/Media.java | 13 - .../main/socialmedia/linkedin/Profile.java | 14 - .../main/socialmedia/linkedin/Text.java | 10 - .../socialmedia/twitter/TwitterPublisher.java | 120 -------- .../src/main/resources/application.yml | 2 - .../abstraction/post/PostPublisherTest.java | 195 ------------ .../OAuth2CredentialsManagerTest.java | 107 ------- .../main/controller/PostsControllerTest.java | 72 ----- .../linkedin/LinkedInPublisherTest.java | 285 ------------------ .../twitter/TwitterPublisherTest.java | 221 -------------- .../src/test/resources/application.yml | 14 - spring-publisher/src/test/resources/data.sql | 4 - 70 files changed, 3526 deletions(-) delete mode 100644 aws-publisher/README.md delete mode 100644 aws-publisher/pom.xml delete mode 100644 aws-publisher/sam.yaml delete mode 100644 aws-publisher/src/assembly/bin.xml delete mode 100644 aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/AWSSpringPublisherApplication.java delete mode 100644 aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/SpringPublisherDynamoDBRepositoryFactory.java delete mode 100644 aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/StreamLambdaHandler.java delete mode 100644 aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/LocalDateTimeConverter.java delete mode 100644 aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/URLConverter.java delete mode 100644 aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepository.java delete mode 100644 aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialDynamo.java delete mode 100644 aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialDynamoRepository.java delete mode 100644 aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepository.java delete mode 100644 aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialDynamo.java delete mode 100644 aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialDynamoRepository.java delete mode 100644 aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostAWSRepository.java delete mode 100644 aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostDynamo.java delete mode 100644 aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostDynamoRepository.java delete mode 100644 aws-publisher/src/main/resources/application-local.yml delete mode 100644 aws-publisher/src/main/resources/application-secure.yml delete mode 100644 aws-publisher/src/main/resources/application.yml delete mode 100644 aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/MockSocialMediaCredentialsHandlingTests.java delete mode 100644 aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/MockSocialMediaSuccessfulTests.java delete mode 100644 aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/StreamLambdaHandlerTest.java delete mode 100644 aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/LocalDateTimeConverterTest.java delete mode 100644 aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/URLConverterTest.java delete mode 100644 aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepositoryTest.java delete mode 100644 aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepositoryTest.java delete mode 100644 aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostAWSRepositoryTest.java delete mode 100644 spring-publisher/HELP.md delete mode 100644 spring-publisher/pom.xml delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/PostPublisher.java delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/repository/Post.java delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/repository/PostRepository.java delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/Acknowledge.java delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/Publication.java delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/SocialMediaPublisher.java delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2AccessTokenResponseConverterWithDefaults.java delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsManager.java delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsUpdated.java delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/UnauthorizedException.java delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth1Credentials.java delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth1CredentialsRepository.java delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth2Credentials.java delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth2CredentialsRepository.java delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/SpringPublisherApplication.java delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/ErrorHandler.java delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/OAuth2CredentialsController.java delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/PostsController.java delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/CredentialsProperties.java delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SecurityFactory.java delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SocialMediaPublisherProperties.java delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SpringPublisherFactory.java delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/ArticleContent.java delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Content.java delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Distribution.java delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInPublisher.java delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInShare.java delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Media.java delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Profile.java delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Text.java delete mode 100644 spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/twitter/TwitterPublisher.java delete mode 100644 spring-publisher/src/main/resources/application.yml delete mode 100644 spring-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/PostPublisherTest.java delete mode 100644 spring-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsManagerTest.java delete mode 100644 spring-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/PostsControllerTest.java delete mode 100644 spring-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInPublisherTest.java delete mode 100644 spring-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/twitter/TwitterPublisherTest.java delete mode 100644 spring-publisher/src/test/resources/application.yml delete mode 100644 spring-publisher/src/test/resources/data.sql diff --git a/aws-publisher/README.md b/aws-publisher/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/aws-publisher/pom.xml b/aws-publisher/pom.xml deleted file mode 100644 index 65eb6a7..0000000 --- a/aws-publisher/pom.xml +++ /dev/null @@ -1,201 +0,0 @@ - - - 4.0.0 - - aws-publisher - 0.5.0-SNAPSHOT - jar - - aws-publisher - https://github.com/awslabs/aws-serverless-java-container - - - com.coderstower - social-media-publisher - 0.0.1-SNAPSHOT - - - - - - com.coderstower - spring-publisher - 0.0.1-SNAPSHOT - - - - com.amazonaws.serverless - aws-serverless-java-container-springboot2 - 1.5 - - - - io.github.boostchicken - spring-data-dynamodb - 5.2.3 - - - - org.springframework.boot - spring-boot-starter-web - - - - com.amazonaws - DynamoDBLocal - 1.13.0 - test - - - com.amazonaws - aws-java-sdk-dynamodb - - - com.amazonaws - aws-java-sdk-core - - - - - - org.junit-pioneer - junit-pioneer - 0.5.1 - test - - - - org.springframework.security - spring-security-test - test - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - copy - test-compile - - copy-dependencies - - - test - so,dll,dylib - ${project.build.directory}/native-libs - - - - - - - - - - shaded-jar - - - - org.apache.maven.plugins - maven-shade-plugin - 2.3 - - false - - - - package - - shade - - - - - org.apache.tomcat.embed:* - - - - - - - - - - - assembly-zip - - true - - - - - - org.apache.maven.plugins - maven-jar-plugin - 3.1.1 - - - default-jar - none - - - - - - org.apache.maven.plugins - maven-dependency-plugin - 3.1.1 - - - copy-dependencies - package - - copy-dependencies - - - ${project.build.directory}${file.separator}lib - runtime - - - - - - org.apache.maven.plugins - maven-assembly-plugin - 3.1.0 - - - zip-assembly - package - - single - - - ${project.artifactId}-${project.version} - - src${file.separator}assembly${file.separator}bin.xml - - false - - - - - - - - - - - - dynamodb-local-oregon - DynamoDB Local Release Repository - https://s3-us-west-2.amazonaws.com/dynamodb-local/release - - - diff --git a/aws-publisher/sam.yaml b/aws-publisher/sam.yaml deleted file mode 100644 index 8aa4d14..0000000 --- a/aws-publisher/sam.yaml +++ /dev/null @@ -1,30 +0,0 @@ -AWSTemplateFormatVersion: '2010-09-09' -Transform: AWS::Serverless-2016-10-31 -Description: AWS Serverless Spring Boot API - my.service::aws-publisher -Globals: - Api: - EndpointConfiguration: REGIONAL - -Resources: - AwsPublisherFunction: - Type: AWS::Serverless::Function - Properties: - Handler: my.service.StreamLambdaHandler::handleRequest - Runtime: java8 - CodeUri: target/aws-publisher-1.0-SNAPSHOT-lambda-package.zip - MemorySize: 512 - Policies: AWSLambdaBasicExecutionRole - Timeout: 30 - Events: - GetResource: - Type: Api - Properties: - Path: /{proxy+} - Method: any - -Outputs: - AwsPublisherApi: - Description: URL for application - Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/ping' - Export: - Name: AwsPublisherApi diff --git a/aws-publisher/src/assembly/bin.xml b/aws-publisher/src/assembly/bin.xml deleted file mode 100644 index 1e08505..0000000 --- a/aws-publisher/src/assembly/bin.xml +++ /dev/null @@ -1,27 +0,0 @@ - - lambda-package - - zip - - false - - - - ${project.build.directory}${file.separator}lib - lib - - tomcat-embed* - - - - - ${project.build.directory}${file.separator}classes - - ** - - ${file.separator} - - - \ No newline at end of file diff --git a/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/AWSSpringPublisherApplication.java b/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/AWSSpringPublisherApplication.java deleted file mode 100644 index 9326545..0000000 --- a/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/AWSSpringPublisherApplication.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws; - -import com.coderstower.socialmediapubisher.springpublisher.main.controller.ErrorHandler; -import com.coderstower.socialmediapubisher.springpublisher.main.controller.OAuth2CredentialsController; -import com.coderstower.socialmediapubisher.springpublisher.main.controller.PostsController; -import com.coderstower.socialmediapubisher.springpublisher.main.factory.SpringPublisherFactory; -import com.coderstower.socialmediapubisher.springpublisher.main.factory.SecurityFactory; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; -import org.springframework.context.annotation.Import; - - -@SpringBootApplication -// We use direct @Import instead of @ComponentScan to speed up cold starts -// @ComponentScan(basePackages = "my.service.controller") -@Import({PostsController.class, OAuth2CredentialsController.class, SecurityFactory.class, - SpringPublisherFactory.class, SpringPublisherDynamoDBRepositoryFactory.class, ErrorHandler.class}) -public class AWSSpringPublisherApplication extends SpringBootServletInitializer { - - /* - * Create required HandlerMapping, to avoid several default HandlerMapping instances being created - - @Bean - public HandlerMapping handlerMapping() { - return new RequestMappingHandlerMapping(); - }*/ - - /* - * Create required HandlerAdapter, to avoid several default HandlerAdapter instances being created - - @Bean - public HandlerAdapter handlerAdapter() { - return new RequestMappingHandlerAdapter(); - }*/ - - /* - * optimization - avoids creating default exception resolvers; not required as the serverless container handles - * all exceptions - * - * By default, an ExceptionHandlerExceptionResolver is created which creates many dependent object, including - * an expensive ObjectMapper instance. - * - * To enable custom @ControllerAdvice classes remove this bean. - */ - /*@Bean - public HandlerExceptionResolver handlerExceptionResolver() { - return new HandlerExceptionResolver() { - - @Override - public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { - return null; - } - }; - }*/ - - public static void main(String[] args) { - SpringApplication.run(AWSSpringPublisherApplication.class, args); - } -} \ No newline at end of file diff --git a/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/SpringPublisherDynamoDBRepositoryFactory.java b/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/SpringPublisherDynamoDBRepositoryFactory.java deleted file mode 100644 index fc378ec..0000000 --- a/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/SpringPublisherDynamoDBRepositoryFactory.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws; - -import com.amazonaws.auth.AWSStaticCredentialsProvider; -import com.amazonaws.auth.BasicAWSCredentials; -import com.amazonaws.client.builder.AwsClientBuilder; -import com.amazonaws.regions.Regions; -import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; -import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder; -import com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth1.OAuth1CredentialAWSRepository; -import com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth1.OAuth1CredentialDynamoRepository; -import com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth2.OAuth2CredentialAWSRepository; -import com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth2.OAuth2CredentialDynamoRepository; -import com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.post.PostAWSRepository; -import com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.post.PostDynamoRepository; -import org.socialsignin.spring.data.dynamodb.repository.config.EnableDynamoDBRepositories; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; - -@Configuration -@EnableDynamoDBRepositories - (basePackages = "com.coderstower.socialmediapubisher.springpublisher.main.aws.repository") -public class SpringPublisherDynamoDBRepositoryFactory { - @Bean("amazonDynamoDB") - @Profile("!local") - public AmazonDynamoDB amazonDynamoDB() { - return AmazonDynamoDBClientBuilder - .standard() - .withRegion(Regions.US_EAST_1) - .build(); - } - - @Bean("amazonDynamoDB") - @Profile("local") - public AmazonDynamoDB amazonDynamoDBLocal(@Value("${amazon.dynamodb.secretkey}") String amazonAWSSecretKey, - @Value("${amazon.dynamodb.accesskey}") String amazonAWSAccessKey, - @Value("${amazon.dynamodb.endpoint}") String amazonAWSEndpoint) { - return AmazonDynamoDBClientBuilder.standard() - .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(amazonAWSEndpoint, Regions.US_EAST_1.getName())) - .withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(amazonAWSAccessKey, amazonAWSSecretKey))) - .build(); - } - - @Bean - public OAuth1CredentialAWSRepository oauth1CredentialAWSRepository( - OAuth1CredentialDynamoRepository oauth1CredentialDynamoRepository) { - return new OAuth1CredentialAWSRepository( - oauth1CredentialDynamoRepository); - } - - @Bean - public OAuth2CredentialAWSRepository oauth2CredentialAWSRepository( - OAuth2CredentialDynamoRepository oauth2CredentialDynamoRepository) { - return new OAuth2CredentialAWSRepository( - oauth2CredentialDynamoRepository); - } - - @Bean - public PostAWSRepository postAWSRepository( - PostDynamoRepository postDynamoRepository) { - return new PostAWSRepository(postDynamoRepository); - } -} diff --git a/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/StreamLambdaHandler.java b/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/StreamLambdaHandler.java deleted file mode 100644 index bbf1798..0000000 --- a/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/StreamLambdaHandler.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws; - - -import com.amazonaws.serverless.exceptions.ContainerInitializationException; -import com.amazonaws.serverless.proxy.model.AwsProxyRequest; -import com.amazonaws.serverless.proxy.model.AwsProxyResponse; -import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestStreamHandler; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - - -public class StreamLambdaHandler implements RequestStreamHandler { - private static SpringBootLambdaContainerHandler handler; - static { - try { - handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(AWSSpringPublisherApplication.class); - } catch (ContainerInitializationException e) { - // if we fail here. We re-throw the exception to force another cold start - e.printStackTrace(); - throw new RuntimeException("Could not initialize Spring Boot application", e); - } - } - - @Override - public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) - throws IOException { - handler.proxyStream(inputStream, outputStream, context); - } -} \ No newline at end of file diff --git a/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/LocalDateTimeConverter.java b/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/LocalDateTimeConverter.java deleted file mode 100644 index 8b22cf5..0000000 --- a/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/LocalDateTimeConverter.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository; - -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverter; - -import java.time.LocalDateTime; - -public class LocalDateTimeConverter implements DynamoDBTypeConverter { - @Override - public String convert(final LocalDateTime time) { - return time.toString(); - } - - @Override - public LocalDateTime unconvert(final String stringValue) { - return LocalDateTime.parse(stringValue); - } -} diff --git a/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/URLConverter.java b/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/URLConverter.java deleted file mode 100644 index 586b257..0000000 --- a/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/URLConverter.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository; - -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverter; - -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URL; -import java.time.LocalDateTime; - -public class URLConverter implements DynamoDBTypeConverter { - @Override - public String convert(final URL url) { - return url.toString(); - } - - @Override - public URL unconvert(final String stringValue) { - try { - return URI.create(stringValue).toURL(); - } catch (MalformedURLException e) { - throw new IllegalArgumentException(e); - } - } -} diff --git a/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepository.java b/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepository.java deleted file mode 100644 index b0d28c3..0000000 --- a/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepository.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth1; - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth1Credentials; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth1CredentialsRepository; - -import java.util.Optional; - -public class OAuth1CredentialAWSRepository implements OAuth1CredentialsRepository { - private final OAuth1CredentialDynamoRepository oauth1CredentialDynamoRepository; - - public OAuth1CredentialAWSRepository(OAuth1CredentialDynamoRepository oauth1CredentialDynamoRepository) { - this.oauth1CredentialDynamoRepository = oauth1CredentialDynamoRepository; - } - - @Override - public Optional getCredentials(String id) { - return oauth1CredentialDynamoRepository.findById(id).map(this::convert); - } - - private OAuth1Credentials convert(OAuth1CredentialDynamo oauth1CredentialDynamo) { - return OAuth1Credentials.builder() - .accessToken(oauth1CredentialDynamo.getAccessToken()) - .consumerKey(oauth1CredentialDynamo.getConsumerKey()) - .consumerSecret(oauth1CredentialDynamo.getConsumerSecret()) - .id(oauth1CredentialDynamo.getId()) - .tokenSecret(oauth1CredentialDynamo.getTokenSecret()) - .build(); - } -} diff --git a/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialDynamo.java b/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialDynamo.java deleted file mode 100644 index d870fc8..0000000 --- a/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialDynamo.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth1; - -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@DynamoDBTable(tableName = "Oauth1Credentials") -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class OAuth1CredentialDynamo { - @DynamoDBHashKey - private String id; - @DynamoDBAttribute - private String consumerKey; - @DynamoDBAttribute - private String consumerSecret; - @DynamoDBAttribute - private String accessToken; - @DynamoDBAttribute - private String tokenSecret; -} diff --git a/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialDynamoRepository.java b/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialDynamoRepository.java deleted file mode 100644 index c9723ee..0000000 --- a/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialDynamoRepository.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth1; - -import org.springframework.data.repository.CrudRepository; -import org.springframework.stereotype.Repository; - -@Repository -public interface OAuth1CredentialDynamoRepository extends CrudRepository { -} diff --git a/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepository.java b/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepository.java deleted file mode 100644 index 4cc4340..0000000 --- a/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepository.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth2; - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2Credentials; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2CredentialsRepository; - -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; - -public class OAuth2CredentialAWSRepository implements OAuth2CredentialsRepository { - private final OAuth2CredentialDynamoRepository oauth2CredentialDynamoRepository; - - public OAuth2CredentialAWSRepository(OAuth2CredentialDynamoRepository oauth2CredentialDynamoRepository) { - this.oauth2CredentialDynamoRepository = oauth2CredentialDynamoRepository; - } - - @Override - public OAuth2Credentials save(OAuth2Credentials oAuth2Credentials) { - return convert(oauth2CredentialDynamoRepository.save(convert(oAuth2Credentials))); - } - - @Override - public OAuth2Credentials update(OAuth2Credentials oAuth2Credentials) { - return convert(oauth2CredentialDynamoRepository.save(convert(oAuth2Credentials))); - } - - @Override - public Optional getCredentials(String id) { - return oauth2CredentialDynamoRepository.findById(id).map(this::convert); - } - - @Override - public List findAll() { - return StreamSupport.stream(oauth2CredentialDynamoRepository.findAll().spliterator(), false) - .map(this::convert) - .collect(Collectors.toList()); - } - - private OAuth2CredentialDynamo convert(OAuth2Credentials oAuth2Credentials) { - return OAuth2CredentialDynamo.builder() - .accessToken(oAuth2Credentials.getAccessToken()) - .id(oAuth2Credentials.getId()) - .expirationDate(oAuth2Credentials.getExpirationDate()) - .allowedGroups(oAuth2Credentials.getAllowedGroups()) - .build(); - } - - private OAuth2Credentials convert(OAuth2CredentialDynamo oauth2CredentialDynamo) { - return OAuth2Credentials.builder() - .accessToken(oauth2CredentialDynamo.getAccessToken()) - .id(oauth2CredentialDynamo.getId()) - .expirationDate(oauth2CredentialDynamo.getExpirationDate()) - .allowedGroups(oauth2CredentialDynamo.getAllowedGroups()) - .build(); - } -} diff --git a/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialDynamo.java b/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialDynamo.java deleted file mode 100644 index 9932991..0000000 --- a/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialDynamo.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth2; - -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverted; -import com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.LocalDateTimeConverter; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.time.LocalDateTime; -import java.util.List; - -@Data -@DynamoDBTable(tableName = "Oauth2Credentials") -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class OAuth2CredentialDynamo { - @DynamoDBHashKey - private String id; - @DynamoDBAttribute - private String accessToken; - @DynamoDBAttribute - @DynamoDBTypeConverted(converter = LocalDateTimeConverter.class) - private LocalDateTime expirationDate; - @DynamoDBAttribute - private List allowedGroups; -} diff --git a/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialDynamoRepository.java b/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialDynamoRepository.java deleted file mode 100644 index 11d8525..0000000 --- a/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialDynamoRepository.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth2; - -import org.socialsignin.spring.data.dynamodb.repository.EnableScan; -import org.springframework.data.repository.CrudRepository; -import org.springframework.stereotype.Repository; - -@EnableScan -public interface OAuth2CredentialDynamoRepository extends CrudRepository { -} diff --git a/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostAWSRepository.java b/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostAWSRepository.java deleted file mode 100644 index babb954..0000000 --- a/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostAWSRepository.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.post; - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.PostRepository; - -import java.util.Arrays; -import java.util.Comparator; -import java.util.Optional; -import java.util.stream.StreamSupport; - -public class PostAWSRepository implements PostRepository { - private final PostDynamoRepository postDynamoRepository; - - public PostAWSRepository(PostDynamoRepository postDynamoRepository) { - this.postDynamoRepository = postDynamoRepository; - } - - @Override - public Optional getNextToPublish(String group) { - return StreamSupport.stream(postDynamoRepository.findAll().spliterator(), false) - .filter(postDynamo -> group.equals(postDynamo.getGroup())) - .min(Comparator.comparing(PostDynamo::getPublishedDate)) - .map(this::convert); - } - - @Override - public Post update(Post post) { - return convert(postDynamoRepository.save(convert(post))); - } - - private PostDynamo convert(Post post) { - return PostDynamo.builder() - .id(post.getId()) - .description(post.getDescription()) - .publishedDate(post.getPublishedDate()) - .name(post.getName()) - .tags(post.getTags()) - .url(post.getUrl()) - .group(post.getGroup()) - .build(); - } - - private Post convert(PostDynamo postDynamo) { - return Post.builder() - .id(postDynamo.getId()) - .description(postDynamo.getDescription()) - .publishedDate(postDynamo.getPublishedDate()) - .name(postDynamo.getName()) - .tags(postDynamo.getTags()) - .group(postDynamo.getGroup()) - .url(postDynamo.getUrl()).build(); - } -} diff --git a/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostDynamo.java b/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostDynamo.java deleted file mode 100644 index 3d80374..0000000 --- a/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostDynamo.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.post; - -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAutoGeneratedKey; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverted; -import com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.LocalDateTimeConverter; -import com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.URLConverter; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.net.URL; -import java.time.LocalDateTime; -import java.util.List; - -@Data -@DynamoDBTable(tableName = "Posts") -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class PostDynamo { - @DynamoDBHashKey - @DynamoDBAutoGeneratedKey - private String id; - @DynamoDBAttribute - private String name; - @DynamoDBAttribute - private String description; - @DynamoDBAttribute - private List tags; - @DynamoDBAttribute - @DynamoDBTypeConverted(converter = URLConverter.class) - private URL url; - @DynamoDBAttribute - @DynamoDBTypeConverted(converter = LocalDateTimeConverter.class) - private LocalDateTime publishedDate; - @DynamoDBAttribute - private String group; -} diff --git a/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostDynamoRepository.java b/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostDynamoRepository.java deleted file mode 100644 index febbb31..0000000 --- a/aws-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostDynamoRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.post; - -import org.socialsignin.spring.data.dynamodb.repository.EnableScan; -import org.springframework.data.repository.CrudRepository; - -import java.util.List; - -@EnableScan -public interface PostDynamoRepository extends - CrudRepository { -} diff --git a/aws-publisher/src/main/resources/application-local.yml b/aws-publisher/src/main/resources/application-local.yml deleted file mode 100644 index ea1e022..0000000 --- a/aws-publisher/src/main/resources/application-local.yml +++ /dev/null @@ -1,15 +0,0 @@ -# Reduce logging level to make sure the application works with SAM local -# https://github.com/awslabs/aws-serverless-java-container/issues/134 -#logging: -# level: -# root: WARN - -amazon: - dynamodb: - endpoint: http://localhost:8000/ - accesskey: key - secretkey: key2 - -spring: - main: - allow-bean-definition-overriding: true \ No newline at end of file diff --git a/aws-publisher/src/main/resources/application-secure.yml b/aws-publisher/src/main/resources/application-secure.yml deleted file mode 100644 index 148018a..0000000 --- a/aws-publisher/src/main/resources/application-secure.yml +++ /dev/null @@ -1,32 +0,0 @@ -social-media-publisher: - principal-names-allowed: - linkedin: xxx - -amazon: - dynamodb: - accesskey: xxx - secretkey: xxx - -spring: - security: - oauth2: - client: - registration: - linkedin: - client-id: xxx - client-secret: xxx - client-authentication-method: post - authorization-grant-type: authorization_code - redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}' - scope: - - openid - - profile - - email - - w_member_social - provider: - linkedin: - authorization-uri: https://www.linkedin.com/oauth/v2/authorization - token-uri: https://www.linkedin.com/oauth/v2/accessToken - user-info-uri: https://api.linkedin.com/v2/userinfo - user-name-attribute: id - jwk-set-uri: https://www.linkedin.com/oauth/openid/jwks \ No newline at end of file diff --git a/aws-publisher/src/main/resources/application.yml b/aws-publisher/src/main/resources/application.yml deleted file mode 100644 index 2f70249..0000000 --- a/aws-publisher/src/main/resources/application.yml +++ /dev/null @@ -1,14 +0,0 @@ -# Reduce logging level to make sure the application works with SAM local -# https://github.com/awslabs/aws-serverless-java-container/issues/134 -#logging: -# level: -# root: WARN - -social-media-publisher: - credentials: - login-host: 'http://localhost:8080' - login-url: '${social-media-publisher.credentials.login-host}/oauth2/{social-media-id}/credentials' - -spring: - main: - allow-bean-definition-overriding: true \ No newline at end of file diff --git a/aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/MockSocialMediaCredentialsHandlingTests.java b/aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/MockSocialMediaCredentialsHandlingTests.java deleted file mode 100644 index fe69fe9..0000000 --- a/aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/MockSocialMediaCredentialsHandlingTests.java +++ /dev/null @@ -1,196 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws; - -import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; -import com.amazonaws.services.dynamodbv2.local.embedded.DynamoDBEmbedded; -import com.amazonaws.services.dynamodbv2.model.AttributeDefinition; -import com.amazonaws.services.dynamodbv2.model.AttributeValue; -import com.amazonaws.services.dynamodbv2.model.CreateTableRequest; -import com.amazonaws.services.dynamodbv2.model.CreateTableResult; -import com.amazonaws.services.dynamodbv2.model.KeySchemaElement; -import com.amazonaws.services.dynamodbv2.model.KeyType; -import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput; -import com.amazonaws.services.dynamodbv2.model.PutItemRequest; -import com.amazonaws.services.dynamodbv2.model.ScalarAttributeType; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; -import org.junitpioneer.jupiter.SetSystemProperty; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Bean; -import org.springframework.http.MediaType; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; -import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.user.DefaultOAuth2User; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.web.servlet.MockMvc; -import twitter4j.Paging; -import twitter4j.ResponseList; -import twitter4j.Twitter; -import twitter4j.TwitterException; - -import java.time.Clock; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -@SpringBootTest -@AutoConfigureMockMvc -@SetSystemProperty(key = "sqlite4java.library.path", value = "target/native-libs") -@TestPropertySource(properties = {"social-media-publisher.principal-names-allowed.linkedin=myuser"}) -@TestMethodOrder(MethodOrderer.OrderAnnotation.class) -@ActiveProfiles({"linkedin", "twitter, secure"}) -class MockSocialMediaCredentialsHandlingTests { - @Autowired - private MockMvc mvc; - @MockBean - private Twitter twitter; - @MockBean - private OAuth2AuthorizedClientService authorizedClientService; - @Autowired - private ClientRegistrationRepository registrations; - - @Test - @Order(1) - void publish_credentialsExpired_unauthorizedException() throws Exception { - mockingTwitter(); - - mvc.perform(post("/posts/group1/next") - .with(csrf()) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isUnauthorized()) - .andExpect(content().string("Unauthorized for linkedin credential1. Please login again here: http://localhost:8080/oauth2/linkedin/credentials")); - } - - @Test - @Order(2) - void login_noSuccessLinkedin_return302() throws Exception { - mvc.perform(get("/oauth2/linkedin/credentials") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isFound()) - .andExpect(header().string("Location", "http://localhost/oauth2/authorization/linkedin")); - } - - @Test - @Order(3) - void login_successLinkedin_updateCredentials() throws Exception { - mockingTwitter(); - - OAuth2AuthenticationToken authenticationToken = createToken(); - OAuth2AuthorizedClient authorizedClient = createAuthorizedClient(authenticationToken); - - when(this.authorizedClientService.loadAuthorizedClient(eq("linkedin"), anyString())) - .thenReturn(authorizedClient); - - mvc.perform(get("/oauth2/linkedin/credentials") - .with(authentication(authenticationToken)) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()); - } - - private OAuth2AuthorizedClient createAuthorizedClient(OAuth2AuthenticationToken authenticationToken) { - OAuth2AccessToken accessToken = new OAuth2AccessToken( - OAuth2AccessToken.TokenType.BEARER, - "newAccessToken", - LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1).toInstant(ZoneOffset.UTC), - LocalDateTime.of(2020, 5, 3, 5, 6, 8, 1).toInstant(ZoneOffset.UTC)); - - ClientRegistration clientRegistration = this.registrations.findByRegistrationId(authenticationToken.getAuthorizedClientRegistrationId()); - return new OAuth2AuthorizedClient(clientRegistration, authenticationToken.getName(), accessToken); - } - - private OAuth2AuthenticationToken createToken() { - Set authorities = new HashSet<>(AuthorityUtils.createAuthorityList("USER")); - OAuth2User oAuth2User = new DefaultOAuth2User(authorities, Collections.singletonMap("id", "myuser"), "id"); - return new OAuth2AuthenticationToken(oAuth2User, authorities, "linkedin"); - } - - private void mockingTwitter() throws TwitterException { - Paging paging = new Paging(1, 1); - when(twitter.getHomeTimeline(paging)).thenReturn(mock(ResponseList.class)); - } - - @TestConfiguration - static class OverriddenConfiguration { - @Bean - public Clock clock() { - return Clock.fixed(ZonedDateTime.of(2020, 3, 3, 5, 6, 8, 1, ZoneId.of("UTC")).toInstant(), ZoneId.of("UTC")); - } - - @Bean - public AmazonDynamoDB amazonDynamoDB() { - AmazonDynamoDB ddb = DynamoDBEmbedded.create().amazonDynamoDB(); - - createTable(ddb, "Oauth1Credentials", "id"); - createTable(ddb, "Oauth2Credentials", "id"); - - PutItemRequest oauth2Credentials = new PutItemRequest(); - oauth2Credentials.setTableName("Oauth2Credentials"); - oauth2Credentials.addItemEntry("id", new AttributeValue("credential1")); - oauth2Credentials.addItemEntry("accessToken", new AttributeValue("access123")); - oauth2Credentials.addItemEntry("expirationDate", new AttributeValue("2020-02-01T05:05:05")); - - ddb.putItem(oauth2Credentials); - - PutItemRequest oauth1Credentials = new PutItemRequest(); - oauth1Credentials.setTableName("Oauth1Credentials"); - oauth1Credentials.addItemEntry("id", new AttributeValue("twitter")); - oauth1Credentials.addItemEntry("accessToken", new AttributeValue("access123")); - oauth1Credentials.addItemEntry("consumerKey", new AttributeValue("consumer123")); - oauth1Credentials.addItemEntry("consumerSecret", new AttributeValue("csecret123")); - oauth1Credentials.addItemEntry("tokenSecret", new AttributeValue("tsecret123")); - - ddb.putItem(oauth1Credentials); - - return ddb; - } - - private CreateTableResult createTable(AmazonDynamoDB ddb, String tableName, String hashKeyName) { - List attributeDefinitions = new ArrayList<>(); - attributeDefinitions.add(new AttributeDefinition(hashKeyName, ScalarAttributeType.S)); - - List ks = new ArrayList<>(); - ks.add(new KeySchemaElement(hashKeyName, KeyType.HASH)); - - ProvisionedThroughput provisionedthroughput = new ProvisionedThroughput(1000L, 1000L); - - CreateTableRequest request = - new CreateTableRequest() - .withTableName(tableName) - .withAttributeDefinitions(attributeDefinitions) - .withKeySchema(ks) - .withProvisionedThroughput(provisionedthroughput); - - return ddb.createTable(request); - } - } - -} diff --git a/aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/MockSocialMediaSuccessfulTests.java b/aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/MockSocialMediaSuccessfulTests.java deleted file mode 100644 index a73c03a..0000000 --- a/aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/MockSocialMediaSuccessfulTests.java +++ /dev/null @@ -1,210 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws; - -import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; -import com.amazonaws.services.dynamodbv2.local.embedded.DynamoDBEmbedded; -import com.amazonaws.services.dynamodbv2.model.AttributeDefinition; -import com.amazonaws.services.dynamodbv2.model.AttributeValue; -import com.amazonaws.services.dynamodbv2.model.CreateTableRequest; -import com.amazonaws.services.dynamodbv2.model.CreateTableResult; -import com.amazonaws.services.dynamodbv2.model.KeySchemaElement; -import com.amazonaws.services.dynamodbv2.model.KeyType; -import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput; -import com.amazonaws.services.dynamodbv2.model.PutItemRequest; -import com.amazonaws.services.dynamodbv2.model.ScalarAttributeType; -import org.junit.jupiter.api.Test; -import org.junitpioneer.jupiter.SetSystemProperty; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Bean; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.web.client.RestTemplate; -import twitter4j.Paging; -import twitter4j.ResponseList; -import twitter4j.Status; -import twitter4j.Twitter; -import twitter4j.TwitterException; - -import java.time.Clock; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.List; - -import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.*; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -@SpringBootTest -@AutoConfigureMockMvc -@SetSystemProperty(key = "sqlite4java.library.path", value = "target/native-libs") -@ActiveProfiles({"linkedin", "twitter"}) -class MockSocialMediaSuccessfulTests { - @Autowired - private MockMvc mvc; - @MockBean - private Twitter twitter; - @MockBean - private RestTemplate restTemplate; - - @Test - void ping() throws Exception { - mvc.perform(get("/ping") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.pong", is("Hello, World!"))); - } - - @Test - void publish_allSocialMedia_success() throws Exception { - mockingTwitter(); - mockingLinkedIn(); - - mvc.perform(post("/posts/group1/next") - .with(csrf()) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(content() - .json("{\"id\":\"2\",\"name\":\"My Post 2\",\"description\":\"My second post\",\"tags\":[\"tag1\",\"tag2\"],\"url\":\"https://coderstower.com/2020/01/13/open-close-principle-by-example/\",\"publishedDate\":\"2020-03-03T05:06:08.000000001\",\"publications\":[{\"id\":\"123\",\"status\":\"SUCCESS\",\"publisher\":\"twitter\",\"publishedDate\":\"2020-03-03T05:06:08.000000001\"},{\"id\":\"shareid\",\"status\":\"SUCCESS\",\"publisher\":\"linkedin\",\"publishedDate\":\"2020-03-03T05:06:08.000000001\"}]}")); - } - - private void mockingLinkedIn() { - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setContentType(MediaType.APPLICATION_JSON); - httpHeaders.add("X-Restli-Protocol-Version", "2.0.0"); - httpHeaders.add("LinkedIn-Version", "202304"); - httpHeaders.setBearerAuth("access123"); - - HttpEntity requestMe = new HttpEntity<>(httpHeaders); - - when(restTemplate.exchange("https://api.linkedin.com/v2/userinfo", HttpMethod.GET, requestMe, Profile.class)) - .thenReturn(ResponseEntity.ok(Profile.builder() - .sub("memberid") - .build())); - - LinkedInShare linkedInShare = LinkedInShare.builder() - .author("urn:li:person:memberid") - .lifecycleState("PUBLISHED") - .commentary("My second post\n\n#tag1 #tag2") - .distribution(Distribution.builder().feedDistribution("MAIN_FEED").build()) - .lifecycleState("PUBLISHED") - .content(Content.builder() - .article(ArticleContent.builder() - .description("My second post") - .title("My Post 2") - .source("https://coderstower.com/2020/01/13/open-close-principle-by-example/") - .build()) - .build()) - .visibility("PUBLIC") - .build(); - - HttpEntity requestShare = new HttpEntity<>(linkedInShare, httpHeaders); - - when(restTemplate.exchange("https://api.linkedin.com/rest/posts", HttpMethod.POST, requestShare, Void.class)) - .thenReturn(ResponseEntity.ok() - .header("X-RestLi-Id", "shareid") - .build()); - } - - private void mockingTwitter() throws TwitterException { - Paging paging = new Paging(1, 1); - when(twitter.getHomeTimeline(paging)).thenReturn(mock(ResponseList.class)); - - Status status = mock(Status.class); - when(status.getId()).thenReturn(123L); - when(twitter.updateStatus("My second post\n\n#tag1 #tag2\n\nhttps://coderstower.com/2020/01/13/open-close-principle-by-example/")).thenReturn(status); - } - - @TestConfiguration - static class OverriddenConfiguration { - @Bean - public Clock clock() { - return Clock.fixed(ZonedDateTime.of(2020, 3, 3, 5, 6, 8, 1, ZoneId.of("UTC")).toInstant(), ZoneId.of("UTC")); - } - - @Bean - public AmazonDynamoDB amazonDynamoDB() { - AmazonDynamoDB ddb = DynamoDBEmbedded.create().amazonDynamoDB(); - - createTable(ddb, "Oauth1Credentials", "id"); - createTable(ddb, "Oauth2Credentials", "id"); - createTable(ddb, "Posts", "id"); - - PutItemRequest oauth2Credentials = new PutItemRequest(); - oauth2Credentials.setTableName("Oauth2Credentials"); - oauth2Credentials.addItemEntry("id", new AttributeValue("linkedin")); - oauth2Credentials.addItemEntry("accessToken", new AttributeValue("access123")); - oauth2Credentials.addItemEntry("allowedGroups", new AttributeValue().withL(List.of(new AttributeValue("group1")))); - oauth2Credentials.addItemEntry("expirationDate", new AttributeValue("2020-04-01T05:05:05")); - - ddb.putItem(oauth2Credentials); - - PutItemRequest oauth1Credentials = new PutItemRequest(); - oauth1Credentials.setTableName("Oauth1Credentials"); - oauth1Credentials.addItemEntry("id", new AttributeValue("twitter")); - oauth1Credentials.addItemEntry("accessToken", new AttributeValue("access123")); - oauth1Credentials.addItemEntry("consumerKey", new AttributeValue("consumer123")); - oauth1Credentials.addItemEntry("consumerSecret", new AttributeValue("csecret123")); - oauth1Credentials.addItemEntry("tokenSecret", new AttributeValue("tsecret123")); - - ddb.putItem(oauth1Credentials); - - PutItemRequest post1 = new PutItemRequest(); - post1.setTableName("Posts"); - post1.addItemEntry("id", new AttributeValue("1")); - post1.addItemEntry("name", new AttributeValue("My Post 1")); - post1.addItemEntry("description", new AttributeValue("My first post")); - post1.addItemEntry("tags", new AttributeValue().withL(new AttributeValue("tag1"), new AttributeValue("tag2"))); - post1.addItemEntry("url", new AttributeValue("https://coderstower.com/2020/02/18/unit-tests-vs-integration-tests/")); - post1.addItemEntry("publishedDate", new AttributeValue("2013-09-17T18:47:52")); - post1.addItemEntry("group", new AttributeValue("group1")); - - ddb.putItem(post1); - - PutItemRequest post2 = new PutItemRequest(); - post2.setTableName("Posts"); - post2.addItemEntry("id", new AttributeValue("2")); - post2.addItemEntry("name", new AttributeValue("My Post 2")); - post2.addItemEntry("description", new AttributeValue("My second post")); - post2.addItemEntry("tags", new AttributeValue().withL(new AttributeValue("tag1"), new AttributeValue("tag2"))); - post2.addItemEntry("url", new AttributeValue("https://coderstower.com/2020/01/13/open-close-principle-by-example/")); - post2.addItemEntry("publishedDate", new AttributeValue("2012-09-17T18:47:52")); - post2.addItemEntry("group", new AttributeValue("group1")); - - ddb.putItem(post2); - - return ddb; - } - - private CreateTableResult createTable(AmazonDynamoDB ddb, String tableName, String hashKeyName) { - List attributeDefinitions = new ArrayList<>(); - attributeDefinitions.add(new AttributeDefinition(hashKeyName, ScalarAttributeType.S)); - - List ks = new ArrayList<>(); - ks.add(new KeySchemaElement(hashKeyName, KeyType.HASH)); - - ProvisionedThroughput provisionedthroughput = new ProvisionedThroughput(1000L, 1000L); - - CreateTableRequest request = - new CreateTableRequest() - .withTableName(tableName) - .withAttributeDefinitions(attributeDefinitions) - .withKeySchema(ks) - .withProvisionedThroughput(provisionedthroughput); - - return ddb.createTable(request); - } - } - -} diff --git a/aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/StreamLambdaHandlerTest.java b/aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/StreamLambdaHandlerTest.java deleted file mode 100644 index 304f307..0000000 --- a/aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/StreamLambdaHandlerTest.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws; - - -import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; -import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; -import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; -import com.amazonaws.serverless.proxy.model.AwsProxyResponse; -import com.amazonaws.services.lambda.runtime.Context; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import javax.ws.rs.HttpMethod; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; - -import static org.junit.jupiter.api.Assertions.*; - -public class StreamLambdaHandlerTest { - - private static StreamLambdaHandler handler; - private static Context lambdaContext; - - @BeforeAll - public static void setUp() { - handler = new StreamLambdaHandler(); - lambdaContext = new MockLambdaContext(); - } - - @Test - public void ping_streamRequest_respondsWithHello() { - InputStream requestStream = new AwsProxyRequestBuilder("/ping", HttpMethod.GET) - .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON) - .buildStream(); - ByteArrayOutputStream responseStream = new ByteArrayOutputStream(); - - handle(requestStream, responseStream); - - AwsProxyResponse response = readResponse(responseStream); - assertNotNull(response); - assertEquals(Response.Status.OK.getStatusCode(), response.getStatusCode()); - - assertFalse(response.isBase64Encoded()); - - assertTrue(response.getBody().contains("pong")); - assertTrue(response.getBody().contains("Hello, World!")); - - assertTrue(response.getMultiValueHeaders().containsKey(HttpHeaders.CONTENT_TYPE)); - assertTrue(response.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_TYPE).startsWith(MediaType.APPLICATION_JSON)); - } - - private void handle(InputStream is, ByteArrayOutputStream os) { - try { - handler.handleRequest(is, os, lambdaContext); - } catch (IOException e) { - e.printStackTrace(); - fail(e.getMessage()); - } - } - - private AwsProxyResponse readResponse(ByteArrayOutputStream responseStream) { - try { - return LambdaContainerHandler.getObjectMapper().readValue(responseStream.toByteArray(), AwsProxyResponse.class); - } catch (IOException e) { - e.printStackTrace(); - fail("Error while parsing response: " + e.getMessage()); - } - return null; - } -} diff --git a/aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/LocalDateTimeConverterTest.java b/aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/LocalDateTimeConverterTest.java deleted file mode 100644 index 00e8a7e..0000000 --- a/aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/LocalDateTimeConverterTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository; - -import org.junit.jupiter.api.Test; - -import java.time.LocalDateTime; - -import static org.assertj.core.api.Assertions.assertThat; - -class LocalDateTimeConverterTest { - private LocalDateTimeConverter localDateTimeConverter = new LocalDateTimeConverter(); - - @Test - public void convert(){ - String date = localDateTimeConverter.convert(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)); - - assertThat(date).isEqualTo("2020-03-03T05:06:08.000000001"); - } - - @Test - public void unconvert(){ - LocalDateTime date = localDateTimeConverter.unconvert("2020-03-03T05:06:08.000000001"); - - assertThat(date).isEqualTo(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)); - } - -} \ No newline at end of file diff --git a/aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/URLConverterTest.java b/aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/URLConverterTest.java deleted file mode 100644 index 590c9c7..0000000 --- a/aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/URLConverterTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository; - -import org.junit.jupiter.api.Test; - -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URL; - -import static org.assertj.core.api.Assertions.assertThat; - -class URLConverterTest { - private URLConverter urlConverter = new URLConverter(); - - @Test - public void convert() throws MalformedURLException { - String url = urlConverter.convert(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()); - - assertThat(url).isEqualTo("https://coderstower.com/2020/01/13/open-close-principle-by-example/"); - } - - @Test - public void unconvert() throws MalformedURLException { - URL url = urlConverter.unconvert("https://coderstower.com/2020/01/13/open-close-principle-by-example/"); - - assertThat(url).isEqualTo(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()); - } - -} \ No newline at end of file diff --git a/aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepositoryTest.java b/aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepositoryTest.java deleted file mode 100644 index 5c30a0b..0000000 --- a/aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth1/OAuth1CredentialAWSRepositoryTest.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth1; - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth1Credentials; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class OAuth1CredentialAWSRepositoryTest { - @Mock - private OAuth1CredentialDynamoRepository oauth1CredentialDynamoRepository; - @InjectMocks - private OAuth1CredentialAWSRepository oauth1CredentialAWSRepository; - - @Test - public void getCredentials(){ - when(oauth1CredentialDynamoRepository.findById("twitter")).thenReturn(Optional.of(OAuth1CredentialDynamo.builder() - .id("twitter") - .accessToken("accessToken") - .tokenSecret("tokenSecret") - .consumerKey("consumerKey") - .consumerSecret("consumerSecret") - .build())); - - Optional credentials = oauth1CredentialAWSRepository.getCredentials("twitter"); - - assertThat(credentials).isEqualTo(Optional.of(OAuth1Credentials.builder() - .id("twitter") - .accessToken("accessToken") - .tokenSecret("tokenSecret") - .consumerKey("consumerKey") - .consumerSecret("consumerSecret") - .build())); - } - -} \ No newline at end of file diff --git a/aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepositoryTest.java b/aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepositoryTest.java deleted file mode 100644 index 3914451..0000000 --- a/aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/oauth2/OAuth2CredentialAWSRepositoryTest.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.oauth2; - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2Credentials; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.time.LocalDateTime; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class OAuth2CredentialAWSRepositoryTest { - @Mock - private OAuth2CredentialDynamoRepository oauth2CredentialDynamoRepository; - @InjectMocks - private OAuth2CredentialAWSRepository oauth2CredentialAWSRepository; - - @Test - public void getCredentials(){ - when(oauth2CredentialDynamoRepository.findById("linkedin")).thenReturn(Optional.of(OAuth2CredentialDynamo.builder() - .id("linkedin") - .accessToken("accessToken") - .expirationDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .build())); - - Optional credentials = oauth2CredentialAWSRepository.getCredentials("linkedin"); - - assertThat(credentials).isEqualTo(Optional.of(OAuth2Credentials.builder() - .id("linkedin") - .accessToken("accessToken") - .expirationDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .build())); - } - - @Test - public void update(){ - when(oauth2CredentialDynamoRepository.save(OAuth2CredentialDynamo.builder() - .id("linkedin") - .accessToken("accessToken") - .expirationDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .build())).thenReturn(OAuth2CredentialDynamo.builder() - .id("linkedin") - .accessToken("accessToken") - .expirationDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .build()); - - OAuth2Credentials credentials = oauth2CredentialAWSRepository.update(OAuth2Credentials.builder() - .id("linkedin") - .accessToken("accessToken") - .expirationDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .build()); - - assertThat(credentials).isEqualTo(OAuth2Credentials.builder() - .id("linkedin") - .accessToken("accessToken") - .expirationDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .build()); - } - -} \ No newline at end of file diff --git a/aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostAWSRepositoryTest.java b/aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostAWSRepositoryTest.java deleted file mode 100644 index 6a33102..0000000 --- a/aws-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/aws/repository/post/PostAWSRepositoryTest.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.aws.repository.post; - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.net.MalformedURLException; -import java.net.URI; -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class PostAWSRepositoryTest { - @Mock - private PostDynamoRepository postDynamoRepository; - @InjectMocks - private PostAWSRepository postAWSRepository; - - @Test - public void getNextToPublish() throws MalformedURLException { - when(postDynamoRepository.findAll()).thenReturn(List.of(PostDynamo.builder() - .id("2") - .name("My Post 2") - .description("My second post") - .tags(List.of("tag1", "tag2")) - .group("group1") - .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) - .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) - .build(), PostDynamo.builder() - .id("1") - .name("My Post 1") - .description("My first post") - .tags(List.of("tag1", "tag2")) - .group("group1") - .url(URI.create("https://coderstower.com/2020/02/18/unit-tests-vs-integration-tests/").toURL()) - .publishedDate(LocalDateTime.parse("2013-09-17T18:47:52")) - .build())); - - Optional post = postAWSRepository.getNextToPublish("group1"); - - assertThat(post).isEqualTo(Optional.of(Post.builder() - .id("2") - .name("My Post 2") - .description("My second post") - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) - .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) - .group("group1") - .build())); - } - - @Test - public void update() throws MalformedURLException { - when(postDynamoRepository.save(PostDynamo.builder() - .id("2") - .name("My Post 2") - .description("My second post") - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) - .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) - .build())) - .thenReturn(PostDynamo.builder() - .id("2") - .name("My Post 2") - .description("My second post") - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) - .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) - .build()); - - Post post = postAWSRepository.update(Post.builder() - .id("2") - .name("My Post 2") - .description("My second post") - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) - .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) - .build()); - - assertThat(post).isEqualTo(Post.builder() - .id("2") - .name("My Post 2") - .description("My second post") - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) - .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) - .build()); - } - -} \ No newline at end of file diff --git a/spring-publisher/HELP.md b/spring-publisher/HELP.md deleted file mode 100644 index 97150fe..0000000 --- a/spring-publisher/HELP.md +++ /dev/null @@ -1,8 +0,0 @@ -# Getting Started - -### Reference Documentation -For further reference, please consider the following sections: - -* [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) -* [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/maven-plugin/) - diff --git a/spring-publisher/pom.xml b/spring-publisher/pom.xml deleted file mode 100644 index 5f85380..0000000 --- a/spring-publisher/pom.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - com.coderstower - social-media-publisher - 0.0.1-SNAPSHOT - - - 4.0.0 - spring-publisher - 0.0.1-SNAPSHOT - spring-publisher - Demo project for Spring Boot - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-security - - - org.springframework.boot - spring-boot-starter-oauth2-client - - - org.twitter4j - twitter4j-core - 4.0.7 - - - - diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/PostPublisher.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/PostPublisher.java deleted file mode 100644 index 2fab901..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/PostPublisher.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.post; - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.PostRepository; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Acknowledge; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Publication; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.SocialMediaPublisher; - -import java.time.Clock; -import java.time.LocalDateTime; -import java.util.List; -import java.util.stream.Collectors; - -public class PostPublisher { - private final List socialMediaPublishers; - private final PostRepository postRepository; - private final Clock clock; - - public PostPublisher(List socialMediaPublishers, PostRepository postRepository, Clock clock) { - this.socialMediaPublishers = socialMediaPublishers; - this.postRepository = postRepository; - this.clock = clock; - } - - public Post publishNext(String group) { - ping(socialMediaPublishers); - - Post nextPost = postRepository.getNextToPublish(group) - .orElseThrow(() -> new IllegalStateException("There is not next post to publish")); - - List publishedPosts = publish(socialMediaPublishers, nextPost); - - if (publishedWellOK(publishedPosts)) { - Post toUpdate = nextPost.updateLastDatePublished(LocalDateTime.now(clock)); - - return postRepository.update(toUpdate) - .updatePublications(publishedPosts); - } else { - throw new IllegalStateException("Error publishing the post: " + nextPost - .updatePublications(publishedPosts)); - } - } - - private boolean publishedWellOK(List publishedPosts) { - return publishedPosts.stream() - .allMatch(publishedPost -> publishedPost.getStatus().equals(Publication.Status.SUCCESS)); - } - - private List publish(List socialMediaPublishers, Post nextPost) { - return socialMediaPublishers.stream() - .flatMap(socialMediaPublisher -> socialMediaPublisher.publish(nextPost).stream()) - .collect(Collectors.toList()); - } - - private void ping(List socialMediaPublishers) { - for (SocialMediaPublisher socialMediaPublisher : socialMediaPublishers) { - Acknowledge ping = socialMediaPublisher.ping(); - - if (!Acknowledge.Status.SUCCESS.equals(ping.getStatus())) { - throw new IllegalStateException("Ping to " + socialMediaPublisher.getName() + " failed: " + ping); - } - } - } -} diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/repository/Post.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/repository/Post.java deleted file mode 100644 index 3c5b945..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/repository/Post.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository; - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Publication; -import lombok.Builder; -import lombok.Data; - -import java.net.URL; -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -@Data -@Builder -public class Post { - private static final String BASIC_FORMAT = "%s\n\n%s\n\n%s"; - private static final String BASIC_FORMAT_WITHOUT_URL = "%s\n\n%s"; - private final String id; - private final String name; - private final String description; - private final List tags; - private final URL url; - private final LocalDateTime publishedDate; - private final List publications; - private final String group; - - public String basicFormat() { - return String.format(BASIC_FORMAT, - description, - Optional.ofNullable(tags) - .orElse(List.of()) - .stream() - .map(tag -> "#" + tag) - .collect(Collectors.joining(" ")) - , url); - } - - public String basicFormatWithoutURL() { - return String.format(BASIC_FORMAT_WITHOUT_URL, - description, - Optional.ofNullable(tags) - .orElse(List.of()) - .stream() - .map(tag -> "#" + tag) - .collect(Collectors.joining(" "))); - } - - public Post updateLastDatePublished(LocalDateTime publishedDate) { - return Post.builder() - .id(id) - .name(name) - .description(description) - .tags(tags) - .url(url) - .publications(publications) - .group(group) - .publishedDate(publishedDate).build(); - } - - public Post updatePublications(List publications) { - return Post.builder() - .id(id) - .name(name) - .description(description) - .tags(tags) - .url(url) - .publications(publications) - .group(group) - .publishedDate(publishedDate).build(); - } -} diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/repository/PostRepository.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/repository/PostRepository.java deleted file mode 100644 index 19ad73f..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/repository/PostRepository.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository; - -import java.util.Optional; - -public interface PostRepository { - Optional getNextToPublish(String group); - Post update(Post post); -} diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/Acknowledge.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/Acknowledge.java deleted file mode 100644 index 3500256..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/Acknowledge.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia; - -import lombok.Builder; -import lombok.Data; - -@Data -@Builder -public class Acknowledge { - private final Status status; - private final String description; - private final Exception exception; - - public enum Status{ - SUCCESS, - FAILURE - } -} diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/Publication.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/Publication.java deleted file mode 100644 index 70495eb..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/Publication.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia; - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; -import lombok.Builder; -import lombok.Data; - -import java.time.LocalDateTime; - -@Data -@Builder -public class Publication { - private final String id; - private final Status status; - private final String publisher; - private final String credentialId; - private final LocalDateTime publishedDate; - - public enum Status{ - SUCCESS, FAILURE - } -} diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/SocialMediaPublisher.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/SocialMediaPublisher.java deleted file mode 100644 index a555fc8..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/socialmedia/SocialMediaPublisher.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia; - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; - -import java.util.List; - -public interface SocialMediaPublisher { - String getName(); - Acknowledge ping(); - List publish(Post post); -} diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2AccessTokenResponseConverterWithDefaults.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2AccessTokenResponseConverterWithDefaults.java deleted file mode 100644 index 3fd5e99..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2AccessTokenResponseConverterWithDefaults.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.security; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * Copy from https://github.com/jgrandja/oauth2login-demo/blob/linkedin/src/main/java/sample/web/OAuth2AccessTokenResponseConverterWithDefaults.java - * - * Linkedin token response doesn't have the tokenType property. That property is required by the specification. - * - * This Converter uses Baerer token by default - */ -public class OAuth2AccessTokenResponseConverterWithDefaults implements Converter, OAuth2AccessTokenResponse> { - private static final Set TOKEN_RESPONSE_PARAMETER_NAMES = Stream.of( - OAuth2ParameterNames.ACCESS_TOKEN, - OAuth2ParameterNames.TOKEN_TYPE, - OAuth2ParameterNames.EXPIRES_IN, - OAuth2ParameterNames.REFRESH_TOKEN, - OAuth2ParameterNames.SCOPE).collect(Collectors.toSet()); - - private OAuth2AccessToken.TokenType defaultAccessTokenType = OAuth2AccessToken.TokenType.BEARER; - - @Override - public OAuth2AccessTokenResponse convert(Map tokenResponseParameters) { - String accessToken = tokenResponseParameters.get(OAuth2ParameterNames.ACCESS_TOKEN); - - OAuth2AccessToken.TokenType accessTokenType = this.defaultAccessTokenType; - if (OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase( - tokenResponseParameters.get(OAuth2ParameterNames.TOKEN_TYPE))) { - accessTokenType = OAuth2AccessToken.TokenType.BEARER; - } - - long expiresIn = 0; - if (tokenResponseParameters.containsKey(OAuth2ParameterNames.EXPIRES_IN)) { - try { - expiresIn = Long.valueOf(tokenResponseParameters.get(OAuth2ParameterNames.EXPIRES_IN)); - } catch (NumberFormatException ex) { } - } - - Set scopes = Collections.emptySet(); - if (tokenResponseParameters.containsKey(OAuth2ParameterNames.SCOPE)) { - String scope = tokenResponseParameters.get(OAuth2ParameterNames.SCOPE); - scopes = Arrays.stream(StringUtils.delimitedListToStringArray(scope, " ")).collect(Collectors.toSet()); - } - - Map additionalParameters = new LinkedHashMap<>(); - tokenResponseParameters.entrySet().stream() - .filter(e -> !TOKEN_RESPONSE_PARAMETER_NAMES.contains(e.getKey())) - .forEach(e -> additionalParameters.put(e.getKey(), e.getValue())); - - return OAuth2AccessTokenResponse.withToken(accessToken) - .tokenType(accessTokenType) - .expiresIn(expiresIn) - .scopes(scopes) - .additionalParameters(additionalParameters) - .build(); - } - - public final void setDefaultAccessTokenType(OAuth2AccessToken.TokenType defaultAccessTokenType) { - Assert.notNull(defaultAccessTokenType, "defaultAccessTokenType cannot be null"); - this.defaultAccessTokenType = defaultAccessTokenType; - } -} diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsManager.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsManager.java deleted file mode 100644 index f578999..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsManager.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.security; - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2Credentials; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2CredentialsRepository; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; - -import java.time.LocalDateTime; -import java.time.ZoneOffset; -import java.util.Map; -import java.util.Optional; - -public class OAuth2CredentialsManager { - private final OAuth2CredentialsRepository oAuth2CredentialsRepository; - private final Map principalNamesAllowed; - - public OAuth2CredentialsManager(OAuth2CredentialsRepository oAuth2CredentialsRepository, Map principalNamesAllowed) { - this.oAuth2CredentialsRepository = oAuth2CredentialsRepository; - this.principalNamesAllowed = principalNamesAllowed; - } - - public OAuth2Credentials update(OAuth2AuthorizedClient authorizedClient, String socialAccount) { - if (isNotAllowed(authorizedClient, socialAccount)) { - throw new UnauthorizedException(); - } - - OAuth2Credentials credentials = oAuth2CredentialsRepository.getCredentials(socialAccount) - .orElseGet(() -> oAuth2CredentialsRepository.save(OAuth2Credentials.builder() - .id(socialAccount) - .build())); - - OAuth2Credentials updatedCredentials = credentials.update( - authorizedClient.getAccessToken().getTokenValue(), - LocalDateTime.ofInstant( - authorizedClient.getAccessToken().getExpiresAt(), - ZoneOffset.UTC) - ); - - return oAuth2CredentialsRepository.update(updatedCredentials); - } - - private boolean isNotAllowed(OAuth2AuthorizedClient authorizedClient, String socialAccount) { - return Optional.ofNullable(authorizedClient) - .map(OAuth2AuthorizedClient::getPrincipalName) - .filter(principalName -> principalName.equals(principalNamesAllowed.get(socialAccount))) - .isEmpty(); - } -} diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsUpdated.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsUpdated.java deleted file mode 100644 index e977097..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsUpdated.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.security; - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2Credentials; -import lombok.Builder; -import lombok.Data; - -@Data -@Builder -public class OAuth2CredentialsUpdated { - private final OAuth2Credentials oAuth2Credentials; -} diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/UnauthorizedException.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/UnauthorizedException.java deleted file mode 100644 index 840d1ef..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/UnauthorizedException.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.security; - -public class UnauthorizedException extends RuntimeException { - public UnauthorizedException() { - } - - public UnauthorizedException(String message) { - super(message); - } -} diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth1Credentials.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth1Credentials.java deleted file mode 100644 index 44fb499..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth1Credentials.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository; - -import lombok.Builder; -import lombok.Data; - -@Data -@Builder -public class OAuth1Credentials { - private final String id; - private final String consumerKey; - private final String consumerSecret; - private final String accessToken; - private final String tokenSecret; -} diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth1CredentialsRepository.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth1CredentialsRepository.java deleted file mode 100644 index 1a9cee7..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth1CredentialsRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository; - -import java.util.Optional; - -public interface OAuth1CredentialsRepository { - Optional getCredentials(String id); -} diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth2Credentials.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth2Credentials.java deleted file mode 100644 index 3362ff5..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth2Credentials.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository; - -import lombok.Builder; -import lombok.Data; - -import java.time.LocalDateTime; -import java.util.List; - -@Data -@Builder -public class OAuth2Credentials { - private final String id; - private final String accessToken; - private final LocalDateTime expirationDate; - private final List allowedGroups; - - public OAuth2Credentials update(String accessToken, LocalDateTime expirationDate){ - return OAuth2Credentials.builder() - .id(id) - .accessToken(accessToken) - .expirationDate(expirationDate) - .build(); - } -} diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth2CredentialsRepository.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth2CredentialsRepository.java deleted file mode 100644 index be73c19..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/repository/OAuth2CredentialsRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository; - -import java.util.List; -import java.util.Optional; - -public interface OAuth2CredentialsRepository { - OAuth2Credentials save(OAuth2Credentials oAuth2Credentials); - - OAuth2Credentials update(OAuth2Credentials oAuth2Credentials); - - Optional getCredentials(String id); - List findAll(); -} diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/SpringPublisherApplication.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/SpringPublisherApplication.java deleted file mode 100644 index 822ed8a..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/SpringPublisherApplication.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class SpringPublisherApplication { - - public static void main(String[] args) { - SpringApplication - .run(SpringPublisherApplication.class, - args); - } - -} diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/ErrorHandler.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/ErrorHandler.java deleted file mode 100644 index 51d8e31..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/ErrorHandler.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.controller; - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.UnauthorizedException; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; - -@ControllerAdvice -public class ErrorHandler { - @ExceptionHandler(UnauthorizedException.class) - public ResponseEntity unauthorized(UnauthorizedException ex) { - return ResponseEntity.status(401).body(ex.getMessage()); - } -} diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/OAuth2CredentialsController.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/OAuth2CredentialsController.java deleted file mode 100644 index 98addd0..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/OAuth2CredentialsController.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.controller; - - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.OAuth2CredentialsManager; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@Slf4j -public class OAuth2CredentialsController { - private final OAuth2CredentialsManager oAuth2CredentialsManager; - - public OAuth2CredentialsController(OAuth2CredentialsManager oAuth2CredentialsManager) { - this.oAuth2CredentialsManager = oAuth2CredentialsManager; - } - - /** - * Endpoint secured to redirect to the right authorization provider. - * Needs to be a GET to allow access from the browser and the whole 0Auth2 flow - */ - @RequestMapping(path = "/oauth2/{socialAccount}/credentials", method = RequestMethod.GET) - public ResponseEntity updateOAuth2Credentials(@RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient, @PathVariable String socialAccount) { - oAuth2CredentialsManager.update(authorizedClient, socialAccount); - - return ResponseEntity.ok("Credentials updated"); - } -} diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/PostsController.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/PostsController.java deleted file mode 100644 index 6201df2..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/PostsController.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.controller; - - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.PostPublisher; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -import java.util.HashMap; -import java.util.Map; - -@RestController -@Slf4j -public class PostsController { - private final PostPublisher postPublisher; - - public PostsController(PostPublisher postPublisher) { - this.postPublisher = postPublisher; - } - - @RequestMapping(path = "/ping", method = RequestMethod.GET) - public Map ping() { - Map pong = new HashMap<>(); - pong.put("pong", "Hello, World!"); - - log.info("Pong: {}", pong); - - return pong; - } - - @RequestMapping(path = "/posts/{group}/next", method = RequestMethod.POST) - public Post postNext(@PathVariable String group) { - Post post = postPublisher.publishNext(group); - - log.info("Published Post from group {}: {}", group, post); - - return post; - } -} diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/CredentialsProperties.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/CredentialsProperties.java deleted file mode 100644 index 767d86f..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/CredentialsProperties.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.factory; - -import lombok.Builder; -import lombok.Data; -import org.springframework.boot.context.properties.ConstructorBinding; -import org.springframework.web.util.UriTemplate; - -@Data -@ConstructorBinding -@Builder -public class CredentialsProperties { - private final UriTemplate loginUrl; -} diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SecurityFactory.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SecurityFactory.java deleted file mode 100644 index 852d93b..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SecurityFactory.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.factory; - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.OAuth2AccessTokenResponseConverterWithDefaults; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; -import org.springframework.core.annotation.Order; -import org.springframework.http.converter.FormHttpMessageConverter; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient; -import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; -import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; -import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler; -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; -import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; -import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; -import org.springframework.web.client.RestTemplate; - -import java.util.Arrays; - -@Configuration -@EnableWebSecurity -public class SecurityFactory { - /** - * Creating individual security by social network. If we generalize, - * Spring Security shows a list of social networks to login with. - */ - @Order(1) - @Profile("secure") - @Configuration - public static class LinkedinSecurity extends WebSecurityConfigurerAdapter { - private final ClientRegistrationRepository clientRegistrationRepository; - - public LinkedinSecurity(ClientRegistrationRepository clientRegistrationRepository) { - this.clientRegistrationRepository = clientRegistrationRepository; - } - - @Override - protected void configure(HttpSecurity http) throws Exception { - http - .authorizeRequests() - .antMatchers("/oauth2/linkedin/credentials").authenticated() - .and() - .oauth2Login() - /* - Create a new ClientRegistrationRepository with only Linkedin configuration to avoid - using other OAuth2 configuration over this endpoint - */ - .clientRegistrationRepository(new InMemoryClientRegistrationRepository(clientRegistrationRepository.findByRegistrationId("linkedin"))) - .tokenEndpoint() - .accessTokenResponseClient(authorizationCodeTokenResponseClient()); - } - - private OAuth2AccessTokenResponseClient authorizationCodeTokenResponseClient() { - OAuth2AccessTokenResponseHttpMessageConverter tokenResponseHttpMessageConverter = - new OAuth2AccessTokenResponseHttpMessageConverter(); - tokenResponseHttpMessageConverter.setTokenResponseConverter(new OAuth2AccessTokenResponseConverterWithDefaults()); - - RestTemplate restTemplate = new RestTemplate(Arrays.asList( - new FormHttpMessageConverter(), tokenResponseHttpMessageConverter)); - restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); - - DefaultAuthorizationCodeTokenResponseClient tokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient(); - tokenResponseClient.setRestOperations(restTemplate); - - return tokenResponseClient; - } - } - - @Order(2) - @Configuration - public static class NoSecurity extends WebSecurityConfigurerAdapter { - @Override - protected void configure(HttpSecurity http) throws Exception { - http - .csrf().disable() - .authorizeRequests() - .antMatchers("/**") - .permitAll(); - } - } -} diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SocialMediaPublisherProperties.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SocialMediaPublisherProperties.java deleted file mode 100644 index a47c283..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SocialMediaPublisherProperties.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.factory; - -import lombok.Builder; -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.ConstructorBinding; - -import java.util.Map; - -@ConfigurationProperties(prefix = "social-media-publisher") -@Data -@ConstructorBinding -@Builder -public class SocialMediaPublisherProperties { - private final Map principalNamesAllowed; - private final CredentialsProperties credentials; -} diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SpringPublisherFactory.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SpringPublisherFactory.java deleted file mode 100644 index 0381f89..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/factory/SpringPublisherFactory.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.factory; - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.PostPublisher; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.PostRepository; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.SocialMediaPublisher; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.OAuth2CredentialsManager; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth1CredentialsRepository; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2CredentialsRepository; -import com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin.LinkedInPublisher; -import com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.twitter.TwitterPublisher; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; -import org.springframework.web.client.RestTemplate; -import twitter4j.Twitter; -import twitter4j.TwitterFactory; - -import java.time.Clock; -import java.util.List; - - -@Configuration -@EnableConfigurationProperties({SocialMediaPublisherProperties.class}) -public class SpringPublisherFactory { - @Bean - public Clock clock() { - return Clock.systemDefaultZone(); - } - - @Bean - public PostPublisher postPublisher(List socialMediaPublishers, PostRepository postRepository, Clock clock) { - return new PostPublisher(socialMediaPublishers, postRepository, clock); - } - - @Bean - @Profile("twitter") - public TwitterPublisher twitterPublisher(OAuth1CredentialsRepository oauth1CredentialsRepository, Twitter twitter, Clock clock) { - return new TwitterPublisher("twitter", oauth1CredentialsRepository, twitter, clock); - } - - @Bean - @Profile("linkedin") - public LinkedInPublisher linkedInPublisher(OAuth2CredentialsRepository oauth2CredentialsRepository, RestTemplate restTemplate, Clock clock, SocialMediaPublisherProperties socialMediaPublisherProperties) { - return new LinkedInPublisher("linkedin", oauth2CredentialsRepository, restTemplate, clock, socialMediaPublisherProperties.getCredentials().getLoginUrl()); - } - - @Bean - public OAuth2CredentialsManager oAuth2CredentialsManager(OAuth2CredentialsRepository oAuth2CredentialsRepository, SocialMediaPublisherProperties socialMediaPublisherProperties) { - return new OAuth2CredentialsManager(oAuth2CredentialsRepository, socialMediaPublisherProperties.getPrincipalNamesAllowed()); - } - - @Bean - public RestTemplate restTemplate() { - return new RestTemplate(); - } - - @Bean - public Twitter twitter() { - return TwitterFactory.getSingleton(); - } -} diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/ArticleContent.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/ArticleContent.java deleted file mode 100644 index ef2f0e6..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/ArticleContent.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin; - -import lombok.Builder; -import lombok.Data; -import lombok.Singular; - -import java.util.List; - -@Data -@Builder -public class ArticleContent { - private final String title; - private final String description; - private final String source; -} diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Content.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Content.java deleted file mode 100644 index cfa8965..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Content.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin; - -import lombok.Builder; -import lombok.Data; - -@Data -@Builder -public class Content { - private final ArticleContent article; -} diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Distribution.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Distribution.java deleted file mode 100644 index 597be90..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Distribution.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin; - -import lombok.Builder; -import lombok.Data; - -@Data -@Builder -public class Distribution { - private final String feedDistribution; -} diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInPublisher.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInPublisher.java deleted file mode 100644 index abc83c3..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInPublisher.java +++ /dev/null @@ -1,176 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin; - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Acknowledge; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Publication; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.SocialMediaPublisher; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.UnauthorizedException; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2Credentials; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2CredentialsRepository; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.client.HttpClientErrorException; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.UriTemplate; - -import java.time.Clock; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -@Slf4j -public class LinkedInPublisher implements SocialMediaPublisher { - private final String name; - private final OAuth2CredentialsRepository oauth2CredentialsRepository; - private final RestTemplate restTemplate; - private final Clock clock; - private final UriTemplate loginURL; - - public LinkedInPublisher(String name, OAuth2CredentialsRepository oauth2CredentialsRepository, RestTemplate restTemplate, Clock clock, UriTemplate loginURL) { - this.name = name; - this.oauth2CredentialsRepository = oauth2CredentialsRepository; - this.restTemplate = restTemplate; - this.clock = clock; - this.loginURL = loginURL; - } - - @Override - public String getName() { - return name; - } - - @Override - public Acknowledge ping() { - List credentials = oauth2CredentialsRepository.findAll(); - - if (credentials.isEmpty()) { - throw new IllegalArgumentException("The credentials for " + name + " doesn't exist"); - } - - for (OAuth2Credentials credential : credentials) { - if (areCredentialsExpired(credential)) { - throw new UnauthorizedException("Unauthorized for " + name + " " + credential.getId() + ". Please login again here: " + loginURL.expand(name)); - } - } - - return Acknowledge.builder() - .status(Acknowledge.Status.SUCCESS) - .build(); - } - - @Override - public List publish(Post post) { - List credentials = oauth2CredentialsRepository.findAll() - .stream() - .filter(oAuth2Credentials -> oAuth2Credentials.getAllowedGroups().contains(post.getGroup())) - .collect(Collectors.toList()); - - List publications = new ArrayList<>(); - - for (OAuth2Credentials credential : credentials) { - try { - Profile profile = getProfile(credential); - - LinkedInShare linkedInShare = LinkedInShare.builder() - .author("urn:li:person:" + profile.getSub()) - .commentary(post.basicFormatWithoutURL()) - .distribution(Distribution.builder().feedDistribution("MAIN_FEED").build()) - .lifecycleState("PUBLISHED") - .content(Content.builder() - .article(ArticleContent.builder() - .description(post.getDescription()) - .title(post.getName()) - .source(post.getUrl().toString()) - .build()) - .build()) - .visibility("PUBLIC") - .build(); - - String shareId = publish(linkedInShare, credential); - - if (Objects.nonNull(shareId)) { - publications.add(Publication.builder() - .id(shareId) - .status(Publication.Status.SUCCESS) - .publisher(name) - .publishedDate(LocalDateTime.now(clock)) - .credentialId(credential.getId()) - .build()); - } else { - publications.add(Publication.builder() - .status(Publication.Status.FAILURE) - .publisher(name) - .publishedDate(LocalDateTime.now(clock)) - .credentialId(credential.getId()) - .build()); - } - } catch (HttpClientErrorException e) { - log.error("Error publishing to " + name + ", credentials " + credential.getId() + " response body: " + e.getResponseBodyAsString(), e); - - publications.add(Publication.builder() - .status(Publication.Status.FAILURE) - .publisher(name) - .publishedDate(LocalDateTime.now(clock)) - .credentialId(credential.getId()) - .build()); - - } catch (Exception e) { - log.error("Error publishing to " + name + ", credentials " + credential.getId(), e); - - publications.add(Publication.builder() - .status(Publication.Status.FAILURE) - .publisher(name) - .publishedDate(LocalDateTime.now(clock)) - .credentialId(credential.getId()) - .build()); - } - } - return publications; - } - - private String publish(LinkedInShare linkedInShare, OAuth2Credentials credentials) { - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setContentType(MediaType.APPLICATION_JSON); - httpHeaders.add("X-Restli-Protocol-Version", "2.0.0"); - httpHeaders.add("LinkedIn-Version", "202304"); - httpHeaders.setBearerAuth(credentials.getAccessToken()); - - HttpEntity requestEntity = new HttpEntity<>(linkedInShare, httpHeaders); - - ResponseEntity response = restTemplate.exchange("https://api.linkedin.com/rest/posts", HttpMethod.POST, requestEntity, Void.class); - - if (!response.getStatusCode().is2xxSuccessful()) { - throw new IllegalStateException("Problem trying to share a linkedin post: " + response.getStatusCode()); - } - - return response.getHeaders().getFirst("X-RestLi-Id"); - } - - private Profile getProfile(OAuth2Credentials credentials) { - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setContentType(MediaType.APPLICATION_JSON); - httpHeaders.add("X-Restli-Protocol-Version", "2.0.0"); - httpHeaders.add("LinkedIn-Version", "202304"); - httpHeaders.setBearerAuth(credentials.getAccessToken()); - - HttpEntity requestEntity = new HttpEntity<>(httpHeaders); - - ResponseEntity response = restTemplate.exchange("https://api.linkedin.com/v2/userinfo", HttpMethod.GET, requestEntity, Profile.class); - - if (!response.getStatusCode().is2xxSuccessful()) { - throw new IllegalStateException("Problem trying to get the profile: " + response.getStatusCode()); - } - - return response.getBody(); - } - - private boolean areCredentialsExpired(OAuth2Credentials credentials) { - return credentials.getExpirationDate().isBefore(LocalDateTime.now(clock)); - } -} diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInShare.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInShare.java deleted file mode 100644 index 4cbd39a..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInShare.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin; - -import lombok.Builder; -import lombok.Data; - -@Data -@Builder -public class LinkedInShare { - private final String commentary; - private final Distribution distribution; - private final String author; - private final String lifecycleState; - private final Content content; - private final String visibility; -} diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Media.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Media.java deleted file mode 100644 index 71f30fd..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Media.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin; - -import lombok.Builder; -import lombok.Data; - -@Data -@Builder -public class Media { - private final String status; - private final Text description; - private final Text title; - private final String originalUrl; -} diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Profile.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Profile.java deleted file mode 100644 index 9282df0..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Profile.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class Profile { - private String sub; -} diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Text.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Text.java deleted file mode 100644 index 7d48e12..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/Text.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin; - -import lombok.Builder; -import lombok.Data; - -@Data -@Builder -public class Text { - private final String text; -} diff --git a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/twitter/TwitterPublisher.java b/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/twitter/TwitterPublisher.java deleted file mode 100644 index c039bcb..0000000 --- a/spring-publisher/src/main/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/twitter/TwitterPublisher.java +++ /dev/null @@ -1,120 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.twitter; - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Acknowledge; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Publication; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.SocialMediaPublisher; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth1Credentials; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth1CredentialsRepository; -import lombok.extern.slf4j.Slf4j; -import twitter4j.Paging; -import twitter4j.Status; -import twitter4j.Twitter; -import twitter4j.TwitterException; -import twitter4j.auth.AccessToken; -import twitter4j.auth.NullAuthorization; - -import java.time.Clock; -import java.time.LocalDateTime; -import java.util.List; -import java.util.Objects; - -@Slf4j -public class TwitterPublisher implements SocialMediaPublisher { - private final String name; - private final OAuth1CredentialsRepository oauth1CredentialsRepository; - private final Twitter twitter; - private final Clock clock; - - public TwitterPublisher(String name, OAuth1CredentialsRepository oauth1CredentialsRepository, Twitter twitter, Clock clock) { - this.name = name; - this.oauth1CredentialsRepository = oauth1CredentialsRepository; - this.twitter = twitter; - this.clock = clock; - } - - @Override - public String getName() { - return name; - } - - @Override - public Acknowledge ping() { - OAuth1Credentials credentials = oauth1CredentialsRepository.getCredentials(name) - .orElseThrow(() -> new IllegalArgumentException("The credentials for " + name + " doesn't exist")); - - try { - if (areNotCredentialsReady(twitter)) { - setCredentials(twitter, credentials); - } - - Paging paging = new Paging(1, 1); - List statuses = twitter.getHomeTimeline(paging); - - if (Objects.nonNull(statuses)) { - return Acknowledge.builder() - .status(Acknowledge.Status.SUCCESS) - .build(); - } else { - return Acknowledge.builder() - .status(Acknowledge.Status.FAILURE) - .build(); - } - } catch (TwitterException e) { - log.error("Error ping to " + name, e); - - return Acknowledge.builder() - .description("Ping error") - .status(Acknowledge.Status.FAILURE) - .exception(e) - .build(); - } - } - - @Override - public List publish(Post post) { - OAuth1Credentials credentials = oauth1CredentialsRepository.getCredentials(name) - .orElseThrow(() -> new IllegalArgumentException("The credentials for " + name + " doesn't exist")); - - try { - if (areNotCredentialsReady(twitter)) { - setCredentials(twitter, credentials); - } - - Status statuses = twitter.updateStatus(post.basicFormat()); - - if (Objects.nonNull(statuses)) { - return List.of(Publication.builder() - .id(String.valueOf(statuses.getId())) - .status(Publication.Status.SUCCESS) - .publisher(name) - .publishedDate(LocalDateTime.now(clock)) - .build()); - } else { - return List.of(Publication.builder() - .status(Publication.Status.FAILURE) - .publisher(name) - .publishedDate(LocalDateTime.now(clock)) - .build()); - } - } catch (TwitterException e) { - log.error("Error publishing to " + name, e); - - return List.of(Publication.builder() - .status(Publication.Status.FAILURE) - .publisher(name) - .publishedDate(LocalDateTime.now(clock)) - .build()); - } - } - - private void setCredentials(Twitter twitter, OAuth1Credentials credentials) { - AccessToken accessToken = new AccessToken(credentials.getAccessToken(), credentials.getTokenSecret()); - twitter.setOAuthConsumer(credentials.getConsumerKey(), credentials.getConsumerSecret()); - twitter.setOAuthAccessToken(accessToken); - } - - private boolean areNotCredentialsReady(Twitter twitter) throws TwitterException { - return twitter.getAuthorization() == NullAuthorization.getInstance(); - } -} diff --git a/spring-publisher/src/main/resources/application.yml b/spring-publisher/src/main/resources/application.yml deleted file mode 100644 index 139597f..0000000 --- a/spring-publisher/src/main/resources/application.yml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/spring-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/PostPublisherTest.java b/spring-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/PostPublisherTest.java deleted file mode 100644 index 01e1c46..0000000 --- a/spring-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/post/PostPublisherTest.java +++ /dev/null @@ -1,195 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.post; - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.PostRepository; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Acknowledge; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Publication; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.SocialMediaPublisher; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.net.MalformedURLException; -import java.net.URI; -import java.time.Clock; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.List; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class PostPublisherTest { - @Mock - private SocialMediaPublisher socialMediaPublisher1; - @Mock - private SocialMediaPublisher socialMediaPublisher2; - @Mock - private PostRepository postRepository; - private PostPublisher postPublisher; - - @BeforeEach - public void before() { - this.postPublisher = new PostPublisher(List.of(socialMediaPublisher1, socialMediaPublisher2), postRepository, Clock - .fixed(ZonedDateTime.of(2020, 3, 3, 5, 6, 8, 1, ZoneId.of("UTC")).toInstant(), ZoneId.of("UTC"))); - } - - @Test - public void publishNext_pingFailed_exception() { - when(socialMediaPublisher1.ping()).thenReturn(Acknowledge.builder() - .status(Acknowledge.Status.SUCCESS).build()); - when(socialMediaPublisher2.ping()).thenReturn(Acknowledge.builder() - .status(Acknowledge.Status.FAILURE).build()); - when(socialMediaPublisher2.getName()).thenReturn("LINKEDIN"); - - IllegalStateException exception = assertThrows(IllegalStateException.class, () -> postPublisher.publishNext("group1")); - - assertThat(exception.getMessage()).isEqualTo("Ping to LINKEDIN failed: " + Acknowledge.builder() - .status(Acknowledge.Status.FAILURE).build()); - } - - @Test - public void publishNext_noPost_exception() { - when(postRepository.getNextToPublish("group1")).thenReturn(Optional.empty()); - when(socialMediaPublisher1.ping()).thenReturn(Acknowledge.builder() - .status(Acknowledge.Status.SUCCESS).build()); - when(socialMediaPublisher2.ping()).thenReturn(Acknowledge.builder() - .status(Acknowledge.Status.SUCCESS).build()); - - IllegalStateException exception = assertThrows(IllegalStateException.class, () -> postPublisher.publishNext("group1")); - - assertThat(exception.getMessage()).isEqualTo("There is not next post to publish"); - } - - @Test - public void publishNext_publishFailed_exception() throws MalformedURLException { - Post post = Post.builder() - .id("1") - .description("Post 1") - .name("Post") - .publishedDate(LocalDateTime.of(2020, 2, 3, 5, 6, 8, 1)) - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://myblog/post").toURL()) - .group("group1") - .build(); - - Post expectedPost = Post.builder() - .id("1") - .description("Post 1") - .name("Post") - .publishedDate(LocalDateTime.of(2020, 2, 3, 5, 6, 8, 1)) - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://myblog/post").toURL()) - .publications(List - .of(Publication.builder() - .id("id") - .publishedDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .status(Publication.Status.SUCCESS) - .publisher("TWITTER") - .build(), - Publication.builder() - .publishedDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .status(Publication.Status.FAILURE) - .publisher("LINKEDIN") - .build())) - .group("group1") - .build(); - - when(postRepository.getNextToPublish("group1")).thenReturn(Optional.of(post)); - when(socialMediaPublisher1.ping()).thenReturn(Acknowledge.builder() - .status(Acknowledge.Status.SUCCESS).build()); - when(socialMediaPublisher1.publish(post)).thenReturn(List.of(Publication.builder() - .id("id") - .publishedDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .status(Publication.Status.SUCCESS) - .publisher("TWITTER") - .build())); - when(socialMediaPublisher2.ping()).thenReturn(Acknowledge.builder() - .status(Acknowledge.Status.SUCCESS).build()); - when(socialMediaPublisher2.publish(post)).thenReturn(List.of(Publication.builder() - .publishedDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .status(Publication.Status.FAILURE) - .publisher("LINKEDIN") - .build())); - - IllegalStateException exception = assertThrows(IllegalStateException.class, () -> postPublisher.publishNext("group1")); - - assertThat(exception.getMessage()).isEqualTo("Error publishing the post: " + expectedPost); - } - - @Test - public void publishNext_publishSuccess_publications() throws MalformedURLException { - Post post = Post.builder() - .id("1") - .description("Post 1") - .name("Post") - .publishedDate(LocalDateTime.of(2020, 2, 3, 5, 6, 8, 1)) - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://myblog/post").toURL()) - .group("group1") - .build(); - - Post postUpdated = Post.builder() - .id("1") - .description("Post 1") - .name("Post") - .publishedDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://myblog/post").toURL()) - .group("group1") - .build(); - - Post expectedPost = Post.builder() - .id("1") - .description("Post 1") - .name("Post") - .publishedDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://myblog/post").toURL()) - .group("group1") - .publications(List - .of(Publication.builder() - .id("id") - .publishedDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .status(Publication.Status.SUCCESS) - .publisher("TWITTER") - .build(), - Publication.builder() - .id("id") - .publishedDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .status(Publication.Status.SUCCESS) - .publisher("LINKEDIN") - .build())) - .build(); - - when(postRepository.getNextToPublish("group1")).thenReturn(Optional.of(post)); - when(socialMediaPublisher1.ping()).thenReturn(Acknowledge.builder() - .status(Acknowledge.Status.SUCCESS).build()); - when(socialMediaPublisher1.publish(post)).thenReturn(List.of(Publication.builder() - .id("id") - .publishedDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .status(Publication.Status.SUCCESS) - .publisher("TWITTER") - .build())); - when(socialMediaPublisher2.ping()).thenReturn(Acknowledge.builder() - .status(Acknowledge.Status.SUCCESS).build()); - when(socialMediaPublisher2.publish(post)).thenReturn(List.of(Publication.builder() - .id("id") - .publishedDate(LocalDateTime.of(2020, 3, 3, 5, 6, 8, 1)) - .status(Publication.Status.SUCCESS) - .publisher("LINKEDIN") - .build())); - when(postRepository.update(postUpdated)).thenReturn(postUpdated); - - Post publishedPost = postPublisher.publishNext("group1"); - - assertThat(publishedPost).isEqualTo(expectedPost); - } - -} \ No newline at end of file diff --git a/spring-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsManagerTest.java b/spring-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsManagerTest.java deleted file mode 100644 index c1b728e..0000000 --- a/spring-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/abstraction/security/OAuth2CredentialsManagerTest.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.abstraction.security; - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2Credentials; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2CredentialsRepository; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; -import org.springframework.security.oauth2.core.OAuth2AccessToken; - -import java.time.LocalDateTime; -import java.time.ZoneOffset; -import java.util.Map; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class OAuth2CredentialsManagerTest { - @Mock - private OAuth2CredentialsRepository oAuth2CredentialsRepository; - @Mock - private OAuth2AuthorizedClient oAuth2AuthorizedClient; - - private OAuth2CredentialsManager oAuth2CredentialsManager; - - @BeforeEach - public void before() { - oAuth2CredentialsManager = new OAuth2CredentialsManager(oAuth2CredentialsRepository, Map.of("linkedin", "myuser")); - } - - @Test - public void update_notAllowed_unauthorized() { - when(oAuth2AuthorizedClient.getPrincipalName()).thenReturn("otheruser"); - - assertThrows(UnauthorizedException.class, () -> oAuth2CredentialsManager.update(oAuth2AuthorizedClient, "linkedin")); - } - - @Test - public void update_credentialsNotFound_created() { - when(oAuth2AuthorizedClient.getPrincipalName()).thenReturn("myuser"); - when(oAuth2CredentialsRepository.getCredentials("linkedin")).thenReturn(Optional.empty()); - when(oAuth2CredentialsRepository.save(OAuth2Credentials.builder() - .id("linkedin") - .build())).thenReturn(OAuth2Credentials.builder() - .id("linkedin") - .build()); - when(oAuth2AuthorizedClient.getAccessToken()).thenReturn(new OAuth2AccessToken( - OAuth2AccessToken.TokenType.BEARER, - "newAccessToken", - LocalDateTime.of(2020, 4, 3, 5, 6, 8, 1).toInstant(ZoneOffset.UTC), - LocalDateTime.of(2020, 6, 3, 5, 6, 8, 1).toInstant(ZoneOffset.UTC))); - when(oAuth2CredentialsRepository.update(OAuth2Credentials.builder() - .id("linkedin") - .accessToken("newAccessToken") - .expirationDate(LocalDateTime.of(2020, 6, 3, 5, 6, 8, 1)) - .build())).thenReturn(OAuth2Credentials.builder() - .id("linkedin") - .accessToken("newAccessToken") - .expirationDate(LocalDateTime.of(2020, 6, 3, 5, 6, 8, 1)) - .build()); - - OAuth2Credentials credentials = oAuth2CredentialsManager.update(oAuth2AuthorizedClient, "linkedin"); - - assertThat(credentials).isEqualTo(OAuth2Credentials.builder() - .id("linkedin") - .accessToken("newAccessToken") - .expirationDate(LocalDateTime.of(2020, 6, 3, 5, 6, 8, 1)) - .build()); - } - - @Test - public void update_credentialsFound_updated() { - when(oAuth2AuthorizedClient.getPrincipalName()).thenReturn("myuser"); - when(oAuth2CredentialsRepository.getCredentials("linkedin")).thenReturn(Optional.of(OAuth2Credentials.builder() - .id("linkedin") - .accessToken("previousAccessToken") - .expirationDate(LocalDateTime.of(2020, 2, 3, 5, 6, 8, 1)) - .build())); - when(oAuth2AuthorizedClient.getAccessToken()).thenReturn(new OAuth2AccessToken( - OAuth2AccessToken.TokenType.BEARER, - "newAccessToken", - LocalDateTime.of(2020, 4, 3, 5, 6, 8, 1).toInstant(ZoneOffset.UTC), - LocalDateTime.of(2020, 6, 3, 5, 6, 8, 1).toInstant(ZoneOffset.UTC))); - when(oAuth2CredentialsRepository.update(OAuth2Credentials.builder() - .id("linkedin") - .accessToken("newAccessToken") - .expirationDate(LocalDateTime.of(2020, 6, 3, 5, 6, 8, 1)) - .build())).thenReturn(OAuth2Credentials.builder() - .id("linkedin") - .accessToken("newAccessToken") - .expirationDate(LocalDateTime.of(2020, 6, 3, 5, 6, 8, 1)) - .build()); - - OAuth2Credentials credentials = oAuth2CredentialsManager.update(oAuth2AuthorizedClient, "linkedin"); - - assertThat(credentials).isEqualTo(OAuth2Credentials.builder() - .id("linkedin") - .accessToken("newAccessToken") - .expirationDate(LocalDateTime.of(2020, 6, 3, 5, 6, 8, 1)) - .build()); - } -} \ No newline at end of file diff --git a/spring-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/PostsControllerTest.java b/spring-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/PostsControllerTest.java deleted file mode 100644 index d96ab8c..0000000 --- a/spring-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/controller/PostsControllerTest.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.controller; - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.PostPublisher; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Publication; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.net.MalformedURLException; -import java.net.URI; -import java.time.LocalDateTime; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class PostsControllerTest { - @Mock - private PostPublisher postPublisher; - @InjectMocks - private PostsController postsController; - - @Test - public void ping() { - Map result = postsController.ping(); - - Map pong = new HashMap<>(); - pong.put("pong", "Hello, World!"); - - assertThat(result).isEqualTo(pong); - } - - @Test - public void postNext() throws MalformedURLException { - when(postPublisher.publishNext("group1")).thenReturn(Post.builder() - .id("2") - .name("My Post 2") - .description("My second post") - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) - .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) - .publications(List.of(Publication.builder() - .status(Publication.Status.FAILURE) - .publisher("twitter") - .build())) - .group("group1") - .build()); - - Post result = postsController.postNext("group1"); - - assertThat(result).isEqualTo(Post.builder() - .id("2") - .name("My Post 2") - .description("My second post") - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) - .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) - .publications(List.of(Publication.builder() - .status(Publication.Status.FAILURE) - .publisher("twitter") - .build())) - .group("group1") - .build()); - } - -} \ No newline at end of file diff --git a/spring-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInPublisherTest.java b/spring-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInPublisherTest.java deleted file mode 100644 index 962d522..0000000 --- a/spring-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/linkedin/LinkedInPublisherTest.java +++ /dev/null @@ -1,285 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.linkedin; - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Acknowledge; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Publication; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.UnauthorizedException; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2Credentials; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth2CredentialsRepository; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.UriTemplate; - -import java.net.MalformedURLException; -import java.net.URI; -import java.time.Clock; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.List; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class LinkedInPublisherTest { - @Mock - private OAuth2CredentialsRepository oauth2CredentialsRepository; - @Mock - private RestTemplate restTemplate; - - private LinkedInPublisher linkedInPublisher; - private ZonedDateTime now; - - @BeforeEach - public void before() { - this.now = ZonedDateTime.of(2020, 3, 3, 5, 6, 8, 1, ZoneId.of("UTC")); - this.linkedInPublisher = new LinkedInPublisher("linkedin", oauth2CredentialsRepository, restTemplate, Clock - .fixed(now.toInstant(), ZoneId.of("UTC")), new UriTemplate("http://localhost:8080/oauth2/{social-media}/credentials")); - } - - @Test - public void ping_noCredential_exception() { - when(oauth2CredentialsRepository.findAll()).thenReturn(List.of()); - - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> linkedInPublisher.ping()); - - assertThat(exception.getMessage()).isEqualTo("The credentials for linkedin doesn't exist"); - } - - @Test - public void ping_expiredCredential_exception() { - when(oauth2CredentialsRepository.findAll()).thenReturn(List.of(OAuth2Credentials.builder() - .expirationDate(now.toLocalDateTime().minusMonths(1)) - .id("credential1") - .build())); - - UnauthorizedException exception = assertThrows(UnauthorizedException.class, () -> linkedInPublisher.ping()); - - assertThat(exception.getMessage()).isEqualTo("Unauthorized for linkedin credential1. Please login again here: http://localhost:8080/oauth2/linkedin/credentials"); - } - - @Test - public void ping_goodCredential_success() { - when(oauth2CredentialsRepository.findAll()).thenReturn(List.of(OAuth2Credentials.builder() - .expirationDate(now.toLocalDateTime().plusMonths(1)) - .build())); - - Acknowledge ack = linkedInPublisher.ping(); - - assertThat(ack).isEqualTo(Acknowledge.builder() - .status(Acknowledge.Status.SUCCESS) - .build()); - } - - @Test - public void publish_noProfile_publicationError() { - HttpHeaders httpHeadersProfile = new HttpHeaders(); - httpHeadersProfile.setContentType(MediaType.APPLICATION_JSON); - httpHeadersProfile.add("X-Restli-Protocol-Version", "2.0.0"); - httpHeadersProfile.setBearerAuth("accessToken"); - - HttpEntity requestEntityProfile = new HttpEntity<>(httpHeadersProfile); - - when(oauth2CredentialsRepository.findAll()).thenReturn(List.of(OAuth2Credentials.builder() - .accessToken("accessToken") - .allowedGroups(List.of("group1")) - .build())); - when(restTemplate.exchange("https://api.linkedin.com/v2/me", HttpMethod.GET, requestEntityProfile, Profile.class)) - .thenReturn(ResponseEntity.notFound().build()); - - List publication = linkedInPublisher.publish(Post.builder() - .group("group1") - .build()); - - assertThat(publication).isEqualTo(List.of(Publication.builder() - .status(Publication.Status.FAILURE) - .publisher("linkedin") - .publishedDate(now.toLocalDateTime()) - .build())); - } - - @Test - public void publish_noShare_publicationError() throws MalformedURLException { - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setContentType(MediaType.APPLICATION_JSON); - httpHeaders.add("X-Restli-Protocol-Version", "2.0.0"); - httpHeaders.setBearerAuth("accessToken"); - - when(oauth2CredentialsRepository.findAll()).thenReturn(List.of(OAuth2Credentials.builder() - .accessToken("accessToken") - .allowedGroups(List.of("group1")) - .build())); - - HttpEntity requestEntityProfile = new HttpEntity<>(httpHeaders); - - when(restTemplate.exchange("https://api.linkedin.com/v2/me", HttpMethod.GET, requestEntityProfile, Profile.class)) - .thenReturn(ResponseEntity.ok(Profile.builder() - .sub("memberid") - .build())); - - LinkedInShare linkedInShare = LinkedInShare.builder() - .author("urn:li:person:memberid") - .lifecycleState("PUBLISHED") - .commentary("commentary") - .distribution(Distribution.builder().feedDistribution("MAIN_FEED").build()) - .lifecycleState("PUBLISHED") - .content(Content.builder() - .article(ArticleContent.builder() - .description("My second post\n\n#tag1 #tag2") - .title("My second post") - .source("https://coderstower.com/2020/01/13/open-close-principle-by-example/") - .build()) - .build()) - .visibility("PUBLIC") - .build(); - - HttpEntity requestShare = new HttpEntity<>(linkedInShare, httpHeaders); - - when(restTemplate.exchange("https://api.linkedin.com/v2/ugcPosts", HttpMethod.POST, requestShare, Void.class)) - .thenReturn(ResponseEntity.badRequest().build()); - - List publication = linkedInPublisher.publish(Post.builder() - .id("2") - .name("My Post 2") - .description("My second post") - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) - .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) - .group("group1") - .build()); - - assertThat(publication).isEqualTo(List.of(Publication.builder() - .status(Publication.Status.FAILURE) - .publisher("linkedin") - .publishedDate(now.toLocalDateTime()) - .build())); - } - - @Test - public void publish_noShareId_publicationError() throws MalformedURLException { - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setContentType(MediaType.APPLICATION_JSON); - httpHeaders.add("X-Restli-Protocol-Version", "2.0.0"); - httpHeaders.setBearerAuth("accessToken"); - - when(oauth2CredentialsRepository.findAll()).thenReturn(List.of(OAuth2Credentials.builder() - .accessToken("accessToken") - .allowedGroups(List.of("group1")) - .build())); - - HttpEntity requestEntityProfile = new HttpEntity<>(httpHeaders); - - when(restTemplate.exchange("https://api.linkedin.com/v2/me", HttpMethod.GET, requestEntityProfile, Profile.class)) - .thenReturn(ResponseEntity.ok(Profile.builder() - .sub("memberid") - .build())); - - LinkedInShare linkedInShare = LinkedInShare.builder() - .author("urn:li:person:memberid") - .lifecycleState("PUBLISHED") - .commentary("commentary") - .distribution(Distribution.builder().feedDistribution("MAIN_FEED").build()) - .lifecycleState("PUBLISHED") - .content(Content.builder() - .article(ArticleContent.builder() - .description("My second post\n\n#tag1 #tag2") - .title("My second post") - .source("https://coderstower.com/2020/01/13/open-close-principle-by-example/") - .build()) - .build()) - .visibility("PUBLIC") - .build(); - - HttpEntity requestShare = new HttpEntity<>(linkedInShare, httpHeaders); - - when(restTemplate.exchange("https://api.linkedin.com/v2/ugcPosts", HttpMethod.POST, requestShare, Void.class)) - .thenReturn(ResponseEntity.ok().build()); - - List publication = linkedInPublisher.publish(Post.builder() - .id("2") - .name("My Post 2") - .description("My second post") - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) - .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) - .group("group1") - .build()); - - assertThat(publication).isEqualTo(List.of(Publication.builder() - .status(Publication.Status.FAILURE) - .publisher("linkedin") - .publishedDate(now.toLocalDateTime()) - .build())); - } - - @Test - public void publish_shareId_publicationSucced() throws MalformedURLException { - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setContentType(MediaType.APPLICATION_JSON); - httpHeaders.add("X-Restli-Protocol-Version", "2.0.0"); - httpHeaders.add("LinkedIn-Version", "202304"); - httpHeaders.setBearerAuth("accessToken"); - - when(oauth2CredentialsRepository.findAll()).thenReturn(List.of(OAuth2Credentials.builder() - .accessToken("accessToken") - .allowedGroups(List.of("group1")) - .build())); - - HttpEntity requestEntityProfile = new HttpEntity<>(httpHeaders); - - when(restTemplate.exchange("https://api.linkedin.com/v2/userinfo", HttpMethod.GET, requestEntityProfile, Profile.class)) - .thenReturn(ResponseEntity.ok(Profile.builder() - .sub("memberid") - .build())); - - LinkedInShare linkedInShare = LinkedInShare.builder() - .author("urn:li:person:memberid") - .lifecycleState("PUBLISHED") - .commentary("My second post\n\n#tag1 #tag2") - .distribution(Distribution.builder().feedDistribution("MAIN_FEED").build()) - .lifecycleState("PUBLISHED") - .content(Content.builder() - .article(ArticleContent.builder() - .description("My second post") - .title("My Post 2") - .source("https://coderstower.com/2020/01/13/open-close-principle-by-example/") - .build()) - .build()) - .visibility("PUBLIC") - .build(); - - HttpEntity requestShare = new HttpEntity<>(linkedInShare, httpHeaders); - - when(restTemplate.exchange("https://api.linkedin.com/rest/posts", HttpMethod.POST, requestShare, Void.class)) - .thenReturn(ResponseEntity.ok().header("X-RestLi-Id", "shareId").build()); - - List publication = linkedInPublisher.publish(Post.builder() - .id("2") - .name("My Post 2") - .description("My second post") - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) - .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) - .group("group1") - .build()); - - assertThat(publication).isEqualTo(List.of(Publication.builder() - .id("shareId") - .status(Publication.Status.SUCCESS) - .publisher("linkedin") - .publishedDate(now.toLocalDateTime()) - .build())); - } -} \ No newline at end of file diff --git a/spring-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/twitter/TwitterPublisherTest.java b/spring-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/twitter/TwitterPublisherTest.java deleted file mode 100644 index a968230..0000000 --- a/spring-publisher/src/test/java/com/coderstower/socialmediapubisher/springpublisher/main/socialmedia/twitter/TwitterPublisherTest.java +++ /dev/null @@ -1,221 +0,0 @@ -package com.coderstower.socialmediapubisher.springpublisher.main.socialmedia.twitter; - -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.repository.Post; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Acknowledge; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.post.socialmedia.Publication; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth1Credentials; -import com.coderstower.socialmediapubisher.springpublisher.abstraction.security.repository.OAuth1CredentialsRepository; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import twitter4j.Paging; -import twitter4j.ResponseList; -import twitter4j.Status; -import twitter4j.Twitter; -import twitter4j.TwitterException; -import twitter4j.auth.AccessToken; -import twitter4j.auth.Authorization; -import twitter4j.auth.NullAuthorization; - -import java.net.MalformedURLException; -import java.net.URI; -import java.time.Clock; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.List; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -class TwitterPublisherTest { - @Mock - private OAuth1CredentialsRepository oauth1CredentialsRepository; - @Mock - private Twitter twitter; - private TwitterPublisher twitterPublisher; - private ZonedDateTime now; - - @BeforeEach - public void before() { - this.now = ZonedDateTime.of(2020, 3, 3, 5, 6, 8, 1, ZoneId.of("UTC")); - this.twitterPublisher = new TwitterPublisher("twitter", oauth1CredentialsRepository, twitter, Clock - .fixed(now.toInstant(), ZoneId.of("UTC"))); - } - - @Test - public void ping_noCredential_exception() { - when(oauth1CredentialsRepository.getCredentials("twitter")).thenReturn(Optional.empty()); - - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> twitterPublisher.ping()); - - assertThat(exception.getMessage()).isEqualTo("The credentials for twitter doesn't exist"); - } - - @Test - public void ping_credentialNoSetTwitterError_ackFailure() throws TwitterException { - when(oauth1CredentialsRepository.getCredentials("twitter")).thenReturn(Optional.of(OAuth1Credentials.builder() - .accessToken("accessToken") - .consumerKey("consumerKey") - .consumerSecret("consumerSecret") - .tokenSecret("tokenSecret") - .build())); - when(twitter.getAuthorization()).thenReturn(NullAuthorization.getInstance()); - when(twitter.getHomeTimeline(new Paging(1, 1))).thenThrow(new TwitterException("error")); - - Acknowledge ack = twitterPublisher.ping(); - - assertThat(ack).isEqualTo(Acknowledge.builder() - .status(Acknowledge.Status.FAILURE) - .description("Ping error") - .exception(new TwitterException("error")) - .build()); - verify(twitter).setOAuthConsumer("consumerKey", "consumerSecret"); - verify(twitter).setOAuthAccessToken(new AccessToken("accessToken", "tokenSecret")); - } - - @Test - public void ping_credentialSetEmptyStatus_ackFailure() throws TwitterException { - when(oauth1CredentialsRepository.getCredentials("twitter")).thenReturn(Optional.of(OAuth1Credentials.builder() - .accessToken("accessToken") - .consumerKey("consumerKey") - .consumerSecret("consumerSecret") - .tokenSecret("tokenSecret") - .build())); - when(twitter.getAuthorization()).thenReturn(mock(Authorization.class)); - when(twitter.getHomeTimeline(new Paging(1, 1))).thenReturn(null); - - Acknowledge ack = twitterPublisher.ping(); - - assertThat(ack).isEqualTo(Acknowledge.builder() - .status(Acknowledge.Status.FAILURE) - .build()); - verify(twitter, times(0)).setOAuthConsumer("consumerKey", "consumerSecret"); - verify(twitter, times(0)).setOAuthAccessToken(new AccessToken("accessToken", "tokenSecret")); - } - - @Test - public void ping_credentialSetStatus_ackSucced() throws TwitterException { - when(oauth1CredentialsRepository.getCredentials("twitter")).thenReturn(Optional.of(OAuth1Credentials.builder() - .accessToken("accessToken") - .consumerKey("consumerKey") - .consumerSecret("consumerSecret") - .tokenSecret("tokenSecret") - .build())); - when(twitter.getAuthorization()).thenReturn(mock(Authorization.class)); - when(twitter.getHomeTimeline(new Paging(1, 1))).thenReturn(mock(ResponseList.class)); - - Acknowledge ack = twitterPublisher.ping(); - - assertThat(ack).isEqualTo(Acknowledge.builder() - .status(Acknowledge.Status.SUCCESS) - .build()); - verify(twitter, times(0)).setOAuthConsumer("consumerKey", "consumerSecret"); - verify(twitter, times(0)).setOAuthAccessToken(new AccessToken("accessToken", "tokenSecret")); - } - - @Test - public void publish_noCredential_exception() { - when(oauth1CredentialsRepository.getCredentials("twitter")).thenReturn(Optional.empty()); - - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> twitterPublisher.publish(Post.builder().build())); - - assertThat(exception.getMessage()).isEqualTo("The credentials for twitter doesn't exist"); - } - - @Test - public void publish_credentialNoSetTwitterError_shareFailure() throws TwitterException, MalformedURLException { - when(oauth1CredentialsRepository.getCredentials("twitter")).thenReturn(Optional.of(OAuth1Credentials.builder() - .accessToken("accessToken") - .consumerKey("consumerKey") - .consumerSecret("consumerSecret") - .tokenSecret("tokenSecret") - .build())); - when(twitter.getAuthorization()).thenReturn(NullAuthorization.getInstance()); - when(twitter.updateStatus("My second post\n\n#tag1 #tag2\n\nhttps://coderstower.com/2020/01/13/open-close-principle-by-example/")).thenThrow(new TwitterException("error")); - - List publish = twitterPublisher.publish(Post.builder() - .id("2") - .name("My Post 2") - .description("My second post") - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) - .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) - .build()); - - assertThat(publish).isEqualTo(List.of(Publication.builder() - .status(Publication.Status.FAILURE) - .publisher("twitter") - .publishedDate(now.toLocalDateTime()) - .build())); - verify(twitter).setOAuthConsumer("consumerKey", "consumerSecret"); - verify(twitter).setOAuthAccessToken(new AccessToken("accessToken", "tokenSecret")); - } - - @Test - public void publish_credentialSetEmptyStatus_shareFailure() throws TwitterException, MalformedURLException { - when(oauth1CredentialsRepository.getCredentials("twitter")).thenReturn(Optional.of(OAuth1Credentials.builder() - .accessToken("accessToken") - .consumerKey("consumerKey") - .consumerSecret("consumerSecret") - .tokenSecret("tokenSecret") - .build())); - when(twitter.getAuthorization()).thenReturn(mock(Authorization.class)); - when(twitter.updateStatus("My second post\n\n#tag1 #tag2\n\nhttps://coderstower.com/2020/01/13/open-close-principle-by-example/")).thenReturn(null); - - List publish = twitterPublisher.publish(Post.builder() - .id("2") - .name("My Post 2") - .description("My second post") - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) - .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) - .build()); - - assertThat(publish).isEqualTo(List.of(Publication.builder() - .status(Publication.Status.FAILURE) - .publisher("twitter") - .publishedDate(now.toLocalDateTime()) - .build())); - verify(twitter, times(0)).setOAuthConsumer("consumerKey", "consumerSecret"); - verify(twitter, times(0)).setOAuthAccessToken(new AccessToken("accessToken", "tokenSecret")); - } - - @Test - public void publish_credentialSetStatus_ackSucced() throws TwitterException, MalformedURLException { - when(oauth1CredentialsRepository.getCredentials("twitter")).thenReturn(Optional.of(OAuth1Credentials.builder() - .accessToken("accessToken") - .consumerKey("consumerKey") - .consumerSecret("consumerSecret") - .tokenSecret("tokenSecret") - .build())); - when(twitter.getAuthorization()).thenReturn(mock(Authorization.class)); - - Status status = mock(Status.class); - when(status.getId()).thenReturn(123L); - when(twitter.updateStatus("My second post\n\n#tag1 #tag2\n\nhttps://coderstower.com/2020/01/13/open-close-principle-by-example/")).thenReturn(status); - - List publish = twitterPublisher.publish(Post.builder() - .id("2") - .name("My Post 2") - .description("My second post") - .tags(List.of("tag1", "tag2")) - .url(URI.create("https://coderstower.com/2020/01/13/open-close-principle-by-example/").toURL()) - .publishedDate(LocalDateTime.parse("2012-09-17T18:47:52")) - .build()); - - assertThat(publish).isEqualTo(List.of(Publication.builder() - .id("123") - .status(Publication.Status.SUCCESS) - .publisher("twitter") - .publishedDate(now.toLocalDateTime()) - .build())); - verify(twitter, times(0)).setOAuthConsumer("consumerKey", "consumerSecret"); - verify(twitter, times(0)).setOAuthAccessToken(new AccessToken("accessToken", "tokenSecret")); - } -} \ No newline at end of file diff --git a/spring-publisher/src/test/resources/application.yml b/spring-publisher/src/test/resources/application.yml deleted file mode 100644 index 38c8c5f..0000000 --- a/spring-publisher/src/test/resources/application.yml +++ /dev/null @@ -1,14 +0,0 @@ -spring: - datasource: - url: jdbc:h2:mem:testdb - driverClassName: org.h2.Driver - username: sa - password: password - platform: org.hibernate.dialect.H2Dialect - jpa: - hibernate: - ddl-auto: create - show-sql: true - properties: - hibernate: - format_sql: true \ No newline at end of file diff --git a/spring-publisher/src/test/resources/data.sql b/spring-publisher/src/test/resources/data.sql deleted file mode 100644 index caa2e18..0000000 --- a/spring-publisher/src/test/resources/data.sql +++ /dev/null @@ -1,4 +0,0 @@ -INSERT INTO post_entity (id, name, description, tags, url, published_date) VALUES ('1', 'My Post 1', 'My first post', 'tag1,tag2', 'https://coderstower.com/2020/02/18/unit-tests-vs-integration-tests/', '2013-09-17 18:47:52'); -INSERT INTO post_entity (id, name, description, tags, url, published_date) VALUES ('2', 'My Post 2', 'My second post', 'tag1,tag2', 'https://coderstower.com/2020/01/13/open-close-principle-by-example/', '2012-09-17 18:47:52'); - -INSERT INTO oauth1credential_entity (id, consumer_key, consumer_secret, access_token, token_secret) VALUES ('twitter', 'Consumer twitter', 'Consumer Secret twitter', 'Access Token twitter', 'Token Secret twitter'); \ No newline at end of file From 5d25ad2dfdc9ce72809c4227f15c916b1981cbc5 Mon Sep 17 00:00:00 2001 From: Daniel Pelaez Date: Mon, 6 May 2024 13:11:34 -0500 Subject: [PATCH 16/17] Adding zip packaging and read only profile --- build.gradle.kts | 11 ++++ .../aws/AWSSpringPublisherApplication.java | 60 ------------------- .../lambda/AWSSpringPublisherApplication.java | 25 ++++++++ .../aws/{ => lambda}/StreamLambdaHandler.java | 2 +- ...ingPublisherDynamoDBRepositoryFactory.java | 11 +++- .../post/ReadOnlyPostAWSRepository.java | 19 ++++++ 6 files changed, 66 insertions(+), 62 deletions(-) delete mode 100644 src/main/java/com/coderstower/socialmediapubisher/application/aws/AWSSpringPublisherApplication.java create mode 100644 src/main/java/com/coderstower/socialmediapubisher/application/aws/lambda/AWSSpringPublisherApplication.java rename src/main/java/com/coderstower/socialmediapubisher/application/aws/{ => lambda}/StreamLambdaHandler.java (95%) rename src/main/java/com/coderstower/socialmediapubisher/application/aws/{ => repository}/SpringPublisherDynamoDBRepositoryFactory.java (88%) create mode 100644 src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/post/ReadOnlyPostAWSRepository.java diff --git a/build.gradle.kts b/build.gradle.kts index befc95c..aa4a7c1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -74,3 +74,14 @@ java { tasks.withType { useJUnitPlatform() } + +tasks.register("zip", Zip::class) { + description = "Zip to deploy to lambda." + group = "build" + + from(sourceSets.main.get().output) + into("lib") { + from(configurations.runtimeClasspath) + } +} + diff --git a/src/main/java/com/coderstower/socialmediapubisher/application/aws/AWSSpringPublisherApplication.java b/src/main/java/com/coderstower/socialmediapubisher/application/aws/AWSSpringPublisherApplication.java deleted file mode 100644 index 89406f7..0000000 --- a/src/main/java/com/coderstower/socialmediapubisher/application/aws/AWSSpringPublisherApplication.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.coderstower.socialmediapubisher.application.aws; - -import com.coderstower.socialmediapubisher.application.controller.OAuth2CredentialsController; -import com.coderstower.socialmediapubisher.application.factory.SecurityFactory; -import com.coderstower.socialmediapubisher.application.controller.ErrorHandler; -import com.coderstower.socialmediapubisher.application.controller.PostsController; -import com.coderstower.socialmediapubisher.application.factory.SpringPublisherFactory; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; -import org.springframework.context.annotation.Import; - - -@SpringBootApplication -// We use direct @Import instead of @ComponentScan to speed up cold starts -// @ComponentScan(basePackages = "my.service.controller") -@Import({PostsController.class, OAuth2CredentialsController.class, SecurityFactory.class, - SpringPublisherFactory.class, SpringPublisherDynamoDBRepositoryFactory.class, ErrorHandler.class}) -public class AWSSpringPublisherApplication extends SpringBootServletInitializer { - - /* - * Create required HandlerMapping, to avoid several default HandlerMapping instances being created - - @Bean - public HandlerMapping handlerMapping() { - return new RequestMappingHandlerMapping(); - }*/ - - /* - * Create required HandlerAdapter, to avoid several default HandlerAdapter instances being created - - @Bean - public HandlerAdapter handlerAdapter() { - return new RequestMappingHandlerAdapter(); - }*/ - - /* - * optimization - avoids creating default exception resolvers; not required as the serverless container handles - * all exceptions - * - * By default, an ExceptionHandlerExceptionResolver is created which creates many dependent object, including - * an expensive ObjectMapper instance. - * - * To enable custom @ControllerAdvice classes remove this bean. - */ - /*@Bean - public HandlerExceptionResolver handlerExceptionResolver() { - return new HandlerExceptionResolver() { - - @Override - public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { - return null; - } - }; - }*/ - - public static void main(String[] args) { - SpringApplication.run(AWSSpringPublisherApplication.class, args); - } -} \ No newline at end of file diff --git a/src/main/java/com/coderstower/socialmediapubisher/application/aws/lambda/AWSSpringPublisherApplication.java b/src/main/java/com/coderstower/socialmediapubisher/application/aws/lambda/AWSSpringPublisherApplication.java new file mode 100644 index 0000000..925569d --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/application/aws/lambda/AWSSpringPublisherApplication.java @@ -0,0 +1,25 @@ +package com.coderstower.socialmediapubisher.application.aws.lambda; + +import com.coderstower.socialmediapubisher.application.aws.repository.SpringPublisherDynamoDBRepositoryFactory; +import com.coderstower.socialmediapubisher.application.controller.OAuth2CredentialsController; +import com.coderstower.socialmediapubisher.application.factory.SecurityFactory; +import com.coderstower.socialmediapubisher.application.controller.ErrorHandler; +import com.coderstower.socialmediapubisher.application.controller.PostsController; +import com.coderstower.socialmediapubisher.application.factory.SpringPublisherFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; +import org.springframework.context.annotation.Import; + + +@SpringBootApplication +// We use direct @Import instead of @ComponentScan to speed up cold starts +// @ComponentScan(basePackages = "my.service.controller") +@Import({PostsController.class, OAuth2CredentialsController.class, SecurityFactory.class, + SpringPublisherFactory.class, SpringPublisherDynamoDBRepositoryFactory.class, ErrorHandler.class}) +public class AWSSpringPublisherApplication extends SpringBootServletInitializer { + + public static void main(String[] args) { + SpringApplication.run(AWSSpringPublisherApplication.class, args); + } +} \ No newline at end of file diff --git a/src/main/java/com/coderstower/socialmediapubisher/application/aws/StreamLambdaHandler.java b/src/main/java/com/coderstower/socialmediapubisher/application/aws/lambda/StreamLambdaHandler.java similarity index 95% rename from src/main/java/com/coderstower/socialmediapubisher/application/aws/StreamLambdaHandler.java rename to src/main/java/com/coderstower/socialmediapubisher/application/aws/lambda/StreamLambdaHandler.java index 58bba3a..5f34f68 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/application/aws/StreamLambdaHandler.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/aws/lambda/StreamLambdaHandler.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.application.aws; +package com.coderstower.socialmediapubisher.application.aws.lambda; import com.amazonaws.serverless.exceptions.ContainerInitializationException; diff --git a/src/main/java/com/coderstower/socialmediapubisher/application/aws/SpringPublisherDynamoDBRepositoryFactory.java b/src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/SpringPublisherDynamoDBRepositoryFactory.java similarity index 88% rename from src/main/java/com/coderstower/socialmediapubisher/application/aws/SpringPublisherDynamoDBRepositoryFactory.java rename to src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/SpringPublisherDynamoDBRepositoryFactory.java index 064d5b5..d0a660f 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/application/aws/SpringPublisherDynamoDBRepositoryFactory.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/SpringPublisherDynamoDBRepositoryFactory.java @@ -1,4 +1,4 @@ -package com.coderstower.socialmediapubisher.application.aws; +package com.coderstower.socialmediapubisher.application.aws.repository; import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; @@ -12,6 +12,7 @@ import com.coderstower.socialmediapubisher.application.aws.repository.oauth2.OAuth2CredentialDynamoRepository; import com.coderstower.socialmediapubisher.application.aws.repository.post.PostAWSRepository; import com.coderstower.socialmediapubisher.application.aws.repository.post.PostDynamoRepository; +import com.coderstower.socialmediapubisher.application.aws.repository.post.ReadOnlyPostAWSRepository; import org.socialsignin.spring.data.dynamodb.repository.config.EnableDynamoDBRepositories; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; @@ -57,8 +58,16 @@ public OAuth2CredentialAWSRepository oauth2CredentialAWSRepository( } @Bean + @Profile("!read-only") public PostAWSRepository postAWSRepository( PostDynamoRepository postDynamoRepository) { return new PostAWSRepository(postDynamoRepository); } + + @Bean + @Profile("read-only") + public PostAWSRepository readOnlyPostAWSRepository( + PostDynamoRepository postDynamoRepository) { + return new ReadOnlyPostAWSRepository(postDynamoRepository); + } } diff --git a/src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/post/ReadOnlyPostAWSRepository.java b/src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/post/ReadOnlyPostAWSRepository.java new file mode 100644 index 0000000..2507145 --- /dev/null +++ b/src/main/java/com/coderstower/socialmediapubisher/application/aws/repository/post/ReadOnlyPostAWSRepository.java @@ -0,0 +1,19 @@ +package com.coderstower.socialmediapubisher.application.aws.repository.post; + +import com.coderstower.socialmediapubisher.domain.post.repository.Post; +import com.coderstower.socialmediapubisher.domain.post.repository.PostRepository; + +import java.util.Comparator; +import java.util.Optional; +import java.util.stream.StreamSupport; + +public class ReadOnlyPostAWSRepository extends PostAWSRepository { + public ReadOnlyPostAWSRepository(PostDynamoRepository postDynamoRepository) { + super(postDynamoRepository); + } + + @Override + public Post update(Post post) { + return post; + } +} From 08c90ec4ef207c61dc767275eeb3908df4342941 Mon Sep 17 00:00:00 2001 From: Daniel Pelaez Date: Sat, 18 May 2024 18:46:39 -0500 Subject: [PATCH 17/17] Adding zip packaging and read only profile --- src/assembly/bin.xml | 27 ------------------- .../linkedin/ReadOnlyNoPublishTest.java | 19 +++++++++++++ .../OAuth2CredentialsController.java | 2 ++ 3 files changed, 21 insertions(+), 27 deletions(-) delete mode 100644 src/assembly/bin.xml create mode 100644 src/itest/java/com/coderstower/socialmediapubisher/application/linkedin/ReadOnlyNoPublishTest.java diff --git a/src/assembly/bin.xml b/src/assembly/bin.xml deleted file mode 100644 index 1e08505..0000000 --- a/src/assembly/bin.xml +++ /dev/null @@ -1,27 +0,0 @@ - - lambda-package - - zip - - false - - - - ${project.build.directory}${file.separator}lib - lib - - tomcat-embed* - - - - - ${project.build.directory}${file.separator}classes - - ** - - ${file.separator} - - - \ No newline at end of file diff --git a/src/itest/java/com/coderstower/socialmediapubisher/application/linkedin/ReadOnlyNoPublishTest.java b/src/itest/java/com/coderstower/socialmediapubisher/application/linkedin/ReadOnlyNoPublishTest.java new file mode 100644 index 0000000..d5e7991 --- /dev/null +++ b/src/itest/java/com/coderstower/socialmediapubisher/application/linkedin/ReadOnlyNoPublishTest.java @@ -0,0 +1,19 @@ +package com.coderstower.socialmediapubisher.application.linkedin; + +import com.coderstower.socialmediapubisher.extesion.ITestExtension; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles("read-only") +@ExtendWith(ITestExtension.class) +public class ReadOnlyNoPublishTest { + + @Test + public void testContext() { + + } +} diff --git a/src/main/java/com/coderstower/socialmediapubisher/application/controller/OAuth2CredentialsController.java b/src/main/java/com/coderstower/socialmediapubisher/application/controller/OAuth2CredentialsController.java index a5ea9c1..880247f 100644 --- a/src/main/java/com/coderstower/socialmediapubisher/application/controller/OAuth2CredentialsController.java +++ b/src/main/java/com/coderstower/socialmediapubisher/application/controller/OAuth2CredentialsController.java @@ -3,6 +3,7 @@ import com.coderstower.socialmediapubisher.domain.security.OAuth2CredentialsManager; import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Profile; import org.springframework.http.ResponseEntity; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient; @@ -13,6 +14,7 @@ @RestController @Slf4j +@Profile("linkedin") public class OAuth2CredentialsController { private final OAuth2CredentialsManager oAuth2CredentialsManager;