├── .gitignore ├── .mvn └── wrapper │ ├── MavenWrapperDownloader.java │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── HELP.md ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── murray │ │ └── communications │ │ ├── CommunicationsApplication.java │ │ ├── adapters │ │ ├── rest │ │ │ ├── MessageAdapterService.java │ │ │ ├── convert │ │ │ │ ├── SmsDTOtoSmsMessage.java │ │ │ │ └── SmsMessageToDTO.java │ │ │ └── impl │ │ │ │ └── MessageAdapterServiceImpl.java │ │ └── web │ │ │ ├── SmsMessageAdapter.java │ │ │ ├── convert │ │ │ ├── NewSMSMessageDtoToRestSmsDto.java │ │ │ ├── SMSMessageDtoToRestSmsDto.java │ │ │ ├── SmsDtoToSMSMessageDto.java │ │ │ └── SmsMessageToSMSMessageDTo.java │ │ │ └── impl │ │ │ └── WebMessageAdapterImpl.java │ │ ├── config │ │ ├── SecurityRestConfig.java │ │ ├── SecurityWebConfig.java │ │ ├── ServiceConfig.java │ │ ├── SwaggerConfig.java │ │ └── ThymeleafWebConfig.java │ │ ├── controllers │ │ ├── advice │ │ │ └── CommunicationExceptionHandler.java │ │ ├── rest │ │ │ ├── SmsRestController.java │ │ │ ├── UserAuthenticationController.java │ │ │ └── UserAuthenticationRequest.java │ │ └── web │ │ │ ├── AdminSmsController.java │ │ │ ├── CustomErrorController.java │ │ │ └── MainController.java │ │ ├── domain │ │ ├── entities │ │ │ ├── AbstractAuditableEntity.java │ │ │ ├── AbstractPersistableEntity.java │ │ │ ├── messages │ │ │ │ ├── BandWidthCredentials.java │ │ │ │ └── SmsMessage.java │ │ │ └── users │ │ │ │ ├── ApplicationRole.java │ │ │ │ └── ApplicationUser.java │ │ └── respository │ │ │ ├── messages │ │ │ └── SmsMessageRepository.java │ │ │ └── users │ │ │ └── ApplicationUserRepository.java │ │ ├── dtos │ │ ├── enums │ │ │ └── MessageStatus.java │ │ ├── rest │ │ │ ├── SmsDto.java │ │ │ └── SmsSearchDto.java │ │ └── web │ │ │ ├── NewSMSMessageDto.java │ │ │ ├── SMSMessageDto.java │ │ │ └── SmsSearchResults.java │ │ ├── exceptions │ │ ├── BandWidthException.java │ │ ├── InvalidJwtAuthenticationException.java │ │ ├── MessageCreationException.java │ │ ├── MessageNotFoundException.java │ │ └── MessageProcessingException.java │ │ ├── security │ │ ├── AuthenticationFacade.java │ │ ├── impl │ │ │ ├── AuthenticationFacadeImpl.java │ │ │ └── CustomUserDetailsServiceImpl.java │ │ ├── jwt │ │ │ ├── JwtAuthenticationEntryPoint.java │ │ │ ├── JwtSecurityConfigurer.java │ │ │ ├── JwtTokenAuthenticationFilter.java │ │ │ ├── JwtTokenProviderService.java │ │ │ └── enums │ │ │ │ └── JwtTokenKey.java │ │ └── jwtaudit │ │ │ ├── AuditingCredentials.java │ │ │ ├── Credentials.java │ │ │ └── JwtClaimsToAuditingCredientalsResolver.java │ │ └── services │ │ ├── formatters │ │ ├── CustomDateFormatter.java │ │ └── impl │ │ │ └── CustomDateFormatterImpl.java │ │ ├── messages │ │ ├── Impl │ │ │ ├── BandwidthMessageSender.java │ │ │ └── MessageServiceImpl.java │ │ └── MessageService.java │ │ └── users │ │ ├── UserAuthenticatonService.java │ │ └── impl │ │ └── UserAuthenticationServiceImpl.java └── resources │ ├── application-h2.properties │ ├── application.properties │ ├── data.sql │ ├── messages_en.properties │ ├── static │ ├── css │ │ ├── bootstrap.css │ │ └── main.css │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ ├── img │ │ └── favgreen.png │ └── js │ │ ├── bootstrap.js │ │ └── bootstrap.min.js │ └── templates │ ├── error │ ├── access-denied.html │ └── error.html │ ├── fragments │ ├── Layout.html │ ├── footer.html │ └── header.html │ ├── index.html │ ├── login.html │ └── sms │ ├── edit-sms.html │ ├── new-sms.html │ └── overview.html └── test ├── java └── com │ └── murray │ └── communications │ ├── CommunicationsApplicationTests.java │ ├── config │ └── TestConfig.java │ ├── controllers │ └── SmsRestControllerITTest.java │ ├── domain │ ├── entities │ │ └── messages │ │ │ └── SmsMessageTest.java │ └── respository │ │ └── messages │ │ └── SmsMessageRepositoryTest.java │ └── services │ └── messages │ └── Impl │ └── MessageSenderTest.java └── resources └── test-data.sql /.gitignore: -------------------------------------------------------------------------------- 1 | # OS generated files # 2 | ###################### 3 | comms* 4 | target/ 5 | .DS_Store 6 | .DS_Store? 7 | ._* 8 | .Spotlight-V100 9 | .Trashes 10 | ehthumbs.db 11 | Thumbs.db 12 | spring-shell.log 13 | .idea/** 14 | .idea/artifacts/ 15 | .idea/axpm.iml 16 | .idea/compiler.xml 17 | .idea/encodings.xml 18 | .idea/libraries/ 19 | .idea/misc.xml 20 | .idea/modules.xml 21 | .idea/workspace.xml 22 | .gradle/** 23 | build/** 24 | gradle/** 25 | 26 | -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | https://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | import java.io.File; 21 | import java.io.FileInputStream; 22 | import java.io.FileOutputStream; 23 | import java.io.IOException; 24 | import java.net.URL; 25 | import java.nio.channels.Channels; 26 | import java.nio.channels.ReadableByteChannel; 27 | import java.util.Properties; 28 | 29 | public class MavenWrapperDownloader { 30 | 31 | /** 32 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 33 | */ 34 | private static final String DEFAULT_DOWNLOAD_URL = 35 | "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"; 36 | 37 | /** 38 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 39 | * use instead of the default one. 40 | */ 41 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 42 | ".mvn/wrapper/maven-wrapper.properties"; 43 | 44 | /** 45 | * Path where the maven-wrapper.jar will be saved to. 46 | */ 47 | private static final String MAVEN_WRAPPER_JAR_PATH = 48 | ".mvn/wrapper/maven-wrapper.jar"; 49 | 50 | /** 51 | * Name of the property which should be used to override the default download url for the wrapper. 52 | */ 53 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 54 | 55 | public static void main(String args[]) { 56 | System.out.println("- Downloader started"); 57 | File baseDirectory = new File(args[0]); 58 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 59 | 60 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 61 | // wrapperUrl parameter. 62 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 63 | String url = DEFAULT_DOWNLOAD_URL; 64 | if(mavenWrapperPropertyFile.exists()) { 65 | FileInputStream mavenWrapperPropertyFileInputStream = null; 66 | try { 67 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 68 | Properties mavenWrapperProperties = new Properties(); 69 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 70 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 71 | } catch (IOException e) { 72 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 73 | } finally { 74 | try { 75 | if(mavenWrapperPropertyFileInputStream != null) { 76 | mavenWrapperPropertyFileInputStream.close(); 77 | } 78 | } catch (IOException e) { 79 | // Ignore ... 80 | } 81 | } 82 | } 83 | System.out.println("- Downloading from: : " + url); 84 | 85 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 86 | if(!outputFile.getParentFile().exists()) { 87 | if(!outputFile.getParentFile().mkdirs()) { 88 | System.out.println( 89 | "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 90 | } 91 | } 92 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 93 | try { 94 | downloadFileFromURL(url, outputFile); 95 | System.out.println("Done"); 96 | System.exit(0); 97 | } catch (Throwable e) { 98 | System.out.println("- Error downloading"); 99 | e.printStackTrace(); 100 | System.exit(1); 101 | } 102 | } 103 | 104 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 105 | URL website = new URL(urlString); 106 | ReadableByteChannel rbc; 107 | rbc = Channels.newChannel(website.openStream()); 108 | FileOutputStream fos = new FileOutputStream(destination); 109 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 110 | fos.close(); 111 | rbc.close(); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garytxo/spring-web-and-rest-thymeleaf/0fa03044ce5e563f43b575b6dfa84e09f202bf0b/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip 2 | -------------------------------------------------------------------------------- /HELP.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ### Reference Documentation 4 | For further reference, please consider the following sections: 5 | 6 | * [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) 7 | * [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.1.8.RELEASE/maven-plugin/) 8 | * [Spring Web](https://docs.spring.io/spring-boot/docs/{bootVersion}/reference/htmlsingle/#boot-features-developing-web-applications) 9 | * [Spring Security](https://docs.spring.io/spring-boot/docs/{bootVersion}/reference/htmlsingle/#boot-features-security) 10 | * [Spring Data JPA](https://docs.spring.io/spring-boot/docs/{bootVersion}/reference/htmlsingle/#boot-features-jpa-and-spring-data) 11 | 12 | ### Guides 13 | The following guides illustrate how to use some features concretely: 14 | 15 | * [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) 16 | * [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/) 17 | * [Building REST services with Spring](https://spring.io/guides/tutorials/bookmarks/) 18 | * [Securing a Web Application](https://spring.io/guides/gs/securing-web/) 19 | * [Spring Boot and OAuth2](https://spring.io/guides/tutorials/spring-boot-oauth2/) 20 | * [Authenticating a User with LDAP](https://spring.io/guides/gs/authenticating-ldap/) 21 | * [Accessing Data with JPA](https://spring.io/guides/gs/accessing-data-jpa/) 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spring ThymeLeaf (boostrap) and REST API 2 | This is a sample of how one can expose a REST API to external users and also provide a light weight admin section 3 | using ThymeLeaf and boostrap 4 | 5 | ## Prerequisite 6 | In order to install and start the application please ensure that you have the following: 7 | - Java JDK (v8+) 8 | - Maven (v3+) 9 | - MySQL (optional MySQL Workbench) 10 | 11 | ## frameworks used 12 | - Spring boot 13 | - Spring security 14 | - JWT 15 | - ThymeLeaf and bootstrap 16 | 17 | 18 | ## Instructions 19 | 20 | 1. The code can be found in github repo [communications](https://github.com/garytxo/communications) 21 | 22 | 2. Clone repository locally using 23 | 24 | `git clone https://github.com/garytxo/communications` 25 | 26 | 3. Install dependencies and run test 27 | 28 | `mvn clean install` 29 | 30 | 4. Start your server using 31 | 32 | `mvn spring-boot:run` 33 | 34 | 5. To view the REST API endpoints check the swagger documentation [http://localhost:8080/swagger-ui.html](http://localhost:8080/swagger-ui.html) 35 | 36 | 6. Also you can [login](http://localhost:8080/login) into the admin section using `admin@admin.com` : `admin` 37 | 38 | 39 | ## Change H2 to Mysql database 40 | By default the application is configured to run against H2 database, in order to change to run against mysql please following the instructions 41 | below: 42 | 43 | 1. create the financial database schema by opening a terminal and executing the following command 44 | 45 | `mysql -uroot -Bse'CREATE DATABASE communications'` 46 | 47 | 2. Update the application-mysql.properties connection details 48 | 49 | 3. Change the active profile to ``mysql` in the application.properties 50 | 51 | `spring.profiles.active=mysql` 52 | 53 | 54 | ## using REST API 55 | There is a two step policy to use the REST API: 56 | 1. One needs to get a authorization token by calling : `curl -X POST "http://localhost:8080/auth/signin" -H "accept: */*" -H "Content-Type: application/json" -d "{ \"password\": \"admin\", \"username\": \"admin@admin.com\"}"` 57 | 58 | 2. Then using the token in the authoriation header when can perform REST calls such as a new message: 59 | `curl -X POST "http://localhost:8080/v1/sms" -H "accept: */*" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbkBhZG1pbi5jb20iLCJyb2xlcyI6WyJST0xFX1VTRVIiLCJST0xFX0FETUlOIl0sInVzZXJJZCI6MSwiYmFuZHdpZHRoSWQiOjEsImlhdCI6MTU3MDcyMDY5MiwiZXhwIjoxNTcwNzIwODcyfQ.nu4M6ghYD2ohx9KWGhfoD-8CEEzRKXeuNsTPABU5QdY" -H "Content-Type: application/json" -d "{ \"message\": \"Hello world\", \"receiver\": 4175409749, \"sendOn\": \"2019-10-13\", \"sender\": 18445014846}"` 60 | 61 | 62 | ## For further info check 63 | * [SpringBoot and JWT](https://github.com/hantsy/springboot-jwt-sample) 64 | * [Use ThymeLeaf for frontend](https://dimitr.im/consuming-rest-apis-with-spring) 65 | * [Bandwidth Application](https://dev.bandwidth.com/account/applications/about.html) 66 | * [SMS Callbacks](https://dev.bandwidth.com/messaging/callbacks/messageEvents.html) 67 | * [Bootstrap v3](https://getbootstrap.com/docs/3.3) 68 | * [Date selector](https://tempusdominus.github.io/bootstrap-3) 69 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Mingw, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | ########################################################################################## 204 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 205 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 206 | ########################################################################################## 207 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 208 | if [ "$MVNW_VERBOSE" = true ]; then 209 | echo "Found .mvn/wrapper/maven-wrapper.jar" 210 | fi 211 | else 212 | if [ "$MVNW_VERBOSE" = true ]; then 213 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 214 | fi 215 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" 216 | while IFS="=" read key value; do 217 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 218 | esac 219 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 220 | if [ "$MVNW_VERBOSE" = true ]; then 221 | echo "Downloading from: $jarUrl" 222 | fi 223 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 224 | 225 | if command -v wget > /dev/null; then 226 | if [ "$MVNW_VERBOSE" = true ]; then 227 | echo "Found wget ... using wget" 228 | fi 229 | wget "$jarUrl" -O "$wrapperJarPath" 230 | elif command -v curl > /dev/null; then 231 | if [ "$MVNW_VERBOSE" = true ]; then 232 | echo "Found curl ... using curl" 233 | fi 234 | curl -o "$wrapperJarPath" "$jarUrl" 235 | else 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Falling back to using Java to download" 238 | fi 239 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 240 | if [ -e "$javaClass" ]; then 241 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 242 | if [ "$MVNW_VERBOSE" = true ]; then 243 | echo " - Compiling MavenWrapperDownloader.java ..." 244 | fi 245 | # Compiling the Java class 246 | ("$JAVA_HOME/bin/javac" "$javaClass") 247 | fi 248 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 249 | # Running the downloader 250 | if [ "$MVNW_VERBOSE" = true ]; then 251 | echo " - Running MavenWrapperDownloader.java ..." 252 | fi 253 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 254 | fi 255 | fi 256 | fi 257 | fi 258 | ########################################################################################## 259 | # End of extension 260 | ########################################################################################## 261 | 262 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 263 | if [ "$MVNW_VERBOSE" = true ]; then 264 | echo $MAVEN_PROJECTBASEDIR 265 | fi 266 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 267 | 268 | # For Cygwin, switch paths to Windows format before running java 269 | if $cygwin; then 270 | [ -n "$M2_HOME" ] && 271 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 272 | [ -n "$JAVA_HOME" ] && 273 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 274 | [ -n "$CLASSPATH" ] && 275 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 276 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 277 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 278 | fi 279 | 280 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 281 | 282 | exec "$JAVACMD" \ 283 | $MAVEN_OPTS \ 284 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 285 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 286 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 287 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" 124 | FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( 125 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | echo Found %WRAPPER_JAR% 132 | ) else ( 133 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 134 | echo Downloading from: %DOWNLOAD_URL% 135 | powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" 136 | echo Finished downloading %WRAPPER_JAR% 137 | ) 138 | @REM End of extension 139 | 140 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 141 | if ERRORLEVEL 1 goto error 142 | goto end 143 | 144 | :error 145 | set ERROR_CODE=1 146 | 147 | :end 148 | @endlocal & set ERROR_CODE=%ERROR_CODE% 149 | 150 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 151 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 152 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 153 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 154 | :skipRcPost 155 | 156 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 157 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 158 | 159 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 160 | 161 | exit /B %ERROR_CODE% 162 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.1.8.RELEASE 9 | 10 | 11 | com.murray 12 | communications 13 | 0.0.1-SNAPSHOT 14 | communications 15 | Demo project for Spring Boot 16 | 17 | 18 | 1.8 19 | 2.12.7.1 20 | 2.9.2 21 | 2.4.1 22 | 0.9.1 23 | 3.0.4.RELEASE 24 | 4.2.2 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-data-jpa 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-security 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-web 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-thymeleaf 43 | 44 | 45 | org.thymeleaf.extras 46 | thymeleaf-extras-springsecurity5 47 | ${thymeleaf.extras.version} 48 | 49 | 50 | 51 | org.thymeleaf.extras 52 | thymeleaf-extras-java8time 53 | ${thymeleaf.extras.version} 54 | 55 | 56 | 57 | nz.net.ultraq.thymeleaf 58 | thymeleaf-layout-dialect 59 | ${thymeleaf.dialect.verion} 60 | 61 | 62 | 63 | com.fasterxml.jackson.core 64 | jackson-databind 65 | ${jackson.version} 66 | 67 | 68 | 69 | 70 | 71 | io.jsonwebtoken 72 | jjwt 73 | ${jjwt.verion} 74 | 75 | 76 | 77 | 78 | com.h2database 79 | h2 80 | runtime 81 | 2.2.220 82 | 83 | 84 | 85 | 86 | io.springfox 87 | springfox-swagger2 88 | ${swagger.verion} 89 | 90 | 91 | 92 | io.springfox 93 | springfox-swagger-ui 94 | ${swagger.verion} 95 | 96 | 97 | 98 | 99 | com.squareup.okhttp3 100 | okhttp 101 | ${httpok.version} 102 | 103 | 104 | 105 | 106 | org.projectlombok 107 | lombok 108 | true 109 | 110 | 111 | org.springframework.boot 112 | spring-boot-starter-test 113 | test 114 | 115 | 116 | org.springframework.security 117 | spring-security-test 118 | test 119 | 120 | 121 | io.rest-assured 122 | rest-assured 123 | test 124 | 125 | 126 | 127 | 128 | 129 | 130 | org.springframework.boot 131 | spring-boot-maven-plugin 132 | 133 | 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/CommunicationsApplication.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; 7 | 8 | @SpringBootApplication 9 | @EnableAutoConfiguration(exclude = {SecurityAutoConfiguration.class}) 10 | public class CommunicationsApplication { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(CommunicationsApplication.class, args); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/adapters/rest/MessageAdapterService.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.adapters.rest; 2 | 3 | import com.murray.communications.dtos.rest.SmsSearchDto; 4 | import com.murray.communications.security.jwtaudit.AuditingCredentials; 5 | import com.murray.communications.dtos.rest.SmsDto; 6 | 7 | import java.util.List; 8 | 9 | public interface MessageAdapterService { 10 | 11 | /** 12 | * Save a new @{@link SmsDto} 13 | * 14 | * @return {@link SmsDto} 15 | */ 16 | SmsDto save(AuditingCredentials credentials, SmsDto smsDto); 17 | 18 | /** 19 | * Find sms by id 20 | * 21 | * @param id 22 | * @return 23 | */ 24 | SmsDto find(Long id); 25 | 26 | /** 27 | * Update certain fields of a @{@link SmsDto} 28 | * 29 | * @return {@link SmsDto} 30 | */ 31 | SmsDto update(AuditingCredentials credentials, SmsDto smsDto); 32 | 33 | 34 | /** 35 | * Send SMS message to 36 | * @param credentials 37 | * @param id 38 | */ 39 | void sendSms(AuditingCredentials credentials , Long id); 40 | 41 | /** 42 | * Soft delete the sms message 43 | * 44 | * @param smsId 45 | */ 46 | void delete(AuditingCredentials credentials,Long smsId); 47 | 48 | 49 | /** 50 | * Search for sms's using the optional {@link SmsSearchDto} 51 | * 52 | * @param smsSearch {@link SmsSearchDto} 53 | * @return list {@link SmsDto} 54 | */ 55 | List findBy(SmsSearchDto smsSearch); 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/adapters/rest/convert/SmsDTOtoSmsMessage.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.adapters.rest.convert; 2 | 3 | import com.murray.communications.domain.entities.messages.SmsMessage; 4 | import com.murray.communications.dtos.rest.SmsDto; 5 | import org.springframework.core.convert.converter.Converter; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class SmsDTOtoSmsMessage implements Converter { 10 | 11 | @Override 12 | public SmsMessage convert(SmsDto dto) { 13 | SmsMessage smsMessage = new SmsMessage(); 14 | 15 | smsMessage.setId(dto.getId()); 16 | smsMessage.setStatus(dto.getStatus()); 17 | smsMessage.setSendDate(dto.getSendOn()); 18 | smsMessage.setReceiver(dto.getReceiver()); 19 | smsMessage.setSender(dto.getSender()); 20 | smsMessage.setMessage(dto.getMessage()); 21 | 22 | 23 | return smsMessage; 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/adapters/rest/convert/SmsMessageToDTO.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.adapters.rest.convert; 2 | 3 | import com.murray.communications.domain.entities.messages.SmsMessage; 4 | import com.murray.communications.dtos.rest.SmsDto; 5 | import org.springframework.core.convert.converter.Converter; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class SmsMessageToDTO implements Converter { 10 | 11 | @Override 12 | public SmsDto convert(SmsMessage smsMessage) { 13 | 14 | SmsDto dto = new SmsDto(); 15 | 16 | dto.setMessage(smsMessage.getMessage()); 17 | dto.setStatus(smsMessage.getStatus()); 18 | dto.setId(smsMessage.getId()); 19 | dto.setCreatedOn(smsMessage.getCreatedDate()); 20 | dto.setReceiver(smsMessage.getReceiver()); 21 | dto.setSender(smsMessage.getSender()); 22 | dto.setSendOn(smsMessage.getSendDate()); 23 | dto.setApplicationName(smsMessage.getBandWidthCredentials().getApplicationName()); 24 | return dto; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/adapters/rest/impl/MessageAdapterServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.adapters.rest.impl; 2 | 3 | import com.murray.communications.domain.entities.messages.SmsMessage; 4 | import com.murray.communications.dtos.rest.SmsSearchDto; 5 | import com.murray.communications.security.jwtaudit.AuditingCredentials; 6 | import com.murray.communications.services.messages.MessageService; 7 | import com.murray.communications.adapters.rest.MessageAdapterService; 8 | import com.murray.communications.dtos.rest.SmsDto; 9 | import com.murray.communications.exceptions.MessageCreationException; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.springframework.core.convert.ConversionService; 12 | import sun.reflect.generics.reflectiveObjects.NotImplementedException; 13 | 14 | import java.util.List; 15 | 16 | /** 17 | * {@inheritDoc} 18 | */ 19 | @Slf4j 20 | public class MessageAdapterServiceImpl implements MessageAdapterService { 21 | 22 | 23 | private final MessageService messageService; 24 | 25 | private final ConversionService conversionService; 26 | 27 | 28 | public MessageAdapterServiceImpl(MessageService messageService, ConversionService conversionService) { 29 | this.messageService = messageService; 30 | this.conversionService = conversionService; 31 | } 32 | 33 | /** 34 | * {@inheritDoc} 35 | */ 36 | @Override 37 | public SmsDto find(Long id) { 38 | SmsMessage message = messageService.findBy(id); 39 | return conversionService.convert(message, SmsDto.class); 40 | } 41 | 42 | /** 43 | * {@inheritDoc} 44 | */ 45 | @Override 46 | public SmsDto save(AuditingCredentials credentials, SmsDto smsDto) { 47 | 48 | try { 49 | 50 | log.info("Saving smsDto"); 51 | SmsMessage smsMessage = messageService.createSms(credentials, smsDto.getReceiver(), smsDto.getMessage(), smsDto.getSendOn()); 52 | return conversionService.convert(smsMessage, SmsDto.class); 53 | 54 | } catch (NullPointerException np) { 55 | 56 | throw new MessageCreationException("Parameter null", np); 57 | } 58 | 59 | } 60 | 61 | /** 62 | * {@inheritDoc} 63 | */ 64 | @Override 65 | public SmsDto update(AuditingCredentials credentials, SmsDto dto) { 66 | 67 | SmsMessage smsMessage = conversionService.convert(dto, SmsMessage.class); 68 | SmsMessage updated = messageService.update(credentials, smsMessage); 69 | return conversionService.convert(updated, SmsDto.class); 70 | } 71 | 72 | /** 73 | * {@inheritDoc} 74 | */ 75 | @Override 76 | public void delete(AuditingCredentials credentials, Long smsId) { 77 | messageService.deleteMessageBy(credentials, smsId); 78 | } 79 | 80 | /** 81 | * {@inheritDoc} 82 | */ 83 | @Override 84 | public List findBy(SmsSearchDto smsSearch) { 85 | throw new NotImplementedException(); 86 | } 87 | 88 | /** 89 | * {@inheritDoc} 90 | */ 91 | @Override 92 | public void sendSms(AuditingCredentials credentials, Long id) { 93 | 94 | messageService.sendSms(credentials, id); 95 | 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/adapters/web/SmsMessageAdapter.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.adapters.web; 2 | 3 | import com.murray.communications.dtos.web.NewSMSMessageDto; 4 | import com.murray.communications.dtos.web.SMSMessageDto; 5 | import com.murray.communications.dtos.web.SmsSearchResults; 6 | 7 | /** 8 | * Manages the Sms web calls that use the REST and SMS Service 9 | */ 10 | public interface SmsMessageAdapter { 11 | 12 | 13 | /** 14 | * find all message for a specific page 15 | * @param page actual page of results 16 | * @param size number of elements to returm 17 | * @return SmsSearchResults 18 | */ 19 | SmsSearchResults findAllMessages(Integer page, Integer size); 20 | 21 | /** 22 | * Calls the REST API to get the JWT token for the current user logged in 23 | * 24 | * @return 25 | */ 26 | String getTokenForLoggedUser(); 27 | 28 | 29 | /** 30 | * Save sms 31 | * 32 | * @param smsDto 33 | * @return 34 | */ 35 | SMSMessageDto saveSms(NewSMSMessageDto smsDto); 36 | 37 | 38 | /** 39 | * find the sms message by their unquie id 40 | * 41 | * @param id 42 | * @return SMSMessageDto 43 | */ 44 | SMSMessageDto findMessageBy(Long id); 45 | 46 | 47 | /** 48 | * Update the sms certain fields 49 | * 50 | * @param dto 51 | * @return 52 | */ 53 | SMSMessageDto update(SMSMessageDto dto); 54 | 55 | /** 56 | * Delete sms by id 57 | * @param id 58 | */ 59 | void deleteById(Long id); 60 | 61 | 62 | /** 63 | * Delete sms by id 64 | * @param id 65 | */ 66 | void sendMessage(Long id); 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/adapters/web/convert/NewSMSMessageDtoToRestSmsDto.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.adapters.web.convert; 2 | 3 | import com.murray.communications.services.formatters.CustomDateFormatter; 4 | import com.murray.communications.dtos.rest.SmsDto; 5 | import com.murray.communications.dtos.web.NewSMSMessageDto; 6 | import org.springframework.core.convert.converter.Converter; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.time.LocalDate; 10 | 11 | @Component 12 | public class NewSMSMessageDtoToRestSmsDto implements Converter { 13 | 14 | private final CustomDateFormatter formatter; 15 | 16 | public NewSMSMessageDtoToRestSmsDto(CustomDateFormatter formatter) { 17 | this.formatter = formatter; 18 | } 19 | 20 | @Override 21 | public SmsDto convert(NewSMSMessageDto newSMSMessage) { 22 | 23 | SmsDto dto = new SmsDto(); 24 | dto.setReceiver(newSMSMessage.getReceiver()); 25 | dto.setSender(newSMSMessage.getSender()); 26 | dto.setMessage(newSMSMessage.getMessage()); 27 | dto.setSendOn(LocalDate.parse(newSMSMessage.getSendOn(), formatter.localDateFormatter())); 28 | 29 | return dto; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/adapters/web/convert/SMSMessageDtoToRestSmsDto.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.adapters.web.convert; 2 | 3 | import com.murray.communications.dtos.enums.MessageStatus; 4 | import com.murray.communications.dtos.rest.SmsDto; 5 | import com.murray.communications.dtos.web.SMSMessageDto; 6 | import com.murray.communications.services.formatters.CustomDateFormatter; 7 | import org.springframework.core.convert.converter.Converter; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.time.LocalDate; 11 | 12 | @Component 13 | public class SMSMessageDtoToRestSmsDto implements Converter { 14 | 15 | private final CustomDateFormatter formatter; 16 | 17 | public SMSMessageDtoToRestSmsDto(CustomDateFormatter formatter) { 18 | this.formatter = formatter; 19 | } 20 | 21 | @Override 22 | public SmsDto convert(SMSMessageDto messageDto) { 23 | 24 | SmsDto dto = new SmsDto(); 25 | dto.setStatus(MessageStatus.valueOf(messageDto.getStatus())); 26 | dto.setSendOn(LocalDate.parse(messageDto.getSendOn(), formatter.localDateFormatter())); 27 | dto.setMessage(messageDto.getMessage()); 28 | dto.setSender(messageDto.getSender()); 29 | dto.setReceiver(messageDto.getReceiver()); 30 | dto.setId(messageDto.getId()); 31 | dto.setApplicationName(messageDto.getApplicationName()); 32 | 33 | return dto; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/adapters/web/convert/SmsDtoToSMSMessageDto.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.adapters.web.convert; 2 | 3 | import com.murray.communications.services.formatters.CustomDateFormatter; 4 | import com.murray.communications.dtos.rest.SmsDto; 5 | import com.murray.communications.dtos.web.SMSMessageDto; 6 | import org.springframework.core.convert.converter.Converter; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class SmsDtoToSMSMessageDto implements Converter { 11 | 12 | private final CustomDateFormatter formatter; 13 | 14 | public SmsDtoToSMSMessageDto(CustomDateFormatter formatter) { 15 | this.formatter = formatter; 16 | } 17 | 18 | @Override 19 | public SMSMessageDto convert(SmsDto restDto) { 20 | 21 | SMSMessageDto dto = new SMSMessageDto(); 22 | dto.setId(restDto.getId()); 23 | dto.setReceiver(restDto.getReceiver()); 24 | dto.setSender(restDto.getSender()); 25 | dto.setMessage(restDto.getMessage()); 26 | 27 | dto.setSendOn(restDto.getSendOn().format(formatter.localDateFormatter())); 28 | dto.setStatus(restDto.getStatus().name()); 29 | dto.setApplicationName(restDto.getApplicationName()); 30 | 31 | return dto; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/adapters/web/convert/SmsMessageToSMSMessageDTo.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.adapters.web.convert; 2 | 3 | import com.murray.communications.domain.entities.messages.SmsMessage; 4 | import com.murray.communications.dtos.web.SMSMessageDto; 5 | import com.murray.communications.services.formatters.CustomDateFormatter; 6 | import org.springframework.core.convert.converter.Converter; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class SmsMessageToSMSMessageDTo implements Converter { 11 | 12 | private final CustomDateFormatter formatter; 13 | 14 | public SmsMessageToSMSMessageDTo(CustomDateFormatter formatter) { 15 | 16 | this.formatter = formatter; 17 | } 18 | 19 | @Override 20 | public SMSMessageDto convert(SmsMessage smsMessage) { 21 | 22 | SMSMessageDto dto = new SMSMessageDto(); 23 | dto.setStatus(smsMessage.getStatus().name()); 24 | dto.setId(smsMessage.getId()); 25 | dto.setSendOn(smsMessage.getSendDate().format(formatter.localDateFormatter())); 26 | dto.setMessage(smsMessage.getMessage()); 27 | dto.setSender(smsMessage.getSender()); 28 | dto.setReceiver(smsMessage.getReceiver()); 29 | dto.setCreatedOn(smsMessage.getCreatedDate()); 30 | dto.setApplicationName(smsMessage.getBandWidthCredentials().getApplicationName()); 31 | 32 | return dto; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/adapters/web/impl/WebMessageAdapterImpl.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.adapters.web.impl; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.murray.communications.adapters.web.SmsMessageAdapter; 6 | import com.murray.communications.controllers.rest.UserAuthenticationRequest; 7 | import com.murray.communications.domain.entities.messages.SmsMessage; 8 | import com.murray.communications.domain.entities.users.ApplicationUser; 9 | import com.murray.communications.domain.respository.messages.SmsMessageRepository; 10 | import com.murray.communications.dtos.rest.SmsDto; 11 | import com.murray.communications.dtos.web.NewSMSMessageDto; 12 | import com.murray.communications.dtos.web.SMSMessageDto; 13 | import com.murray.communications.dtos.web.SmsSearchResults; 14 | import com.murray.communications.security.AuthenticationFacade; 15 | import lombok.extern.slf4j.Slf4j; 16 | import org.springframework.core.convert.ConversionService; 17 | import org.springframework.data.domain.Page; 18 | import org.springframework.data.domain.PageRequest; 19 | import org.springframework.data.domain.Pageable; 20 | import org.springframework.data.domain.Sort; 21 | import org.springframework.http.*; 22 | import org.springframework.transaction.annotation.Transactional; 23 | import org.springframework.web.client.RestTemplate; 24 | 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | 28 | import static com.murray.communications.security.jwt.enums.JwtTokenKey.REQUEST_HEADER_PREFIX; 29 | 30 | /** 31 | * {@inheritDoc} 32 | */ 33 | @Slf4j 34 | public class WebMessageAdapterImpl implements SmsMessageAdapter { 35 | 36 | private final RestTemplate restTemplate; 37 | 38 | 39 | private final AuthenticationFacade authenticationFacade; 40 | private final String baseUrl; 41 | private final ConversionService conversionService; 42 | private final SmsMessageRepository smsMessageRepository; 43 | 44 | public WebMessageAdapterImpl(AuthenticationFacade authenticationFacade, String baseUrl, ConversionService conversionService, SmsMessageRepository smsMessageRepository) { 45 | this.authenticationFacade = authenticationFacade; 46 | this.baseUrl = baseUrl; 47 | this.conversionService = conversionService; 48 | this.smsMessageRepository = smsMessageRepository; 49 | this.restTemplate = new RestTemplate(); 50 | } 51 | 52 | /** 53 | * {@inheritDoc} 54 | */ 55 | @Override 56 | @Transactional 57 | public SmsSearchResults findAllMessages(Integer page, Integer size) { 58 | 59 | Pageable sortedByMessageDescending = PageRequest.of(page, size, Sort.by("createdDate").descending()); 60 | Page pageResults = smsMessageRepository.findAllByDeletedIsFalse(sortedByMessageDescending); 61 | 62 | SmsSearchResults results = new SmsSearchResults(); 63 | results.setMessages(messageDtos(pageResults)); 64 | results.setPageNumber(pageResults.getNumber()); 65 | results.setTotalElements(pageResults.getTotalElements()); 66 | results.setTotalPages(pageResults.getTotalPages()); 67 | 68 | 69 | return results; 70 | } 71 | 72 | private List messageDtos(Page page) { 73 | 74 | List messageDtos = new ArrayList<>(); 75 | page.get().forEach( 76 | msg -> { 77 | messageDtos.add(conversionService.convert(msg, SMSMessageDto.class)); 78 | } 79 | ); 80 | return messageDtos; 81 | } 82 | 83 | /** 84 | * {@inheritDoc} 85 | */ 86 | @Override 87 | public String getTokenForLoggedUser() { 88 | 89 | ApplicationUser user = authenticationFacade.getAuthenticationUser(); 90 | HttpEntity userAuthenticationRequestHttpEntity = new HttpEntity<>(UserAuthenticationRequest.builder() 91 | .username(user.getUsername()).password(user.getPassword()).build(), httpHeaders()); 92 | 93 | ResponseEntity response = restTemplate 94 | .exchange(baseUrl.concat("/auth/signin"), 95 | HttpMethod.POST, userAuthenticationRequestHttpEntity, String.class); 96 | 97 | return response.getBody(); 98 | } 99 | 100 | /** 101 | * {@inheritDoc} 102 | */ 103 | @Override 104 | public SMSMessageDto saveSms(final NewSMSMessageDto newSMSMessageDto) { 105 | 106 | SmsDto smsDto = conversionService.convert(newSMSMessageDto, SmsDto.class); 107 | 108 | String token = getTokenForLoggedUser(); 109 | 110 | ObjectMapper objectMapper = new ObjectMapper(); 111 | 112 | 113 | try { 114 | String test = objectMapper.writeValueAsString(smsDto); 115 | log.info("jsont to send:{}",test); 116 | } catch (JsonProcessingException e) { 117 | e.printStackTrace(); 118 | } 119 | 120 | HttpHeaders httpHeaders = httpHeaders(token); 121 | HttpEntity smsDtoHttpEntity = getSmsEntity(smsDto, httpHeaders); 122 | 123 | ResponseEntity responseEntity = restTemplate.exchange( 124 | baseUrl.concat("/v1/sms"), 125 | HttpMethod.POST, smsDtoHttpEntity, SmsDto.class 126 | ); 127 | 128 | return conversionService.convert(responseEntity.getBody(), SMSMessageDto.class); 129 | } 130 | 131 | /** 132 | * {@inheritDoc} 133 | */ 134 | @Override 135 | public SMSMessageDto findMessageBy(Long id) { 136 | 137 | String token = getTokenForLoggedUser(); 138 | 139 | HttpHeaders httpHeaders = httpHeaders(token); 140 | HttpEntity smsDtoHttpEntity = getSmsEntity(new SmsDto(), httpHeaders); 141 | 142 | ResponseEntity responseEntity = restTemplate.exchange( 143 | baseUrl.concat("/v1/sms/" + id), 144 | HttpMethod.GET, smsDtoHttpEntity, SmsDto.class 145 | ); 146 | 147 | return conversionService.convert(responseEntity.getBody(), SMSMessageDto.class); 148 | 149 | } 150 | 151 | 152 | /** 153 | * {@inheritDoc} 154 | */ 155 | @Override 156 | public SMSMessageDto update(SMSMessageDto messageDto) { 157 | String token = getTokenForLoggedUser(); 158 | HttpHeaders httpHeaders = httpHeaders(token); 159 | SmsDto smsDto = conversionService.convert(messageDto, SmsDto.class); 160 | HttpEntity smsDtoHttpEntity = getSmsEntity(smsDto, httpHeaders); 161 | 162 | ResponseEntity responseEntity = restTemplate.exchange( 163 | baseUrl.concat("/v1/sms/" + messageDto.getId()), 164 | HttpMethod.PUT, smsDtoHttpEntity, SmsDto.class 165 | ); 166 | 167 | return conversionService.convert(responseEntity.getBody(), SMSMessageDto.class); 168 | } 169 | /** 170 | * {@inheritDoc} 171 | */ 172 | @Override 173 | public void deleteById(Long id) { 174 | 175 | String token = getTokenForLoggedUser(); 176 | HttpHeaders httpHeaders = httpHeaders(token); 177 | SmsDto smsDto = conversionService.convert(new SmsDto(), SmsDto.class); 178 | HttpEntity smsDtoHttpEntity = getSmsEntity(smsDto, httpHeaders); 179 | 180 | ResponseEntity responseEntity = restTemplate.exchange( 181 | baseUrl.concat("/v1/sms/" + id), 182 | HttpMethod.DELETE, smsDtoHttpEntity, SmsDto.class 183 | ); 184 | 185 | if(!responseEntity.getStatusCode().is2xxSuccessful()){ 186 | throw new IllegalArgumentException("Issue deleting sms"); 187 | } 188 | 189 | } 190 | /** 191 | * {@inheritDoc} 192 | */ 193 | @Override 194 | public void sendMessage(Long id) { 195 | 196 | String token = getTokenForLoggedUser(); 197 | HttpHeaders httpHeaders = httpHeaders(token); 198 | SmsDto smsDto = conversionService.convert(new SmsDto(), SmsDto.class); 199 | HttpEntity smsDtoHttpEntity = getSmsEntity(smsDto, httpHeaders); 200 | 201 | ResponseEntity responseEntity = restTemplate.exchange( 202 | baseUrl.concat("/v1/sms/").concat(Long.toString(id)).concat("/send"), 203 | HttpMethod.PUT, smsDtoHttpEntity, SmsDto.class 204 | ); 205 | 206 | if(!responseEntity.getStatusCode().is2xxSuccessful()){ 207 | throw new IllegalArgumentException("Issue deleting sms"); 208 | } 209 | } 210 | 211 | HttpEntity getSmsEntity(SmsDto dto, HttpHeaders httpHeaders) { 212 | 213 | return new HttpEntity<>(dto, httpHeaders); 214 | } 215 | 216 | HttpHeaders httpHeaders(final String token) { 217 | HttpHeaders httpHeaders = httpHeaders(); 218 | httpHeaders.add("Authorization", REQUEST_HEADER_PREFIX.getName() + token); 219 | 220 | return httpHeaders; 221 | } 222 | 223 | HttpHeaders httpHeaders() { 224 | 225 | HttpHeaders headers = new HttpHeaders(); 226 | headers.setContentType(MediaType.APPLICATION_JSON); 227 | headers.setAccept(acceptableMediaTypes()); 228 | headers.set(HttpHeaders.CONTENT_TYPE, "application/json"); 229 | return headers; 230 | } 231 | 232 | private List acceptableMediaTypes() { 233 | 234 | List acceptableMediaTypes = new ArrayList(); 235 | acceptableMediaTypes.add(MediaType.APPLICATION_JSON); 236 | 237 | return acceptableMediaTypes; 238 | 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/config/SecurityRestConfig.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.config; 2 | 3 | import com.murray.communications.security.jwt.JwtSecurityConfigurer; 4 | import com.murray.communications.security.jwt.JwtTokenProviderService; 5 | import com.murray.communications.security.jwtaudit.JwtClaimsToAuditingCredientalsResolver; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.core.annotation.Order; 10 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 11 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 12 | import org.springframework.security.config.http.SessionCreationPolicy; 13 | import org.springframework.web.method.support.HandlerMethodArgumentResolver; 14 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 15 | 16 | import java.util.List; 17 | 18 | @Configuration 19 | @Order(2) 20 | public class SecurityRestConfig extends WebSecurityConfigurerAdapter implements WebMvcConfigurer { 21 | 22 | @Autowired 23 | private JwtTokenProviderService jwtTokenProviderService; 24 | 25 | 26 | @Override 27 | protected void configure(HttpSecurity http) throws Exception { 28 | //@formatter:off 29 | http 30 | .httpBasic().disable() 31 | .csrf().disable() 32 | .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) 33 | .and() 34 | .authorizeRequests() 35 | .antMatchers("/auth/signin").permitAll() 36 | .antMatchers("/v1/sms/**").authenticated() 37 | .anyRequest().authenticated() 38 | .and() 39 | .apply(new JwtSecurityConfigurer(jwtTokenProviderService)); 40 | //@formatter:on 41 | } 42 | 43 | @Override 44 | public void addArgumentResolvers(List resolvers) { 45 | 46 | resolvers.add(jwtRequestHeaderMethodArgumentResolver()); 47 | } 48 | 49 | @Bean 50 | public HandlerMethodArgumentResolver jwtRequestHeaderMethodArgumentResolver() { 51 | 52 | return new JwtClaimsToAuditingCredientalsResolver(jwtTokenProviderService); 53 | } 54 | 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/config/SecurityWebConfig.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.config; 2 | 3 | import com.murray.communications.domain.respository.users.ApplicationUserRepository; 4 | import com.murray.communications.security.AuthenticationFacade; 5 | import com.murray.communications.security.impl.AuthenticationFacadeImpl; 6 | import com.murray.communications.security.impl.CustomUserDetailsServiceImpl; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.core.annotation.Order; 10 | import org.springframework.security.authentication.AuthenticationManager; 11 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 12 | import org.springframework.security.config.annotation.web.builders.WebSecurity; 13 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 14 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 15 | import org.springframework.security.core.userdetails.UserDetailsService; 16 | import org.springframework.security.crypto.password.NoOpPasswordEncoder; 17 | import org.springframework.security.web.util.matcher.AntPathRequestMatcher; 18 | 19 | @Configuration 20 | @EnableWebSecurity 21 | @Order(1) 22 | public class SecurityWebConfig extends WebSecurityConfigurerAdapter { 23 | 24 | @Bean 25 | public static NoOpPasswordEncoder passwordEncoder() { 26 | return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance(); 27 | } 28 | 29 | @Bean 30 | @Override 31 | public AuthenticationManager authenticationManagerBean() throws Exception { 32 | return super.authenticationManagerBean(); 33 | } 34 | 35 | @Bean 36 | public UserDetailsService userDetailsService(ApplicationUserRepository userRepository) { 37 | 38 | return new CustomUserDetailsServiceImpl(userRepository); 39 | } 40 | 41 | @Bean 42 | public AuthenticationFacade authenticationFacade() { 43 | 44 | return new AuthenticationFacadeImpl(); 45 | } 46 | 47 | @Override 48 | protected void configure(HttpSecurity http) throws Exception { 49 | //@formatter:off 50 | http 51 | .httpBasic().disable() 52 | .csrf().disable() 53 | .authorizeRequests() 54 | .antMatchers("/").permitAll() 55 | .antMatchers("/h2-console/**").permitAll() 56 | .antMatchers("/login*").permitAll() 57 | .antMatchers( "/swagger-ui.html", "/webjars/**", "/swagger-resources/**").permitAll() 58 | //Handled by the SecurityRestConfig so permit from here 59 | .antMatchers("/auth/signin").permitAll() 60 | .antMatchers("/v1/sms/**").permitAll() 61 | //AUTHENTICATED weburls 62 | .anyRequest().authenticated() 63 | .and() 64 | .formLogin() 65 | .loginPage("/login") 66 | .loginProcessingUrl("/login") 67 | .successForwardUrl("/") 68 | .and() 69 | .logout() 70 | .invalidateHttpSession(true) 71 | .clearAuthentication(true) 72 | .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) 73 | .logoutSuccessUrl("/login?logout") 74 | .permitAll(); 75 | //.and() 76 | // .exceptionHandling() 77 | // .accessDeniedHandler(accessDeniedHandler); 78 | //@formatter:on 79 | } 80 | 81 | 82 | @Override 83 | public void configure(WebSecurity web) throws Exception { 84 | 85 | web.ignoring() 86 | .antMatchers("swagger-ui.html", "/webjars/**", 87 | "/resources/**", 88 | "/static/**", 89 | "/css/**", 90 | "/js/**", 91 | "/img/**", 92 | "/h2-console/**"); 93 | } 94 | 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/config/ServiceConfig.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.config; 2 | 3 | import com.murray.communications.adapters.web.SmsMessageAdapter; 4 | import com.murray.communications.adapters.web.impl.WebMessageAdapterImpl; 5 | import com.murray.communications.services.messages.Impl.MessageServiceImpl; 6 | import com.murray.communications.services.messages.MessageService; 7 | import com.murray.communications.adapters.rest.MessageAdapterService; 8 | import com.murray.communications.adapters.rest.impl.MessageAdapterServiceImpl; 9 | import com.murray.communications.domain.respository.messages.SmsMessageRepository; 10 | import com.murray.communications.domain.respository.users.ApplicationUserRepository; 11 | import com.murray.communications.security.AuthenticationFacade; 12 | import com.murray.communications.security.jwt.JwtTokenProviderService; 13 | import com.murray.communications.services.formatters.CustomDateFormatter; 14 | import com.murray.communications.services.formatters.impl.CustomDateFormatterImpl; 15 | import com.murray.communications.services.users.UserAuthenticatonService; 16 | import com.murray.communications.services.users.impl.UserAuthenticationServiceImpl; 17 | import org.springframework.beans.factory.annotation.Value; 18 | import org.springframework.context.annotation.Bean; 19 | import org.springframework.context.annotation.Configuration; 20 | import org.springframework.core.convert.ConversionService; 21 | import org.springframework.security.authentication.AuthenticationManager; 22 | import org.springframework.security.core.userdetails.UserDetailsService; 23 | 24 | @Configuration 25 | public class ServiceConfig { 26 | 27 | @Bean 28 | public SmsMessageAdapter webMessageAdapterService(AuthenticationFacade authenticationFacade, 29 | @Value("${communication.base-url}") 30 | String baseUrl, 31 | ConversionService conversionService, 32 | SmsMessageRepository smsMessageRepository) { 33 | 34 | return new WebMessageAdapterImpl(authenticationFacade, baseUrl, conversionService,smsMessageRepository); 35 | } 36 | 37 | @Bean 38 | public MessageAdapterService smsAdapterService(MessageService messageService, ConversionService conversionService) { 39 | 40 | return new MessageAdapterServiceImpl(messageService, conversionService); 41 | } 42 | 43 | @Bean 44 | public MessageService messageService(SmsMessageRepository smsMessageRepository,ApplicationUserRepository userRepository) { 45 | 46 | return new MessageServiceImpl(smsMessageRepository,userRepository); 47 | } 48 | 49 | @Bean 50 | public UserAuthenticatonService userAuthenticatonService(AuthenticationManager authenticationManager, 51 | ApplicationUserRepository userRepository, JwtTokenProviderService jwtTokenProviderService) { 52 | 53 | return new UserAuthenticationServiceImpl(authenticationManager, userRepository, jwtTokenProviderService); 54 | } 55 | 56 | @Bean 57 | public JwtTokenProviderService jwtTokenProviderService(@Value("${security.jwt.token.secret-key:secret}") 58 | String secretKey, 59 | @Value("${security.jwt.token.expire-length:3600000}") 60 | long validityInMilliseconds, 61 | 62 | UserDetailsService userDetailsService) { 63 | 64 | return new JwtTokenProviderService(secretKey, validityInMilliseconds, userDetailsService); 65 | } 66 | 67 | @Bean 68 | public CustomDateFormatter customDateFormatter() { 69 | 70 | return new CustomDateFormatterImpl(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.http.HttpHeaders; 6 | import springfox.documentation.builders.PathSelectors; 7 | import springfox.documentation.builders.RequestHandlerSelectors; 8 | import springfox.documentation.service.ApiInfo; 9 | import springfox.documentation.service.ApiKey; 10 | import springfox.documentation.service.Contact; 11 | import springfox.documentation.spi.DocumentationType; 12 | import springfox.documentation.spring.web.plugins.Docket; 13 | import springfox.documentation.swagger.web.ApiKeyVehicle; 14 | import springfox.documentation.swagger.web.SecurityConfiguration; 15 | import springfox.documentation.swagger.web.SecurityConfigurationBuilder; 16 | import springfox.documentation.swagger2.annotations.EnableSwagger2; 17 | 18 | import java.util.ArrayList; 19 | import java.util.Arrays; 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | 23 | /** 24 | * Handles the application web configuration such as: 25 | *
    26 | *
  • Registering the Spring Converters
  • 27 | * 28 | *
  • Swagger configuration
  • 29 | *
