├── PPMToolDesign.zip ├── PPMToolDesign ├── App.css ├── Dashboard.html ├── LandingPage.html ├── LoginPage.html ├── ProjectBoard.html ├── ProjectForm.html ├── ProjectTaskForm.html ├── RegistrationPage.html └── Template.html ├── PPMToolFullStack ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── agileintelligence │ │ │ └── ppmtool │ │ │ ├── PpmtoolApplication.java │ │ │ ├── domain │ │ │ ├── Backlog.java │ │ │ ├── Project.java │ │ │ ├── ProjectTask.java │ │ │ └── User.java │ │ │ ├── exceptions │ │ │ ├── CustomResponseEntityExceptionHandler.java │ │ │ ├── InvalidLoginResponse.java │ │ │ ├── ProjectIdException.java │ │ │ ├── ProjectIdExceptionResponse.java │ │ │ ├── ProjectNotFoundException.java │ │ │ ├── ProjectNotFoundExceptionResponse.java │ │ │ ├── UsernameAlreadyExistsException.java │ │ │ └── UsernameAlreadyExistsResponse.java │ │ │ ├── payload │ │ │ ├── JWTLoginSucessReponse.java │ │ │ └── LoginRequest.java │ │ │ ├── repositories │ │ │ ├── BacklogRepository.java │ │ │ ├── ProjectRepository.java │ │ │ ├── ProjectTaskRepository.java │ │ │ └── UserRepository.java │ │ │ ├── security │ │ │ ├── JwtAuthenticationEntryPoint.java │ │ │ ├── JwtAuthenticationFilter.java │ │ │ ├── JwtTokenProvider.java │ │ │ ├── SecurityConfig.java │ │ │ └── SecurityConstants.java │ │ │ ├── services │ │ │ ├── CustomUserDetailsService.java │ │ │ ├── MapValidationErrorService.java │ │ │ ├── ProjectService.java │ │ │ ├── ProjectTaskService.java │ │ │ └── UserService.java │ │ │ ├── validator │ │ │ └── UserValidator.java │ │ │ └── web │ │ │ ├── BacklogController.java │ │ │ ├── ProjectController.java │ │ │ └── UserController.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── io │ └── agileintelligence │ └── ppmtool │ └── PpmtoolApplicationTests.java └── ppmtool-react-client ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── src ├── App.css ├── App.js ├── App.test.js ├── actions │ ├── backlogActions.js │ ├── projectActions.js │ ├── securityActions.js │ └── types.js ├── components │ ├── Dashboard.js │ ├── Layout │ │ ├── Header.js │ │ └── Landing.js │ ├── Project │ │ ├── AddProject.js │ │ ├── CreateProjectButton.js │ │ ├── ProjectItem.js │ │ └── UpdateProject.js │ ├── ProjectBoard │ │ ├── Backlog.js │ │ ├── ProjectBoard.js │ │ └── ProjectTasks │ │ │ ├── AddProjectTask.js │ │ │ ├── ProjectTask.js │ │ │ └── UpdateProjectTask.js │ └── UserManagement │ │ ├── Login.js │ │ └── Register.js ├── index.css ├── index.js ├── logo.svg ├── reducers │ ├── backlogReducer.js │ ├── errorReducer.js │ ├── index.js │ ├── projectReducer.js │ └── securityReducer.js ├── registerServiceWorker.js ├── securityUtils │ ├── SecureRoute.js │ └── setJWTToken.js └── store.js └── yarn.lock /PPMToolDesign.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgileIntelligence/AgileIntPPMTool/eaa4739bc3142beeeaacb854135dc8154f4b89ba/PPMToolDesign.zip -------------------------------------------------------------------------------- /PPMToolDesign/App.css: -------------------------------------------------------------------------------- 1 | img { 2 | width: 100%; 3 | } 4 | 5 | .navbar { 6 | background-color: #e3f2fd; 7 | } 8 | .fa.fa-edit { 9 | color: #18a2b9; 10 | } 11 | 12 | .list-group-item.delete:hover { 13 | cursor: -webkit-grab; 14 | background-color: pink; 15 | } 16 | 17 | .list-group-item.update:hover { 18 | cursor: -webkit-grab; 19 | background-color: gainsboro; 20 | } 21 | 22 | .list-group-item.board:hover { 23 | cursor: -webkit-grab; 24 | background-color: gainsboro; 25 | } 26 | 27 | .fa.fa-minus-circle { 28 | color: red; 29 | } 30 | 31 | .landing { 32 | position: relative; 33 | /* background: url("../img/showcase.jpg") no-repeat; */ 34 | background-size: cover; 35 | background-position: center; 36 | height: 100vh; 37 | margin-top: -24px; 38 | margin-bottom: -50px; 39 | } 40 | 41 | .landing-inner { 42 | padding-top: 80px; 43 | } 44 | 45 | .dark-overlay { 46 | background-color: rgba(0, 0, 0, 0.7); 47 | position: absolute; 48 | top: 0; 49 | left: 0; 50 | width: 100%; 51 | height: 100%; 52 | } 53 | 54 | .card-form { 55 | opacity: 0.9; 56 | } 57 | 58 | .latest-profiles-img { 59 | width: 40px; 60 | height: 40px; 61 | } 62 | 63 | .form-control::placeholder { 64 | color: #bbb !important; 65 | } 66 | -------------------------------------------------------------------------------- /PPMToolDesign/Dashboard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | PPM Tool 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 61 | 62 | 63 | 64 |
65 |
66 |
67 |
68 |

Projects

69 |
70 | 71 | Create a Project 72 | 73 |
74 |
75 | 76 | 77 |
78 |
79 |
80 |
81 | REACT 82 |
83 |
84 |

Spring / React Project

85 |

Project to create a Kanban Board with Spring Boot and React

86 |
87 | 106 |
107 |
108 |
109 | 110 | 111 |
112 |
113 |
114 |
115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 127 | 129 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /PPMToolDesign/LandingPage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | Kanban Tool 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 61 | 62 | 63 | 64 | 65 | 66 |
67 |
68 |
69 |
70 |
71 |

Personal Kanban Tool

72 |

73 | Create your account to join active projects or start you own 74 |

75 |
76 | 77 | Sign Up 78 | 79 | 80 | Login 81 | 82 |
83 |
84 |
85 |
86 |
87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 97 | 99 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /PPMToolDesign/LoginPage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | Kanban Tool 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 61 | 62 | 63 |
64 |
65 |
66 |
67 |

Log In

68 |
69 |
70 | 71 |
72 |
73 | 74 |
75 | 76 |
77 |
78 |
79 |
80 |
81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 91 | 93 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /PPMToolDesign/ProjectBoard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | Kanban Tool 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 61 | 62 | 63 |
64 | 65 | Create Project Task 66 | 67 |
68 |
69 | 70 |
71 |
72 |
73 |
74 |
75 |

TO DO

76 |
77 |
78 | 79 | 80 |
81 | 82 |
83 | ID: projectSequence -- Priority: priorityString 84 |
85 |
86 |
project_task.summary
87 |

88 | project_task.acceptanceCriteria 89 |

90 | 91 | View / Update 92 | 93 | 94 | 97 |
98 |
99 | 100 | 101 |
102 |
103 |
104 |
105 |

In Progress

106 |
107 |
108 | 109 | 110 | 111 |
112 |
113 |
114 |
115 |

Done

116 |
117 |
118 | 119 | 120 | 121 |
122 |
123 |
124 | 125 | 126 |
127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 136 | 138 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /PPMToolDesign/ProjectForm.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | PPM Tool 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 61 | 62 | 63 | 64 |
65 |
66 |
67 |
68 |
Create / Edit Project form
69 |
70 |
71 |
72 | 73 |
74 |
75 | 77 |
78 | 79 |
80 | 81 |
82 |
Start Date
83 |
84 | 85 |
86 |
Estimated End Date
87 |
88 | 89 |
90 | 91 | 92 |
93 |
94 |
95 |
96 |
97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 107 | 109 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /PPMToolDesign/ProjectTaskForm.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | Kanban Tool 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 61 | 62 | 63 | 64 | 65 |
66 |
67 |
68 |
69 | 70 | Back to Project Board 71 | 72 |

Add /Update Project Task

73 |

Project Name + Project Code

74 |
75 |
76 | 77 |
78 |
79 | 80 |
81 |
Due Date
82 |
83 | 84 |
85 |
86 | 92 |
93 | 94 |
95 | 101 |
102 | 103 | 104 |
105 |
106 |
107 |
108 |
109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 120 | 122 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /PPMToolDesign/RegistrationPage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | Kanban Tool 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 61 | 62 | 63 | 64 | 65 |
66 |
67 |
68 |
69 |

Sign Up

70 |

Create your Account

71 |
72 |
73 | 75 |
76 |
77 | 78 | 79 |
80 |
81 | 82 |
83 |
84 | 86 |
87 | 88 |
89 |
90 |
91 |
92 |
93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 105 | 107 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /PPMToolDesign/Template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | Kanban Tool 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 76 | 78 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /PPMToolFullStack/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### removed idea 14 | .idea/ 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ -------------------------------------------------------------------------------- /PPMToolFullStack/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgileIntelligence/AgileIntPPMTool/eaa4739bc3142beeeaacb854135dc8154f4b89ba/PPMToolFullStack/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /PPMToolFullStack/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip 2 | -------------------------------------------------------------------------------- /PPMToolFullStack/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 | # http://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 Migwn, 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 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 204 | echo $MAVEN_PROJECTBASEDIR 205 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 206 | 207 | # For Cygwin, switch paths to Windows format before running java 208 | if $cygwin; then 209 | [ -n "$M2_HOME" ] && 210 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 211 | [ -n "$JAVA_HOME" ] && 212 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 213 | [ -n "$CLASSPATH" ] && 214 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 215 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 216 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 217 | fi 218 | 219 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 220 | 221 | exec "$JAVACMD" \ 222 | $MAVEN_OPTS \ 223 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 224 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 225 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 226 | -------------------------------------------------------------------------------- /PPMToolFullStack/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 http://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 enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 84 | @REM Fallback to current working directory if not found. 85 | 86 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 87 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 88 | 89 | set EXEC_DIR=%CD% 90 | set WDIR=%EXEC_DIR% 91 | :findBaseDir 92 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 93 | cd .. 94 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 95 | set WDIR=%CD% 96 | goto findBaseDir 97 | 98 | :baseDirFound 99 | set MAVEN_PROJECTBASEDIR=%WDIR% 100 | cd "%EXEC_DIR%" 101 | goto endDetectBaseDir 102 | 103 | :baseDirNotFound 104 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 105 | cd "%EXEC_DIR%" 106 | 107 | :endDetectBaseDir 108 | 109 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 110 | 111 | @setlocal EnableExtensions EnableDelayedExpansion 112 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 113 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 114 | 115 | :endReadAdditionalConfig 116 | 117 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 118 | 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 123 | if ERRORLEVEL 1 goto error 124 | goto end 125 | 126 | :error 127 | set ERROR_CODE=1 128 | 129 | :end 130 | @endlocal & set ERROR_CODE=%ERROR_CODE% 131 | 132 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 133 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 134 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 135 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 136 | :skipRcPost 137 | 138 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 139 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 140 | 141 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 142 | 143 | exit /B %ERROR_CODE% 144 | -------------------------------------------------------------------------------- /PPMToolFullStack/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | io.agileintelligence 7 | ppmtool 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | ppmtool 12 | Personal Project Management Tool 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.0.5.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-data-jpa 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-web 35 | 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-devtools 40 | runtime 41 | 42 | 43 | com.h2database 44 | h2 45 | runtime 46 | 47 | 48 | mysql 49 | mysql-connector-java 50 | runtime 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-starter-test 55 | test 56 | 57 | 58 | org.springframework.boot 59 | spring-boot-starter-security 60 | 61 | 62 | com.google.code.gson 63 | gson 64 | 2.8.5 65 | 66 | 67 | io.jsonwebtoken 68 | jjwt 69 | 0.9.0 70 | 71 | 72 | 73 | 74 | 75 | 76 | org.springframework.boot 77 | spring-boot-maven-plugin 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/PpmtoolApplication.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 7 | 8 | import java.security.Principal; 9 | 10 | @SpringBootApplication 11 | public class PpmtoolApplication { 12 | 13 | 14 | @Bean 15 | BCryptPasswordEncoder bCryptPasswordEncoder(){ 16 | return new BCryptPasswordEncoder(); 17 | } 18 | 19 | 20 | public static void main(String[] args) { 21 | SpringApplication.run(PpmtoolApplication.class, args); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/domain/Backlog.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.domain; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonIgnore; 5 | 6 | import javax.persistence.*; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | @Entity 11 | public class Backlog { 12 | 13 | @Id 14 | @GeneratedValue(strategy = GenerationType.IDENTITY) 15 | private Long id; 16 | private Integer PTSequence = 0; 17 | private String projectIdentifier; 18 | 19 | //OneToOne with project 20 | @OneToOne(fetch = FetchType.EAGER) 21 | @JoinColumn(name="project_id",nullable = false) 22 | @JsonIgnore 23 | private Project project; 24 | 25 | 26 | @OneToMany(cascade = CascadeType.REFRESH, fetch = FetchType.EAGER, mappedBy = "backlog", orphanRemoval = true) 27 | private List projectTasks = new ArrayList<>(); 28 | 29 | 30 | 31 | public Backlog() { 32 | } 33 | 34 | public Long getId() { 35 | return id; 36 | } 37 | 38 | public void setId(Long id) { 39 | this.id = id; 40 | } 41 | 42 | public Integer getPTSequence() { 43 | return PTSequence; 44 | } 45 | 46 | public void setPTSequence(Integer PTSequence) { 47 | this.PTSequence = PTSequence; 48 | } 49 | 50 | public String getProjectIdentifier() { 51 | return projectIdentifier; 52 | } 53 | 54 | public void setProjectIdentifier(String projectIdentifier) { 55 | this.projectIdentifier = projectIdentifier; 56 | } 57 | 58 | public Project getProject() { 59 | return project; 60 | } 61 | 62 | public void setProject(Project project) { 63 | this.project = project; 64 | } 65 | 66 | public List getProjectTasks() { 67 | return projectTasks; 68 | } 69 | 70 | public void setProjectTasks(List projectTasks) { 71 | this.projectTasks = projectTasks; 72 | } 73 | 74 | 75 | } 76 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/domain/Project.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.domain; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonFormat; 5 | import com.fasterxml.jackson.annotation.JsonIgnore; 6 | 7 | import javax.persistence.*; 8 | import javax.validation.constraints.NotBlank; 9 | import javax.validation.constraints.Size; 10 | import java.util.Date; 11 | 12 | @Entity 13 | public class Project { 14 | 15 | @Id 16 | @GeneratedValue(strategy = GenerationType.IDENTITY) 17 | private Long id; 18 | @NotBlank(message = "Project name is required") 19 | private String projectName; 20 | @NotBlank(message ="Project Identifier is required") 21 | @Size(min=4, max=5, message = "Please use 4 to 5 characters") 22 | @Column(updatable = false, unique = true) 23 | private String projectIdentifier; 24 | @NotBlank(message = "Project description is required") 25 | private String description; 26 | @JsonFormat(pattern = "yyyy-mm-dd") 27 | private Date start_date; 28 | @JsonFormat(pattern = "yyyy-mm-dd") 29 | private Date end_date; 30 | @JsonFormat(pattern = "yyyy-mm-dd") 31 | @Column(updatable = false) 32 | private Date created_At; 33 | @JsonFormat(pattern = "yyyy-mm-dd") 34 | private Date updated_At; 35 | 36 | @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "project") 37 | @JsonIgnore 38 | private Backlog backlog; 39 | 40 | @ManyToOne(fetch = FetchType.LAZY) 41 | @JsonIgnore 42 | private User user; 43 | 44 | 45 | private String projectLeader; 46 | 47 | 48 | 49 | public Project() { 50 | } 51 | 52 | public Long getId() { 53 | return id; 54 | } 55 | 56 | public void setId(Long id) { 57 | this.id = id; 58 | } 59 | 60 | public String getProjectName() { 61 | return projectName; 62 | } 63 | 64 | public void setProjectName(String projectName) { 65 | this.projectName = projectName; 66 | } 67 | 68 | public String getProjectIdentifier() { 69 | return projectIdentifier; 70 | } 71 | 72 | public void setProjectIdentifier(String projectIdentifier) { 73 | this.projectIdentifier = projectIdentifier; 74 | } 75 | 76 | public String getDescription() { 77 | return description; 78 | } 79 | 80 | public void setDescription(String description) { 81 | this.description = description; 82 | } 83 | 84 | public Date getStart_date() { 85 | return start_date; 86 | } 87 | 88 | public void setStart_date(Date start_date) { 89 | this.start_date = start_date; 90 | } 91 | 92 | public Date getEnd_date() { 93 | return end_date; 94 | } 95 | 96 | public void setEnd_date(Date end_date) { 97 | this.end_date = end_date; 98 | } 99 | 100 | public Date getCreated_At() { 101 | return created_At; 102 | } 103 | 104 | public void setCreated_At(Date created_At) { 105 | this.created_At = created_At; 106 | } 107 | 108 | public Date getUpdated_At() { 109 | return updated_At; 110 | } 111 | 112 | public void setUpdated_At(Date updated_At) { 113 | this.updated_At = updated_At; 114 | } 115 | 116 | public Backlog getBacklog() { 117 | return backlog; 118 | } 119 | 120 | public void setBacklog(Backlog backlog) { 121 | this.backlog = backlog; 122 | } 123 | 124 | public User getUser() { 125 | return user; 126 | } 127 | 128 | public void setUser(User user) { 129 | this.user = user; 130 | } 131 | 132 | public String getProjectLeader() { 133 | return projectLeader; 134 | } 135 | 136 | public void setProjectLeader(String projectLeader) { 137 | this.projectLeader = projectLeader; 138 | } 139 | 140 | @PrePersist 141 | protected void onCreate(){ 142 | this.created_At = new Date(); 143 | } 144 | 145 | @PreUpdate 146 | protected void onUpdate(){ 147 | this.updated_At = new Date(); 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/domain/ProjectTask.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import com.fasterxml.jackson.annotation.JsonIgnore; 5 | 6 | import javax.persistence.*; 7 | import javax.validation.constraints.NotBlank; 8 | import java.util.Date; 9 | 10 | @Entity 11 | public class ProjectTask { 12 | 13 | @Id 14 | @GeneratedValue(strategy = GenerationType.IDENTITY) 15 | private Long id; 16 | @Column(updatable = false, unique = true) 17 | private String projectSequence; 18 | @NotBlank(message = "Please include a project summary") 19 | private String summary; 20 | private String acceptanceCriteria; 21 | private String status; 22 | private Integer priority; 23 | @JsonFormat(pattern = "yyyy-mm-dd") 24 | private Date dueDate; 25 | //ManyToOne with Backlog 26 | @ManyToOne(fetch = FetchType.EAGER) 27 | @JoinColumn(name="backlog_id", updatable = false, nullable = false) 28 | @JsonIgnore 29 | private Backlog backlog; 30 | 31 | @Column(updatable = false) 32 | private String projectIdentifier; 33 | @JsonFormat(pattern = "yyyy-mm-dd") 34 | private Date create_At; 35 | @JsonFormat(pattern = "yyyy-mm-dd") 36 | private Date update_At; 37 | 38 | public ProjectTask() { 39 | } 40 | 41 | public Long getId() { 42 | return id; 43 | } 44 | 45 | public void setId(Long id) { 46 | this.id = id; 47 | } 48 | 49 | public String getProjectSequence() { 50 | return projectSequence; 51 | } 52 | 53 | public void setProjectSequence(String projectSequence) { 54 | this.projectSequence = projectSequence; 55 | } 56 | 57 | public String getSummary() { 58 | return summary; 59 | } 60 | 61 | public void setSummary(String summary) { 62 | this.summary = summary; 63 | } 64 | 65 | public String getAcceptanceCriteria() { 66 | return acceptanceCriteria; 67 | } 68 | 69 | public void setAcceptanceCriteria(String acceptanceCriteria) { 70 | this.acceptanceCriteria = acceptanceCriteria; 71 | } 72 | 73 | public String getStatus() { 74 | return status; 75 | } 76 | 77 | public void setStatus(String status) { 78 | this.status = status; 79 | } 80 | 81 | public Integer getPriority() { 82 | return priority; 83 | } 84 | 85 | public void setPriority(Integer priority) { 86 | this.priority = priority; 87 | } 88 | 89 | public Date getDueDate() { 90 | return dueDate; 91 | } 92 | 93 | public void setDueDate(Date dueDate) { 94 | this.dueDate = dueDate; 95 | } 96 | 97 | public String getProjectIdentifier() { 98 | return projectIdentifier; 99 | } 100 | 101 | public void setProjectIdentifier(String projectIdentifier) { 102 | this.projectIdentifier = projectIdentifier; 103 | } 104 | 105 | public Date getCreate_At() { 106 | return create_At; 107 | } 108 | 109 | public void setCreate_At(Date create_At) { 110 | this.create_At = create_At; 111 | } 112 | 113 | public Date getUpdate_At() { 114 | return update_At; 115 | } 116 | 117 | public void setUpdate_At(Date update_At) { 118 | this.update_At = update_At; 119 | } 120 | 121 | public Backlog getBacklog() { 122 | return backlog; 123 | } 124 | 125 | public void setBacklog(Backlog backlog) { 126 | this.backlog = backlog; 127 | } 128 | 129 | @PrePersist 130 | protected void onCreate(){ 131 | this.create_At = new Date(); 132 | } 133 | 134 | @PreUpdate 135 | protected void onUpdate(){ 136 | this.update_At = new Date(); 137 | } 138 | 139 | @Override 140 | public String toString() { 141 | return "ProjectTask{" + 142 | "id=" + id + 143 | ", projectSequence='" + projectSequence + '\'' + 144 | ", summary='" + summary + '\'' + 145 | ", acceptanceCriteria='" + acceptanceCriteria + '\'' + 146 | ", status='" + status + '\'' + 147 | ", priority=" + priority + 148 | ", dueDate=" + dueDate + 149 | ", backlog=" + backlog + 150 | ", projectIdentifier='" + projectIdentifier + '\'' + 151 | ", create_At=" + create_At + 152 | ", update_At=" + update_At + 153 | '}'; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/domain/User.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import org.springframework.security.core.GrantedAuthority; 5 | import org.springframework.security.core.userdetails.UserDetails; 6 | 7 | import javax.persistence.*; 8 | import javax.validation.constraints.Email; 9 | import javax.validation.constraints.NotBlank; 10 | import java.util.ArrayList; 11 | import java.util.Collection; 12 | import java.util.Date; 13 | import java.util.List; 14 | 15 | @Entity 16 | public class User implements UserDetails { 17 | @Id 18 | @GeneratedValue(strategy = GenerationType.IDENTITY) 19 | private Long id; 20 | 21 | @Email(message = "Username needs to be an email") 22 | @NotBlank(message = "username is required") 23 | @Column(unique = true) 24 | private String username; 25 | @NotBlank(message = "Please enter your full name") 26 | private String fullName; 27 | @NotBlank(message = "Password field is required") 28 | private String password; 29 | @Transient 30 | private String confirmPassword; 31 | private Date create_At; 32 | private Date update_At; 33 | 34 | //OneToMany with Project 35 | @OneToMany(cascade = CascadeType.REFRESH, fetch = FetchType.EAGER, mappedBy = "user", orphanRemoval = true) 36 | private List projects = new ArrayList<>(); 37 | 38 | 39 | public User() { 40 | } 41 | 42 | public Long getId() { 43 | return id; 44 | } 45 | 46 | public void setId(Long id) { 47 | this.id = id; 48 | } 49 | 50 | public String getUsername() { 51 | return username; 52 | } 53 | 54 | public void setUsername(String username) { 55 | this.username = username; 56 | } 57 | 58 | public String getFullName() { 59 | return fullName; 60 | } 61 | 62 | public void setFullName(String fullName) { 63 | this.fullName = fullName; 64 | } 65 | 66 | public String getPassword() { 67 | return password; 68 | } 69 | 70 | public void setPassword(String password) { 71 | this.password = password; 72 | } 73 | 74 | public String getConfirmPassword() { 75 | return confirmPassword; 76 | } 77 | 78 | public void setConfirmPassword(String confirmPassword) { 79 | this.confirmPassword = confirmPassword; 80 | } 81 | 82 | public Date getCreate_At() { 83 | return create_At; 84 | } 85 | 86 | public void setCreate_At(Date create_At) { 87 | this.create_At = create_At; 88 | } 89 | 90 | public Date getUpdate_At() { 91 | return update_At; 92 | } 93 | 94 | public void setUpdate_At(Date update_At) { 95 | this.update_At = update_At; 96 | } 97 | 98 | public List getProjects() { 99 | return projects; 100 | } 101 | 102 | public void setProjects(List projects) { 103 | this.projects = projects; 104 | } 105 | 106 | @PrePersist 107 | protected void onCreate(){ 108 | this.create_At = new Date(); 109 | } 110 | 111 | @PreUpdate 112 | protected void onUpdate(){ 113 | this.update_At = new Date(); 114 | } 115 | 116 | /* 117 | UserDetails interface methods 118 | */ 119 | 120 | @Override 121 | @JsonIgnore 122 | public Collection getAuthorities() { 123 | return null; 124 | } 125 | 126 | @Override 127 | @JsonIgnore 128 | public boolean isAccountNonExpired() { 129 | return true; 130 | } 131 | 132 | @Override 133 | @JsonIgnore 134 | public boolean isAccountNonLocked() { 135 | return true; 136 | } 137 | 138 | @Override 139 | @JsonIgnore 140 | public boolean isCredentialsNonExpired() { 141 | return true; 142 | } 143 | 144 | @Override 145 | @JsonIgnore 146 | public boolean isEnabled() { 147 | return true; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/exceptions/CustomResponseEntityExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.exceptions; 2 | 3 | 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.web.bind.annotation.ControllerAdvice; 7 | import org.springframework.web.bind.annotation.ExceptionHandler; 8 | import org.springframework.web.bind.annotation.RestController; 9 | import org.springframework.web.context.request.WebRequest; 10 | import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; 11 | 12 | @ControllerAdvice 13 | @RestController 14 | public class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler { 15 | 16 | @ExceptionHandler 17 | public final ResponseEntity handleProjectIdException(ProjectIdException ex, WebRequest request){ 18 | ProjectIdExceptionResponse exceptionResponse = new ProjectIdExceptionResponse(ex.getMessage()); 19 | return new ResponseEntity(exceptionResponse, HttpStatus.BAD_REQUEST); 20 | } 21 | 22 | @ExceptionHandler 23 | public final ResponseEntity handleProjectNotFoundException(ProjectNotFoundException ex, WebRequest request){ 24 | ProjectNotFoundExceptionResponse exceptionResponse = new ProjectNotFoundExceptionResponse(ex.getMessage()); 25 | return new ResponseEntity(exceptionResponse, HttpStatus.BAD_REQUEST); 26 | } 27 | 28 | 29 | @ExceptionHandler 30 | public final ResponseEntity handleUsernameAlreadyExists(UsernameAlreadyExistsException ex, WebRequest request){ 31 | UsernameAlreadyExistsResponse exceptionResponse = new UsernameAlreadyExistsResponse(ex.getMessage()); 32 | return new ResponseEntity(exceptionResponse, HttpStatus.BAD_REQUEST); 33 | } 34 | 35 | 36 | } 37 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/exceptions/InvalidLoginResponse.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.exceptions; 2 | 3 | public class InvalidLoginResponse { 4 | private String username; 5 | private String password; 6 | 7 | public InvalidLoginResponse() { 8 | this.username = "Invalid Username"; 9 | this.password = "Invalid Password"; 10 | } 11 | 12 | public String getUsername() { 13 | return username; 14 | } 15 | 16 | public void setUsername(String username) { 17 | this.username = username; 18 | } 19 | 20 | public String getPassword() { 21 | return password; 22 | } 23 | 24 | public void setPassword(String password) { 25 | this.password = password; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/exceptions/ProjectIdException.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.exceptions; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(HttpStatus.BAD_REQUEST) 7 | public class ProjectIdException extends RuntimeException { 8 | 9 | public ProjectIdException(String message) { 10 | super(message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/exceptions/ProjectIdExceptionResponse.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.exceptions; 2 | 3 | public class ProjectIdExceptionResponse { 4 | 5 | private String projectIdentifier; 6 | 7 | public ProjectIdExceptionResponse(String projectIdentifier) { 8 | this.projectIdentifier = projectIdentifier; 9 | } 10 | 11 | public String getProjectIdentifier() { 12 | return projectIdentifier; 13 | } 14 | 15 | public void setProjectIdentifier(String projectIdentifier) { 16 | this.projectIdentifier = projectIdentifier; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/exceptions/ProjectNotFoundException.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.exceptions; 2 | 3 | 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.web.bind.annotation.ResponseStatus; 6 | 7 | @ResponseStatus(HttpStatus.BAD_REQUEST) 8 | public class ProjectNotFoundException extends RuntimeException { 9 | public ProjectNotFoundException(String message) { 10 | super(message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/exceptions/ProjectNotFoundExceptionResponse.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.exceptions; 2 | 3 | public class ProjectNotFoundExceptionResponse { 4 | 5 | private String ProjectNotFound; 6 | 7 | public ProjectNotFoundExceptionResponse(String projectNotFound) { 8 | ProjectNotFound = projectNotFound; 9 | } 10 | 11 | public String getProjectNotFound() { 12 | return ProjectNotFound; 13 | } 14 | 15 | public void setProjectNotFound(String projectNotFound) { 16 | ProjectNotFound = projectNotFound; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/exceptions/UsernameAlreadyExistsException.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.exceptions; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(HttpStatus.BAD_REQUEST) 7 | public class UsernameAlreadyExistsException extends RuntimeException { 8 | 9 | public UsernameAlreadyExistsException(String message) { 10 | super(message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/exceptions/UsernameAlreadyExistsResponse.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.exceptions; 2 | 3 | public class UsernameAlreadyExistsResponse { 4 | 5 | private String username; 6 | 7 | public UsernameAlreadyExistsResponse(String username) { 8 | this.username = username; 9 | } 10 | 11 | public String getUsername() { 12 | return username; 13 | } 14 | 15 | public void setUsername(String username) { 16 | this.username = username; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/payload/JWTLoginSucessReponse.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.payload; 2 | 3 | public class JWTLoginSucessReponse { 4 | private boolean success; 5 | private String token; 6 | 7 | public JWTLoginSucessReponse(boolean success, String token) { 8 | this.success = success; 9 | this.token = token; 10 | } 11 | 12 | public boolean isSuccess() { 13 | return success; 14 | } 15 | 16 | public void setSuccess(boolean success) { 17 | this.success = success; 18 | } 19 | 20 | public String getToken() { 21 | return token; 22 | } 23 | 24 | public void setToken(String token) { 25 | this.token = token; 26 | } 27 | 28 | @Override 29 | public String toString() { 30 | return "JWTLoginSucessReponse{" + 31 | "success=" + success + 32 | ", token='" + token + '\'' + 33 | '}'; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/payload/LoginRequest.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.payload; 2 | 3 | 4 | import javax.validation.constraints.NotBlank; 5 | 6 | public class LoginRequest { 7 | 8 | @NotBlank(message = "Username cannot be blank") 9 | private String username; 10 | @NotBlank(message = "Password cannot be blank") 11 | private String password; 12 | 13 | public String getUsername() { 14 | return username; 15 | } 16 | 17 | public void setUsername(String username) { 18 | this.username = username; 19 | } 20 | 21 | public String getPassword() { 22 | return password; 23 | } 24 | 25 | public void setPassword(String password) { 26 | this.password = password; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/repositories/BacklogRepository.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.repositories; 2 | 3 | import io.agileintelligence.ppmtool.domain.Backlog; 4 | import org.springframework.data.repository.CrudRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface BacklogRepository extends CrudRepository { 9 | 10 | Backlog findByProjectIdentifier(String Identifier); 11 | } 12 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/repositories/ProjectRepository.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.repositories; 2 | 3 | import io.agileintelligence.ppmtool.domain.Project; 4 | import org.springframework.data.repository.CrudRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface ProjectRepository extends CrudRepository { 9 | 10 | Project findByProjectIdentifier(String projectId); 11 | 12 | @Override 13 | Iterable findAll(); 14 | 15 | Iterable findAllByProjectLeader(String username); 16 | } 17 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/repositories/ProjectTaskRepository.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.repositories; 2 | 3 | import io.agileintelligence.ppmtool.domain.ProjectTask; 4 | import org.springframework.data.repository.CrudRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.List; 8 | 9 | @Repository 10 | public interface ProjectTaskRepository extends CrudRepository { 11 | 12 | List findByProjectIdentifierOrderByPriority(String id); 13 | 14 | ProjectTask findByProjectSequence(String sequence); 15 | } 16 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/repositories/UserRepository.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.repositories; 2 | 3 | import io.agileintelligence.ppmtool.domain.User; 4 | import org.springframework.data.repository.CrudRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.Optional; 8 | 9 | @Repository 10 | public interface UserRepository extends CrudRepository { 11 | 12 | 13 | User findByUsername(String username); 14 | User getById(Long id); 15 | } 16 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/security/JwtAuthenticationEntryPoint.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.security; 2 | 3 | import com.google.gson.Gson; 4 | import io.agileintelligence.ppmtool.exceptions.InvalidLoginResponse; 5 | import org.springframework.security.core.AuthenticationException; 6 | import org.springframework.security.web.AuthenticationEntryPoint; 7 | import org.springframework.stereotype.Component; 8 | 9 | import javax.servlet.ServletException; 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | import java.io.IOException; 13 | 14 | @Component 15 | public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { 16 | 17 | @Override 18 | public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, 19 | AuthenticationException e) throws IOException, ServletException { 20 | 21 | InvalidLoginResponse loginResponse = new InvalidLoginResponse(); 22 | String jsonLoginResponse = new Gson().toJson(loginResponse); 23 | 24 | 25 | httpServletResponse.setContentType("application/json"); 26 | httpServletResponse.setStatus(401); 27 | httpServletResponse.getWriter().print(jsonLoginResponse); 28 | 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/security/JwtAuthenticationFilter.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.security; 2 | 3 | import io.agileintelligence.ppmtool.domain.User; 4 | import io.agileintelligence.ppmtool.services.CustomUserDetailsService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 7 | import org.springframework.security.core.context.SecurityContextHolder; 8 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; 9 | import org.springframework.util.StringUtils; 10 | import org.springframework.web.filter.OncePerRequestFilter; 11 | 12 | import javax.servlet.FilterChain; 13 | import javax.servlet.ServletException; 14 | import javax.servlet.http.HttpServletRequest; 15 | import javax.servlet.http.HttpServletResponse; 16 | import java.io.IOException; 17 | import java.util.Collections; 18 | 19 | import static io.agileintelligence.ppmtool.security.SecurityConstants.HEADER_STRING; 20 | import static io.agileintelligence.ppmtool.security.SecurityConstants.TOKEN_PREFIX; 21 | 22 | public class JwtAuthenticationFilter extends OncePerRequestFilter { 23 | 24 | @Autowired 25 | private JwtTokenProvider tokenProvider; 26 | 27 | @Autowired 28 | private CustomUserDetailsService customUserDetailsService; 29 | 30 | @Override 31 | protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, 32 | FilterChain filterChain) throws ServletException, IOException { 33 | 34 | try { 35 | 36 | String jwt = getJWTFromRequest(httpServletRequest); 37 | 38 | if(StringUtils.hasText(jwt)&& tokenProvider.validateToken(jwt)){ 39 | Long userId = tokenProvider.getUserIdFromJWT(jwt); 40 | User userDetails = customUserDetailsService.loadUserById(userId); 41 | 42 | UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( 43 | userDetails, null, Collections.emptyList()); 44 | 45 | authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest)); 46 | SecurityContextHolder.getContext().setAuthentication(authentication); 47 | 48 | } 49 | 50 | }catch (Exception ex){ 51 | logger.error("Could not set user authentication in security context", ex); 52 | } 53 | 54 | 55 | filterChain.doFilter(httpServletRequest, httpServletResponse); 56 | 57 | } 58 | 59 | 60 | 61 | private String getJWTFromRequest(HttpServletRequest request){ 62 | String bearerToken = request.getHeader(HEADER_STRING); 63 | 64 | if(StringUtils.hasText(bearerToken)&&bearerToken.startsWith(TOKEN_PREFIX)){ 65 | return bearerToken.substring(7, bearerToken.length()); 66 | } 67 | 68 | return null; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/security/JwtTokenProvider.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.security; 2 | 3 | import io.agileintelligence.ppmtool.domain.User; 4 | import io.jsonwebtoken.*; 5 | import org.springframework.security.core.Authentication; 6 | import org.springframework.stereotype.Component; 7 | 8 | import java.util.Date; 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | import static io.agileintelligence.ppmtool.security.SecurityConstants.EXPIRATION_TIME; 13 | import static io.agileintelligence.ppmtool.security.SecurityConstants.SECRET; 14 | 15 | @Component 16 | public class JwtTokenProvider { 17 | 18 | //Generate the token 19 | 20 | public String generateToken(Authentication authentication){ 21 | User user = (User)authentication.getPrincipal(); 22 | Date now = new Date(System.currentTimeMillis()); 23 | 24 | Date expiryDate = new Date(now.getTime()+EXPIRATION_TIME); 25 | 26 | String userId = Long.toString(user.getId()); 27 | 28 | Map claims = new HashMap<>(); 29 | claims.put("id", (Long.toString(user.getId()))); 30 | claims.put("username", user.getUsername()); 31 | claims.put("fullName", user.getFullName()); 32 | 33 | return Jwts.builder() 34 | .setSubject(userId) 35 | .setClaims(claims) 36 | .setIssuedAt(now) 37 | .setExpiration(expiryDate) 38 | .signWith(SignatureAlgorithm.HS512, SECRET) 39 | .compact(); 40 | } 41 | 42 | //Validate the token 43 | public boolean validateToken(String token){ 44 | try{ 45 | Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token); 46 | return true; 47 | }catch (SignatureException ex){ 48 | System.out.println("Invalid JWT Signature"); 49 | }catch (MalformedJwtException ex){ 50 | System.out.println("Invalid JWT Token"); 51 | }catch (ExpiredJwtException ex){ 52 | System.out.println("Expired JWT token"); 53 | }catch (UnsupportedJwtException ex){ 54 | System.out.println("Unsupported JWT token"); 55 | }catch (IllegalArgumentException ex){ 56 | System.out.println("JWT claims string is empty"); 57 | } 58 | return false; 59 | } 60 | 61 | 62 | //Get user Id from token 63 | 64 | public Long getUserIdFromJWT(String token){ 65 | Claims claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody(); 66 | String id = (String)claims.get("id"); 67 | 68 | return Long.parseLong(id); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/security/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.security; 2 | 3 | import io.agileintelligence.ppmtool.services.CustomUserDetailsService; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.security.authentication.AuthenticationManager; 8 | import org.springframework.security.config.BeanIds; 9 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 10 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 11 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 12 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 13 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 14 | import org.springframework.security.config.http.SessionCreationPolicy; 15 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 16 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 17 | 18 | import static io.agileintelligence.ppmtool.security.SecurityConstants.H2_URL; 19 | import static io.agileintelligence.ppmtool.security.SecurityConstants.SIGN_UP_URLS; 20 | 21 | @Configuration 22 | @EnableWebSecurity 23 | @EnableGlobalMethodSecurity( 24 | securedEnabled = true, 25 | jsr250Enabled = true, 26 | prePostEnabled = true 27 | ) 28 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 29 | 30 | @Autowired 31 | private JwtAuthenticationEntryPoint unauthorizedHandler; 32 | 33 | @Autowired 34 | private CustomUserDetailsService customUserDetailsService; 35 | 36 | @Bean 37 | public JwtAuthenticationFilter jwtAuthenticationFilter() {return new JwtAuthenticationFilter();} 38 | 39 | @Autowired 40 | private BCryptPasswordEncoder bCryptPasswordEncoder; 41 | 42 | @Override 43 | protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { 44 | authenticationManagerBuilder.userDetailsService(customUserDetailsService).passwordEncoder(bCryptPasswordEncoder); 45 | } 46 | 47 | @Override 48 | @Bean(BeanIds.AUTHENTICATION_MANAGER) 49 | protected AuthenticationManager authenticationManager() throws Exception { 50 | return super.authenticationManager(); 51 | } 52 | 53 | @Override 54 | protected void configure(HttpSecurity http) throws Exception { 55 | http.cors().and().csrf().disable() 56 | .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() 57 | .sessionManagement() 58 | .sessionCreationPolicy(SessionCreationPolicy.STATELESS) 59 | .and() 60 | .headers().frameOptions().sameOrigin() //To enable H2 Database 61 | .and() 62 | .authorizeRequests() 63 | .antMatchers( 64 | "/", 65 | "/favicon.ico", 66 | "/**/*.png", 67 | "/**/*.gif", 68 | "/**/*.svg", 69 | "/**/*.jpg", 70 | "/**/*.html", 71 | "/**/*.css", 72 | "/**/*.js" 73 | ).permitAll() 74 | .antMatchers(SIGN_UP_URLS).permitAll() 75 | .antMatchers(H2_URL).permitAll() 76 | .anyRequest().authenticated(); 77 | 78 | http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/security/SecurityConstants.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.security; 2 | 3 | public class SecurityConstants { 4 | 5 | public static final String SIGN_UP_URLS = "/api/users/**"; 6 | public static final String H2_URL = "h2-console/**"; 7 | public static final String SECRET ="SecretKeyToGenJWTs"; 8 | public static final String TOKEN_PREFIX= "Bearer "; 9 | public static final String HEADER_STRING = "Authorization"; 10 | public static final long EXPIRATION_TIME = 300_000; //30 seconds 11 | } 12 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/services/CustomUserDetailsService.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.services; 2 | 3 | import io.agileintelligence.ppmtool.domain.User; 4 | import io.agileintelligence.ppmtool.repositories.UserRepository; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.security.core.userdetails.UserDetails; 7 | import org.springframework.security.core.userdetails.UserDetailsService; 8 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.transaction.annotation.Transactional; 11 | 12 | @Service 13 | public class CustomUserDetailsService implements UserDetailsService { 14 | 15 | @Autowired 16 | private UserRepository userRepository; 17 | 18 | @Override 19 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 20 | User user = userRepository.findByUsername(username); 21 | if(user==null) new UsernameNotFoundException("User not found"); 22 | return user; 23 | } 24 | 25 | 26 | @Transactional 27 | public User loadUserById(Long id){ 28 | User user = userRepository.getById(id); 29 | if(user==null) new UsernameNotFoundException("User not found"); 30 | return user; 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/services/MapValidationErrorService.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.services; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.http.ResponseEntity; 5 | import org.springframework.stereotype.Service; 6 | import org.springframework.validation.BindingResult; 7 | import org.springframework.validation.FieldError; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | @Service 13 | public class MapValidationErrorService { 14 | 15 | public ResponseEntity MapValidationService(BindingResult result){ 16 | 17 | if(result.hasErrors()){ 18 | Map errorMap = new HashMap<>(); 19 | 20 | for(FieldError error: result.getFieldErrors()){ 21 | errorMap.put(error.getField(), error.getDefaultMessage()); 22 | } 23 | return new ResponseEntity>(errorMap, HttpStatus.BAD_REQUEST); 24 | } 25 | 26 | return null; 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/services/ProjectService.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.services; 2 | 3 | import io.agileintelligence.ppmtool.domain.Backlog; 4 | import io.agileintelligence.ppmtool.domain.Project; 5 | import io.agileintelligence.ppmtool.domain.User; 6 | import io.agileintelligence.ppmtool.exceptions.ProjectIdException; 7 | import io.agileintelligence.ppmtool.exceptions.ProjectNotFoundException; 8 | import io.agileintelligence.ppmtool.repositories.BacklogRepository; 9 | import io.agileintelligence.ppmtool.repositories.ProjectRepository; 10 | import io.agileintelligence.ppmtool.repositories.UserRepository; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.stereotype.Service; 13 | 14 | @Service 15 | public class ProjectService { 16 | 17 | @Autowired 18 | private ProjectRepository projectRepository; 19 | 20 | @Autowired 21 | private BacklogRepository backlogRepository; 22 | 23 | @Autowired 24 | private UserRepository userRepository; 25 | 26 | public Project saveOrUpdateProject(Project project, String username){ 27 | 28 | if(project.getId() != null){ 29 | Project existingProject = projectRepository.findByProjectIdentifier(project.getProjectIdentifier()); 30 | if(existingProject !=null &&(!existingProject.getProjectLeader().equals(username))){ 31 | throw new ProjectNotFoundException("Project not found in your account"); 32 | }else if(existingProject == null){ 33 | throw new ProjectNotFoundException("Project with ID: '"+project.getProjectIdentifier()+"' cannot be updated because it doesn't exist"); 34 | } 35 | } 36 | 37 | try{ 38 | 39 | User user = userRepository.findByUsername(username); 40 | project.setUser(user); 41 | project.setProjectLeader(user.getUsername()); 42 | project.setProjectIdentifier(project.getProjectIdentifier().toUpperCase()); 43 | 44 | if(project.getId()==null){ 45 | Backlog backlog = new Backlog(); 46 | project.setBacklog(backlog); 47 | backlog.setProject(project); 48 | backlog.setProjectIdentifier(project.getProjectIdentifier().toUpperCase()); 49 | } 50 | 51 | if(project.getId()!=null){ 52 | project.setBacklog(backlogRepository.findByProjectIdentifier(project.getProjectIdentifier().toUpperCase())); 53 | } 54 | 55 | return projectRepository.save(project); 56 | 57 | }catch (Exception e){ 58 | throw new ProjectIdException("Project ID '"+project.getProjectIdentifier().toUpperCase()+"' already exists"); 59 | } 60 | 61 | } 62 | 63 | 64 | public Project findProjectByIdentifier(String projectId, String username){ 65 | 66 | //Only want to return the project if the user looking for it is the owner 67 | 68 | Project project = projectRepository.findByProjectIdentifier(projectId.toUpperCase()); 69 | 70 | if(project == null){ 71 | throw new ProjectIdException("Project ID '"+projectId+"' does not exist"); 72 | 73 | } 74 | 75 | if(!project.getProjectLeader().equals(username)){ 76 | throw new ProjectNotFoundException("Project not found in your account"); 77 | } 78 | 79 | 80 | 81 | return project; 82 | } 83 | 84 | public Iterable findAllProjects(String username){ 85 | return projectRepository.findAllByProjectLeader(username); 86 | } 87 | 88 | 89 | public void deleteProjectByIdentifier(String projectid, String username){ 90 | 91 | 92 | projectRepository.delete(findProjectByIdentifier(projectid, username)); 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/services/ProjectTaskService.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.services; 2 | 3 | import io.agileintelligence.ppmtool.domain.Backlog; 4 | import io.agileintelligence.ppmtool.domain.Project; 5 | import io.agileintelligence.ppmtool.domain.ProjectTask; 6 | import io.agileintelligence.ppmtool.exceptions.ProjectNotFoundException; 7 | import io.agileintelligence.ppmtool.repositories.BacklogRepository; 8 | import io.agileintelligence.ppmtool.repositories.ProjectRepository; 9 | import io.agileintelligence.ppmtool.repositories.ProjectTaskRepository; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Service; 12 | 13 | import java.util.List; 14 | 15 | @Service 16 | public class ProjectTaskService { 17 | 18 | 19 | @Autowired 20 | private BacklogRepository backlogRepository; 21 | 22 | @Autowired 23 | private ProjectTaskRepository projectTaskRepository; 24 | 25 | @Autowired 26 | private ProjectRepository projectRepository; 27 | 28 | @Autowired 29 | private ProjectService projectService; 30 | 31 | 32 | public ProjectTask addProjectTask(String projectIdentifier, ProjectTask projectTask, String username){ 33 | 34 | 35 | //PTs to be added to a specific project, project != null, BL exists 36 | Backlog backlog = projectService.findProjectByIdentifier(projectIdentifier, username).getBacklog(); //backlogRepository.findByProjectIdentifier(projectIdentifier); 37 | //set the bl to pt 38 | System.out.println(backlog); 39 | projectTask.setBacklog(backlog); 40 | //we want our project sequence to be like this: IDPRO-1 IDPRO-2 ...100 101 41 | Integer BacklogSequence = backlog.getPTSequence(); 42 | // Update the BL SEQUENCE 43 | BacklogSequence++; 44 | 45 | backlog.setPTSequence(BacklogSequence); 46 | 47 | //Add Sequence to Project Task 48 | projectTask.setProjectSequence(backlog.getProjectIdentifier()+"-"+BacklogSequence); 49 | projectTask.setProjectIdentifier(projectIdentifier); 50 | 51 | //INITIAL priority when priority null 52 | 53 | //INITIAL status when status is null 54 | if(projectTask.getStatus()==""|| projectTask.getStatus()==null){ 55 | projectTask.setStatus("TO_DO"); 56 | } 57 | 58 | //Fix bug with priority in Spring Boot Server, needs to check null first 59 | if(projectTask.getPriority()==null||projectTask.getPriority()==0){ //In the future we need projectTask.getPriority()== 0 to handle the form 60 | projectTask.setPriority(3); 61 | } 62 | 63 | return projectTaskRepository.save(projectTask); 64 | 65 | 66 | } 67 | 68 | public IterablefindBacklogById(String id, String username){ 69 | 70 | projectService.findProjectByIdentifier(id, username); 71 | 72 | return projectTaskRepository.findByProjectIdentifierOrderByPriority(id); 73 | } 74 | 75 | 76 | public ProjectTask findPTByProjectSequence(String backlog_id, String pt_id, String username){ 77 | 78 | //make sure we are searching on an existing backlog 79 | projectService.findProjectByIdentifier(backlog_id, username); 80 | 81 | 82 | //make sure that our task exists 83 | ProjectTask projectTask = projectTaskRepository.findByProjectSequence(pt_id); 84 | 85 | if(projectTask == null){ 86 | throw new ProjectNotFoundException("Project Task '"+pt_id+"' not found"); 87 | } 88 | 89 | //make sure that the backlog/project id in the path corresponds to the right project 90 | if(!projectTask.getProjectIdentifier().equals(backlog_id)){ 91 | throw new ProjectNotFoundException("Project Task '"+pt_id+"' does not exist in project: '"+backlog_id); 92 | } 93 | 94 | 95 | return projectTask; 96 | } 97 | 98 | public ProjectTask updateByProjectSequence(ProjectTask updatedTask, String backlog_id, String pt_id, String username){ 99 | ProjectTask projectTask = findPTByProjectSequence(backlog_id, pt_id, username); 100 | 101 | projectTask = updatedTask; 102 | 103 | return projectTaskRepository.save(projectTask); 104 | } 105 | 106 | 107 | public void deletePTByProjectSequence(String backlog_id, String pt_id, String username){ 108 | ProjectTask projectTask = findPTByProjectSequence(backlog_id, pt_id, username); 109 | projectTaskRepository.delete(projectTask); 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/services/UserService.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.services; 2 | 3 | 4 | import io.agileintelligence.ppmtool.domain.User; 5 | import io.agileintelligence.ppmtool.exceptions.UsernameAlreadyExistsException; 6 | import io.agileintelligence.ppmtool.repositories.UserRepository; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 9 | import org.springframework.stereotype.Service; 10 | 11 | @Service 12 | public class UserService { 13 | 14 | @Autowired 15 | private UserRepository userRepository; 16 | 17 | 18 | @Autowired 19 | private BCryptPasswordEncoder bCryptPasswordEncoder; 20 | 21 | public User saveUser (User newUser){ 22 | 23 | try{ 24 | newUser.setPassword(bCryptPasswordEncoder.encode(newUser.getPassword())); 25 | //Username has to be unique (exception) 26 | newUser.setUsername(newUser.getUsername()); 27 | // Make sure that password and confirmPassword match 28 | // We don't persist or show the confirmPassword 29 | newUser.setConfirmPassword(""); 30 | return userRepository.save(newUser); 31 | 32 | }catch (Exception e){ 33 | throw new UsernameAlreadyExistsException("Username '"+newUser.getUsername()+"' already exists"); 34 | } 35 | 36 | } 37 | 38 | 39 | 40 | } 41 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/validator/UserValidator.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.validator; 2 | 3 | import io.agileintelligence.ppmtool.domain.User; 4 | import org.springframework.stereotype.Component; 5 | import org.springframework.validation.Errors; 6 | import org.springframework.validation.Validator; 7 | 8 | @Component 9 | public class UserValidator implements Validator { 10 | 11 | @Override 12 | public boolean supports(Class aClass) { 13 | return User.class.equals(aClass); 14 | } 15 | 16 | @Override 17 | public void validate(Object object, Errors errors) { 18 | 19 | User user = (User) object; 20 | 21 | if(user.getPassword().length() <6){ 22 | errors.rejectValue("password","Length", "Password must be at least 6 characters"); 23 | } 24 | 25 | if(!user.getPassword().equals(user.getConfirmPassword())){ 26 | errors.rejectValue("confirmPassword","Match", "Passwords must match"); 27 | 28 | } 29 | 30 | //confirmPassword 31 | 32 | 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/web/BacklogController.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.web; 2 | 3 | import io.agileintelligence.ppmtool.domain.ProjectTask; 4 | import io.agileintelligence.ppmtool.services.MapValidationErrorService; 5 | import io.agileintelligence.ppmtool.services.ProjectTaskService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.validation.BindingResult; 10 | import org.springframework.web.bind.annotation.*; 11 | 12 | import javax.validation.Valid; 13 | import java.security.Principal; 14 | import java.util.List; 15 | 16 | @RestController 17 | @RequestMapping("/api/backlog") 18 | @CrossOrigin 19 | public class BacklogController { 20 | 21 | @Autowired 22 | private ProjectTaskService projectTaskService; 23 | 24 | @Autowired 25 | private MapValidationErrorService mapValidationErrorService; 26 | 27 | 28 | @PostMapping("/{backlog_id}") 29 | public ResponseEntity addPTtoBacklog(@Valid @RequestBody ProjectTask projectTask, 30 | BindingResult result, @PathVariable String backlog_id, Principal principal){ 31 | //show delete 32 | //custom exception 33 | 34 | ResponseEntity errorMap = mapValidationErrorService.MapValidationService(result); 35 | if (errorMap != null) return errorMap; 36 | 37 | ProjectTask projectTask1 = projectTaskService.addProjectTask(backlog_id, projectTask, principal.getName()); 38 | 39 | return new ResponseEntity(projectTask1, HttpStatus.CREATED); 40 | 41 | } 42 | 43 | @GetMapping("/{backlog_id}") 44 | public Iterable getProjectBacklog(@PathVariable String backlog_id, Principal principal){ 45 | 46 | return projectTaskService.findBacklogById(backlog_id, principal.getName()); 47 | 48 | } 49 | 50 | @GetMapping("/{backlog_id}/{pt_id}") 51 | public ResponseEntity getProjectTask(@PathVariable String backlog_id, @PathVariable String pt_id, Principal principal){ 52 | ProjectTask projectTask = projectTaskService.findPTByProjectSequence(backlog_id, pt_id, principal.getName()); 53 | return new ResponseEntity( projectTask, HttpStatus.OK); 54 | } 55 | 56 | 57 | @PatchMapping("/{backlog_id}/{pt_id}") 58 | public ResponseEntity updateProjectTask(@Valid @RequestBody ProjectTask projectTask, BindingResult result, 59 | @PathVariable String backlog_id, @PathVariable String pt_id, Principal principal ){ 60 | 61 | ResponseEntity errorMap = mapValidationErrorService.MapValidationService(result); 62 | if (errorMap != null) return errorMap; 63 | 64 | ProjectTask updatedTask = projectTaskService.updateByProjectSequence(projectTask,backlog_id,pt_id, principal.getName()); 65 | 66 | return new ResponseEntity(updatedTask,HttpStatus.OK); 67 | 68 | } 69 | 70 | 71 | @DeleteMapping("/{backlog_id}/{pt_id}") 72 | public ResponseEntity deleteProjectTask(@PathVariable String backlog_id, @PathVariable String pt_id, Principal principal){ 73 | projectTaskService.deletePTByProjectSequence(backlog_id, pt_id, principal.getName()); 74 | 75 | return new ResponseEntity("Project Task "+pt_id+" was deleted successfully", HttpStatus.OK); 76 | } 77 | 78 | 79 | } 80 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/web/ProjectController.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.web; 2 | 3 | 4 | import io.agileintelligence.ppmtool.domain.Project; 5 | import io.agileintelligence.ppmtool.services.MapValidationErrorService; 6 | import io.agileintelligence.ppmtool.services.ProjectService; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.security.authentication.AuthenticationManager; 11 | import org.springframework.security.core.Authentication; 12 | import org.springframework.security.core.context.SecurityContext; 13 | import org.springframework.security.core.context.SecurityContextHolder; 14 | import org.springframework.validation.BindingResult; 15 | import org.springframework.validation.FieldError; 16 | import org.springframework.web.bind.annotation.*; 17 | 18 | import javax.validation.Valid; 19 | import java.security.Principal; 20 | import java.util.HashMap; 21 | import java.util.List; 22 | import java.util.Map; 23 | 24 | @RestController 25 | @RequestMapping("/api/project") 26 | @CrossOrigin 27 | public class ProjectController { 28 | 29 | @Autowired 30 | private ProjectService projectService; 31 | 32 | @Autowired 33 | private MapValidationErrorService mapValidationErrorService; 34 | 35 | 36 | 37 | 38 | @PostMapping("") 39 | public ResponseEntity createNewProject(@Valid @RequestBody Project project, BindingResult result, Principal principal){ 40 | 41 | ResponseEntity errorMap = mapValidationErrorService.MapValidationService(result); 42 | if(errorMap!=null) return errorMap; 43 | 44 | Project project1 = projectService.saveOrUpdateProject(project, principal.getName()); 45 | return new ResponseEntity(project1, HttpStatus.CREATED); 46 | } 47 | 48 | 49 | @GetMapping("/{projectId}") 50 | public ResponseEntity getProjectById(@PathVariable String projectId, Principal principal){ 51 | 52 | Project project = projectService.findProjectByIdentifier(projectId, principal.getName()); 53 | 54 | return new ResponseEntity(project, HttpStatus.OK); 55 | } 56 | 57 | 58 | @GetMapping("/all") 59 | public Iterable getAllProjects(Principal principal){return projectService.findAllProjects(principal.getName());} 60 | 61 | 62 | @DeleteMapping("/{projectId}") 63 | public ResponseEntity deleteProject(@PathVariable String projectId, Principal principal){ 64 | projectService.deleteProjectByIdentifier(projectId, principal.getName()); 65 | 66 | return new ResponseEntity("Project with ID: '"+projectId+"' was deleted", HttpStatus.OK); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/java/io/agileintelligence/ppmtool/web/UserController.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool.web; 2 | 3 | import io.agileintelligence.ppmtool.domain.User; 4 | import io.agileintelligence.ppmtool.payload.JWTLoginSucessReponse; 5 | import io.agileintelligence.ppmtool.payload.LoginRequest; 6 | import io.agileintelligence.ppmtool.security.JwtTokenProvider; 7 | import io.agileintelligence.ppmtool.services.MapValidationErrorService; 8 | import io.agileintelligence.ppmtool.services.UserService; 9 | import io.agileintelligence.ppmtool.validator.UserValidator; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.http.HttpStatus; 12 | import org.springframework.http.ResponseEntity; 13 | import org.springframework.security.authentication.AuthenticationManager; 14 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 15 | import org.springframework.security.core.Authentication; 16 | import org.springframework.security.core.context.SecurityContextHolder; 17 | import org.springframework.validation.BindingResult; 18 | import org.springframework.web.bind.annotation.PostMapping; 19 | import org.springframework.web.bind.annotation.RequestBody; 20 | import org.springframework.web.bind.annotation.RequestMapping; 21 | import org.springframework.web.bind.annotation.RestController; 22 | 23 | import javax.validation.Valid; 24 | 25 | import static io.agileintelligence.ppmtool.security.SecurityConstants.TOKEN_PREFIX; 26 | 27 | @RestController 28 | @RequestMapping("/api/users") 29 | public class UserController { 30 | 31 | @Autowired 32 | private MapValidationErrorService mapValidationErrorService; 33 | 34 | @Autowired 35 | private UserService userService; 36 | 37 | @Autowired 38 | private UserValidator userValidator; 39 | 40 | @Autowired 41 | private JwtTokenProvider tokenProvider; 42 | 43 | @Autowired 44 | private AuthenticationManager authenticationManager; 45 | 46 | 47 | 48 | @PostMapping("/login") 49 | public ResponseEntity authenticateUser(@Valid @RequestBody LoginRequest loginRequest, BindingResult result){ 50 | ResponseEntity errorMap = mapValidationErrorService.MapValidationService(result); 51 | if(errorMap != null) return errorMap; 52 | 53 | Authentication authentication = authenticationManager.authenticate( 54 | new UsernamePasswordAuthenticationToken( 55 | loginRequest.getUsername(), 56 | loginRequest.getPassword() 57 | ) 58 | ); 59 | 60 | SecurityContextHolder.getContext().setAuthentication(authentication); 61 | String jwt = TOKEN_PREFIX + tokenProvider.generateToken(authentication); 62 | 63 | return ResponseEntity.ok(new JWTLoginSucessReponse(true, jwt)); 64 | } 65 | 66 | @PostMapping("/register") 67 | public ResponseEntity registerUser(@Valid @RequestBody User user, BindingResult result){ 68 | // Validate passwords match 69 | userValidator.validate(user,result); 70 | 71 | ResponseEntity errorMap = mapValidationErrorService.MapValidationService(result); 72 | if(errorMap != null)return errorMap; 73 | 74 | User newUser = userService.saveUser(user); 75 | 76 | return new ResponseEntity(newUser, HttpStatus.CREATED); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /PPMToolFullStack/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | #spring.jpa.show-sql=true 2 | # 3 | #spring.datasource.url = jdbc:mysql://localhost:3306/ppmtcourse 4 | #spring.datasource.username=agileintelligence 5 | #spring.datasource.password=password 6 | # 7 | ##Using the right database platform is extremly important on Spring Boot 2.0 8 | #spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect 9 | # 10 | # 11 | ##CONFLICTS WITH HEROKU from local host 12 | #spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL57Dialect 13 | #spring.jpa.hibernate.ddl-auto=update -------------------------------------------------------------------------------- /PPMToolFullStack/src/test/java/io/agileintelligence/ppmtool/PpmtoolApplicationTests.java: -------------------------------------------------------------------------------- 1 | package io.agileintelligence.ppmtool; 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 PpmtoolApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /ppmtool-react-client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /ppmtool-react-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ppmtool-react-client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "axios": "^0.18.1", 7 | "bootstrap": "^4.3.1", 8 | "classnames": "^2.2.6", 9 | "jwt-decode": "^2.2.0", 10 | "react": "^16.10.2", 11 | "react-dom": "^16.10.2", 12 | "react-redux": "^5.1.2", 13 | "react-router-dom": "^4.3.1", 14 | "react-scripts": "^3.2.0", 15 | "redux": "^4.0.4", 16 | "redux-thunk": "^2.3.0" 17 | }, 18 | "scripts": { 19 | "start": "react-scripts start", 20 | "build": "react-scripts build", 21 | "test": "react-scripts test --env=jsdom", 22 | "eject": "react-scripts eject" 23 | }, 24 | "proxy": "https://agileintelligencecourse.herokuapp.com", 25 | "browserslist": { 26 | "production": [ 27 | ">0.2%", 28 | "not dead", 29 | "not op_mini all" 30 | ], 31 | "development": [ 32 | "last 1 chrome version", 33 | "last 1 firefox version", 34 | "last 1 safari version" 35 | ] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ppmtool-react-client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AgileIntelligence/AgileIntPPMTool/eaa4739bc3142beeeaacb854135dc8154f4b89ba/ppmtool-react-client/public/favicon.ico -------------------------------------------------------------------------------- /ppmtool-react-client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 23 | 25 | 27 | 28 | PPM Tool 29 | 30 | 31 | 32 | 35 |
36 | 37 |
38 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /ppmtool-react-client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /ppmtool-react-client/src/App.css: -------------------------------------------------------------------------------- 1 | img { 2 | width: 100%; 3 | } 4 | 5 | .navbar { 6 | background-color: #e3f2fd; 7 | } 8 | .fa.fa-edit { 9 | color: #18a2b9; 10 | } 11 | 12 | .list-group-item.delete:hover { 13 | cursor: -webkit-grab; 14 | background-color: pink; 15 | } 16 | 17 | .list-group-item.update:hover { 18 | cursor: -webkit-grab; 19 | background-color: gainsboro; 20 | } 21 | 22 | .list-group-item.board:hover { 23 | cursor: -webkit-grab; 24 | background-color: gainsboro; 25 | } 26 | 27 | .fa.fa-minus-circle { 28 | color: red; 29 | } 30 | 31 | .landing { 32 | position: relative; 33 | /* background: url("../img/showcase.jpg") no-repeat; */ 34 | background-size: cover; 35 | background-position: center; 36 | height: 100vh; 37 | margin-top: -24px; 38 | margin-bottom: -50px; 39 | } 40 | 41 | .landing-inner { 42 | padding-top: 80px; 43 | } 44 | 45 | .dark-overlay { 46 | background-color: rgba(0, 0, 0, 0.7); 47 | position: absolute; 48 | top: 0; 49 | left: 0; 50 | width: 100%; 51 | height: 100%; 52 | } 53 | 54 | .card-form { 55 | opacity: 0.9; 56 | } 57 | 58 | .latest-profiles-img { 59 | width: 40px; 60 | height: 40px; 61 | } 62 | 63 | .form-control::placeholder { 64 | color: #bbb !important; 65 | } 66 | -------------------------------------------------------------------------------- /ppmtool-react-client/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import "./App.css"; 3 | import Dashboard from "./components/Dashboard"; 4 | import Header from "./components/Layout/Header"; 5 | import "bootstrap/dist/css/bootstrap.min.css"; 6 | import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; 7 | import AddProject from "./components/Project/AddProject"; 8 | import { Provider } from "react-redux"; 9 | import store from "./store"; 10 | import UpdateProject from "./components/Project/UpdateProject"; 11 | import ProjectBoard from "./components/ProjectBoard/ProjectBoard"; 12 | import AddProjectTask from "./components/ProjectBoard/ProjectTasks/AddProjectTask"; 13 | import UpdateProjectTask from "./components/ProjectBoard/ProjectTasks/UpdateProjectTask"; 14 | import Landing from "./components/Layout/Landing"; 15 | import Register from "./components/UserManagement/Register"; 16 | import Login from "./components/UserManagement/Login"; 17 | import jwt_decode from "jwt-decode"; 18 | import setJWTToken from "./securityUtils/setJWTToken"; 19 | import { SET_CURRENT_USER } from "./actions/types"; 20 | import { logout } from "./actions/securityActions"; 21 | import SecuredRoute from "./securityUtils/SecureRoute"; 22 | 23 | const jwtToken = localStorage.jwtToken; 24 | 25 | if (jwtToken) { 26 | setJWTToken(jwtToken); 27 | const decoded_jwtToken = jwt_decode(jwtToken); 28 | store.dispatch({ 29 | type: SET_CURRENT_USER, 30 | payload: decoded_jwtToken 31 | }); 32 | 33 | const currentTime = Date.now() / 1000; 34 | if (decoded_jwtToken.exp < currentTime) { 35 | store.dispatch(logout()); 36 | window.location.href = "/"; 37 | } 38 | } 39 | 40 | class App extends Component { 41 | render() { 42 | return ( 43 | 44 | 45 |
46 |
47 | { 48 | //Public Routes 49 | } 50 | 51 | 52 | 53 | 54 | 55 | { 56 | //Private Routes 57 | } 58 | 59 | 60 | 61 | 66 | 71 | 76 | 81 | 82 |
83 |
84 |
85 | ); 86 | } 87 | } 88 | 89 | export default App; 90 | -------------------------------------------------------------------------------- /ppmtool-react-client/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /ppmtool-react-client/src/actions/backlogActions.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { 3 | GET_ERRORS, 4 | GET_BACKLOG, 5 | GET_PROJECT_TASK, 6 | DELETE_PROJECT_TASK 7 | } from "./types"; 8 | 9 | //Fix bug with priority in Spring Boot Server, needs to check null first 10 | export const addProjectTask = ( 11 | backlog_id, 12 | project_task, 13 | history 14 | ) => async dispatch => { 15 | try { 16 | await axios.post(`/api/backlog/${backlog_id}`, project_task); 17 | history.push(`/projectBoard/${backlog_id}`); 18 | dispatch({ 19 | type: GET_ERRORS, 20 | payload: {} 21 | }); 22 | } catch (err) { 23 | dispatch({ 24 | type: GET_ERRORS, 25 | payload: err.response.data 26 | }); 27 | } 28 | }; 29 | 30 | export const getBacklog = backlog_id => async dispatch => { 31 | try { 32 | const res = await axios.get(`/api/backlog/${backlog_id}`); 33 | dispatch({ 34 | type: GET_BACKLOG, 35 | payload: res.data 36 | }); 37 | } catch (err) { 38 | dispatch({ 39 | type: GET_ERRORS, 40 | payload: err.response.data 41 | }); 42 | } 43 | }; 44 | 45 | export const getProjectTask = ( 46 | backlog_id, 47 | pt_id, 48 | history 49 | ) => async dispatch => { 50 | try { 51 | const res = await axios.get(`/api/backlog/${backlog_id}/${pt_id}`); 52 | dispatch({ 53 | type: GET_PROJECT_TASK, 54 | payload: res.data 55 | }); 56 | } catch (err) { 57 | history.push("/dashboard"); 58 | } 59 | }; 60 | 61 | export const updateProjectTask = ( 62 | backlog_id, 63 | pt_id, 64 | project_task, 65 | history 66 | ) => async dispatch => { 67 | try { 68 | await axios.patch(`/api/backlog/${backlog_id}/${pt_id}`, project_task); 69 | history.push(`/projectBoard/${backlog_id}`); 70 | dispatch({ 71 | type: GET_ERRORS, 72 | payload: {} 73 | }); 74 | } catch (err) { 75 | dispatch({ 76 | type: GET_ERRORS, 77 | payload: err.response.data 78 | }); 79 | } 80 | }; 81 | 82 | export const deleteProjectTask = (backlog_id, pt_id) => async dispatch => { 83 | if ( 84 | window.confirm( 85 | `You are deleting project task ${pt_id}, this action cannot be undone` 86 | ) 87 | ) { 88 | await axios.delete(`/api/backlog/${backlog_id}/${pt_id}`); 89 | dispatch({ 90 | type: DELETE_PROJECT_TASK, 91 | payload: pt_id 92 | }); 93 | } 94 | }; 95 | -------------------------------------------------------------------------------- /ppmtool-react-client/src/actions/projectActions.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { GET_ERRORS, GET_PROJECTS, GET_PROJECT, DELETE_PROJECT } from "./types"; 3 | 4 | export const createProject = (project, history) => async dispatch => { 5 | try { 6 | await axios.post("/api/project", project); 7 | history.push("/dashboard"); 8 | dispatch({ 9 | type: GET_ERRORS, 10 | payload: {} 11 | }); 12 | } catch (err) { 13 | dispatch({ 14 | type: GET_ERRORS, 15 | payload: err.response.data 16 | }); 17 | } 18 | }; 19 | 20 | export const getProjects = () => async dispatch => { 21 | const res = await axios.get("/api/project/all"); 22 | dispatch({ 23 | type: GET_PROJECTS, 24 | payload: res.data 25 | }); 26 | }; 27 | 28 | export const getProject = (id, history) => async dispatch => { 29 | try { 30 | const res = await axios.get(`/api/project/${id}`); 31 | dispatch({ 32 | type: GET_PROJECT, 33 | payload: res.data 34 | }); 35 | } catch (error) { 36 | history.push("/dashboard"); 37 | } 38 | }; 39 | 40 | export const deleteProject = id => async dispatch => { 41 | if ( 42 | window.confirm( 43 | "Are you sure? This will delete the project and all the data related to it" 44 | ) 45 | ) { 46 | await axios.delete(`/api/project/${id}`); 47 | dispatch({ 48 | type: DELETE_PROJECT, 49 | payload: id 50 | }); 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /ppmtool-react-client/src/actions/securityActions.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { GET_ERRORS, SET_CURRENT_USER } from "./types"; 3 | import setJWTToken from "../securityUtils/setJWTToken"; 4 | import jwt_decode from "jwt-decode"; 5 | 6 | export const createNewUser = (newUser, history) => async dispatch => { 7 | try { 8 | await axios.post("/api/users/register", newUser); 9 | history.push("/login"); 10 | dispatch({ 11 | type: GET_ERRORS, 12 | payload: {} 13 | }); 14 | } catch (err) { 15 | dispatch({ 16 | type: GET_ERRORS, 17 | payload: err.response.data 18 | }); 19 | } 20 | }; 21 | 22 | export const login = LoginRequest => async dispatch => { 23 | try { 24 | // post => Login Request 25 | const res = await axios.post("/api/users/login", LoginRequest); 26 | // extract token from res.data 27 | const { token } = res.data; 28 | // store the token in the localStorage 29 | localStorage.setItem("jwtToken", token); 30 | // set our token in header *** 31 | setJWTToken(token); 32 | // decode token on React 33 | const decoded = jwt_decode(token); 34 | // dispatch to our securityReducer 35 | dispatch({ 36 | type: SET_CURRENT_USER, 37 | payload: decoded 38 | }); 39 | } catch (err) { 40 | dispatch({ 41 | type: GET_ERRORS, 42 | payload: err.response.data 43 | }); 44 | } 45 | }; 46 | 47 | export const logout = () => dispatch => { 48 | localStorage.removeItem("jwtToken"); 49 | setJWTToken(false); 50 | dispatch({ 51 | type: SET_CURRENT_USER, 52 | payload: {} 53 | }); 54 | }; 55 | -------------------------------------------------------------------------------- /ppmtool-react-client/src/actions/types.js: -------------------------------------------------------------------------------- 1 | export const GET_ERRORS = "GET_ERRORS"; 2 | export const GET_PROJECTS = "GET_PROJECTS"; 3 | export const GET_PROJECT = "GET_PROJECT"; 4 | export const DELETE_PROJECT = "DELETE_PROJECT"; 5 | 6 | //Types for BACKLOG ACTIONS 7 | 8 | export const GET_BACKLOG = "GET_BACKLOG"; 9 | export const GET_PROJECT_TASK = "GET_PROJECT_TASK"; 10 | export const DELETE_PROJECT_TASK = "DELETE_PROJECT_TASK"; 11 | 12 | export const SET_CURRENT_USER = "SET_CURRENT_USER"; 13 | -------------------------------------------------------------------------------- /ppmtool-react-client/src/components/Dashboard.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import ProjectItem from "./Project/ProjectItem"; 3 | import CreateProjectButton from "./Project/CreateProjectButton"; 4 | import { connect } from "react-redux"; 5 | import { getProjects } from "../actions/projectActions"; 6 | import PropTypes from "prop-types"; 7 | 8 | class Dashboard extends Component { 9 | componentDidMount() { 10 | this.props.getProjects(); 11 | } 12 | 13 | render() { 14 | const { projects } = this.props.project; 15 | 16 | return ( 17 |
18 |
19 |
20 |
21 |

Projects

22 |
23 | 24 | 25 |
26 |
27 | {projects.map(project => ( 28 | 29 | ))} 30 |
31 |
32 |
33 |
34 | ); 35 | } 36 | } 37 | 38 | Dashboard.propTypes = { 39 | project: PropTypes.object.isRequired, 40 | getProjects: PropTypes.func.isRequired 41 | }; 42 | 43 | const mapStateToProps = state => ({ 44 | project: state.project 45 | }); 46 | 47 | export default connect( 48 | mapStateToProps, 49 | { getProjects } 50 | )(Dashboard); 51 | -------------------------------------------------------------------------------- /ppmtool-react-client/src/components/Layout/Header.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import PropTypes from "prop-types"; 4 | import { connect } from "react-redux"; 5 | import { logout } from "../../actions/securityActions"; 6 | 7 | class Header extends Component { 8 | logout() { 9 | this.props.logout(); 10 | window.location.href = "/"; 11 | } 12 | render() { 13 | const { validToken, user } = this.props.security; 14 | 15 | const userIsAuthenticated = ( 16 |
17 |
    18 |
  • 19 | 20 | Dashboard 21 | 22 |
  • 23 |
24 | 25 |
    26 |
  • 27 | 28 | 29 | {user.fullName} 30 | 31 |
  • 32 |
  • 33 | 38 | Logout 39 | 40 |
  • 41 |
42 |
43 | ); 44 | 45 | const userIsNotAuthenticated = ( 46 |
47 |
    48 |
  • 49 | 50 | Sign Up 51 | 52 |
  • 53 |
  • 54 | 55 | Login 56 | 57 |
  • 58 |
59 |
60 | ); 61 | 62 | let headerLinks; 63 | 64 | if (validToken && user) { 65 | headerLinks = userIsAuthenticated; 66 | } else { 67 | headerLinks = userIsNotAuthenticated; 68 | } 69 | 70 | return ( 71 | 87 | ); 88 | } 89 | } 90 | 91 | Header.propTypes = { 92 | logout: PropTypes.func.isRequired, 93 | security: PropTypes.object.isRequired 94 | }; 95 | 96 | const mapStateToProps = state => ({ 97 | security: state.security 98 | }); 99 | 100 | export default connect( 101 | mapStateToProps, 102 | { logout } 103 | )(Header); 104 | -------------------------------------------------------------------------------- /ppmtool-react-client/src/components/Layout/Landing.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import { connect } from "react-redux"; 4 | import PropTypes from "prop-types"; 5 | 6 | class Landing extends Component { 7 | componentDidMount() { 8 | if (this.props.security.validToken) { 9 | this.props.history.push("/dashboard"); 10 | } 11 | } 12 | render() { 13 | return ( 14 |
15 |
16 |
17 |
18 |
19 |

20 | Personal Project Management Tool 21 |

22 |

23 | Create your account to join active projects or start your own 24 |

25 |
26 | 27 | Sign Up 28 | 29 | 30 | Login 31 | 32 |
33 |
34 |
35 |
36 |
37 | ); 38 | } 39 | } 40 | 41 | Landing.propTypes = { 42 | security: PropTypes.object.isRequired 43 | }; 44 | 45 | const mapStateToProps = state => ({ 46 | security: state.security 47 | }); 48 | 49 | export default connect(mapStateToProps)(Landing); 50 | -------------------------------------------------------------------------------- /ppmtool-react-client/src/components/Project/AddProject.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import PropTypes from "prop-types"; 3 | import { connect } from "react-redux"; 4 | import { createProject } from "../../actions/projectActions"; 5 | import classnames from "classnames"; 6 | 7 | class AddProject extends Component { 8 | constructor() { 9 | super(); 10 | 11 | this.state = { 12 | projectName: "", 13 | projectIdentifier: "", 14 | description: "", 15 | start_date: "", 16 | end_date: "", 17 | errors: {} 18 | }; 19 | 20 | this.onChange = this.onChange.bind(this); 21 | this.onSubmit = this.onSubmit.bind(this); 22 | } 23 | 24 | //life cycle hooks 25 | componentWillReceiveProps(nextProps) { 26 | if (nextProps.errors) { 27 | this.setState({ errors: nextProps.errors }); 28 | } 29 | } 30 | 31 | onChange(e) { 32 | this.setState({ [e.target.name]: e.target.value }); 33 | } 34 | 35 | onSubmit(e) { 36 | e.preventDefault(); 37 | const newProject = { 38 | projectName: this.state.projectName, 39 | projectIdentifier: this.state.projectIdentifier, 40 | description: this.state.description, 41 | start_date: this.state.start_date, 42 | end_date: this.state.end_date 43 | }; 44 | this.props.createProject(newProject, this.props.history); 45 | } 46 | 47 | render() { 48 | const { errors } = this.state; 49 | 50 | return ( 51 |
52 |
53 |
54 |
55 |
56 |
Create Project form
57 |
58 |
59 |
60 | 70 | {errors.projectName && ( 71 |
72 | {errors.projectName} 73 |
74 | )} 75 |
76 |
77 | 87 | {errors.projectIdentifier && ( 88 |
89 | {errors.projectIdentifier} 90 |
91 | )} 92 |
93 |
94 |