30 | */ 31 | @Configuration 32 | @EnableSwagger2 33 | public class SwaggerConfig { 34 | 35 | @Bean 36 | public Docket api() { 37 | return new Docket(DocumentationType.SWAGGER_2) 38 | .select() 39 | .apis(RequestHandlerSelectors.basePackage("com.murray.communications.controllers.rest")) 40 | .paths(PathSelectors.any()) 41 | .build() 42 | .apiInfo(apiInfo()) 43 | .securitySchemes(Arrays.asList(apiKey())); 44 | } 45 | 46 | private ApiKey apiKey() { 47 | return new ApiKey("Authorization", "Authorization", "header"); 48 | } 49 | @Bean 50 | public SecurityConfiguration securityInfo() { 51 | 52 | Map maps = new HashMap<>(); 53 | maps.put(ApiKeyVehicle.HEADER.getValue(), HttpHeaders.AUTHORIZATION); 54 | 55 | return SecurityConfigurationBuilder.builder() 56 | .additionalQueryStringParams(maps) 57 | .build(); 58 | 59 | } 60 | 61 | private ApiInfo apiInfo() { 62 | return new ApiInfo( 63 | "COMMUNICATIONS REST API", 64 | "REST API of communication services", 65 | "v1", 66 | "", 67 | new Contact("", "", ""), 68 | "", 69 | "", 70 | new ArrayList<>()); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/config/ThymeleafWebConfig.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.config; 2 | 3 | import org.springframework.beans.BeansException; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.context.ApplicationContextAware; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.Description; 9 | import org.springframework.context.support.ResourceBundleMessageSource; 10 | import org.springframework.web.servlet.LocaleResolver; 11 | import org.springframework.web.servlet.ViewResolver; 12 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 13 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 14 | import org.springframework.web.servlet.i18n.SessionLocaleResolver; 15 | import org.thymeleaf.extras.java8time.dialect.Java8TimeDialect; 16 | import org.thymeleaf.spring5.ISpringTemplateEngine; 17 | import org.thymeleaf.spring5.SpringTemplateEngine; 18 | import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver; 19 | import org.thymeleaf.spring5.view.ThymeleafViewResolver; 20 | import org.thymeleaf.templatemode.TemplateMode; 21 | import org.thymeleaf.templateresolver.ITemplateResolver; 22 | 23 | import java.util.Locale; 24 | 25 | 26 | /** 27 | * Handles the application web configuration such as: 28 | *
    29 | *
  • Thymeleaf viewers and resolvers
  • 30 | *
  • Message resource bundle
  • 31 | *
32 | */ 33 | @Configuration 34 | public class ThymeleafWebConfig implements WebMvcConfigurer, ApplicationContextAware { 35 | 36 | 37 | private ApplicationContext applicationContext; 38 | 39 | @Override 40 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 41 | this.applicationContext = applicationContext; 42 | } 43 | 44 | @Bean 45 | @Description("Resolve Locale to english") 46 | public LocaleResolver localeResolver() { 47 | SessionLocaleResolver localeResolver = new SessionLocaleResolver(); 48 | localeResolver.setDefaultLocale(new Locale("en")); 49 | return localeResolver; 50 | } 51 | 52 | @Bean 53 | @Description("Thymeleaf Template Resolver") 54 | public ViewResolver htmlViewResolver() { 55 | 56 | ThymeleafViewResolver resolver = new ThymeleafViewResolver(); 57 | SpringTemplateEngine templateEngine = (SpringTemplateEngine) templateEngine(htmlTemplateResolver()); 58 | templateEngine.setTemplateEngineMessageSource(messageSource()); 59 | templateEngine.addDialect(new Java8TimeDialect()); 60 | resolver.setTemplateEngine(templateEngine); 61 | resolver.setContentType("text/html"); 62 | resolver.setCache(false); 63 | resolver.setCharacterEncoding("UTF-8"); 64 | resolver.setViewNames(new String[]{"*.html"}); 65 | return resolver; 66 | } 67 | 68 | 69 | private ITemplateResolver htmlTemplateResolver() { 70 | SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver(); 71 | resolver.setApplicationContext(applicationContext); 72 | resolver.setPrefix("resources"); 73 | resolver.setCacheable(false); 74 | resolver.setTemplateMode(TemplateMode.HTML); 75 | 76 | return resolver; 77 | } 78 | 79 | @Bean 80 | @Description("Spring Message Resolver") 81 | public ResourceBundleMessageSource messageSource() { 82 | ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); 83 | messageSource.setBasename("messages"); 84 | return messageSource; 85 | } 86 | 87 | @Bean 88 | @Description("Thymeleaf JS Resolver") 89 | public ViewResolver javascriptViewResolver() { 90 | ThymeleafViewResolver resolver = new ThymeleafViewResolver(); 91 | resolver.setTemplateEngine(templateEngine(javascriptTemplateResolver())); 92 | resolver.setContentType("application/javascript"); 93 | resolver.setCharacterEncoding("UTF-8"); 94 | resolver.setViewNames(new String[]{"*.js"}); 95 | return resolver; 96 | } 97 | 98 | private ITemplateResolver javascriptTemplateResolver() { 99 | SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver(); 100 | resolver.setApplicationContext(applicationContext); 101 | resolver.setPrefix("resources/static/js/"); 102 | resolver.setCacheable(false); 103 | resolver.setTemplateMode(TemplateMode.JAVASCRIPT); 104 | return resolver; 105 | } 106 | 107 | 108 | private ISpringTemplateEngine templateEngine(ITemplateResolver templateResolver) { 109 | SpringTemplateEngine engine = new SpringTemplateEngine(); 110 | engine.setTemplateResolver(templateResolver); 111 | return engine; 112 | } 113 | 114 | 115 | @Override 116 | public void addResourceHandlers(ResourceHandlerRegistry registry) { 117 | registry.addResourceHandler("swagger-ui.html") 118 | .addResourceLocations("classpath:/META-INF/resources/"); 119 | 120 | registry.addResourceHandler("/webjars/**") 121 | .addResourceLocations("classpath:/META-INF/resources/webjars/"); 122 | 123 | registry.addResourceHandler("/h2-console"); 124 | 125 | registry.addResourceHandler( 126 | "/webjars/**", "/img/**", "/css/**", "/js/**", "/fonts/**") 127 | .addResourceLocations( 128 | "classpath:/META-INF/resources/webjars/", 129 | "classpath:/static/img/", 130 | "classpath:/static/css/", 131 | "classpath:/static/js/", 132 | "classpath:/static/fonts/"); 133 | 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/controllers/advice/CommunicationExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.controllers.advice; 2 | 3 | import com.murray.communications.exceptions.InvalidJwtAuthenticationException; 4 | import com.murray.communications.exceptions.MessageCreationException; 5 | import com.murray.communications.exceptions.MessageNotFoundException; 6 | import com.murray.communications.exceptions.MessageProcessingException; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.web.bind.annotation.ControllerAdvice; 10 | import org.springframework.web.bind.annotation.ExceptionHandler; 11 | import org.springframework.web.context.request.WebRequest; 12 | 13 | import static org.springframework.http.HttpStatus.*; 14 | import static org.springframework.http.ResponseEntity.status; 15 | 16 | /** 17 | * This handler will catch all the exception thrown by the REST or Controller layer and convert them to an HTTP code and message. 18 | */ 19 | @Slf4j 20 | @ControllerAdvice 21 | public class CommunicationExceptionHandler { 22 | 23 | 24 | @ExceptionHandler(value = {InvalidJwtAuthenticationException.class}) 25 | public ResponseEntity invalidJwtAuthentication(InvalidJwtAuthenticationException ex, WebRequest request) { 26 | log.error("InvalidJwtAuthenticationException...", ex); 27 | return status(UNAUTHORIZED).build(); 28 | } 29 | 30 | @ExceptionHandler(value = {MessageCreationException.class}) 31 | public ResponseEntity messageCreationException(MessageCreationException ex, WebRequest request) { 32 | log.error("MessageCreationException...", ex); 33 | return status(NOT_ACCEPTABLE).build(); 34 | } 35 | 36 | @ExceptionHandler(value = {MessageNotFoundException.class}) 37 | public ResponseEntity messageNotfoundException(MessageNotFoundException ex, WebRequest request) { 38 | log.error("MessageNotFoundException...", ex); 39 | return status(NOT_FOUND).build(); 40 | } 41 | 42 | @ExceptionHandler(value = {MessageProcessingException.class}) 43 | public ResponseEntity messageProcessingException(MessageProcessingException ex, WebRequest request) { 44 | log.error("MessageProcessingException...", ex); 45 | return status(BAD_REQUEST).build(); 46 | } 47 | 48 | 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/controllers/rest/SmsRestController.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.controllers.rest; 2 | 3 | 4 | import com.murray.communications.adapters.rest.MessageAdapterService; 5 | import com.murray.communications.security.jwtaudit.AuditingCredentials; 6 | import com.murray.communications.security.jwtaudit.Credentials; 7 | import com.murray.communications.dtos.rest.SmsDto; 8 | import io.swagger.annotations.*; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.http.HttpHeaders; 11 | import org.springframework.http.HttpStatus; 12 | import org.springframework.http.MediaType; 13 | import org.springframework.http.ResponseEntity; 14 | import org.springframework.web.bind.annotation.*; 15 | 16 | 17 | /** 18 | * Spring MVC REST Controller for the SMS CRUD operations 19 | * Apart from that, it should only call the Adapter method and return the resulting objects. 20 | * 21 | * @see Swagger UI 22 | * @see REST JWT 23 | */ 24 | @Slf4j 25 | @Api(value = "SMS CRUD Operations ", 26 | produces = MediaType.APPLICATION_JSON_UTF8_VALUE 27 | ,authorizations = {@Authorization(value = HttpHeaders.AUTHORIZATION)} 28 | ) 29 | @RestController 30 | @RequestMapping("/v1/sms") 31 | public class SmsRestController { 32 | 33 | private final MessageAdapterService smsAdapterService; 34 | 35 | public SmsRestController(MessageAdapterService smsAdapterService) { 36 | this.smsAdapterService = smsAdapterService; 37 | } 38 | 39 | @PostMapping 40 | @ApiOperation(value = "Save a SMS message that has to be sent in the near future", 41 | authorizations = {@Authorization(value = HttpHeaders.AUTHORIZATION)}) 42 | @ApiResponses(value = { 43 | @ApiResponse(code = 201, message = "Sms successfully created", response = SmsDto.class), 44 | @ApiResponse(code = 406, message = "Error occurred creating SMS message") 45 | }) 46 | @ResponseStatus(code = HttpStatus.CREATED) 47 | public SmsDto save( 48 | @ApiParam(value = "The SMS details to be saved") 49 | @RequestBody SmsDto smsDto, 50 | @Credentials AuditingCredentials auditingCredentials) { 51 | 52 | log.info("Saving SMS:{}", smsDto); 53 | 54 | 55 | return smsAdapterService.save(auditingCredentials, smsDto); 56 | } 57 | 58 | @GetMapping(path = {"/{id}"}) 59 | @ApiOperation(value = "Find message by id") 60 | @ApiResponses(value = { 61 | @ApiResponse(code = 200, message = "Message found", response = SmsDto.class), 62 | @ApiResponse(code = 404, message = "Message not found") 63 | }) 64 | @ResponseStatus(code = HttpStatus.OK) 65 | public SmsDto findById( 66 | @ApiParam(value = "The SMS unique id") 67 | @PathVariable("id") Long messageId, 68 | @Credentials AuditingCredentials auditingCredentials) { 69 | 70 | log.info("Find SMS by id:{}", messageId); 71 | 72 | 73 | return smsAdapterService.find(messageId); 74 | } 75 | 76 | 77 | @PutMapping(path = {"/{id}"}) 78 | @ApiOperation(value = "Update SMS message details") 79 | @ApiResponses(value = { 80 | @ApiResponse(code = 200, message = "Message found", response = SmsDto.class), 81 | @ApiResponse(code = 404, message = "Message not found") 82 | }) 83 | @ResponseStatus(code = HttpStatus.OK) 84 | public SmsDto update( 85 | @ApiParam(value = "The SMS unique id") 86 | @PathVariable("id") Long messageId, 87 | @RequestBody SmsDto smsDto, 88 | @Credentials AuditingCredentials auditingCredentials 89 | ) { 90 | 91 | log.info("Updating SMS with id:{}", messageId); 92 | 93 | return smsAdapterService.update(auditingCredentials,smsDto); 94 | 95 | } 96 | 97 | @DeleteMapping(path = {"/{id}"}) 98 | @ApiOperation(value = "Update SMS message details") 99 | @ApiResponses(value = { 100 | @ApiResponse(code = 200, message = "Message deleted "), 101 | @ApiResponse(code = 404, message = "Message not found") 102 | }) 103 | @ResponseStatus(code = HttpStatus.OK) 104 | public ResponseEntity delete( 105 | @ApiParam(value = "The SMS unique id") 106 | @PathVariable("id") Long messageId, 107 | @Credentials AuditingCredentials auditingCredentials) { 108 | 109 | log.info("Deleting SMS with id:{}", messageId); 110 | 111 | smsAdapterService.delete(auditingCredentials,messageId); 112 | 113 | return ResponseEntity.ok().build(); 114 | } 115 | 116 | 117 | @PutMapping(path = {"/{id}/send"}) 118 | @ApiOperation(value = "Update SMS message details") 119 | @ApiResponses(value = { 120 | @ApiResponse(code = 200, message = "Message found", response = SmsDto.class), 121 | @ApiResponse(code = 404, message = "Message not found") 122 | }) 123 | @ResponseStatus(code = HttpStatus.OK) 124 | public ResponseEntity send( 125 | @ApiParam(value = "The SMS unique id") 126 | @PathVariable("id") Long messageId, 127 | @Credentials AuditingCredentials auditingCredentials 128 | ) { 129 | 130 | log.info("Sending SMS with id:{}", messageId); 131 | 132 | smsAdapterService.sendSms(auditingCredentials,messageId); 133 | 134 | return ResponseEntity.ok().build(); 135 | } 136 | 137 | 138 | } 139 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/controllers/rest/UserAuthenticationController.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.controllers.rest; 2 | 3 | import com.murray.communications.services.users.UserAuthenticatonService; 4 | import org.springframework.http.HttpHeaders; 5 | import org.springframework.http.MediaType; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.PostMapping; 8 | import org.springframework.web.bind.annotation.RequestBody; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | import static org.springframework.http.ResponseEntity.ok; 13 | 14 | @RestController 15 | @RequestMapping("/auth") 16 | public class UserAuthenticationController { 17 | 18 | 19 | private final UserAuthenticatonService userAuthenticatonService; 20 | 21 | public UserAuthenticationController(UserAuthenticatonService userAuthenticatonService) { 22 | this.userAuthenticatonService = userAuthenticatonService; 23 | } 24 | 25 | @PostMapping("/signin") 26 | public ResponseEntity signin(@RequestBody UserAuthenticationRequest authenticationRequest) { 27 | 28 | String token = userAuthenticatonService.signIn(authenticationRequest.getUsername(), authenticationRequest.getPassword()); 29 | HttpHeaders responseHeaders = new HttpHeaders(); 30 | responseHeaders.setContentType(MediaType.APPLICATION_JSON); 31 | return ResponseEntity.ok() 32 | .headers(responseHeaders) 33 | .body(token); 34 | 35 | 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/controllers/rest/UserAuthenticationRequest.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.controllers.rest; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | /** 9 | * User at 10 | */ 11 | @Data 12 | @Builder 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | public class UserAuthenticationRequest { 16 | 17 | private String username; 18 | private String password; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/controllers/web/AdminSmsController.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.controllers.web; 2 | 3 | import com.murray.communications.adapters.web.SmsMessageAdapter; 4 | import com.murray.communications.dtos.web.SMSMessageDto; 5 | import com.murray.communications.dtos.web.SmsSearchResults; 6 | import com.murray.communications.dtos.web.NewSMSMessageDto; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.ui.Model; 10 | import org.springframework.validation.BindingResult; 11 | import org.springframework.web.bind.annotation.GetMapping; 12 | import org.springframework.web.bind.annotation.ModelAttribute; 13 | import org.springframework.web.bind.annotation.PathVariable; 14 | import org.springframework.web.bind.annotation.PostMapping; 15 | 16 | import javax.servlet.http.HttpServletRequest; 17 | import javax.validation.Valid; 18 | 19 | @Slf4j 20 | @Controller 21 | public class AdminSmsController { 22 | 23 | 24 | private final SmsMessageAdapter smsMessageAdapter; 25 | 26 | public AdminSmsController(SmsMessageAdapter smsMessageAdapter) { 27 | this.smsMessageAdapter = smsMessageAdapter; 28 | } 29 | 30 | @GetMapping({"/sms", "/sms-overview.html"}) 31 | public String smsOverview(HttpServletRequest request, Model model) { 32 | log.warn("TBC get the latest results for message sent per status last 10days"); 33 | 34 | int page = 0; 35 | int size = 10; 36 | 37 | if (request.getParameter("page") != null && !request.getParameter("page").isEmpty()) { 38 | page = Integer.parseInt(request.getParameter("page")) - 1; 39 | if (page < 0) { 40 | page = 0; 41 | } 42 | } 43 | 44 | if (request.getParameter("size") != null && !request.getParameter("size").isEmpty()) { 45 | size = Integer.parseInt(request.getParameter("size")); 46 | } 47 | SmsSearchResults results = smsMessageAdapter.findAllMessages(page, size); 48 | 49 | log.info("Found {} messages", results.getTotalElements()); 50 | 51 | model.addAttribute("results", results); 52 | 53 | return "sms/overview"; 54 | } 55 | 56 | @GetMapping({"/sms/new-sms.html"}) 57 | public String newSms(NewSMSMessageDto newSMSMessageDto) { 58 | 59 | return "sms/new-sms"; 60 | } 61 | 62 | @PostMapping("/sms/save") 63 | public String saveSms(@Valid NewSMSMessageDto newSMSMessageDto, BindingResult bindingResult, 64 | Model model) { 65 | 66 | log.info("saving newSMSMessageDto:{}", newSMSMessageDto); 67 | 68 | if (bindingResult.hasErrors()) { 69 | return "sms/new-sms"; 70 | } 71 | 72 | log.info("saving newSMSMessageDto:{}", newSMSMessageDto); 73 | 74 | 75 | SMSMessageDto smsMessage = smsMessageAdapter.saveSms(newSMSMessageDto); 76 | 77 | log.info("New sms id:{}", smsMessage.getId()); 78 | 79 | 80 | model.addAttribute("smsMessage", smsMessage); 81 | 82 | 83 | return "redirect:/sms-overview.html"; 84 | } 85 | 86 | @GetMapping("/sms-edit/{id}") 87 | public String showEditSmsForm(@PathVariable("id") Long id, Model model) { 88 | 89 | SMSMessageDto smsMessage = smsMessageAdapter.findMessageBy(id); 90 | 91 | model.addAttribute("smsMessage", smsMessage); 92 | return "sms/edit-sms"; 93 | } 94 | 95 | @PostMapping("/sms/update") 96 | public String updateSms(@Valid @ModelAttribute("smsMessage") SMSMessageDto smsMessage, 97 | BindingResult bindingResult, 98 | Model model) { 99 | 100 | log.info("updating sms:{}", smsMessage); 101 | if (bindingResult.hasErrors()) { 102 | log.warn("updating sms validation errors"); 103 | return "sms/edit-sms"; 104 | } 105 | 106 | SMSMessageDto smsMessageDto = smsMessageAdapter.update(smsMessage); 107 | 108 | model.addAttribute("smsMessage", smsMessageDto); 109 | 110 | return "redirect:/sms-overview.html"; 111 | } 112 | 113 | @GetMapping("/sms-delete/{id}") 114 | public String deleteSms(@PathVariable("id") Long id, Model model) { 115 | 116 | log.warn("deleting sms :{}",id); 117 | 118 | smsMessageAdapter.deleteById(id); 119 | 120 | return "redirect:/sms-overview.html"; 121 | } 122 | 123 | 124 | @GetMapping("/sms-send/{id}") 125 | public String sendSms(@PathVariable("id") Long id, Model model) { 126 | 127 | log.warn("sending sms :{}",id); 128 | 129 | smsMessageAdapter.sendMessage(id); 130 | 131 | return "redirect:/sms-overview.html"; 132 | } 133 | 134 | 135 | 136 | } 137 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/controllers/web/CustomErrorController.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.controllers.web; 2 | 3 | import org.springframework.boot.web.servlet.error.ErrorAttributes; 4 | import org.springframework.boot.web.servlet.error.ErrorController; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.ui.Model; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.context.request.ServletWebRequest; 10 | import org.springframework.web.context.request.WebRequest; 11 | 12 | import javax.servlet.http.HttpServletRequest; 13 | import java.util.Map; 14 | 15 | @Controller 16 | public class CustomErrorController implements ErrorController { 17 | 18 | private final static String ERROR_PATH = "/error"; 19 | /** 20 | * Error Attributes in the Application 21 | */ 22 | private ErrorAttributes errorAttributes; 23 | 24 | public CustomErrorController(ErrorAttributes errorAttributes) { 25 | this.errorAttributes = errorAttributes; 26 | } 27 | 28 | @GetMapping("/error") 29 | public String handleError(Model model, WebRequest webRequest) { 30 | 31 | final Throwable error = errorAttributes.getError(webRequest); 32 | model.addAttribute("exception", error); 33 | model.addAttribute("message", error == null ? "" : error.getMessage()); 34 | 35 | return "/error/error"; 36 | } 37 | 38 | 39 | /** 40 | * Returns the path of the error page. 41 | * 42 | * @return the error path 43 | */ 44 | @Override 45 | public String getErrorPath() { 46 | return ERROR_PATH; 47 | } 48 | 49 | 50 | private boolean getTraceParameter(HttpServletRequest request) { 51 | String parameter = request.getParameter("trace"); 52 | if (parameter == null) { 53 | return false; 54 | } 55 | return !"false".equals(parameter.toLowerCase()); 56 | } 57 | 58 | private Map getErrorAttributes(HttpServletRequest request, 59 | boolean includeStackTrace) { 60 | WebRequest requestAttributes = new ServletWebRequest(request); 61 | 62 | return this.errorAttributes.getErrorAttributes(requestAttributes, 63 | includeStackTrace); 64 | } 65 | 66 | private HttpStatus getStatus(HttpServletRequest request) { 67 | Integer statusCode = (Integer) request 68 | .getAttribute("javax.servlet.error.status_code"); 69 | if (statusCode != null) { 70 | try { 71 | return HttpStatus.valueOf(statusCode); 72 | } catch (Exception ex) { 73 | } 74 | } 75 | return HttpStatus.INTERNAL_SERVER_ERROR; 76 | 77 | 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/controllers/web/MainController.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.controllers.web; 2 | 3 | 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.ui.Model; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | 9 | import java.util.Date; 10 | 11 | @Controller 12 | public class MainController { 13 | 14 | 15 | @RequestMapping("/") 16 | public String index(Model model) { 17 | model.addAttribute("datetime", new Date()); 18 | model.addAttribute("username", "Test"); 19 | model.addAttribute("projectname", "WebApp"); 20 | 21 | return "index"; 22 | } 23 | 24 | @GetMapping("/login") 25 | public String login() { 26 | return "login"; 27 | } 28 | 29 | @GetMapping("/access-denied") 30 | public String accessDenied() { 31 | return "/error/access-denied"; 32 | } 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/domain/entities/AbstractAuditableEntity.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.domain.entities; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.springframework.data.annotation.CreatedBy; 6 | import org.springframework.data.annotation.CreatedDate; 7 | import org.springframework.data.annotation.LastModifiedBy; 8 | import org.springframework.data.annotation.LastModifiedDate; 9 | import org.springframework.data.jpa.domain.support.AuditingEntityListener; 10 | 11 | import javax.persistence.*; 12 | import java.io.Serializable; 13 | import java.time.LocalDateTime; 14 | 15 | /** 16 | * Put in place so we can use the {@link org.springframework.data.domain.AuditorAware} instead of setting in 17 | * service implmentation. 18 | * @param 19 | * @param 20 | */ 21 | @Getter 22 | @Setter 23 | @MappedSuperclass 24 | @EntityListeners(AuditingEntityListener.class) 25 | public class AbstractAuditableEntity extends AbstractPersistableEntity implements Serializable { 26 | 27 | @CreatedDate 28 | LocalDateTime createdDate; 29 | 30 | @LastModifiedDate 31 | LocalDateTime lastModifiedDate; 32 | 33 | @CreatedBy 34 | @ManyToOne 35 | @JoinColumn(name = "created_by") 36 | U createdBy; 37 | 38 | @LastModifiedBy 39 | @ManyToOne 40 | @JoinColumn(name = "last_modified_by") 41 | U lastModifiedBy; 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/domain/entities/AbstractPersistableEntity.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.domain.entities; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | import javax.persistence.*; 7 | import java.io.Serializable; 8 | 9 | /** 10 | * Abstract entity class that defines the JPA entity id field type and versioning 11 | * which is used in optimistic locking. Optimistic locking is when you check if the record 12 | * was updated by someone else before you commit the transaction. 13 | * 14 | * @param 15 | */ 16 | @Setter 17 | @Getter 18 | @MappedSuperclass 19 | public abstract class AbstractPersistableEntity implements Serializable { 20 | 21 | 22 | @Id 23 | @GeneratedValue(strategy = GenerationType.IDENTITY) 24 | private ID id; 25 | 26 | @Version 27 | private Long version; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/domain/entities/messages/BandWidthCredentials.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.domain.entities.messages; 2 | 3 | 4 | import com.murray.communications.domain.entities.AbstractAuditableEntity; 5 | import com.murray.communications.domain.entities.users.ApplicationUser; 6 | import lombok.EqualsAndHashCode; 7 | import lombok.Getter; 8 | import lombok.Setter; 9 | 10 | import javax.persistence.Entity; 11 | import javax.persistence.OneToMany; 12 | import javax.persistence.OneToOne; 13 | import javax.persistence.Table; 14 | import javax.validation.constraints.NotEmpty; 15 | import java.io.Serializable; 16 | import java.util.List; 17 | 18 | /** 19 | * Holds user specific bandwidth details 20 | */ 21 | @Entity 22 | @Table(name = "bandwidth_credentials") 23 | @Getter 24 | @Setter 25 | public class BandWidthCredentials extends AbstractAuditableEntity implements Serializable { 26 | 27 | 28 | @OneToOne(mappedBy = "bandWidthCredentials") 29 | private ApplicationUser user; 30 | 31 | @EqualsAndHashCode.Exclude 32 | @OneToMany(mappedBy="bandWidthCredentials") 33 | private List messages; 34 | 35 | /** 36 | * Bandwidth message API 37 | */ 38 | @NotEmpty 39 | private String messageApi; 40 | 41 | /** 42 | * Bandwidth message API secret 43 | */ 44 | @NotEmpty 45 | private String messageSecret; 46 | 47 | 48 | /** 49 | * User define name for the bandwidth details 50 | */ 51 | @NotEmpty 52 | private String applicationName; 53 | 54 | /** 55 | * Bandwidth application id 56 | */ 57 | @NotEmpty 58 | private String applicationId; 59 | 60 | /** 61 | * Actual valid phone number defined and accociated with the 62 | * bandwidth sub account location 63 | */ 64 | @NotEmpty 65 | private Long senderPhoneNumber; 66 | 67 | 68 | /** 69 | * Dash board user name 70 | */ 71 | @NotEmpty 72 | private String dashboardUserName; 73 | 74 | /** 75 | * Dash board user password 76 | */ 77 | @NotEmpty 78 | private String dashboardPwd; 79 | 80 | /** 81 | * Dash board valid account id account 82 | */ 83 | @NotEmpty 84 | private String dashboardAccountId; 85 | 86 | /** 87 | * Dashboard subaccount 88 | */ 89 | @NotEmpty 90 | private String dashboardSubAccountId; 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/domain/entities/messages/SmsMessage.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.domain.entities.messages; 2 | 3 | import com.murray.communications.domain.entities.AbstractAuditableEntity; 4 | import com.murray.communications.domain.entities.users.ApplicationUser; 5 | import com.murray.communications.dtos.enums.MessageStatus; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | import lombok.NonNull; 9 | 10 | import javax.persistence.*; 11 | import java.io.Serializable; 12 | import java.time.LocalDate; 13 | 14 | @Entity 15 | @Table(name = "sms_message") 16 | @Data 17 | public class SmsMessage extends AbstractAuditableEntity implements Serializable { 18 | 19 | /** 20 | * Message status, indicating if the message has been delivered, error etc... 21 | */ 22 | @Column(name = "status", nullable = false) 23 | @Enumerated(EnumType.STRING) 24 | private MessageStatus status; 25 | 26 | @EqualsAndHashCode.Exclude 27 | @ManyToOne 28 | @JoinColumn(name = "bandwidth_id") 29 | private BandWidthCredentials bandWidthCredentials; 30 | /** 31 | * Phone number that received the message 32 | */ 33 | private Long receiver; 34 | 35 | /** 36 | * Phone number used for sending the message 37 | */ 38 | private Long sender; 39 | 40 | /** 41 | * Actual sms content 42 | */ 43 | private String message; 44 | 45 | /** 46 | * Timestamp when the message was send to bandwidth for processing 47 | */ 48 | private LocalDate sendDate; 49 | 50 | /** 51 | * Bandwidth delivery response which is return on the callback hook. 52 | */ 53 | @Column 54 | private String deliverResponse; 55 | 56 | 57 | @Column 58 | boolean deleted = false; 59 | 60 | public SmsMessage() { 61 | } 62 | 63 | public SmsMessage(final @NonNull Long receiver, final @NonNull Long sender, final @NonNull String message) { 64 | this.receiver = receiver; 65 | this.sender = sender; 66 | this.message = message; 67 | this.status = MessageStatus.CREATED; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/domain/entities/users/ApplicationRole.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.domain.entities.users; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.persistence.*; 9 | 10 | @Data 11 | @Builder 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | @Entity 15 | @Table(name = "application_role") 16 | public class ApplicationRole { 17 | 18 | @Id 19 | @GeneratedValue(strategy = GenerationType.AUTO) 20 | @Column(name = "role_id") 21 | private int id; 22 | @Column(name = "ROLE") 23 | private String role; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/domain/entities/users/ApplicationUser.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.domain.entities.users; 2 | 3 | import com.murray.communications.domain.entities.messages.BandWidthCredentials; 4 | import lombok.*; 5 | import org.hibernate.validator.constraints.Length; 6 | import org.springframework.security.core.GrantedAuthority; 7 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 8 | import org.springframework.security.core.userdetails.UserDetails; 9 | 10 | import javax.persistence.*; 11 | import javax.validation.constraints.Email; 12 | import javax.validation.constraints.NotEmpty; 13 | import java.util.Collection; 14 | import java.util.List; 15 | import java.util.Set; 16 | 17 | import static java.util.stream.Collectors.toList; 18 | 19 | /** 20 | * The AuthenticatedUser is a standard JPA entity which implements the 21 | * Spring Security specific UserDetails interface. 22 | */ 23 | @Entity 24 | @Table(name = "application_user", uniqueConstraints = @UniqueConstraint(columnNames = "username")) 25 | @Builder 26 | @Getter 27 | @Setter 28 | @NoArgsConstructor 29 | @AllArgsConstructor 30 | public class ApplicationUser implements UserDetails { 31 | 32 | @Id 33 | @GeneratedValue(strategy = GenerationType.AUTO) 34 | @Column(name = "user_id") 35 | Long id; 36 | 37 | @Column(name = "username") 38 | @Email(message = "username should be a valid email address") 39 | @NotEmpty(message = "username field is mandatory") 40 | private String username; 41 | 42 | 43 | @Length(min = 8, message = "Password must have at least 8 characters") 44 | @NotEmpty(message = "Password is mandatory") 45 | private String password; 46 | 47 | private Boolean enabled; 48 | 49 | @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) 50 | @JoinTable(name = "application_user_role", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id")) 51 | private Set roles; 52 | 53 | @EqualsAndHashCode.Exclude 54 | @OneToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER) 55 | @JoinTable(name = "user_bandwidth_credentials", 56 | joinColumns = { @JoinColumn(name = "user_id", referencedColumnName = "user_id") }, 57 | inverseJoinColumns = { @JoinColumn(name = "bandwidth_id", referencedColumnName = "id") }) 58 | private BandWidthCredentials bandWidthCredentials; 59 | 60 | 61 | @Override 62 | public Collection getAuthorities() { 63 | return this.roles.stream() 64 | .map(ApplicationRole::getRole) 65 | .map(SimpleGrantedAuthority::new) 66 | .collect(toList()); 67 | } 68 | 69 | public List gerRolesOnly() { 70 | return 71 | this.roles.stream() 72 | .map(ApplicationRole::getRole) 73 | .collect(toList()); 74 | } 75 | 76 | @Override 77 | public String getPassword() { 78 | return this.password; 79 | } 80 | 81 | @Override 82 | public String getUsername() { 83 | return this.username; 84 | } 85 | 86 | @Override 87 | public boolean isEnabled() { 88 | return enabled; 89 | } 90 | 91 | @Override 92 | public boolean isAccountNonExpired() { 93 | return true; 94 | } 95 | 96 | @Override 97 | public boolean isAccountNonLocked() { 98 | return true; 99 | } 100 | 101 | @Override 102 | public boolean isCredentialsNonExpired() { 103 | return true; 104 | } 105 | 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/domain/respository/messages/SmsMessageRepository.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.domain.respository.messages; 2 | 3 | import com.murray.communications.domain.entities.messages.SmsMessage; 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.repository.PagingAndSortingRepository; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import java.time.LocalDate; 10 | 11 | @Repository 12 | public interface SmsMessageRepository extends PagingAndSortingRepository { 13 | 14 | 15 | /** 16 | * find all sms send between the two date and sent by a specific number findAllBySendOnBetweenAndSender 17 | */ 18 | Page findAllBySendDateBetweenAndSenderAndDeletedIsFalse(LocalDate from, LocalDate to, Long sender, Pageable pageable); 19 | 20 | /** 21 | * Find all that are not deleted 22 | * @param pageable 23 | * @return 24 | */ 25 | Page findAllByDeletedIsFalse(Pageable pageable); 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/domain/respository/users/ApplicationUserRepository.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.domain.respository.users; 2 | 3 | import com.murray.communications.domain.entities.users.ApplicationUser; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Optional; 7 | 8 | /** 9 | * JPA repo to extract application users 10 | */ 11 | public interface ApplicationUserRepository extends JpaRepository { 12 | 13 | Optional findByUsername(String userName); 14 | 15 | 16 | Optional findByUsernameAndPassword(String userName, String password); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/dtos/enums/MessageStatus.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.dtos.enums; 2 | 3 | /** 4 | * Message , sms or email, actual status 5 | */ 6 | public enum MessageStatus { 7 | /** 8 | * Message saved in database 9 | */ 10 | CREATED, 11 | /** 12 | * Message send to bandwidth for processing. Bandwidth will then hit the webhook to 13 | * update the status. 14 | */ 15 | PROCESSING, 16 | /** 17 | * When message successfully sent from bandwidth to the recipient 18 | * This is the result of the call back url 19 | */ 20 | SENT_SUCCESS, 21 | 22 | /** 23 | * If issue occurred when bandwidth we attempting to send sms. 24 | * Again this is related to the callback results. 25 | */ 26 | SENT_ERROR, 27 | /** 28 | * Interal error occurred 29 | */ 30 | ERROR, 31 | 32 | /** 33 | * Soft deleted message from system 34 | */ 35 | DELETED; 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/dtos/rest/SmsDto.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.dtos.rest; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 6 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 7 | import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; 8 | import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; 9 | import com.murray.communications.dtos.enums.MessageStatus; 10 | import io.swagger.annotations.ApiModel; 11 | import io.swagger.annotations.ApiModelProperty; 12 | import lombok.AllArgsConstructor; 13 | import lombok.Data; 14 | import lombok.NoArgsConstructor; 15 | 16 | import java.time.LocalDate; 17 | import java.time.LocalDateTime; 18 | 19 | /** 20 | * Transfer transfer data object 21 | */ 22 | @JsonInclude(JsonInclude.Include.NON_NULL) 23 | @ApiModel(value = "SmsDto", 24 | description = "Represents a SMS message that is stored and sent either directly or in the future" 25 | ) 26 | @Data 27 | @NoArgsConstructor 28 | @AllArgsConstructor 29 | public class SmsDto { 30 | 31 | @JsonProperty(value = "id") 32 | @ApiModelProperty(value = "Sms unique internal id") 33 | private Long id; 34 | 35 | @JsonProperty(value = "createdOn") 36 | @ApiModelProperty(value = "Timestamp when the sms was saved") 37 | private LocalDateTime createdOn; 38 | 39 | @JsonProperty(value = "status") 40 | @ApiModelProperty(value = "SMS status ", 41 | allowableValues = "CREATED, SEND, ERROR", 42 | example = "CREATED" 43 | ) 44 | private MessageStatus status; 45 | 46 | @JsonProperty(value = "receiver") 47 | @ApiModelProperty(value = "phone number that is to receive the message ", 48 | required = true, 49 | example = "4175409749" 50 | ) 51 | private Long receiver; 52 | 53 | @JsonProperty(value = "sender") 54 | @ApiModelProperty(value = "phone number that is to sending the message ", 55 | example = "18445014846" 56 | ) 57 | private Long sender; 58 | 59 | @JsonProperty(value = "message") 60 | @ApiModelProperty(value = "sms message content", 61 | example = "Hello world", required = true 62 | ) 63 | private String message; 64 | 65 | @JsonDeserialize(using = LocalDateDeserializer.class) 66 | @JsonSerialize(using = LocalDateSerializer.class) 67 | @JsonProperty(value = "sendOn") 68 | @ApiModelProperty(value = "Timestamp when the sms should be sent to the receiver", 69 | example = "YYYY-MM-dd : 2019-10-13") 70 | private LocalDate sendOn; 71 | 72 | 73 | @JsonProperty(value = "applicationName") 74 | @ApiModelProperty(value = "external application name that is processsing the message", 75 | example = "test-bandwidth-app" 76 | ) 77 | private String applicationName; 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/dtos/rest/SmsSearchDto.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.dtos.rest; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.murray.communications.dtos.enums.MessageStatus; 5 | import io.swagger.annotations.ApiModel; 6 | import io.swagger.annotations.ApiModelProperty; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Data; 9 | import lombok.NoArgsConstructor; 10 | 11 | import java.time.LocalDate; 12 | import java.util.List; 13 | 14 | /** 15 | * Transfer search criteria 16 | */ 17 | @ApiModel(value = "SmsSearchDto", 18 | description = "defines search criteria that can be use to" + 19 | "search for sms messages." 20 | ) 21 | @Data 22 | @NoArgsConstructor 23 | @AllArgsConstructor 24 | public class SmsSearchDto { 25 | 26 | @JsonProperty(value = "id") 27 | @ApiModelProperty(value = "Sms unique internal id") 28 | private Long id; 29 | 30 | @JsonProperty(value = "createdBetween") 31 | @ApiModelProperty(value = "Filter between creation dates, usually define two dates", 32 | example = "[{2019-09-27,2019-09-31}]") 33 | private List createdBetween; 34 | 35 | 36 | @JsonProperty(value = "sendOnBetween") 37 | @ApiModelProperty(value = "Filter between dates when message to be sent, " + 38 | "usually define two dates", 39 | example = "[{2019-09-27,2019-09-31}]") 40 | private List sendOnBetween; 41 | 42 | @JsonProperty(value = "sender") 43 | @ApiModelProperty(value = "filter on sender number") 44 | private Integer sender; 45 | 46 | @JsonProperty(value = "receiver") 47 | @ApiModelProperty(value = "filter on receiver number ") 48 | private Integer receiver; 49 | 50 | @JsonProperty(value = "status") 51 | @ApiModelProperty(value = "filter on message number ", 52 | allowableValues = "CREATED, SEND, ERROR") 53 | private MessageStatus status; 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/dtos/web/NewSMSMessageDto.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.dtos.web; 2 | 3 | import lombok.Data; 4 | import lombok.NoArgsConstructor; 5 | 6 | import javax.validation.constraints.*; 7 | 8 | @Data 9 | @NoArgsConstructor 10 | public class NewSMSMessageDto { 11 | 12 | @NotNull 13 | @Min(8) 14 | private Long receiver; 15 | 16 | private Long sender; 17 | 18 | @NotBlank(message = "Message cannot be blank") 19 | @Size(max = 500, message 20 | = "Message less that 500 character") 21 | private String message; 22 | 23 | @NotBlank(message = "Send date is mandatory") 24 | @Pattern(regexp = "^(((0[1-9]|1[012])/(0[1-9]|[12][0-9]|3[01])/2[0-9])[0-9]{2})", 25 | message = "send date format in correct MM/DD/YYYY") 26 | private String sendOn; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/dtos/web/SMSMessageDto.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.dtos.web; 2 | 3 | import lombok.Data; 4 | import lombok.NoArgsConstructor; 5 | 6 | import java.time.LocalDateTime; 7 | 8 | @Data 9 | @NoArgsConstructor 10 | public class SMSMessageDto extends NewSMSMessageDto { 11 | 12 | private Long id; 13 | 14 | private LocalDateTime createdOn; 15 | 16 | private String status; 17 | 18 | private String applicationName; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/dtos/web/SmsSearchResults.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.dtos.web; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Represents the Sms search results returned from the repo. 9 | */ 10 | @Data 11 | public class SmsSearchResults { 12 | 13 | /** 14 | * Total number of pages 15 | */ 16 | private Integer totalPages; 17 | 18 | /** 19 | * Actual page number 20 | */ 21 | private Integer pageNumber; 22 | 23 | /** 24 | * Total number message found 25 | */ 26 | private Long totalElements; 27 | 28 | /** 29 | * Messages found 30 | */ 31 | private List messages; 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/exceptions/BandWidthException.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.exceptions; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public class BandWidthException extends Exception { 7 | 8 | private int code; 9 | private String response; 10 | 11 | public BandWidthException(String message, int code, String response) { 12 | super(message); 13 | this.code = code; 14 | this.response = response; 15 | } 16 | public BandWidthException(String message) { 17 | super(message); 18 | this.code=-1; 19 | this.response="internal error"; 20 | } 21 | 22 | public BandWidthException(String message, Throwable cause, int code, String response) { 23 | super(message, cause); 24 | this.code = code; 25 | this.response = response; 26 | } 27 | 28 | public BandWidthException(String message, Throwable cause){ 29 | super(message, cause); 30 | this.code=-1; 31 | this.response="internal error"; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/exceptions/InvalidJwtAuthenticationException.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.exceptions; 2 | 3 | import org.springframework.security.core.AuthenticationException; 4 | 5 | public class InvalidJwtAuthenticationException extends AuthenticationException { 6 | 7 | public InvalidJwtAuthenticationException(String msg) { 8 | super(msg); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/exceptions/MessageCreationException.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.exceptions; 2 | 3 | public class MessageCreationException extends RuntimeException { 4 | 5 | public MessageCreationException() { 6 | } 7 | 8 | public MessageCreationException(String message) { 9 | super(message); 10 | } 11 | 12 | public MessageCreationException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/exceptions/MessageNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.exceptions; 2 | 3 | public class MessageNotFoundException extends RuntimeException { 4 | 5 | public MessageNotFoundException() { 6 | } 7 | 8 | public MessageNotFoundException(String message) { 9 | super(message); 10 | } 11 | 12 | public MessageNotFoundException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/exceptions/MessageProcessingException.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.exceptions; 2 | 3 | public class MessageProcessingException extends RuntimeException { 4 | public MessageProcessingException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/security/AuthenticationFacade.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.security; 2 | 3 | import com.murray.communications.domain.entities.users.ApplicationUser; 4 | 5 | /** 6 | * Custom interface that used to extract the authenticated users specific details. 7 | */ 8 | public interface AuthenticationFacade { 9 | 10 | /** 11 | * Get the {@link ApplicationUser} via the spring security context 12 | * 13 | * @return 14 | */ 15 | ApplicationUser getAuthenticationUser(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/security/impl/AuthenticationFacadeImpl.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.security.impl; 2 | 3 | import com.murray.communications.domain.entities.users.ApplicationUser; 4 | import com.murray.communications.security.AuthenticationFacade; 5 | import org.springframework.security.core.Authentication; 6 | import org.springframework.security.core.context.SecurityContextHolder; 7 | 8 | import java.util.Optional; 9 | 10 | /** 11 | * {@inheritDoc} 12 | */ 13 | public class AuthenticationFacadeImpl implements AuthenticationFacade { 14 | 15 | /** 16 | * {@inheritDoc} 17 | */ 18 | @Override 19 | public ApplicationUser getAuthenticationUser() { 20 | Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); 21 | 22 | return getUserFrom(authentication).orElseThrow(() -> new IllegalArgumentException("No user found")); 23 | } 24 | 25 | private Optional getUserFrom(Authentication authentication) { 26 | 27 | return Optional.ofNullable(((ApplicationUser) authentication.getPrincipal())); 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/security/impl/CustomUserDetailsServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.security.impl; 2 | 3 | import com.murray.communications.domain.respository.users.ApplicationUserRepository; 4 | import org.springframework.security.core.userdetails.UserDetails; 5 | import org.springframework.security.core.userdetails.UserDetailsService; 6 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 7 | 8 | public class CustomUserDetailsServiceImpl implements UserDetailsService { 9 | 10 | private final ApplicationUserRepository userRepository; 11 | 12 | public CustomUserDetailsServiceImpl(ApplicationUserRepository userRepository) { 13 | this.userRepository = userRepository; 14 | } 15 | 16 | @Override 17 | public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException { 18 | 19 | 20 | return this.userRepository.findByUsername(userName) 21 | .orElseThrow(() -> new UsernameNotFoundException("Opps,username: " + userName + " not found")); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/security/jwt/JwtAuthenticationEntryPoint.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.security.jwt; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.security.core.AuthenticationException; 5 | import org.springframework.security.web.AuthenticationEntryPoint; 6 | 7 | import javax.servlet.ServletException; 8 | import javax.servlet.http.HttpServletRequest; 9 | import javax.servlet.http.HttpServletResponse; 10 | import java.io.IOException; 11 | 12 | @Slf4j 13 | public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { 14 | 15 | @Override 16 | public void commence(HttpServletRequest request, HttpServletResponse response, 17 | AuthenticationException authException) throws IOException, ServletException { 18 | log.debug("Jwt authentication failed:{}", authException); 19 | 20 | response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Jwt authentication failed"); 21 | 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/security/jwt/JwtSecurityConfigurer.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.security.jwt; 2 | 3 | import org.springframework.security.config.annotation.SecurityConfigurerAdapter; 4 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 5 | import org.springframework.security.web.DefaultSecurityFilterChain; 6 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 7 | 8 | public class JwtSecurityConfigurer extends SecurityConfigurerAdapter { 9 | 10 | 11 | private final JwtTokenProviderService jwtTokenProviderService; 12 | 13 | public JwtSecurityConfigurer(JwtTokenProviderService jwtTokenProviderService) { 14 | this.jwtTokenProviderService = jwtTokenProviderService; 15 | } 16 | 17 | @Override 18 | public void configure(HttpSecurity http) throws Exception { 19 | 20 | JwtTokenAuthenticationFilter customFilter = new JwtTokenAuthenticationFilter(jwtTokenProviderService); 21 | http.exceptionHandling() 22 | .authenticationEntryPoint(new JwtAuthenticationEntryPoint()) 23 | .and() 24 | .addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/security/jwt/JwtTokenAuthenticationFilter.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.security.jwt; 2 | 3 | import org.springframework.security.core.Authentication; 4 | import org.springframework.security.core.context.SecurityContextHolder; 5 | import org.springframework.util.StringUtils; 6 | import org.springframework.web.filter.GenericFilterBean; 7 | 8 | import javax.servlet.FilterChain; 9 | import javax.servlet.ServletException; 10 | import javax.servlet.ServletRequest; 11 | import javax.servlet.ServletResponse; 12 | import javax.servlet.http.HttpServletRequest; 13 | import java.io.IOException; 14 | 15 | public class JwtTokenAuthenticationFilter extends GenericFilterBean { 16 | 17 | private JwtTokenProviderService jwtTokenProvider; 18 | 19 | public JwtTokenAuthenticationFilter(JwtTokenProviderService jwtTokenProvider) { 20 | this.jwtTokenProvider = jwtTokenProvider; 21 | } 22 | 23 | @Override 24 | public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) 25 | throws IOException, ServletException { 26 | 27 | String token = jwtTokenProvider.resolveToken((HttpServletRequest) req); 28 | 29 | if (!StringUtils.isEmpty(token) && jwtTokenProvider.validateToken(token)) { 30 | Authentication auth = jwtTokenProvider.getAuthentication(token); 31 | 32 | if (auth != null) { 33 | SecurityContextHolder.getContext().setAuthentication(auth); 34 | } 35 | } 36 | filterChain.doFilter(req, res); 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/security/jwt/JwtTokenProviderService.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.security.jwt; 2 | 3 | import com.murray.communications.domain.entities.users.ApplicationUser; 4 | import com.murray.communications.exceptions.InvalidJwtAuthenticationException; 5 | import com.murray.communications.security.jwt.enums.JwtTokenKey; 6 | import io.jsonwebtoken.*; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 9 | import org.springframework.security.core.Authentication; 10 | import org.springframework.security.core.userdetails.UserDetails; 11 | import org.springframework.security.core.userdetails.UserDetailsService; 12 | 13 | import javax.annotation.PostConstruct; 14 | import javax.servlet.http.HttpServletRequest; 15 | import java.time.Instant; 16 | import java.time.temporal.ChronoUnit; 17 | import java.util.Base64; 18 | import java.util.Date; 19 | import java.util.Objects; 20 | import java.util.Optional; 21 | 22 | @Slf4j 23 | public class JwtTokenProviderService { 24 | 25 | public static final String USER = "userId"; 26 | public static final String BANDWIDTH_ID = "bandwidthId"; 27 | public static final String ROLES = "roles"; 28 | private String secretKey; 29 | 30 | private long validityInMilliseconds; 31 | 32 | private UserDetailsService userDetailsService; 33 | 34 | public JwtTokenProviderService(final String secretKey, long validityInMilliseconds, UserDetailsService userDetailsService) { 35 | this.secretKey = secretKey; 36 | this.validityInMilliseconds = validityInMilliseconds; 37 | this.userDetailsService = userDetailsService; 38 | } 39 | 40 | @PostConstruct 41 | protected void init() { 42 | secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes()); 43 | } 44 | 45 | /** 46 | * Create the JWT token which will contain the userName and the user roles 47 | * 48 | * @param applicationUser {@link ApplicationUser} 49 | * @return encrypted string 50 | */ 51 | public String createToken(ApplicationUser applicationUser) { 52 | 53 | Claims claims = Jwts.claims().setSubject(applicationUser.getUsername()); 54 | claims.put(ROLES, applicationUser.gerRolesOnly()); 55 | claims.put(USER, applicationUser.getId()); 56 | claims.put(BANDWIDTH_ID, applicationUser.getBandWidthCredentials().getId()); 57 | 58 | Instant issuedAt = Instant.now().truncatedTo(ChronoUnit.SECONDS); 59 | Instant expiration = issuedAt.plus(3, ChronoUnit.MINUTES); 60 | 61 | return Jwts.builder() 62 | .setClaims(claims) 63 | .setIssuedAt(valueOf(issuedAt)) 64 | .setExpiration(valueOf(expiration)) 65 | .signWith(SignatureAlgorithm.HS256, secretKey) 66 | .compact(); 67 | } 68 | 69 | /** 70 | * Returns the Long value that is stored in the Claims under the claimKey 71 | * @param claimKey String key containing the claims value 72 | * @param claims {@link Claims} 73 | * @return Long 74 | */ 75 | public Optional getLongValueFromClaim(String claimKey,Claims claims){ 76 | 77 | if(Objects.isNull(claims)){ 78 | throw new IllegalArgumentException("Claim was null"); 79 | } 80 | 81 | if(!claims.containsKey(claimKey)){ 82 | throw new IllegalArgumentException(String.format("Claim key %s does not exist",claimKey)); 83 | } 84 | 85 | return Optional.ofNullable(claims.get(claimKey,Long.class)); 86 | 87 | 88 | } 89 | 90 | 91 | 92 | /** 93 | * Checks the http request for an authorization header and return the JWT token if exists. 94 | * 95 | * @param request 96 | * @throws InvalidJwtAuthenticationException 97 | */ 98 | public String readJwtTokenFromAuthorizationHeader(HttpServletRequest request) throws InvalidJwtAuthenticationException { 99 | 100 | 101 | final Optional authHeader = Optional.ofNullable(request.getHeader(JwtTokenKey.AUTHORIZATION.getName())); 102 | 103 | if (!authHeader.isPresent() || !authHeader.orElse("").startsWith(JwtTokenKey.REQUEST_HEADER_PREFIX.getName())) { 104 | throw new InvalidJwtAuthenticationException("Missing authorisation header"); 105 | } 106 | log.info("> Header:{}",authHeader.get()); 107 | 108 | return authHeader.get().substring((JwtTokenKey.REQUEST_HEADER_PREFIX.getName().length())); 109 | } 110 | 111 | 112 | /** 113 | * Returns the Jwt {@link Claims} by parsing the decoded token using the secret key. 114 | * 115 | * @param token jwt encrypted token 116 | * @return {@link Claims} 117 | */ 118 | public Claims getClaimsFrom(final String token) { 119 | 120 | return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody(); 121 | } 122 | 123 | 124 | /** 125 | * Return the bandwidth id from the token 126 | * @param token 127 | * @return 128 | */ 129 | public Long getBandwidthIdFrom(final String token) { 130 | Claims claims = getClaimsFrom(token); 131 | return claims.get(BANDWIDTH_ID,Long.class); 132 | 133 | } 134 | 135 | 136 | /** 137 | * Convert {@link Instant} value to Date 138 | * @param instant 139 | * @return Date 140 | */ 141 | private Date valueOf(Instant instant) { 142 | 143 | return Date.from(instant); 144 | } 145 | 146 | Authentication getAuthentication(String token) { 147 | UserDetails userDetails = this.userDetailsService.loadUserByUsername(getUsername(token)); 148 | return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities()); 149 | } 150 | 151 | private String getUsername(String token) { 152 | return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject(); 153 | } 154 | 155 | String resolveToken(HttpServletRequest req) { 156 | String bearerToken = req.getHeader("Authorization"); 157 | if (bearerToken != null && bearerToken.startsWith("Bearer ")) { 158 | return bearerToken.substring(7, bearerToken.length()); 159 | } 160 | return null; 161 | } 162 | 163 | boolean validateToken(String token) { 164 | try { 165 | Jws claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token); 166 | 167 | if (claims.getBody().getExpiration().before(new Date())) { 168 | return false; 169 | } 170 | 171 | return true; 172 | } catch (JwtException | IllegalArgumentException e) { 173 | throw new InvalidJwtAuthenticationException("Expired or invalid JWT token"); 174 | } 175 | } 176 | 177 | } 178 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/security/jwt/enums/JwtTokenKey.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.security.jwt.enums; 2 | 3 | public enum JwtTokenKey { 4 | 5 | /** 6 | * Encrypted user id key in token 7 | */ 8 | USER_ID("userId"), 9 | 10 | AUTHORIZATION("Authorization"), 11 | 12 | /** 13 | * Encrypted location id key in token 14 | */ 15 | PARENT_COMPANY_ID("bandwithId"), 16 | 17 | /** 18 | * request header prefix 19 | */ 20 | REQUEST_HEADER_PREFIX("Bearer "), 21 | 22 | /** 23 | * claims requests attribute noe 24 | */ 25 | REQUEST_CLAIMS_ATTRIBUTES_NAME("claims"); 26 | 27 | 28 | 29 | private String name; 30 | 31 | JwtTokenKey(String name) { 32 | this.name = name; 33 | } 34 | 35 | public String getName() { 36 | return name; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/security/jwtaudit/AuditingCredentials.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.security.jwtaudit; 2 | 3 | import com.murray.communications.domain.entities.messages.BandWidthCredentials; 4 | import com.murray.communications.domain.entities.users.ApplicationUser; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | import lombok.NonNull; 8 | 9 | /** 10 | * The message auditing credentials and bandwidth credentials, which are passed in JWT header token. 11 | */ 12 | @AllArgsConstructor 13 | public class AuditingCredentials { 14 | 15 | /** 16 | * {@link ApplicationUser} unique identifier 17 | */ 18 | @NonNull 19 | @Getter 20 | private Long userId; 21 | 22 | /** 23 | * {@link BandWidthCredentials} unique identifier 24 | */ 25 | @NonNull 26 | @Getter 27 | private Long bandWidthCredentialsId; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/security/jwtaudit/Credentials.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.security.jwtaudit; 2 | 3 | 4 | import java.lang.annotation.*; 5 | 6 | /** 7 | * This annotation can be used in the FS Controllers. It prefixes a method parameter indicating 8 | * that it's preceding object contains user credentials which are parsed from the request attribute 9 | * by a Spring Mthod Resolver.
10 | * A sample of how this is declared: 11 | *
12 |  * 
13 |  *   public SmsDto save(
14 |  *     @RequestBody SmsDto dto ,
15 |  *     @Credentials AuditingCredentials auditingCredentials)
16 |  * 
17 |  * 
18 | */ 19 | @Target(ElementType.PARAMETER) 20 | @Retention(RetentionPolicy.RUNTIME) 21 | @Documented 22 | public @interface Credentials { 23 | 24 | boolean required() default true; 25 | } -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/security/jwtaudit/JwtClaimsToAuditingCredientalsResolver.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.security.jwtaudit; 2 | 3 | import com.murray.communications.security.jwt.JwtTokenProviderService; 4 | import io.jsonwebtoken.Claims; 5 | import org.springframework.core.MethodParameter; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.security.authentication.AuthenticationServiceException; 8 | import org.springframework.web.bind.support.WebDataBinderFactory; 9 | import org.springframework.web.client.HttpClientErrorException; 10 | import org.springframework.web.context.request.NativeWebRequest; 11 | import org.springframework.web.method.support.HandlerMethodArgumentResolver; 12 | import org.springframework.web.method.support.ModelAndViewContainer; 13 | 14 | import javax.servlet.http.HttpServletRequest; 15 | import java.util.Objects; 16 | 17 | public class JwtClaimsToAuditingCredientalsResolver implements HandlerMethodArgumentResolver { 18 | 19 | private final JwtTokenProviderService jwtTokenProviderService; 20 | 21 | public JwtClaimsToAuditingCredientalsResolver(JwtTokenProviderService jwtTokenProviderService) { 22 | this.jwtTokenProviderService = jwtTokenProviderService; 23 | } 24 | 25 | @Override 26 | public boolean supportsParameter(MethodParameter parameter) { 27 | return parameter.hasParameterAnnotation(Credentials.class) 28 | && parameter.getParameterType().equals(AuditingCredentials.class); 29 | } 30 | 31 | @Override 32 | public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest request, WebDataBinderFactory webDataBinderFactory) throws Exception { 33 | 34 | 35 | AuditingCredentials credentials = toUserCredentials(request); 36 | 37 | if (Objects.isNull(credentials)) { 38 | throw new AuthenticationServiceException("Auditing credentials are invalid"); 39 | } 40 | 41 | return credentials; 42 | } 43 | 44 | /** 45 | * Convert the Claims Token values into {@link AuditingCredentials} 46 | * 47 | * @param request {@link NativeWebRequest} containing the JWY token 48 | * @return {@link AuditingCredentials} 49 | */ 50 | AuditingCredentials toUserCredentials(NativeWebRequest request) { 51 | 52 | Claims claims = readClaimsFrom(request); 53 | 54 | if (Objects.isNull(claims)) { 55 | throw new HttpClientErrorException(HttpStatus.FORBIDDEN, "No claims found in request"); 56 | } 57 | 58 | Long userid = jwtTokenProviderService.getLongValueFromClaim(JwtTokenProviderService.USER, claims) 59 | .orElseThrow(() -> new AuthenticationServiceException("No user id found in header")); 60 | 61 | Long bandWidthId = jwtTokenProviderService.getLongValueFromClaim(JwtTokenProviderService.BANDWIDTH_ID, claims) 62 | .orElseThrow(() -> new AuthenticationServiceException("No bandwidth id found in header")); 63 | 64 | 65 | return new AuditingCredentials(userid, bandWidthId); 66 | } 67 | 68 | /** 69 | * Return the claims object that is a request attribute 70 | */ 71 | Claims readClaimsFrom(NativeWebRequest nativeWebRequest) { 72 | final String token = jwtTokenProviderService.readJwtTokenFromAuthorizationHeader(nativeWebRequest.getNativeRequest(HttpServletRequest.class)); 73 | return jwtTokenProviderService.getClaimsFrom(token); 74 | 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/services/formatters/CustomDateFormatter.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.services.formatters; 2 | 3 | import java.time.format.DateTimeFormatter; 4 | 5 | /** 6 | * Handle all internal date formatting to covert from string to java.time.* 7 | */ 8 | public interface CustomDateFormatter { 9 | 10 | 11 | /** 12 | * Return the {@link DateTimeFormatter} used for formatting 13 | * {@link java.time.LocalDate} 14 | * 15 | * @return {@link DateTimeFormatter} 16 | */ 17 | DateTimeFormatter localDateFormatter(); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/services/formatters/impl/CustomDateFormatterImpl.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.services.formatters.impl; 2 | 3 | import com.murray.communications.services.formatters.CustomDateFormatter; 4 | 5 | import java.time.format.DateTimeFormatter; 6 | 7 | /** 8 | * {@inheritDoc} 9 | */ 10 | public class CustomDateFormatterImpl implements CustomDateFormatter { 11 | 12 | 13 | private DateTimeFormatter localDateFormatter = DateTimeFormatter.ofPattern("MM/dd/yyyy"); 14 | 15 | /** 16 | * {@inheritDoc} 17 | */ 18 | @Override 19 | public DateTimeFormatter localDateFormatter() { 20 | 21 | return localDateFormatter; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/services/messages/Impl/BandwidthMessageSender.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.services.messages.Impl; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.murray.communications.domain.entities.messages.SmsMessage; 6 | import com.murray.communications.dtos.enums.MessageStatus; 7 | import com.murray.communications.domain.entities.messages.BandWidthCredentials; 8 | import com.murray.communications.exceptions.BandWidthException; 9 | import lombok.extern.slf4j.Slf4j; 10 | import okhttp3.*; 11 | 12 | import java.io.IOException; 13 | import java.time.LocalDate; 14 | import java.util.Arrays; 15 | import java.util.Hashtable; 16 | import java.util.Map; 17 | 18 | 19 | /** 20 | * Responsible for building the message HTTP request and handling the response 21 | */ 22 | @Slf4j 23 | class BandwidthMessageSender { 24 | 25 | static final MediaType JSON = MediaType.get("application/json; charset=utf-8"); 26 | static final String HTTPS_MESSAGING_BANDWIDTH_COM_API_V_2_USERS_S_MESSAGES = "https://messaging.bandwidth.com/api/v2/users/%s/messages"; 27 | private final ObjectMapper objectMapper; 28 | 29 | BandwidthMessageSender() { 30 | this.objectMapper = new ObjectMapper(); 31 | } 32 | 33 | /** 34 | * Send {@link SmsMessage} to bandwidth messaging service for handling. 35 | * 36 | * @param smsMessage 37 | * @return 38 | */ 39 | public int send(final SmsMessage smsMessage) throws BandWidthException { 40 | 41 | 42 | if (!smsMessage.getStatus().equals(MessageStatus.CREATED) || smsMessage.isDeleted() 43 | || smsMessage.getSendDate().isBefore(LocalDate.now())) { 44 | log.warn("Skip sending message:{} because status:{}", smsMessage.getId(), smsMessage.getStatus()); 45 | throw new BandWidthException(String.format("Message %s not valid for processing ",smsMessage.getId())); 46 | } 47 | 48 | try { 49 | 50 | OkHttpClient client = new OkHttpClient(); 51 | 52 | Request request = buildPost(smsMessage); 53 | 54 | Response response = client.newCall(request).execute(); 55 | if (!response.isSuccessful()) { 56 | log.error("Error sending message:{} code:{} response:{}", smsMessage.getId(), response.code(), response.body().string()); 57 | throw new BandWidthException("Error sending message", response.code(), response.body().string()); 58 | } 59 | log.info("message successfully processed code:{}, response:{}", response.code(), response.body().string()); 60 | return response.code(); 61 | 62 | } catch (IOException e) { 63 | log.error("Issue sending message:{}", smsMessage.getId(), e); 64 | throw new BandWidthException("Error sending message", e); 65 | } 66 | 67 | } 68 | 69 | /** 70 | * Build http post request using the message and badwidth credientals 71 | * 72 | * @param smsMessage 73 | * @return Request 74 | * @throws JsonProcessingException 75 | */ 76 | private Request buildPost(final SmsMessage smsMessage) throws JsonProcessingException { 77 | 78 | RequestBody body = RequestBody.create(JSON, toJson(smsMessage)); 79 | 80 | String authorization = getAuthorization(smsMessage.getBandWidthCredentials().getMessageApi(), smsMessage.getBandWidthCredentials().getMessageSecret()); 81 | 82 | Request request = new Request.Builder() 83 | .url(toUrl(smsMessage.getBandWidthCredentials())) 84 | .post(body) 85 | .addHeader("Content-Type", "application/json") 86 | .addHeader("Authorization", authorization) 87 | .addHeader("Accept", "*/*") 88 | .addHeader("Cache-Control", "no-cache") 89 | .addHeader("Host", "messaging.bandwidth.com") 90 | .addHeader("Accept-Encoding", "gzip, deflate") 91 | .addHeader("Content-Length", "200") 92 | .addHeader("Connection", "keep-alive") 93 | .addHeader("cache-control", "no-cache") 94 | .build(); 95 | 96 | return request; 97 | 98 | } 99 | 100 | /** 101 | * Generate the basic authorisation header value used in the http header 102 | * 103 | * @param token 104 | * @param secret 105 | * @return 106 | */ 107 | private String getAuthorization(String token, String secret) { 108 | return Credentials.basic(token, secret); 109 | 110 | } 111 | 112 | 113 | /** 114 | * Build the bandwidth url that is used for posting message 115 | * 116 | * @param bandWidthCredentials 117 | * @return 118 | */ 119 | private String toUrl(BandWidthCredentials bandWidthCredentials) { 120 | 121 | return String.format(HTTPS_MESSAGING_BANDWIDTH_COM_API_V_2_USERS_S_MESSAGES, bandWidthCredentials.getDashboardAccountId()); 122 | } 123 | 124 | /** 125 | * Converts the {@link SmsMessage} into json form that can be supported by bandwidth 126 | * 127 | * @param smsMessage 128 | * @return 129 | * @throws JsonProcessingException 130 | */ 131 | private String toJson(SmsMessage smsMessage) throws JsonProcessingException { 132 | 133 | Map payload = new Hashtable<>(); 134 | payload.put("to", Arrays.asList("+" + smsMessage.getReceiver())); 135 | payload.put("from", Long.toString(smsMessage.getBandWidthCredentials().getSenderPhoneNumber())); 136 | payload.put("text", smsMessage.getMessage()); 137 | payload.put("tag", smsMessage.getId()); 138 | payload.put("applicationId", smsMessage.getBandWidthCredentials().getApplicationId()); 139 | 140 | return objectMapper.writeValueAsString(payload); 141 | } 142 | 143 | 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/services/messages/Impl/MessageServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.services.messages.Impl; 2 | 3 | import com.murray.communications.security.jwtaudit.AuditingCredentials; 4 | import com.murray.communications.services.messages.MessageService; 5 | import com.murray.communications.domain.entities.messages.SmsMessage; 6 | import com.murray.communications.domain.entities.users.ApplicationUser; 7 | import com.murray.communications.domain.respository.messages.SmsMessageRepository; 8 | import com.murray.communications.domain.respository.users.ApplicationUserRepository; 9 | import com.murray.communications.dtos.enums.MessageStatus; 10 | import com.murray.communications.exceptions.BandWidthException; 11 | import com.murray.communications.exceptions.MessageNotFoundException; 12 | import com.murray.communications.exceptions.MessageProcessingException; 13 | import lombok.NonNull; 14 | import lombok.extern.slf4j.Slf4j; 15 | import org.springframework.transaction.annotation.Transactional; 16 | 17 | import java.time.LocalDate; 18 | import java.time.LocalDateTime; 19 | 20 | /** 21 | * {@inheritDoc} 22 | */ 23 | @Slf4j 24 | public class MessageServiceImpl implements MessageService { 25 | 26 | private final SmsMessageRepository smsMessageRepository; 27 | 28 | private final ApplicationUserRepository userRepository; 29 | 30 | private final BandwidthMessageSender smsSender; 31 | 32 | public MessageServiceImpl(SmsMessageRepository smsMessageRepository, ApplicationUserRepository userRepository) { 33 | this.smsMessageRepository = smsMessageRepository; 34 | this.userRepository = userRepository; 35 | this.smsSender = new BandwidthMessageSender(); 36 | } 37 | 38 | /** 39 | * {@inheritDoc} 40 | */ 41 | @Override 42 | public SmsMessage findBy(Long id) { 43 | return smsMessageRepository.findById(id) 44 | .orElseThrow(() -> new MessageNotFoundException("No messaged found for id:" + id)); 45 | } 46 | 47 | /** 48 | * {@inheritDoc} 49 | */ 50 | @Transactional 51 | @Override 52 | public SmsMessage createSms(@NonNull AuditingCredentials auditingCredentials, @NonNull Long receiver, @NonNull String message, @NonNull LocalDate sendOn) { 53 | 54 | ApplicationUser user = userRepository.getOne(auditingCredentials.getUserId()); 55 | 56 | log.info("saving sms sender:{} , receiver:{}, message:{}", user.getBandWidthCredentials().getSenderPhoneNumber(), receiver, message); 57 | SmsMessage smsMessage = new SmsMessage(receiver,user.getBandWidthCredentials().getSenderPhoneNumber(), message); 58 | smsMessage.setBandWidthCredentials(user.getBandWidthCredentials()); 59 | smsMessage.setSendDate(sendOn); 60 | smsMessage.setCreatedDate(LocalDateTime.now()); 61 | smsMessage.setCreatedBy(user); 62 | 63 | return smsMessageRepository.save(smsMessage); 64 | } 65 | 66 | /** 67 | * {@inheritDoc} 68 | */ 69 | @Override 70 | public SmsMessage update(@NonNull AuditingCredentials auditingCredentials, SmsMessage smsMessage) { 71 | 72 | log.info("updating sms:{}", smsMessage); 73 | SmsMessage message = smsMessageRepository.findById(smsMessage.getId()) 74 | .orElseThrow(() -> new MessageNotFoundException("No messaged found for id:" + smsMessage.getId())); 75 | 76 | message.setStatus(smsMessage.getStatus()); 77 | message.setReceiver(smsMessage.getReceiver()); 78 | message.setSender(smsMessage.getSender()); 79 | message.setMessage(smsMessage.getMessage()); 80 | message.setSendDate(smsMessage.getSendDate()); 81 | 82 | ApplicationUser user = userRepository.getOne(auditingCredentials.getUserId()); 83 | message.setLastModifiedDate(LocalDateTime.now()); 84 | message.setLastModifiedBy(user); 85 | 86 | return smsMessageRepository.save(message); 87 | } 88 | 89 | /** 90 | * {@inheritDoc} 91 | */ 92 | @Override 93 | public void deleteMessageBy(@NonNull AuditingCredentials auditingCredentials, Long id) { 94 | 95 | log.info("deleting sms:{}", id); 96 | SmsMessage message = smsMessageRepository.findById(id) 97 | .orElseThrow(() -> new MessageNotFoundException("No messaged found for id:" + id)); 98 | 99 | ApplicationUser user = userRepository.getOne(auditingCredentials.getUserId()); 100 | 101 | message.setStatus(MessageStatus.DELETED); 102 | message.setDeleted(true); 103 | message.setLastModifiedDate(LocalDateTime.now()); 104 | message.setLastModifiedBy(user); 105 | 106 | smsMessageRepository.save(message); 107 | } 108 | 109 | /** 110 | * {@inheritDoc} 111 | */ 112 | @Transactional 113 | @Override 114 | public void sendSms(AuditingCredentials credentials, Long id) { 115 | 116 | log.info("attempting to send sms:{}", id); 117 | SmsMessage message = smsMessageRepository.findById(id) 118 | .orElseThrow(() -> new MessageNotFoundException("No messaged found for id:" + id)); 119 | 120 | //send 121 | log.warn("SENDING SMS {} with app id details {}",message.getId(),message.getBandWidthCredentials().getApplicationId()); 122 | 123 | MessageStatus newStatus = sendMessage(message); 124 | 125 | ApplicationUser user = userRepository.getOne(credentials.getUserId()); 126 | message.setStatus(newStatus); 127 | message.setLastModifiedDate(LocalDateTime.now()); 128 | message.setLastModifiedBy(user); 129 | 130 | smsMessageRepository.save(message); 131 | 132 | if(newStatus.equals(MessageStatus.ERROR)){ 133 | throw new MessageProcessingException("Issue processing message with bandwidth with id:"+message.getId()); 134 | } 135 | 136 | } 137 | 138 | /** 139 | * 140 | * @param message 141 | * @return 142 | */ 143 | private MessageStatus sendMessage(SmsMessage message){ 144 | 145 | MessageStatus newStatus = MessageStatus.PROCESSING; 146 | 147 | try { 148 | int result = smsSender.send(message); 149 | log.info("Message id:{} sent successfully to bandwith for processing response code:{}",message.getId(),result); 150 | 151 | } catch (BandWidthException e) { 152 | 153 | log.error("Error sending message to bandwith with id:{}",message.getId(),e); 154 | 155 | newStatus = MessageStatus.ERROR; 156 | } 157 | 158 | return newStatus; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/services/messages/MessageService.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.services.messages; 2 | 3 | import com.murray.communications.security.jwtaudit.AuditingCredentials; 4 | import com.murray.communications.domain.entities.messages.SmsMessage; 5 | 6 | import java.time.LocalDate; 7 | import java.time.ZonedDateTime; 8 | 9 | public interface MessageService { 10 | 11 | 12 | /** 13 | * Create a new SMS message that will be sent using the sender number to the receiver number 14 | * and will be sent on the sendOn date. 15 | * 16 | * @param credentials user details that are creating the 17 | * @param receiver integer receiver number to receive the message 18 | * @param message string message 19 | * @param sendOn {@link ZonedDateTime} when the message should be sent 20 | * @return SmsMessage 21 | */ 22 | SmsMessage createSms(final AuditingCredentials credentials, final Long receiver, final String message, final LocalDate sendOn); 23 | 24 | 25 | /** 26 | * find {@link SmsMessage} by long 27 | * 28 | * @param id 29 | * @return 30 | */ 31 | SmsMessage findBy(Long id); 32 | 33 | /** 34 | * Update sms message detals 35 | * 36 | * @param credentials user details that are creating the 37 | * @param smsMessage 38 | * @return 39 | */ 40 | SmsMessage update(AuditingCredentials credentials, SmsMessage smsMessage); 41 | 42 | /** 43 | * Delete messsage by id. 44 | * 45 | * @param id 46 | */ 47 | void deleteMessageBy(AuditingCredentials credentials,Long id); 48 | 49 | 50 | 51 | /** 52 | * Send SMS message to external source for sending 53 | * @param credentials 54 | * @param id 55 | */ 56 | void sendSms(AuditingCredentials credentials , Long id); 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/services/users/UserAuthenticatonService.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.services.users; 2 | 3 | public interface UserAuthenticatonService { 4 | 5 | /** 6 | * Checks if User exist for the user name and password and if found then 7 | * generate the JWT token that can be used in further requests. 8 | * 9 | * @param userName 10 | * @param password 11 | * @return 12 | */ 13 | String signIn(final String userName, final String password); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/murray/communications/services/users/impl/UserAuthenticationServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.services.users.impl; 2 | 3 | import com.murray.communications.domain.entities.users.ApplicationUser; 4 | import com.murray.communications.domain.respository.users.ApplicationUserRepository; 5 | import com.murray.communications.security.jwt.JwtTokenProviderService; 6 | import com.murray.communications.services.users.UserAuthenticatonService; 7 | import lombok.NonNull; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.security.authentication.AuthenticationManager; 11 | import org.springframework.security.authentication.BadCredentialsException; 12 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 13 | import org.springframework.security.core.AuthenticationException; 14 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 15 | import org.springframework.transaction.annotation.Transactional; 16 | 17 | public class UserAuthenticationServiceImpl implements UserAuthenticatonService { 18 | 19 | private static final Logger LOGGER = LoggerFactory.getLogger(UserAuthenticationServiceImpl.class); 20 | 21 | 22 | private final AuthenticationManager authenticationManager; 23 | 24 | private final ApplicationUserRepository userRepository; 25 | 26 | private final JwtTokenProviderService jwtTokenProviderService; 27 | 28 | public UserAuthenticationServiceImpl(AuthenticationManager authenticationManager, ApplicationUserRepository userRepository, 29 | JwtTokenProviderService jwtTokenProviderService) { 30 | this.authenticationManager = authenticationManager; 31 | this.userRepository = userRepository; 32 | this.jwtTokenProviderService = jwtTokenProviderService; 33 | } 34 | 35 | /** 36 | * {@inheritDoc} 37 | **/ 38 | @Transactional 39 | @Override 40 | public String signIn(@NonNull String userName, @NonNull String password) { 41 | try { 42 | LOGGER.debug("User {} attempting signin with pwd:{}", userName, password); 43 | authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(userName, password)); 44 | ApplicationUser userFound = 45 | this.userRepository.findByUsernameAndPassword(userName, password) 46 | .orElseThrow(() -> new UsernameNotFoundException("Username " + userName + " not found")); 47 | 48 | return jwtTokenProviderService.createToken(userFound); 49 | 50 | 51 | } catch (AuthenticationException e) { 52 | throw new BadCredentialsException("Invalid username/password supplied", e); 53 | } 54 | } 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/resources/application-h2.properties: -------------------------------------------------------------------------------- 1 | #H2 database connection 2 | spring.datasource.url=jdbc:h2:mem:testdb 3 | spring.datasource.driverClassName=org.h2.Driver 4 | spring.datasource.username=sa 5 | spring.datasource.password=sa 6 | spring.jpa.database-platform=org.hibernate.dialect.H2Dialect 7 | 8 | 9 | spring.h2.console.path=/h2-console 10 | spring.h2.console.settings.trace=false 11 | spring.h2.console.settings.web-allow-others=false 12 | spring.h2.console.enabled=true -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | #By default the application is configured to use H2 2 | #In order to change to run against MySQl change active profile to mysql 3 | spring.profiles.active=h2 4 | #anti pattern which should be disable and aid LazyInitializationException 5 | spring.jpa.open-in-view=false 6 | #jackson setup 7 | spring.jackson.mapper.default-view-inclusion=true 8 | spring.jackson.serialization.write-dates-as-timestamps=false 9 | spring.jackson.serialization.indent-output=true 10 | spring.jackson.deserialization.fail-on-ignored-properties=true 11 | 12 | spring.jackson.default-property-inclusion=non_empty 13 | #spring.jackson.date-format=com.fasterxml.jackson.databind.util.ISO8601DateFormat 14 | spring.jackson.time-zone=UTC 15 | #default logging 16 | logging.level.org.springframework.web=INFO 17 | logging.level.org.springframework.security=DEBUG 18 | logging.level.com.murray.communications=DEBUG 19 | logging.file=comms.log 20 | logging.file.max-size=500MB 21 | logging.file.max-history=30 22 | 23 | #define the base url used 24 | communication.base-url=http://localhost:8080 -------------------------------------------------------------------------------- /src/main/resources/data.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO application_role VALUES (1, 'ROLE_ADMIN'); 2 | INSERT INTO application_role VALUES (2, 'ROLE_USER'); 3 | 4 | -- admin user 5 | 6 | insert into application_user (user_id,username, password, enabled) 7 | values (1,''admin@admin.com, 'admin', true); 8 | insert into application_user_role (user_id, role_id) 9 | values (1,1); 10 | insert into application_user_role (user_id, role_id) 11 | values (1,2); 12 | 13 | 14 | insert into bandwidth_credentials (id,version, created_by, created_date, last_modified_by, last_modified_date, 15 | message_api, message_secret,application_name ,application_id, sender_phone_number,dashboard_user_name, dashboard_pwd, 16 | dashboard_account_id,dashboard_sub_account_id) 17 | values (1,1, 1,now(), 1, now(),'meessageapi','messagesecret','test-location-application', 18 | 'e5bca7db-1158-40a3-8b74-sadsa',5012141397, 19 | 'userName','pwd','512312987','24122'); 20 | 21 | 22 | insert into user_bandwidth_credentials (user_id,bandwidth_id) values(1,1); 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/main/resources/messages_en.properties: -------------------------------------------------------------------------------- 1 | #header 2 | header.menu.home=Home 3 | header.menu.Logout=Logout 4 | header.menu.Login=Login 5 | header.menu.admin=Admin 6 | header.menu.user=Users 7 | header.menu.sms=SMS 8 | header.menu.swagger=Swagger 9 | 10 | index.title=Communication Admin 11 | index.secure.welcome=Welcome {0} to the communication admin 12 | index.welcome=Welcome to communication admin 13 | index.login.please=It is necessary to login to perform any action in the communication admin 14 | index.login.login.button=Login 15 | 16 | #sms overview 17 | sms.overview.title=SMS Overview 18 | sms.overview.header=SMS message Overview 19 | sms.overview.message.summary=Latest messages 20 | sms.overview.found.message={0} Message(s) found 21 | 22 | sms.overview.delete.model.header=Delete Message 23 | sms.overview.delete.model.body=Are you sure want to delete this message? 24 | #SMS left nav options 25 | sms.left.nav.options=SMS 26 | sms.left.nav.option.overview=Overview 27 | sms.left.nav.option.new-message=New SMS 28 | sms.left.nav.option.search-messages=Search SMS 29 | sms.left.nav.option.message-errors=SMS Errors 30 | 31 | #New sms message 32 | sms.new.title=Create SMS Message 33 | sms.new.header=Save SMS Message 34 | sms.new.form.sender=Sender Number 35 | sms.new.form.receiver=Receiver Number 36 | sms.new.form.message=Message 37 | sms.new.form.send=Send On 38 | sms.new.form.save=Save 39 | #view message 40 | sms.edit.title=SMS Message 41 | sms.edit.header=SMS Message Details 42 | sms.edit.form.id=Unique identifier 43 | sms.edit.form.status=Status 44 | sms.edit.form.sender=Sender Number 45 | sms.edit.form.receiver=Receiver Number 46 | sms.edit.form.message=Message 47 | sms.edit.form.send=Send On 48 | sms.edit.form.bandwidth.app.name=Bandwidth Name 49 | sms.edit.form.update=Update 50 | sms.edit.form.cancel=Cancel 51 | sms.edit.form.send.message=Send SMS 52 | sms.edit.message.created=SMS successfully created 53 | sms.edit.message.error=Opps, something happened whilst saving SMS. 54 | sms.edit.send.message.model.header=Send Message 55 | sms.edit.send.message.model.body=Are you sure want to send this message? 56 | 57 | 58 | 59 | #login form view 60 | login.form.login.title=Login 61 | login.form.heading=Login page 62 | login.form.userName=User name 63 | login.form.password=Password 64 | login.form.login=Log In 65 | login.form.cancel=Cancel 66 | login.form.loggedOut=You have been logged out. 67 | login.form.invalid.details=Invalid username or password. 68 | 69 | 70 | 71 | 72 | #table headers 73 | admin.sms.latest.messages=Latest message sent 74 | admin.sms.table.header.createdOn=Created 75 | admin.sms.table.header.sender=Sender 76 | admin.sms.table.header.receiver=Receiver 77 | admin.sms.table.header.status=Status 78 | admin.sms.table.header.sentOn=Sent On 79 | admin.sms.table.header.message=Message 80 | admin.sms.table.header.actions=Actions 81 | 82 | 83 | -------------------------------------------------------------------------------- /src/main/resources/static/css/main.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | color: #78ab46; 3 | } 4 | 5 | footer { 6 | position: fixed; 7 | height: 50px; 8 | bottom: 0; 9 | width: 100%; 10 | background-color: #ccc 11 | } 12 | 13 | footer p { 14 | padding: 15px; 15 | } 16 | .table>tbody>tr>td>span.message-cut{ 17 | float: left; 18 | white-space: nowrap; 19 | width: 100px; 20 | overflow: hidden; 21 | text-overflow: ellipsis; 22 | } -------------------------------------------------------------------------------- /src/main/resources/static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garytxo/spring-web-and-rest-thymeleaf/0fa03044ce5e563f43b575b6dfa84e09f202bf0b/src/main/resources/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /src/main/resources/static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garytxo/spring-web-and-rest-thymeleaf/0fa03044ce5e563f43b575b6dfa84e09f202bf0b/src/main/resources/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /src/main/resources/static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garytxo/spring-web-and-rest-thymeleaf/0fa03044ce5e563f43b575b6dfa84e09f202bf0b/src/main/resources/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /src/main/resources/static/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garytxo/spring-web-and-rest-thymeleaf/0fa03044ce5e563f43b575b6dfa84e09f202bf0b/src/main/resources/static/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /src/main/resources/static/img/favgreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garytxo/spring-web-and-rest-thymeleaf/0fa03044ce5e563f43b575b6dfa84e09f202bf0b/src/main/resources/static/img/favgreen.png -------------------------------------------------------------------------------- /src/main/resources/templates/error/access-denied.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | Access Denied 7 | 8 | 9 |
10 |

Access denied

11 |

Back to home page

12 |
13 | 14 | -------------------------------------------------------------------------------- /src/main/resources/templates/error/error.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | Opps, what happened 7 | 8 | 9 |
10 |

Opps, something went wrong

11 |

12 |

13 |

Back to home page

14 |
15 | 16 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/Layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Navbar Template for Bootstrap 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 |
7 |
8 |

9 | © Company.com 10 | 11 | | Logged user: | 12 | Roles: | 13 | Sign Out 14 | 15 |

16 |
17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 |
16 |

17 |

18 |

To be completed!!!!!

19 | 20 |
21 | 22 |
23 |

24 | 25 |

26 |

27 | 28 | 29 | 30 |

31 |
32 | 33 |
34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/main/resources/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 |
10 |
11 |
12 |

13 |
14 |
15 |
16 | 17 |
18 |
19 |
20 |
21 | 22 |
23 |
24 |
25 | : 26 | 32 |
33 |
34 | : 35 | 40 |
41 |
42 |
43 |
44 | 45 | 46 | 47 | 53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | 61 | -------------------------------------------------------------------------------- /src/main/resources/templates/sms/edit-sms.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 |
15 |
16 |

17 |
18 |
19 | 20 | 21 | 48 | 49 | 50 | 51 |
52 |

53 |
54 |
55 | 62 | 63 | 64 |
65 | : 66 | 73 |
74 | 75 |
76 | : 77 | 84 | 85 | 88 | 89 |
90 | 91 |
92 | : 93 | 99 | 102 |
103 |
104 | : 105 | 111 |
112 |
113 | : 114 |
115 | 117 | 118 | 119 | 120 | 121 | 124 | 125 |
126 |
127 |
128 | : 129 | 136 | 139 |
140 | 141 |
142 | 143 | 144 | 145 | 151 | 152 | 153 | 155 | 156 | 157 |
158 |
159 | 160 | 181 |
182 | 183 |
184 | 185 |
186 | 187 | 188 |
189 | 190 | 191 | 206 | 207 | 208 | -------------------------------------------------------------------------------- /src/main/resources/templates/sms/new-sms.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 |
15 |
16 |

17 |
18 |
19 | 20 | 21 | 48 | 49 | 50 | 51 |
52 |

53 |
54 |
55 | 56 | 73 | 74 |
75 | : 76 | 82 | 85 |
86 |
87 | : 88 |
89 | 91 | 92 | 93 | 94 | 95 | 98 | 99 |
100 |
101 |
102 | : 103 | 110 | 113 |
114 | 115 |
116 | 122 |
123 |
124 | 125 |
126 | 127 |
128 | 129 |
130 | 131 | 132 |
133 | 134 | 135 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /src/main/resources/templates/sms/overview.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 |
15 |
16 |

17 |
18 |
19 | 20 | 21 | 49 | 50 | 51 | 52 | 53 |
54 |

55 |
56 | 57 |

58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 81 | 97 | 98 | 99 | 120 | 121 | 122 |
79 | 80 | 82 |
83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 |
96 |
123 | 124 | 125 | 126 | 142 | 143 |
144 |
145 | 146 |
147 | 148 |
149 | 150 | 151 |
152 | 153 | -------------------------------------------------------------------------------- /src/test/java/com/murray/communications/CommunicationsApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class CommunicationsApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/com/murray/communications/config/TestConfig.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.client.RestTemplate; 6 | 7 | @Configuration 8 | public class TestConfig { 9 | 10 | @Bean 11 | public RestTemplate restTemplate() { 12 | return new RestTemplate(); 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /src/test/java/com/murray/communications/controllers/SmsRestControllerITTest.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.controllers; 2 | 3 | import com.murray.communications.controllers.rest.UserAuthenticationRequest; 4 | import com.murray.communications.dtos.enums.MessageStatus; 5 | import com.murray.communications.dtos.rest.SmsDto; 6 | import com.murray.communications.security.jwt.enums.JwtTokenKey; 7 | import com.murray.communications.config.TestConfig; 8 | import io.restassured.RestAssured; 9 | import io.restassured.http.ContentType; 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.junit.runner.RunWith; 13 | import org.springframework.boot.test.context.SpringBootTest; 14 | import org.springframework.boot.web.server.LocalServerPort; 15 | import org.springframework.context.annotation.Import; 16 | import org.springframework.test.context.jdbc.Sql; 17 | import org.springframework.test.context.junit4.SpringRunner; 18 | 19 | import java.time.LocalDate; 20 | 21 | import static io.restassured.RestAssured.given; 22 | import static org.hamcrest.MatcherAssert.assertThat; 23 | import static org.hamcrest.core.Is.is; 24 | import static org.hamcrest.core.IsEqual.equalTo; 25 | import static org.hamcrest.core.IsNull.*; 26 | 27 | @RunWith(SpringRunner.class) 28 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) 29 | @Import(TestConfig.class) 30 | @Sql("classpath:test-data.sql") 31 | public class SmsRestControllerITTest { 32 | 33 | 34 | 35 | @LocalServerPort 36 | private int port; 37 | 38 | private String token; 39 | 40 | @Before 41 | public void setup() { 42 | RestAssured.port = this.port; 43 | token = given() 44 | .contentType(ContentType.JSON) 45 | .body(UserAuthenticationRequest.builder().username("gary@email.com").password("gary").build()) 46 | .when().post("/auth/signin") 47 | .andReturn().asString(); 48 | } 49 | 50 | @Test 51 | public void delete_return_200(){ 52 | 53 | //@formatter:off 54 | given() 55 | .header(JwtTokenKey.AUTHORIZATION.getName(), JwtTokenKey.REQUEST_HEADER_PREFIX.getName()+token) 56 | .contentType(ContentType.JSON) 57 | 58 | .when() 59 | .delete("/v1/sms/3") 60 | 61 | .then() 62 | .statusCode(200); 63 | //@formatter:on 64 | } 65 | 66 | @Test 67 | public void put_send_message_invalid_return400(){ 68 | //@formatter:off 69 | given() 70 | .header("Authorization", "Bearer "+token) 71 | .contentType(ContentType.JSON) 72 | .when() 73 | .put("/v1/sms/3/send") 74 | .then() 75 | .statusCode(400); 76 | //@formatter:on 77 | } 78 | 79 | @Test 80 | public void put_send_message_valid_return200(){ 81 | //@formatter:off 82 | given() 83 | .header("Authorization", "Bearer "+token) 84 | .contentType(ContentType.JSON) 85 | .when() 86 | .put("/v1/sms/1/send") 87 | .then() 88 | .statusCode(200); 89 | //@formatter:on 90 | } 91 | 92 | @Test 93 | public void put_update_return_200(){ 94 | 95 | LocalDate eightDays = LocalDate.now().plusDays(8); 96 | SmsDto update = new SmsDto(); 97 | update.setStatus(MessageStatus.CREATED); 98 | update.setId(1L); 99 | update.setReceiver(999999L); 100 | update.setSender(8888888L); 101 | update.setMessage("Replace message"); 102 | update.setSendOn(eightDays); 103 | 104 | //@formatter:off 105 | SmsDto result = given() 106 | .header("Authorization", "Bearer "+token) 107 | .contentType(ContentType.JSON) 108 | .body(update) 109 | 110 | .when() 111 | .put("/v1/sms/1") 112 | 113 | .then() 114 | .statusCode(200) 115 | .extract().as(SmsDto.class); 116 | //@formatter:on 117 | 118 | assertThat(result.getId(), is(equalTo(update.getId()))); 119 | assertThat(result.getMessage(), is(equalTo(update.getMessage()))); 120 | assertThat(result.getReceiver(), is(equalTo(update.getReceiver()))); 121 | assertThat(result.getSender(), is(equalTo(update.getSender()))); 122 | assertThat(result.getSendOn(), is(equalTo(update.getSendOn()))); 123 | assertThat(result.getStatus(),is(equalTo(MessageStatus.CREATED))); 124 | 125 | } 126 | 127 | @Test 128 | public void get_message_returns_200(){ 129 | 130 | //@formatter:off 131 | SmsDto result = given() 132 | .header("Authorization", "Bearer "+token) 133 | .contentType(ContentType.JSON) 134 | .when() 135 | .get("/v1/sms/1") 136 | 137 | .then() 138 | .statusCode(200) 139 | .extract().as(SmsDto.class); 140 | //@formatter:on 141 | } 142 | 143 | @Test 144 | public void get_not_exist_message_returns_404(){ 145 | 146 | //@formatter:off 147 | given() 148 | .header("Authorization", "Bearer "+token) 149 | .contentType(ContentType.JSON) 150 | 151 | .when() 152 | .get("/v1/sms/190239") 153 | 154 | .then() 155 | .statusCode(404); 156 | //@formatter:on 157 | } 158 | 159 | @Test 160 | public void post_sms_returns_201() { 161 | 162 | SmsDto dto = new SmsDto(); 163 | dto.setMessage("test message"); 164 | dto.setReceiver(222222L); 165 | dto.setSender(22223232L ); 166 | dto.setSendOn(LocalDate.now().plusDays(11L)); 167 | 168 | //@formatter:off 169 | SmsDto result = given() 170 | .header("Authorization", "Bearer "+token) 171 | .contentType(ContentType.JSON) 172 | .body(dto) 173 | 174 | .when() 175 | .post("/v1/sms") 176 | 177 | .then() 178 | .statusCode(201) 179 | .extract().as(SmsDto.class); 180 | //@formatter:on 181 | 182 | assertThat(result.getId(), is(notNullValue())); 183 | assertThat(result.getStatus(),is(equalTo(MessageStatus.CREATED))); 184 | } 185 | 186 | @Test 187 | public void post_sms_returns_406() { 188 | 189 | SmsDto dto = new SmsDto(); 190 | 191 | //@formatter:off 192 | given() 193 | .header("Authorization", "Bearer "+token) 194 | .contentType(ContentType.JSON) 195 | .body(dto) 196 | 197 | .when() 198 | .post("/v1/sms") 199 | 200 | .then() 201 | .statusCode(406); 202 | //@formatter:on 203 | 204 | 205 | } 206 | 207 | 208 | 209 | } 210 | -------------------------------------------------------------------------------- /src/test/java/com/murray/communications/domain/entities/messages/SmsMessageTest.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.domain.entities.messages; 2 | 3 | import com.murray.communications.dtos.enums.MessageStatus; 4 | import org.junit.Test; 5 | 6 | import static org.hamcrest.MatcherAssert.assertThat; 7 | import static org.hamcrest.core.Is.is; 8 | import static org.hamcrest.core.IsEqual.equalTo; 9 | import static org.hamcrest.core.IsNull.notNullValue; 10 | 11 | public class SmsMessageTest { 12 | 13 | @Test 14 | public void newInstance_status_is_created() { 15 | 16 | SmsMessage result = new SmsMessage(12312321L, 1212122L, "Test Message"); 17 | 18 | assertThat(result, is(notNullValue())); 19 | assertThat(result.getStatus(), is(equalTo(MessageStatus.CREATED))); 20 | 21 | } 22 | 23 | @Test(expected = NullPointerException.class) 24 | public void newInstance_nullReceiver_throwError() { 25 | SmsMessage result = new SmsMessage(null, 1212122L, "Test Message"); 26 | } 27 | 28 | @Test(expected = NullPointerException.class) 29 | public void newInstance_nullSender_throwError() { 30 | 31 | SmsMessage result = new SmsMessage(12312321L, null, "Test Message"); 32 | } 33 | 34 | 35 | @Test(expected = NullPointerException.class) 36 | public void newInstance_nullMessage_throwError() { 37 | 38 | SmsMessage result = new SmsMessage(12312321L, 1212122L, null); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/com/murray/communications/domain/respository/messages/SmsMessageRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.domain.respository.messages; 2 | 3 | import com.murray.communications.domain.entities.messages.SmsMessage; 4 | import org.hamcrest.MatcherAssert; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.data.domain.Page; 10 | import org.springframework.data.domain.PageRequest; 11 | import org.springframework.data.domain.Pageable; 12 | import org.springframework.data.domain.Sort; 13 | import org.springframework.test.context.jdbc.Sql; 14 | import org.springframework.test.context.junit4.SpringRunner; 15 | 16 | import java.time.LocalDate; 17 | import java.time.LocalDateTime; 18 | 19 | import static org.hamcrest.MatcherAssert.assertThat; 20 | import static org.hamcrest.core.Is.is; 21 | import static org.hamcrest.core.IsEqual.equalTo; 22 | import static org.hamcrest.core.IsNull.notNullValue; 23 | 24 | @RunWith(SpringRunner.class) 25 | @SpringBootTest 26 | @Sql("classpath:test-data.sql") 27 | public class SmsMessageRepositoryTest { 28 | 29 | @Autowired 30 | private SmsMessageRepository smsMessageRepository; 31 | 32 | @Test 33 | public void saveSms_returnAuditFields() { 34 | 35 | SmsMessage message = new SmsMessage(12121L, 44444L, "Test"); 36 | message.setCreatedDate(LocalDateTime.now()); 37 | smsMessageRepository.save(message); 38 | 39 | SmsMessage result = smsMessageRepository.findById(message.getId()).orElseThrow(() -> new NullPointerException()); 40 | 41 | assertThat(result, is(notNullValue())); 42 | 43 | MatcherAssert.assertThat(result.getId(), is(notNullValue())); 44 | 45 | MatcherAssert.assertThat(result.getCreatedDate(), is(notNullValue())); 46 | 47 | 48 | } 49 | 50 | @Test 51 | public void findAll_returnsThreeOrMore() { 52 | 53 | Iterable messages = smsMessageRepository.findAll(); 54 | 55 | assertThat(messages, is(notNullValue())); 56 | 57 | assertThat(messages.spliterator().getExactSizeIfKnown() >= 3L, is((true))); 58 | 59 | 60 | } 61 | 62 | @Test 63 | public void findFirstThree_orderMessageDescending() { 64 | 65 | Pageable sortedByMessageDescending = PageRequest.of(0, 3, Sort.by("message").descending()); 66 | Page results = smsMessageRepository.findAll(sortedByMessageDescending); 67 | 68 | assertThat(results, is(notNullValue())); 69 | assertThat(results.getTotalElements() > 10L, is(equalTo(true))); 70 | assertThat(results.getTotalPages() > 3L, is(equalTo(true))); 71 | assertThat(results.get().count(), is(equalTo(3L))); 72 | 73 | 74 | } 75 | 76 | @Test 77 | public void findSecondThree_orderMessageDescending() { 78 | 79 | Pageable sortedByMessageDescending = PageRequest.of(1, 3, Sort.by("message").descending()); 80 | Page results = smsMessageRepository.findAll(sortedByMessageDescending); 81 | 82 | assertThat(results, is(notNullValue())); 83 | assertThat(results.getTotalElements() > 10L, is(equalTo(true))); 84 | assertThat(results.getTotalPages() > 3L, is(equalTo(true))); 85 | assertThat(results.get().count(), is(equalTo(3L))); 86 | System.out.println("tot elements:" + results.getTotalElements()); 87 | System.out.println("tot pages:" + results.getTotalPages()); 88 | System.out.println("tot number:" + results.getNumber()); 89 | System.out.println("tot NumberOfElements:" + results.getNumberOfElements()); 90 | 91 | System.out.println("tot getPageable:" + results.getPageable()); 92 | System.out.println("tot nextPageable:" + results.nextPageable()); 93 | 94 | 95 | } 96 | 97 | @Test 98 | public void findSenderMessageSentTwoWeeksAgo_returnThree() { 99 | 100 | long returnThree = 3L; 101 | long sender = 33333333L; 102 | LocalDate from = LocalDate.now().minusDays(14); 103 | LocalDate to = LocalDate.now().minusDays(7); 104 | 105 | Pageable sortedByMessageDescending = PageRequest.of(0, 10, Sort.by("SendDate")); 106 | 107 | Page results = smsMessageRepository.findAllBySendDateBetweenAndSenderAndDeletedIsFalse(from, to, sender, sortedByMessageDescending); 108 | 109 | assertThat(results, is(notNullValue())); 110 | 111 | results.get().forEach( 112 | msg -> System.out.println(msg.getId() + " sentOn:" + msg.getSendDate() + ", message:" + msg.getMessage()) 113 | ); 114 | 115 | assertThat(results.getTotalElements(), is(equalTo(3L))); 116 | 117 | 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /src/test/java/com/murray/communications/services/messages/Impl/MessageSenderTest.java: -------------------------------------------------------------------------------- 1 | package com.murray.communications.services.messages.Impl; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.murray.communications.domain.entities.messages.SmsMessage; 5 | import com.murray.communications.dtos.enums.MessageStatus; 6 | import com.murray.communications.domain.entities.messages.BandWidthCredentials; 7 | import com.murray.communications.exceptions.BandWidthException; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | 11 | import java.time.LocalDate; 12 | 13 | import static org.hamcrest.MatcherAssert.assertThat; 14 | import static org.hamcrest.core.Is.is; 15 | import static org.hamcrest.core.IsEqual.equalTo; 16 | 17 | public class MessageSenderTest { 18 | 19 | private ObjectMapper objectMapper; 20 | private BandwidthMessageSender sender; 21 | 22 | private SmsMessage message; 23 | 24 | @Before 25 | public void setup() { 26 | 27 | message = new SmsMessage(); 28 | message.setId(11212L); 29 | message.setStatus(MessageStatus.CREATED); 30 | message.setMessage("unit test hello"); 31 | message.setSender(5012141397L); 32 | message.setReceiver(34680522200L); 33 | message.setDeleted(false); 34 | message.setSendDate(LocalDate.now().plusDays(1)); 35 | 36 | BandWidthCredentials bandWidthCredentials = new BandWidthCredentials(); 37 | bandWidthCredentials.setId(11L); 38 | bandWidthCredentials.setMessageApi("83babaASs9cf9e1e8811fc527ee7aaea0f03a671265ce2a"); 39 | bandWidthCredentials.setMessageSecret("fe537dasas33247775147014fe3e91dab34d1a471a7"); 40 | bandWidthCredentials.setApplicationName("test-location-application"); 41 | bandWidthCredentials.setSenderPhoneNumber(123123213L); 42 | bandWidthCredentials.setApplicationId("e5bca7db-1158-40a3-8b74-35f846e8b549"); 43 | bandWidthCredentials.setDashboardUserName("userName"); 44 | bandWidthCredentials.setDashboardPwd("Gpwd"); 45 | bandWidthCredentials.setDashboardAccountId("22313"); 46 | bandWidthCredentials.setDashboardSubAccountId("22202"); 47 | 48 | message.setBandWidthCredentials(bandWidthCredentials); 49 | 50 | objectMapper = new ObjectMapper(); 51 | sender = new BandwidthMessageSender(); 52 | 53 | } 54 | 55 | @Test(expected = BandWidthException.class) 56 | public void not_send_message_when_status_is_not_created() throws Exception { 57 | 58 | message.setStatus(MessageStatus.SENT_SUCCESS); 59 | int result = sender.send(message); 60 | assertThat(result, is(equalTo(-1))); 61 | } 62 | 63 | @Test(expected = BandWidthException.class) 64 | public void not_send_message_when_senddate_in_past() throws Exception { 65 | message.setSendDate(LocalDate.now().minusDays(1)); 66 | int result = sender.send(message); 67 | assertThat(result, is(equalTo(-1))); 68 | } 69 | 70 | @Test(expected = BandWidthException.class) 71 | public void not_send_message_when_delete_is_not_processing() throws Exception { 72 | message.setDeleted(true); 73 | int result = sender.send(message); 74 | assertThat(result, is(equalTo(-1))); 75 | } 76 | 77 | @Test 78 | public void return200_when_successfully_send_message() throws Exception { 79 | 80 | int result = sender.send(message); 81 | assertThat(result >= 200, is(equalTo(true))); 82 | 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/test/resources/test-data.sql: -------------------------------------------------------------------------------- 1 | -- The data that is inject after the data.sql, hence why the id's are greater that 2 | -- delete from sms_message where created_by in (10,11); 3 | -- delete from application_user_role where user_id in (10,11); 4 | --delete from application_user where user_id in (10,11); 5 | SET REFERENTIAL_INTEGRITY FALSE; 6 | TRUNCATE TABLE user_bandwidth_credentials; 7 | TRUNCATE TABLE sms_message; 8 | TRUNCATE TABLE application_user_role; 9 | TRUNCATE TABLE bandwidth_credentials; 10 | TRUNCATE TABLE application_user; 11 | SET REFERENTIAL_INTEGRITY TRUE; 12 | 13 | insert into application_user (user_id,username, password, enabled) 14 | values (10,'gary@email.com', 'gary', true); 15 | insert into application_user_role (user_id, role_id) 16 | values (10,1); 17 | insert into application_user_role (user_id, role_id) 18 | values (10,2); 19 | 20 | insert into application_user (user_id,username, password, enabled) 21 | values (11,'testy@email.com', 'password', true); 22 | insert into application_user_role (user_id, role_id) 23 | values (11,1); 24 | insert into application_user_role (user_id, role_id) 25 | values (11,2); 26 | 27 | -- create bandwidth 28 | 29 | insert into bandwidth_credentials (id,version, created_by, created_date, last_modified_by, last_modified_date, 30 | message_api, message_secret,application_name ,application_id, sender_phone_number,dashboard_user_name, dashboard_pwd, 31 | dashboard_account_id,dashboard_sub_account_id) 32 | values (2,1, 10,now(), 10, now(),'832222a800d9cf9e1e8811fc527ee7aaea0f03a671265ce2a','fe227df3e6c6f1433247775147014fe3e91dab34d1a471a7','test-location-application','e5bca7db-123-40a3-8b74-35f846e8b549',217823123, 33 | 'username','password','23213123','123123'); 34 | 35 | insert into bandwidth_credentials (id,version, created_by, created_date, last_modified_by, last_modified_date, 36 | message_api, message_secret,application_name ,application_id, sender_phone_number,dashboard_user_name, dashboard_pwd, 37 | dashboard_account_id,dashboard_sub_account_id) 38 | values (3,1, 10,now(), 10, now(),'message_api3','message_secret3','application_name3','application_id3',3333333, 39 | 'dashboard_user_name3','dashboard_pwd3','dashboard_account_id3','dashboard_sub_account_id3'); 40 | 41 | -- assign the bandwidth details to the users 42 | insert into user_bandwidth_credentials (user_id,bandwidth_id) values(10,2); 43 | insert into user_bandwidth_credentials (user_id,bandwidth_id) values(11,2); 44 | 45 | -- gary test messages 46 | 47 | 48 | insert into sms_message (id,version, created_by, created_date, last_modified_by, last_modified_date, deliver_response, message, receiver, sender, send_date, status,bandwidth_id,deleted) 49 | values (1,1, 10,now(), 10, now(), '', 'AAAAA Message', 1111111, 5012141397, DATEADD('DAY',5, NOW()), 'CREATED',2,false); 50 | insert into sms_message (id,version, created_by, created_date, last_modified_by, last_modified_date, deliver_response, message, receiver, sender, send_date, status,bandwidth_id,deleted) 51 | values (2,1, 10,now(), 10, now(), '', 'BBBB Message', 1111111, 2222222, DATEADD('DAY',5, NOW()), 'CREATED',2,false); 52 | insert into sms_message (id,version, created_by, created_date, last_modified_by, last_modified_date, deliver_response, message, receiver, sender, send_date, status,bandwidth_id,deleted) 53 | values (3,1, 10,now(), 10, now(), '', 'CCCC Message', 1111111, 2222222, DATEADD('DAY',5, NOW()), 'ERROR',2,false); 54 | insert into sms_message (id,version, created_by, created_date, last_modified_by, last_modified_date, deliver_response, message, receiver, sender, send_date, status,bandwidth_id,deleted) 55 | values (4,1, 10,now(), 10, now(), '', 'DDDDD Message', 1111111, 2222222, DATEADD('DAY',2, NOW()), 'ERROR',2,false); 56 | insert into sms_message (id,version, created_by, created_date, last_modified_by, last_modified_date, deliver_response, message, receiver, sender, send_date, status,bandwidth_id,deleted) 57 | values (5,1, 10,now(), 10, now(), '', 'EEEEEE Message', 1111111, 2222222, DATEADD('DAY',1, NOW()), 'ERROR',2,false); 58 | insert into sms_message (id,version, created_by, created_date, last_modified_by, last_modified_date, deliver_response, message, receiver, sender, send_date, status,bandwidth_id,deleted) 59 | values (6,1, 10,now(), 10, now(), '', 'FFFFF Message', 1111111, 2222222, DATEADD('DAY',2, NOW()), 'ERROR',2,false); 60 | 61 | 62 | insert into sms_message (id,version, created_by, created_date, last_modified_by, last_modified_date, deliver_response, message, receiver, sender, send_date, status,bandwidth_id,deleted) 63 | values (7,1, 11,now(), 11, now(), '', 'AAAAA Message', 1111111, 33333333, DATEADD('DAY',-5, NOW()), 'CREATED',3,false); 64 | insert into sms_message (id,version, created_by, created_date, last_modified_by, last_modified_date, deliver_response, message, receiver, sender, send_date, status,bandwidth_id,deleted) 65 | values (8,1, 11,now(), 11, now(), '', 'BBBB Message', 1111111, 2222222, DATEADD('DAY',-7, NOW()), 'CREATED',3,false); 66 | insert into sms_message (id,version, created_by, created_date, last_modified_by, last_modified_date, deliver_response, message, receiver, sender, send_date, status,bandwidth_id,deleted) 67 | values (9,1, 11,now(), 11, now(), '', 'CCCC Message', 1111111, 33333333, DATEADD('DAY',-10, NOW()), 'ERROR',3,false); 68 | insert into sms_message (id,version, created_by, created_date, last_modified_by, last_modified_date, deliver_response, message, receiver, sender, send_date, status,bandwidth_id,deleted) 69 | values (10,1, 11,now(), 11, now(), '', 'DDDDD Message', 1111111, 33333333, DATEADD('DAY',-10, NOW()), 'ERROR',3,false); 70 | insert into sms_message (id,version, created_by, created_date, last_modified_by, last_modified_date, deliver_response, message, receiver, sender, send_date, status,bandwidth_id,deleted) 71 | values (11,1, 11,now(), 11, now(), '', 'EEEEEE Message', 1111111, 33333333, DATEADD('DAY',-8, NOW()), 'ERROR',3,false); --------------------------------------------------------------------------------