├── .gitignore
├── .mvn
└── wrapper
│ ├── maven-wrapper.jar
│ └── maven-wrapper.properties
├── README.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
└── main
├── java
└── com
│ └── vega
│ └── springit
│ ├── SpringitApplication.java
│ ├── bootstrap
│ └── DatabaseLoader.java
│ ├── config
│ ├── AuditorAwareImpl.java
│ └── JpaConfig.java
│ ├── controller
│ ├── AuthController.java
│ ├── LinkController.java
│ └── VoteController.java
│ ├── domain
│ ├── Auditable.java
│ ├── Comment.java
│ ├── Link.java
│ ├── Role.java
│ ├── User.java
│ ├── Vote.java
│ └── validator
│ │ ├── PasswordsMatch.java
│ │ └── PasswordsMatchValidator.java
│ ├── repository
│ ├── CommentRepository.java
│ ├── LinkRepository.java
│ ├── RoleRepository.java
│ ├── UserRepository.java
│ └── VoteRepository.java
│ ├── security
│ ├── SecurityConfiguration.java
│ └── UserDetailsServiceImpl.java
│ └── service
│ ├── BeanUtil.java
│ ├── CommentService.java
│ ├── LinkService.java
│ ├── MailService.java
│ ├── RoleService.java
│ ├── UserService.java
│ └── VoteService.java
└── resources
├── application-dev.properties
├── application-prod.properties
├── application.properties
├── data-mysql.sql
├── schema-mysql.sql
├── static
├── css
│ ├── profile.css
│ └── springit.css
├── favicon.ico
├── images
│ ├── profile_small.png
│ └── spring-boot-logo.png
└── libs
│ └── bootstrap
│ ├── css
│ ├── bootstrap-grid.css
│ ├── bootstrap-grid.css.map
│ ├── bootstrap-grid.min.css
│ ├── bootstrap-grid.min.css.map
│ ├── bootstrap-reboot.css
│ ├── bootstrap-reboot.css.map
│ ├── bootstrap-reboot.min.css
│ ├── bootstrap-reboot.min.css.map
│ ├── bootstrap.css
│ ├── bootstrap.css.map
│ ├── bootstrap.min.css
│ └── bootstrap.min.css.map
│ └── js
│ ├── bootstrap.bundle.js
│ ├── bootstrap.bundle.js.map
│ ├── bootstrap.bundle.min.js
│ ├── bootstrap.bundle.min.js.map
│ ├── bootstrap.js
│ ├── bootstrap.js.map
│ ├── bootstrap.min.js
│ └── bootstrap.min.js.map
└── templates
├── auth
├── activated.html
├── login.html
├── profile.html
└── register.html
├── email
├── activation.html
└── welcome.html
├── layouts
├── main_layout.html
└── new_page.html
└── link
├── list.html
├── submit.html
└── view.html
/.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 |
12 | ### IntelliJ IDEA ###
13 | .idea
14 | *.iws
15 | *.iml
16 | *.ipr
17 |
18 | ### NetBeans ###
19 | nbproject/private/
20 | build/
21 | nbbuild/
22 | dist/
23 | nbdist/
24 | .nb-gradle/
25 |
26 | # mac os finder properties
27 | .DS_Store
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danvega/springit/b1c1301740b1d11d7da4c221b774165c6d8f7648/.mvn/wrapper/maven-wrapper.jar
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.2/apache-maven-3.5.2-bin.zip
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Springit
2 |
3 | This is a reddit clone built using Spring Boot 2. I created this
4 | project to show off all the cool features of Spring Boot 2.
5 |
6 | ## Getting Started
7 |
8 | TODO: How to get started?
9 |
10 | ### Prerequisites
11 |
12 | TODO: What do they need to get started
13 |
14 | ### Installing
15 |
16 | TODO: Installing
17 |
18 | ## Running the tests
19 |
20 | Explain how to run the automated tests for this system
21 |
22 | ### Break down into end to end tests
23 |
24 | Explain what these tests test and why
25 |
26 | ```
27 | Give an example
28 | ```
29 |
30 | ### And coding style tests
31 |
32 | Explain what these tests test and why
33 |
34 | ```
35 | Give an example
36 | ```
37 |
38 | ## Deployment
39 |
40 | Add additional notes about how to deploy this on a live system
41 |
42 | ## Built With
43 |
44 | * [Spring Boot 2](https://projects.spring.io/spring-boot/)
45 | * [Spring Framework 5](https://projects.spring.io/spring-framework/)
46 |
47 | ## Contributing
48 |
49 | Please read [CONTRIBUTING.md](https://gist.github.com/PurpleBooth/b24679402957c63ec426) for details on our code of conduct, and the process for submitting pull requests to us.
50 |
51 | ## Authors
52 |
53 | * **Dan Vega** - [TheRealDanVega](http://www.therealdanvega.com)
54 |
55 | See also the list of [contributors](https://github.com/your/project/contributors) who participated in this project.
56 |
57 | ## License
58 |
59 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details
60 |
61 | ## Acknowledgments
62 |
63 | * Thank you PurpleBooth for the [README template](https://gist.github.com/PurpleBooth/109311bb0361f32d87a2).
64 | * Thank you to the Spring Boot team for the awesome software!
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | com.vega
7 | springit
8 | 0.0.2-SNAPSHOT
9 | jar
10 |
11 | springit
12 | Reddit Clone
13 |
14 |
15 | org.springframework.boot
16 | spring-boot-starter-parent
17 | 2.0.0.RELEASE
18 |
19 |
20 |
21 |
22 | UTF-8
23 | UTF-8
24 | 8
25 |
26 |
27 |
28 |
29 | org.springframework.boot
30 | spring-boot-starter-actuator
31 |
32 |
33 | org.springframework.boot
34 | spring-boot-starter-data-jpa
35 |
36 |
37 | org.springframework.boot
38 | spring-boot-starter-thymeleaf
39 |
40 |
41 | org.thymeleaf.extras
42 | thymeleaf-extras-springsecurity5
43 | 3.0.4.RELEASE
44 |
45 |
46 | org.springframework.boot
47 | spring-boot-starter-web
48 |
49 |
50 | org.springframework.boot
51 | spring-boot-starter-security
52 |
53 |
54 | org.ocpsoft.prettytime
55 | prettytime
56 | 4.0.1.Final
57 |
58 |
59 | org.springframework.boot
60 | spring-boot-configuration-processor
61 | true
62 |
63 |
64 | org.springframework.boot
65 | spring-boot-starter-mail
66 |
67 |
68 | org.springframework.boot
69 | spring-boot-devtools
70 | runtime
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | mysql
79 | mysql-connector-java
80 |
81 |
82 | org.projectlombok
83 | lombok
84 | true
85 |
86 |
87 | org.springframework.boot
88 | spring-boot-starter-test
89 | test
90 |
91 |
92 |
93 |
94 |
95 |
96 | org.springframework.boot
97 | spring-boot-maven-plugin
98 |
99 |
100 |
101 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/src/main/java/com/vega/springit/SpringitApplication.java:
--------------------------------------------------------------------------------
1 | package com.vega.springit;
2 |
3 | import org.ocpsoft.prettytime.PrettyTime;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 | import org.springframework.boot.SpringApplication;
7 | import org.springframework.boot.autoconfigure.SpringBootApplication;
8 | import org.springframework.context.annotation.Bean;
9 | import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
10 | import org.springframework.transaction.annotation.EnableTransactionManagement;
11 | import org.thymeleaf.extras.springsecurity5.dialect.SpringSecurityDialect;
12 |
13 | @SpringBootApplication
14 | @EnableTransactionManagement
15 | public class SpringitApplication {
16 |
17 | private static final Logger log = LoggerFactory.getLogger(SpringitApplication.class);
18 |
19 | public static void main(String[] args) {
20 | SpringApplication.run(SpringitApplication.class, args);
21 | }
22 |
23 | @Bean
24 | PrettyTime prettyTime() {
25 | return new PrettyTime();
26 | }
27 |
28 | // TODO * Configuring this bean should not be needed once Spring Boot's Thymeleaf starter includes configuration
29 | // TODO for thymeleaf-extras-springsecurity5 (instead of thymeleaf-extras-springsecurity4)
30 | @Bean
31 | public SpringSecurityDialect securityDialect() {
32 | return new SpringSecurityDialect();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/vega/springit/bootstrap/DatabaseLoader.java:
--------------------------------------------------------------------------------
1 | package com.vega.springit.bootstrap;
2 |
3 | import com.vega.springit.domain.Comment;
4 | import com.vega.springit.domain.Link;
5 | import com.vega.springit.domain.Role;
6 | import com.vega.springit.domain.User;
7 | import com.vega.springit.repository.CommentRepository;
8 | import com.vega.springit.repository.LinkRepository;
9 | import com.vega.springit.repository.RoleRepository;
10 | import com.vega.springit.repository.UserRepository;
11 | import org.springframework.boot.CommandLineRunner;
12 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
13 | import org.springframework.stereotype.Component;
14 |
15 | import java.util.Arrays;
16 | import java.util.HashMap;
17 | import java.util.HashSet;
18 | import java.util.Map;
19 |
20 | @Component
21 | public class DatabaseLoader implements CommandLineRunner {
22 |
23 | private LinkRepository linkRepository;
24 | private CommentRepository commentRepository;
25 | private UserRepository userRepository;
26 | private RoleRepository roleRepository;
27 |
28 | private Map users = new HashMap<>();
29 |
30 | public DatabaseLoader(LinkRepository linkRepository, CommentRepository commentRepository, UserRepository userRepository, RoleRepository roleRepository) {
31 | this.linkRepository = linkRepository;
32 | this.commentRepository = commentRepository;
33 | this.userRepository = userRepository;
34 | this.roleRepository = roleRepository;
35 | }
36 |
37 | @Override
38 | public void run(String... args) {
39 |
40 | // add users and roles
41 | addUsersAndRoles();
42 |
43 | Map links = new HashMap<>();
44 | links.put("Securing Spring Boot APIs and SPAs with OAuth 2.0","https://auth0.com/blog/securing-spring-boot-apis-and-spas-with-oauth2/?utm_source=reddit&utm_medium=sc&utm_campaign=springboot_spa_securing");
45 | links.put("Easy way to detect Device in Java Web Application using Spring Mobile - Source code to download from GitHub","https://www.opencodez.com/java/device-detection-using-spring-mobile.htm");
46 | links.put("Tutorial series about building microservices with SpringBoot (with Netflix OSS)","https://medium.com/@marcus.eisele/implementing-a-microservice-architecture-with-spring-boot-intro-cdb6ad16806c");
47 | links.put("Detailed steps to send encrypted email using Java / Spring Boot - Source code to download from GitHub","https://www.opencodez.com/java/send-encrypted-email-using-java.htm");
48 | links.put("Build a Secure Progressive Web App With Spring Boot and React","https://dzone.com/articles/build-a-secure-progressive-web-app-with-spring-boo");
49 | links.put("Building Your First Spring Boot Web Application - DZone Java","https://dzone.com/articles/building-your-first-spring-boot-web-application-ex");
50 | links.put("Building Microservices with Spring Boot Fat (Uber) Jar","https://jelastic.com/blog/building-microservices-with-spring-boot-fat-uber-jar/");
51 | links.put("Spring Cloud GCP 1.0 Released","https://cloud.google.com/blog/products/gcp/calling-java-developers-spring-cloud-gcp-1-0-is-now-generally-available");
52 | links.put("Simplest way to Upload and Download Files in Java with Spring Boot - Code to download from Github","https://www.opencodez.com/uncategorized/file-upload-and-download-in-java-spring-boot.htm");
53 | links.put("Add Social Login to Your Spring Boot 2.0 app","https://developer.okta.com/blog/2018/07/24/social-spring-boot");
54 | links.put("File download example using Spring REST Controller","https://www.jeejava.com/file-download-example-using-spring-rest-controller/");
55 |
56 | links.forEach((k,v) -> {
57 | User u1 = users.get("user@gmail.com");
58 | User u2 = users.get("super@gmail.com");
59 | Link link = new Link(k,v);
60 | if(k.startsWith("Build")) {
61 | link.setUser(u1);
62 | } else {
63 | link.setUser(u2);
64 | }
65 |
66 | linkRepository.save(link);
67 |
68 | // we will do something with comments later
69 | Comment spring = new Comment("Thank you for this link related to Spring Boot. I love it, great post!",link);
70 | Comment security = new Comment("I love that you're talking about Spring Security",link);
71 | Comment pwa = new Comment("What is this Progressive Web App thing all about? PWAs sound really cool.",link);
72 | Comment comments[] = {spring,security,pwa};
73 | for(Comment comment : comments) {
74 | commentRepository.save(comment);
75 | link.addComment(comment);
76 | }
77 | });
78 |
79 | long linkCount = linkRepository.count();
80 | System.out.println("Number of links in the database: " + linkCount );
81 | }
82 |
83 | private void addUsersAndRoles() {
84 | BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
85 | String secret = "{bcrypt}" + encoder.encode("password");
86 |
87 | Role userRole = new Role("ROLE_USER");
88 | roleRepository.save(userRole);
89 | Role adminRole = new Role("ROLE_ADMIN");
90 | roleRepository.save(adminRole);
91 |
92 | User user = new User("user@gmail.com",secret,true,"Joe","User","joedirt");
93 | user.addRole(userRole);
94 | user.setConfirmPassword(secret);
95 | userRepository.save(user);
96 | users.put("user@gmail.com",user);
97 |
98 | User admin = new User("admin@gmail.com",secret,true,"Joe","Admin","masteradmin");
99 | admin.setAlias("joeadmin");
100 | admin.addRole(adminRole);
101 | admin.setConfirmPassword(secret);
102 | userRepository.save(admin);
103 | users.put("admin@gmail.com",admin);
104 |
105 | User master = new User("super@gmail.com",secret,true,"Super","User","superduper");
106 | master.addRoles(new HashSet<>(Arrays.asList(userRole,adminRole)));
107 | master.setConfirmPassword(secret);
108 | userRepository.save(master);
109 | users.put("super@gmail.com",master);
110 | }
111 |
112 | }
113 |
--------------------------------------------------------------------------------
/src/main/java/com/vega/springit/config/AuditorAwareImpl.java:
--------------------------------------------------------------------------------
1 | package com.vega.springit.config;
2 |
3 | import com.vega.springit.domain.User;
4 | import org.springframework.data.domain.AuditorAware;
5 | import org.springframework.security.core.context.SecurityContextHolder;
6 |
7 | import java.util.Optional;
8 |
9 | public class AuditorAwareImpl implements AuditorAware {
10 |
11 | @Override
12 | public Optional getCurrentAuditor() {
13 | if(SecurityContextHolder.getContext().getAuthentication() == null || SecurityContextHolder.getContext().getAuthentication().getPrincipal().equals("anonymousUser")) {
14 | return Optional.of("master@gmail.com");
15 | }
16 | return Optional.of(((User) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getEmail());
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/com/vega/springit/config/JpaConfig.java:
--------------------------------------------------------------------------------
1 | package com.vega.springit.config;
2 |
3 | import org.springframework.context.annotation.Bean;
4 | import org.springframework.context.annotation.Configuration;
5 | import org.springframework.data.domain.AuditorAware;
6 | import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
7 |
8 | @Configuration
9 | @EnableJpaAuditing(auditorAwareRef = "auditorAware")
10 | public class JpaConfig {
11 |
12 | @Bean
13 | public AuditorAware auditorAware() {
14 | return new AuditorAwareImpl();
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/vega/springit/controller/AuthController.java:
--------------------------------------------------------------------------------
1 | package com.vega.springit.controller;
2 |
3 | import com.vega.springit.domain.User;
4 | import com.vega.springit.service.UserService;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 | import org.springframework.stereotype.Controller;
8 | import org.springframework.ui.Model;
9 | import org.springframework.validation.BindingResult;
10 | import org.springframework.web.bind.annotation.GetMapping;
11 | import org.springframework.web.bind.annotation.PathVariable;
12 | import org.springframework.web.bind.annotation.PostMapping;
13 | import org.springframework.web.servlet.mvc.support.RedirectAttributes;
14 |
15 | import javax.validation.Valid;
16 | import java.util.Optional;
17 |
18 | @Controller
19 | public class AuthController {
20 |
21 | private final Logger logger = LoggerFactory.getLogger(AuthController.class);
22 | private UserService userService;
23 |
24 | public AuthController(UserService userService) {
25 | this.userService = userService;
26 | }
27 |
28 | @GetMapping("/login")
29 | public String login() {
30 | return "auth/login";
31 | }
32 |
33 | @GetMapping("/profile")
34 | public String profile() {
35 | return "auth/profile";
36 | }
37 |
38 | @GetMapping("/register")
39 | public String register(Model model) {
40 | model.addAttribute("user",new User());
41 | return "auth/register";
42 | }
43 |
44 | @PostMapping("/register")
45 | public String registerNewUser(@Valid User user, BindingResult bindingResult, Model model, RedirectAttributes redirectAttributes) {
46 | if( bindingResult.hasErrors() ) {
47 | // show validation errors
48 | logger.info("Validation errors were found while registering a new user");
49 | model.addAttribute("user",user);
50 | model.addAttribute("validationErrors", bindingResult.getAllErrors());
51 | return "auth/register";
52 | } else {
53 | // Register new user
54 | User newUser = userService.register(user);
55 | redirectAttributes
56 | .addAttribute("id",newUser.getId())
57 | .addFlashAttribute("success",true);
58 | return "redirect:/register";
59 | }
60 | }
61 |
62 | @GetMapping("/activate/{email}/{activationCode}")
63 | public String activate(@PathVariable String email, @PathVariable String activationCode) {
64 | Optional user = userService.findByEmailAndActivationCode(email,activationCode);
65 | if( user.isPresent() ) {
66 | User newUser = user.get();
67 | newUser.setEnabled(true);
68 | newUser.setConfirmPassword(newUser.getPassword());
69 | userService.save(newUser);
70 | userService.sendWelcomeEmail(newUser);
71 | return "auth/activated";
72 | }
73 | return "redirect:/";
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/java/com/vega/springit/controller/LinkController.java:
--------------------------------------------------------------------------------
1 | package com.vega.springit.controller;
2 |
3 | import com.vega.springit.domain.Comment;
4 | import com.vega.springit.domain.Link;
5 | import com.vega.springit.repository.CommentRepository;
6 | import com.vega.springit.service.CommentService;
7 | import com.vega.springit.service.LinkService;
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 | import org.springframework.security.access.annotation.Secured;
11 | import org.springframework.stereotype.Controller;
12 | import org.springframework.ui.Model;
13 | import org.springframework.validation.BindingResult;
14 | import org.springframework.web.bind.annotation.GetMapping;
15 | import org.springframework.web.bind.annotation.PathVariable;
16 | import org.springframework.web.bind.annotation.PostMapping;
17 | import org.springframework.web.servlet.mvc.support.RedirectAttributes;
18 |
19 | import javax.validation.Valid;
20 | import java.util.Optional;
21 |
22 | @Controller
23 | public class LinkController {
24 |
25 | private static final Logger logger = LoggerFactory.getLogger(LinkController.class);
26 |
27 | private LinkService linkService;
28 | private CommentService commentService;
29 |
30 | public LinkController(LinkService linkService, CommentService commentService) {
31 | this.linkService = linkService;
32 | this.commentService = commentService;
33 | }
34 |
35 | @GetMapping("/")
36 | public String list(Model model) {
37 | model.addAttribute("links",linkService.findAll());
38 | return "link/list";
39 | }
40 |
41 | @GetMapping("/link/{id}")
42 | public String read(@PathVariable Long id, Model model) {
43 | Optional link = linkService.findById(id);
44 | if( link.isPresent() ) {
45 | Link currentLink = link.get();
46 | Comment comment = new Comment();
47 | comment.setLink(currentLink);
48 | model.addAttribute("comment",comment);
49 | model.addAttribute("link",currentLink);
50 | model.addAttribute("success", model.containsAttribute("success"));
51 | return "link/view";
52 | } else {
53 | return "redirect:/";
54 | }
55 | }
56 |
57 | @GetMapping("/link/submit")
58 | public String newLinkForm(Model model) {
59 | model.addAttribute("link",new Link());
60 | return "link/submit";
61 | }
62 |
63 | @PostMapping("/link/submit")
64 | public String createLink(@Valid Link link, BindingResult bindingResult, Model model, RedirectAttributes redirectAttributes) {
65 | if( bindingResult.hasErrors() ) {
66 | logger.info("Validation errors were found while submitting a new link.");
67 | model.addAttribute("link",link);
68 | return "link/submit";
69 | } else {
70 | // save our link
71 | linkService.save(link);
72 | logger.info("New link was saved successfully");
73 | redirectAttributes
74 | .addAttribute("id",link.getId())
75 | .addFlashAttribute("success",true);
76 | return "redirect:/link/{id}";
77 | }
78 | }
79 |
80 | @Secured({"ROLE_USER"})
81 | @PostMapping("/link/comments")
82 | public String addComment(@Valid Comment comment, BindingResult bindingResult){
83 | if( bindingResult.hasErrors() ){
84 | logger.info("There was a problem adding a new comment.");
85 | } else {
86 | commentService.save(comment);
87 | logger.info("New comment was saved successfully.");
88 | }
89 | return "redirect:/link/" + comment.getLink().getId();
90 | }
91 |
92 | }
93 |
--------------------------------------------------------------------------------
/src/main/java/com/vega/springit/controller/VoteController.java:
--------------------------------------------------------------------------------
1 | package com.vega.springit.controller;
2 |
3 | import com.vega.springit.domain.Link;
4 | import com.vega.springit.domain.Vote;
5 | import com.vega.springit.repository.LinkRepository;
6 | import com.vega.springit.repository.VoteRepository;
7 | import com.vega.springit.service.LinkService;
8 | import com.vega.springit.service.VoteService;
9 | import org.springframework.security.access.annotation.Secured;
10 | import org.springframework.web.bind.annotation.GetMapping;
11 | import org.springframework.web.bind.annotation.PathVariable;
12 | import org.springframework.web.bind.annotation.RestController;
13 |
14 | import java.util.Optional;
15 |
16 | @RestController
17 | public class VoteController {
18 |
19 | private VoteService voteService;
20 | private LinkService linkService;
21 |
22 | public VoteController(VoteService voteService, LinkService linkService) {
23 | this.voteService = voteService;
24 | this.linkService = linkService;
25 | }
26 |
27 | @Secured({"ROLE_USER"})
28 | @GetMapping("/vote/link/{linkID}/direction/{direction}/votecount/{voteCount}")
29 | public int vote(@PathVariable Long linkID, @PathVariable short direction, @PathVariable int voteCount) {
30 | Optional optionalLink = linkService.findById(linkID);
31 | if( optionalLink.isPresent() ) {
32 | Link link = optionalLink.get();
33 | Vote vote = new Vote(direction, link);
34 | voteService.save(vote);
35 |
36 | int updatedVoteCount = voteCount + direction;
37 | link.setVoteCount(updatedVoteCount);
38 | linkService.save(link);
39 | return updatedVoteCount;
40 | }
41 | return voteCount;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/com/vega/springit/domain/Auditable.java:
--------------------------------------------------------------------------------
1 | package com.vega.springit.domain;
2 |
3 | import org.springframework.data.annotation.CreatedBy;
4 | import org.springframework.data.annotation.CreatedDate;
5 | import org.springframework.data.annotation.LastModifiedBy;
6 | import org.springframework.data.annotation.LastModifiedDate;
7 | import org.springframework.data.jpa.domain.support.AuditingEntityListener;
8 |
9 | import javax.persistence.EntityListeners;
10 | import javax.persistence.MappedSuperclass;
11 | import java.time.LocalDateTime;
12 |
13 | @MappedSuperclass
14 | @EntityListeners(AuditingEntityListener.class)
15 | public abstract class Auditable {
16 |
17 | @CreatedBy
18 | private String createdBy;
19 |
20 | @CreatedDate
21 | private LocalDateTime creationDate;
22 |
23 | @LastModifiedBy
24 | private String lastModifiedBy;
25 |
26 | @LastModifiedDate
27 | private LocalDateTime lastModifiedDate;
28 |
29 | public String getCreatedBy() {
30 | return createdBy;
31 | }
32 |
33 | public void setCreatedBy(String createdBy) {
34 | this.createdBy = createdBy;
35 | }
36 |
37 | public LocalDateTime getCreationDate() {
38 | return creationDate;
39 | }
40 |
41 | public void setCreationDate(LocalDateTime creationDate) {
42 | this.creationDate = creationDate;
43 | }
44 |
45 | public String getLastModifiedBy() {
46 | return lastModifiedBy;
47 | }
48 |
49 | public void setLastModifiedBy(String lastModifiedBy) {
50 | this.lastModifiedBy = lastModifiedBy;
51 | }
52 |
53 | public LocalDateTime getLastModifiedDate() {
54 | return lastModifiedDate;
55 | }
56 |
57 | public void setLastModifiedDate(LocalDateTime lastModifiedDate) {
58 | this.lastModifiedDate = lastModifiedDate;
59 | }
60 | }
--------------------------------------------------------------------------------
/src/main/java/com/vega/springit/domain/Comment.java:
--------------------------------------------------------------------------------
1 | package com.vega.springit.domain;
2 |
3 | import com.vega.springit.service.BeanUtil;
4 | import lombok.*;
5 | import org.ocpsoft.prettytime.PrettyTime;
6 |
7 | import javax.persistence.Entity;
8 | import javax.persistence.GeneratedValue;
9 | import javax.persistence.Id;
10 | import javax.persistence.ManyToOne;
11 | import java.time.LocalDateTime;
12 | import java.time.ZoneId;
13 | import java.util.Date;
14 |
15 | @Entity
16 | @RequiredArgsConstructor
17 | @Getter
18 | @Setter
19 | @NoArgsConstructor
20 | public class Comment extends Auditable {
21 |
22 | @Id
23 | @GeneratedValue
24 | private Long id;
25 |
26 | @NonNull
27 | private String body;
28 |
29 | @ManyToOne
30 | @NonNull
31 | private Link link;
32 |
33 | public String getPrettyTime() {
34 | PrettyTime pt = BeanUtil.getBean(PrettyTime.class);
35 | return pt.format(convertToDateViaInstant(getCreationDate()));
36 | }
37 |
38 | private Date convertToDateViaInstant(LocalDateTime dateToConvert) {
39 | return java.util.Date.from(dateToConvert.atZone(ZoneId.systemDefault()).toInstant());
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/com/vega/springit/domain/Link.java:
--------------------------------------------------------------------------------
1 | package com.vega.springit.domain;
2 |
3 | import com.vega.springit.service.BeanUtil;
4 | import lombok.*;
5 | import org.hibernate.validator.constraints.URL;
6 | import org.ocpsoft.prettytime.PrettyTime;
7 |
8 | import javax.persistence.*;
9 | import javax.validation.constraints.NotEmpty;
10 | import java.net.URI;
11 | import java.net.URISyntaxException;
12 | import java.time.LocalDateTime;
13 | import java.time.ZoneId;
14 | import java.util.ArrayList;
15 | import java.util.Date;
16 | import java.util.List;
17 |
18 | @Entity
19 | @RequiredArgsConstructor
20 | @Getter @Setter
21 | @NoArgsConstructor
22 | public class Link extends Auditable {
23 |
24 | @Id @GeneratedValue
25 | private Long id;
26 |
27 | @NonNull
28 | @NotEmpty(message = "Please enter a title.")
29 | private String title;
30 |
31 | @NonNull
32 | @NotEmpty(message = "Please enter a url.")
33 | @URL(message = "Please enter a valid url.")
34 | private String url;
35 |
36 | @OneToMany(mappedBy = "link")
37 | private List comments = new ArrayList<>();
38 |
39 | @OneToMany(mappedBy = "link")
40 | private List votes = new ArrayList<>();
41 |
42 | private int voteCount = 0;
43 |
44 | @ManyToOne
45 | private User user;
46 |
47 | public void addComment(Comment comment) {
48 | comments.add(comment);
49 | }
50 |
51 | public String getDomainName() throws URISyntaxException {
52 | URI uri = new URI(this.url);
53 | String domain = uri.getHost();
54 | return domain.startsWith("www.") ? domain.substring(4) : domain;
55 | }
56 |
57 | public String getPrettyTime() {
58 | PrettyTime pt = BeanUtil.getBean(PrettyTime.class);
59 | return pt.format(convertToDateViaInstant(getCreationDate()));
60 | }
61 |
62 | private Date convertToDateViaInstant(LocalDateTime dateToConvert) {
63 | return java.util.Date.from(dateToConvert.atZone(ZoneId.systemDefault()).toInstant());
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/java/com/vega/springit/domain/Role.java:
--------------------------------------------------------------------------------
1 | package com.vega.springit.domain;
2 |
3 | import lombok.*;
4 |
5 | import javax.persistence.Entity;
6 | import javax.persistence.GeneratedValue;
7 | import javax.persistence.Id;
8 | import javax.persistence.ManyToMany;
9 | import java.util.Collection;
10 |
11 | @Entity
12 | @RequiredArgsConstructor
13 | @Getter
14 | @Setter
15 | @ToString
16 | @NoArgsConstructor
17 | public class Role {
18 | @Id
19 | @GeneratedValue
20 | private Long id;
21 |
22 | @NonNull
23 | private String name;
24 |
25 | @ManyToMany( mappedBy = "roles")
26 | private Collection users;
27 |
28 | }
--------------------------------------------------------------------------------
/src/main/java/com/vega/springit/domain/User.java:
--------------------------------------------------------------------------------
1 | package com.vega.springit.domain;
2 |
3 | import com.vega.springit.domain.validator.PasswordsMatch;
4 | import lombok.*;
5 | import org.springframework.security.core.GrantedAuthority;
6 | import org.springframework.security.core.authority.SimpleGrantedAuthority;
7 | import org.springframework.security.core.userdetails.UserDetails;
8 |
9 | import javax.persistence.*;
10 | import javax.validation.constraints.NotEmpty;
11 | import javax.validation.constraints.Size;
12 | import java.util.*;
13 | import java.util.stream.Collectors;
14 |
15 | @Entity
16 | @RequiredArgsConstructor
17 | @Getter
18 | @Setter
19 | @ToString
20 | @NoArgsConstructor
21 | @PasswordsMatch
22 | public class User implements UserDetails {
23 |
24 | @Id @GeneratedValue
25 | private Long id;
26 |
27 | @NonNull
28 | @Size(min = 8, max = 20)
29 | @Column(nullable = false, unique = true)
30 | private String email;
31 |
32 | @NonNull
33 | @Column(length = 100)
34 | private String password;
35 |
36 | @NonNull
37 | @Column(nullable = false)
38 | private boolean enabled;
39 |
40 | @ManyToMany(fetch = FetchType.EAGER)
41 | @JoinTable(
42 | name = "users_roles",
43 | joinColumns = @JoinColumn(name = "user_id",referencedColumnName = "id"),
44 | inverseJoinColumns = @JoinColumn(name = "role_id",referencedColumnName = "id")
45 | )
46 | private Set roles = new HashSet<>();
47 |
48 | @NonNull
49 | @NotEmpty(message = "You must enter First Name.")
50 | private String firstName;
51 |
52 | @NonNull
53 | @NotEmpty(message = "You must enter Last Name.")
54 | private String lastName;
55 |
56 | @Transient
57 | @Setter(AccessLevel.NONE)
58 | private String fullName;
59 |
60 | @NonNull
61 | @NotEmpty(message = "Please enter alias.")
62 | @Column(nullable = false, unique = true)
63 | private String alias;
64 |
65 | @Transient
66 | @NotEmpty(message = "Please enter Password Confirmation")
67 | private String confirmPassword;
68 |
69 | private String activationCode;
70 |
71 | public String getFullName(){
72 | return firstName + " " + lastName;
73 | }
74 |
75 | public void addRole(Role role) {
76 | roles.add(role);
77 | }
78 |
79 | public void addRoles(Set roles) {
80 | roles.forEach(this::addRole);
81 | }
82 |
83 | @Override
84 | public Collection extends GrantedAuthority> getAuthorities() {
85 | return roles.stream().map(role -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toList());
86 | }
87 |
88 | @Override
89 | public String getUsername() {
90 | return email;
91 | }
92 |
93 | @Override
94 | public boolean isAccountNonExpired() {
95 | return true;
96 | }
97 |
98 | @Override
99 | public boolean isAccountNonLocked() {
100 | return true;
101 | }
102 |
103 | @Override
104 | public boolean isCredentialsNonExpired() {
105 | return true;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/main/java/com/vega/springit/domain/Vote.java:
--------------------------------------------------------------------------------
1 | package com.vega.springit.domain;
2 |
3 | import lombok.*;
4 |
5 | import javax.persistence.Entity;
6 | import javax.persistence.GeneratedValue;
7 | import javax.persistence.Id;
8 | import javax.persistence.ManyToOne;
9 |
10 | @Entity
11 | @NoArgsConstructor
12 | @RequiredArgsConstructor
13 | @Getter
14 | @Setter
15 | public class Vote extends Auditable {
16 |
17 | @Id
18 | @GeneratedValue
19 | private Long id;
20 |
21 | @NonNull
22 | private short direction;
23 |
24 | @NonNull
25 | @ManyToOne
26 | private Link link;
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/vega/springit/domain/validator/PasswordsMatch.java:
--------------------------------------------------------------------------------
1 | package com.vega.springit.domain.validator;
2 |
3 | import javax.validation.Constraint;
4 | import javax.validation.Payload;
5 | import java.lang.annotation.*;
6 |
7 | @Documented
8 | @Constraint(validatedBy = PasswordsMatchValidator.class)
9 | @Target({ElementType.TYPE })
10 | @Retention(RetentionPolicy.RUNTIME)
11 | public @interface PasswordsMatch {
12 |
13 | String message() default "Password & Password Confirmation do not match.";
14 | Class>[] groups() default {};
15 | Class extends Payload>[] payload() default {};
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/com/vega/springit/domain/validator/PasswordsMatchValidator.java:
--------------------------------------------------------------------------------
1 | package com.vega.springit.domain.validator;
2 |
3 | import com.vega.springit.domain.User;
4 |
5 | import javax.validation.ConstraintValidator;
6 | import javax.validation.ConstraintValidatorContext;
7 |
8 | public class PasswordsMatchValidator implements ConstraintValidator {
9 |
10 | @Override
11 | public boolean isValid(User user, ConstraintValidatorContext context) {
12 | return user.getPassword().equals(user.getConfirmPassword());
13 | }
14 |
15 | @Override
16 | public void initialize(PasswordsMatch constraintAnnotation) {
17 |
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/vega/springit/repository/CommentRepository.java:
--------------------------------------------------------------------------------
1 | package com.vega.springit.repository;
2 |
3 | import com.vega.springit.domain.Comment;
4 | import org.springframework.data.jpa.repository.JpaRepository;
5 |
6 | public interface CommentRepository extends JpaRepository {
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/com/vega/springit/repository/LinkRepository.java:
--------------------------------------------------------------------------------
1 | package com.vega.springit.repository;
2 |
3 | import com.vega.springit.domain.Link;
4 | import org.springframework.data.jpa.repository.JpaRepository;
5 |
6 | public interface LinkRepository extends JpaRepository {
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/com/vega/springit/repository/RoleRepository.java:
--------------------------------------------------------------------------------
1 | package com.vega.springit.repository;
2 |
3 | import com.vega.springit.domain.Role;
4 | import org.springframework.data.jpa.repository.JpaRepository;
5 |
6 | public interface RoleRepository extends JpaRepository {
7 | Role findByName(String name);
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/com/vega/springit/repository/UserRepository.java:
--------------------------------------------------------------------------------
1 | package com.vega.springit.repository;
2 |
3 | import com.vega.springit.domain.User;
4 | import org.springframework.data.jpa.repository.JpaRepository;
5 |
6 | import java.util.Optional;
7 |
8 | public interface UserRepository extends JpaRepository {
9 | Optional findByEmail(String email);
10 |
11 | Optional findByEmailAndActivationCode(String email, String activationCode);
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/vega/springit/repository/VoteRepository.java:
--------------------------------------------------------------------------------
1 | package com.vega.springit.repository;
2 |
3 | import com.vega.springit.domain.Vote;
4 | import org.springframework.data.jpa.repository.JpaRepository;
5 |
6 | public interface VoteRepository extends JpaRepository {
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/com/vega/springit/security/SecurityConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.vega.springit.security;
2 |
3 | import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
4 | import org.springframework.context.annotation.Configuration;
5 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
6 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
7 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
8 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
9 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
10 |
11 | @Configuration
12 | @EnableWebSecurity
13 | @EnableGlobalMethodSecurity(securedEnabled = true)
14 | public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
15 |
16 | private UserDetailsServiceImpl userDetailsService;
17 |
18 | public SecurityConfiguration(UserDetailsServiceImpl userDetailsService) {
19 | this.userDetailsService = userDetailsService;
20 | }
21 |
22 | @Override
23 | protected void configure(HttpSecurity http) throws Exception {
24 | http
25 | .authorizeRequests()
26 | .requestMatchers(EndpointRequest.to("info")).permitAll()
27 | .requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ADMIN")
28 | .antMatchers("/actuator/").hasRole("ADMIN")
29 | .antMatchers("/").permitAll()
30 | .antMatchers("/link/submit").hasRole("USER")
31 | .antMatchers("/h2-console/**").permitAll()
32 | .and()
33 | .formLogin()
34 | .loginPage("/login").permitAll()
35 | .usernameParameter("email")
36 | .and()
37 | .logout()
38 | .and()
39 | .rememberMe()
40 | .and()
41 | .csrf().disable()
42 | .headers().frameOptions().disable();
43 | }
44 |
45 | @Override
46 | protected void configure(AuthenticationManagerBuilder auth) throws Exception {
47 | auth.userDetailsService(userDetailsService);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/vega/springit/security/UserDetailsServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.vega.springit.security;
2 |
3 | import com.vega.springit.domain.User;
4 | import com.vega.springit.repository.UserRepository;
5 | import org.springframework.security.core.userdetails.UserDetails;
6 | import org.springframework.security.core.userdetails.UserDetailsService;
7 | import org.springframework.security.core.userdetails.UsernameNotFoundException;
8 | import org.springframework.stereotype.Service;
9 |
10 | import java.util.Optional;
11 |
12 | @Service
13 | public class UserDetailsServiceImpl implements UserDetailsService {
14 |
15 | private UserRepository userRepository;
16 |
17 | public UserDetailsServiceImpl(UserRepository userRepository) {
18 | this.userRepository = userRepository;
19 | }
20 |
21 | @Override
22 | public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
23 | Optional user = userRepository.findByEmail(email);
24 | if( !user.isPresent() ) {
25 | throw new UsernameNotFoundException(email);
26 | }
27 | return user.get();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/vega/springit/service/BeanUtil.java:
--------------------------------------------------------------------------------
1 | package com.vega.springit.service;
2 |
3 | import org.springframework.beans.BeansException;
4 | import org.springframework.context.ApplicationContext;
5 | import org.springframework.context.ApplicationContextAware;
6 | import org.springframework.stereotype.Service;
7 |
8 | @Service
9 | public class BeanUtil implements ApplicationContextAware {
10 |
11 | private static ApplicationContext context;
12 |
13 | @Override
14 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
15 | context = applicationContext;
16 | }
17 |
18 | public static T getBean(Class beanClass) {
19 | return context.getBean(beanClass);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/com/vega/springit/service/CommentService.java:
--------------------------------------------------------------------------------
1 | package com.vega.springit.service;
2 |
3 | import com.vega.springit.domain.Comment;
4 | import com.vega.springit.repository.CommentRepository;
5 | import org.springframework.stereotype.Service;
6 |
7 | @Service
8 | public class CommentService {
9 |
10 | private final CommentRepository commentRepository;
11 |
12 | public CommentService(CommentRepository commentRepository) {
13 | this.commentRepository = commentRepository;
14 | }
15 |
16 | public Comment save(Comment comment) {
17 | return commentRepository.save(comment);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/vega/springit/service/LinkService.java:
--------------------------------------------------------------------------------
1 | package com.vega.springit.service;
2 |
3 | import com.vega.springit.domain.Link;
4 | import com.vega.springit.repository.LinkRepository;
5 | import org.springframework.stereotype.Service;
6 |
7 | import java.util.List;
8 | import java.util.Optional;
9 |
10 | @Service
11 | public class LinkService {
12 |
13 | private final LinkRepository linkRepository;
14 |
15 | public LinkService(LinkRepository linkRepository) {
16 | this.linkRepository = linkRepository;
17 | }
18 |
19 | public List findAll(){
20 | return linkRepository.findAll();
21 | }
22 |
23 | public Optional findById(Long id) {
24 | return linkRepository.findById(id);
25 | }
26 |
27 | public Link save(Link link) {
28 | return linkRepository.save(link);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/com/vega/springit/service/MailService.java:
--------------------------------------------------------------------------------
1 | package com.vega.springit.service;
2 |
3 | import com.vega.springit.domain.User;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 | import org.springframework.mail.javamail.JavaMailSender;
7 | import org.springframework.mail.javamail.MimeMessageHelper;
8 | import org.springframework.scheduling.annotation.Async;
9 | import org.springframework.stereotype.Service;
10 | import org.thymeleaf.context.Context;
11 | import org.thymeleaf.spring5.SpringTemplateEngine;
12 |
13 | import javax.mail.internet.MimeMessage;
14 | import java.util.Locale;
15 |
16 | @Service
17 | public class MailService {
18 |
19 | private final Logger log = LoggerFactory.getLogger(MailService.class);
20 | private final SpringTemplateEngine templateEngine;
21 | private final JavaMailSender javaMailSender;
22 | private final String BASE_URL = "http://localhost:8080";
23 |
24 | public MailService(JavaMailSender javaMailSender, SpringTemplateEngine templateEngine) {
25 | this.javaMailSender = javaMailSender;
26 | this.templateEngine = templateEngine;
27 | }
28 |
29 | @Async
30 | public void sendEmail(String to, String subject, String content, boolean isMultiPart, boolean isHtml) {
31 | log.debug("Sending Email");
32 |
33 | MimeMessage mimeMessage = this.javaMailSender.createMimeMessage();
34 | try {
35 | MimeMessageHelper message = new MimeMessageHelper(mimeMessage, "UTF-8");
36 | message.setTo(to);
37 | message.setFrom("noreply@springit.com");
38 | message.setSubject(subject);
39 | message.setText(content,isHtml);
40 | javaMailSender.send(mimeMessage);
41 | } catch (Exception e) {
42 | log.warn("Email could not be sent to user '{}': {}", to, e.getMessage());
43 | }
44 | }
45 |
46 | @Async
47 | public void sendEmailFromTemplate(User user, String templateName, String subject) {
48 | Locale locale = Locale.ENGLISH;
49 | Context context = new Context(locale);
50 | context.setVariable("user", user);
51 | context.setVariable("baseURL",BASE_URL);
52 | String content = templateEngine.process(templateName,context);
53 | sendEmail(user.getEmail(),subject,content,false,true);
54 | }
55 |
56 | @Async
57 | public void sendActivationEmail(User user) {
58 | log.debug("Sending activation email to '{}'", user.getEmail());
59 | sendEmailFromTemplate(user, "email/activation", "Springit User Activation");
60 | }
61 |
62 | @Async
63 | public void sendWelcomeEmail(User user) {
64 | log.debug("Sending activation email to '{}'", user.getEmail());
65 | sendEmailFromTemplate(user, "email/welcome", "Welcome new Springit User");
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/com/vega/springit/service/RoleService.java:
--------------------------------------------------------------------------------
1 | package com.vega.springit.service;
2 |
3 | import com.vega.springit.domain.Role;
4 | import com.vega.springit.repository.RoleRepository;
5 | import org.springframework.stereotype.Service;
6 |
7 | @Service
8 | public class RoleService {
9 |
10 | private final RoleRepository roleRepository;
11 |
12 | public RoleService(RoleRepository roleRepository) {
13 | this.roleRepository = roleRepository;
14 | }
15 |
16 | public Role findByName(String name) {
17 | return roleRepository.findByName(name);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/vega/springit/service/UserService.java:
--------------------------------------------------------------------------------
1 | package com.vega.springit.service;
2 |
3 | import com.vega.springit.domain.User;
4 | import com.vega.springit.repository.UserRepository;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
8 | import org.springframework.stereotype.Service;
9 |
10 | import javax.transaction.Transactional;
11 | import java.util.Optional;
12 | import java.util.UUID;
13 |
14 | @Service
15 | public class UserService {
16 |
17 | private final Logger logger = LoggerFactory.getLogger(UserService.class);
18 | private final UserRepository userRepository;
19 | private final BCryptPasswordEncoder encoder;
20 | private final RoleService roleService;
21 | private MailService mailService;
22 |
23 | public UserService(UserRepository userRepository, RoleService roleService, MailService mailService) {
24 | this.userRepository = userRepository;
25 | this.roleService = roleService;
26 | this.mailService = mailService;
27 | encoder = new BCryptPasswordEncoder();
28 | }
29 |
30 | public User register(User user) {
31 | // take the password from the form and encode
32 | String secret = "{bcrypt}" + encoder.encode(user.getPassword());
33 | user.setPassword(secret);
34 | // confirm password
35 | user.setConfirmPassword(secret);
36 |
37 | // assign a role to this user
38 | user.addRole(roleService.findByName("ROLE_USER"));
39 |
40 | // set an activation code
41 | user.setActivationCode(UUID.randomUUID().toString());
42 |
43 | // disable the user
44 | user.setEnabled(false);
45 | // save user
46 | save(user);
47 |
48 | // send the activation email
49 | sendActivationEmail(user);
50 |
51 | // return the user
52 | return user;
53 | }
54 |
55 | public User save(User user) {
56 | return userRepository.save(user);
57 | }
58 |
59 | @Transactional
60 | public void saveUsers(User... users) {
61 | for(User user : users) {
62 | logger.info("Saving User: " + user.getEmail());
63 | userRepository.save(user);
64 | }
65 | }
66 |
67 | public void sendActivationEmail(User user) {
68 | mailService.sendActivationEmail(user);
69 | }
70 |
71 | public void sendWelcomeEmail(User user) {
72 | mailService.sendWelcomeEmail(user);
73 | }
74 |
75 | public Optional findByEmailAndActivationCode(String email, String activationCode) {
76 | return userRepository.findByEmailAndActivationCode(email,activationCode);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/com/vega/springit/service/VoteService.java:
--------------------------------------------------------------------------------
1 | package com.vega.springit.service;
2 |
3 | import com.vega.springit.domain.Vote;
4 | import com.vega.springit.repository.VoteRepository;
5 | import org.springframework.stereotype.Service;
6 |
7 | @Service
8 | public class VoteService {
9 |
10 | private final VoteRepository voteRepository;
11 |
12 | public VoteService(VoteRepository voteRepository) {
13 | this.voteRepository = voteRepository;
14 | }
15 |
16 | public Vote save(Vote vote) {
17 | return voteRepository.save(vote);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/resources/application-dev.properties:
--------------------------------------------------------------------------------
1 | #logging
2 | logging.level.com.vega.springit=DEBUG
3 |
4 | #h2
5 | spring.h2.console.enabled=true
6 |
7 | #datasource (default to testdb)
8 | spring.datasource.name=springit
9 |
10 | # sa | empty password
11 | #spring.datasource.username=
12 | #spring.datasource.password=
13 |
14 | #JPA & Hibernate
15 | #spring.jpa.hibernate.ddl-auto=create-drop
16 | spring.jpa.show-sql=true
17 |
18 | spring.mail.host=localhost
19 | spring.mail.port=1025
--------------------------------------------------------------------------------
/src/main/resources/application-prod.properties:
--------------------------------------------------------------------------------
1 | server.port=${PORT}
2 |
3 | # mysql settings
4 | spring.jpa.hibernate.ddl-auto=create
5 | spring.datasource.url=jdbc:mysql://aad4a43lvy5gye.cpqls22h4x1m.us-east-1.rds.amazonaws.com:3306/ebdb
6 | #spring.datasource.username=YOUR_USERNAME_HERE
7 | #spring.datasource.password=YOUR_PASSWORD_HERE
8 | spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
9 |
10 | spring.datasource.initialization-mode=always
11 |
12 | spring.mail.host=email-smtp.us-east-1.amazonaws.com
13 | spring.mail.port=25
--------------------------------------------------------------------------------
/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | # we need to set an active profile, master branch wasn't compiling
2 | # the default profile is dev but in production the env variable will override this
3 | spring.profiles.active=dev
4 |
5 | # info about our application
6 | info.application.name=Springit
7 | info.application.description=Reddit clone using Spring Boot 2
8 | info.application.version=0.0.1
9 |
10 | #actuator
11 | management.endpoints.web.exposure.include=*
12 | management.endpoint.health.show-details=when_authorized
13 |
14 |
--------------------------------------------------------------------------------
/src/main/resources/data-mysql.sql:
--------------------------------------------------------------------------------
1 | INSERT INTO link (id,creation_date,last_modified_date,created_by,last_modified_by,title,url)
2 | VALUES (1,NOW(),NOW(),null,null,'Getting Started with Spring Boot 2','https://therealdanvega.com/spring-boot-2');
3 |
4 | INSERT INTO comment
5 | (id,created_by,creation_date,last_modified_by,last_modified_date,body,link_id)
6 | VALUES
7 | (1,null,NOW(),null,NOW(),'What an awesome idea for a course!',1);
--------------------------------------------------------------------------------
/src/main/resources/schema-mysql.sql:
--------------------------------------------------------------------------------
1 | CREATE DATABASE IF NOT EXISTS `springit`;
2 | USE `springit`;
3 |
4 | --
5 | -- Table structure for table `comment`
6 | --
7 | DROP TABLE IF EXISTS `comment`;
8 |
9 | CREATE TABLE `comment` (
10 | `id` bigint(20) NOT NULL AUTO_INCREMENT,
11 | `created_by` varchar(255) DEFAULT NULL,
12 | `creation_date` datetime DEFAULT NULL,
13 | `last_modified_by` varchar(255) DEFAULT NULL,
14 | `last_modified_date` datetime DEFAULT NULL,
15 | `body` varchar(255) DEFAULT NULL,
16 | `link_id` bigint(20) DEFAULT NULL,
17 | PRIMARY KEY (`id`),
18 | KEY `FKoutxw6g1ndh1t6282y0fwvami` (`link_id`)
19 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
20 |
21 | --
22 | -- Table structure for table `link`
23 | --
24 |
25 | DROP TABLE IF EXISTS `link`;
26 | CREATE TABLE `link` (
27 | `id` bigint(20) NOT NULL AUTO_INCREMENT,
28 | `created_by` varchar(255) DEFAULT NULL,
29 | `creation_date` datetime DEFAULT NULL,
30 | `last_modified_by` varchar(255) DEFAULT NULL,
31 | `last_modified_date` datetime DEFAULT NULL,
32 | `title` varchar(255) DEFAULT NULL,
33 | `url` varchar(255) DEFAULT NULL,
34 | PRIMARY KEY (`id`)
35 | ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
--------------------------------------------------------------------------------
/src/main/resources/static/css/profile.css:
--------------------------------------------------------------------------------
1 | body {
2 | background: #EBF0F4;
3 | }
4 |
5 | #profile a.nav-link {
6 | text-transform: uppercase;
7 | }
8 | #profile a.nav-link.active {
9 | border-bottom: 1px solid blue;
10 | }
11 |
12 | #profile > .row:first-child {
13 | margin-bottom:20px;
14 | }
15 |
16 | .card {
17 | margin-bottom: 5px;
18 | }
19 |
20 | .card-img-top {
21 | padding-top:10px;
22 | }
23 |
24 | .recentLinks {
25 | padding-top:10px;
26 | }
--------------------------------------------------------------------------------
/src/main/resources/static/css/springit.css:
--------------------------------------------------------------------------------
1 |
2 |
3 | .bg-spring {
4 | background-color: #2E2A28!important;
5 | border-top: 7px solid #6db33f;
6 | }
7 |
8 | nav .nav-item {
9 | font-family: 'Oswald', sans-serif;
10 | text-transform: uppercase;
11 | padding-right:10px;
12 | }
13 |
14 | button.nav-link {
15 | background: #2D2A27;
16 | border:none;
17 | cursor: pointer;
18 | }
19 |
20 | body {
21 | background: #ffffff;
22 | font-family: 'Roboto', sans-serif;
23 |
24 | }
25 |
26 | /* Jumbotron */
27 | .jumbotron {
28 | background: #17629D;
29 | color:#ffffff;
30 | }
31 | .jumbotron {
32 | padding: 80px;
33 | padding-bottom: 40px;
34 | }
35 | .jumpbotron h2 {
36 | font-family: 'Oswald', sans-serif;
37 | }
38 | .jumbotron a, .jumbotron a:visited {
39 | color:#ffffff
40 | }
41 | .jumbotron p {
42 | font-size: 15px;
43 | }
44 |
45 |
46 | /* Links */
47 | .position {
48 | color:darkgray;
49 |
50 | }
51 | .votecount {
52 | color:darkgray;
53 | clear: both;
54 | font-size: 18px;
55 | vertical-align: middle;
56 | }
57 |
58 | .upvote {
59 | vertical-align: top;
60 | }
61 |
62 | .downvote {
63 | vertical-align: bottom;
64 | }
65 |
66 | a.upvote , a.downvote {
67 | color:darkgrey;
68 | }
69 |
70 | .title {
71 | margin:0px;
72 | }
73 | .tagline {
74 | font-size:14px;
75 | }
76 |
77 |
78 | a.comments {
79 | color: #000000
80 | }
81 | a.share {
82 | padding-left: 10px;
83 | color: #000000
84 | }
85 |
86 |
87 | .domain {
88 | color:darkgrey;
89 | font-size:13px;
90 | padding-left:10px;
91 | }
92 | .domain a, .domain a:hover, .domain a:visited {
93 | color:darkgrey;
94 | }
95 |
96 | /* comments */
97 | .comments .title {
98 | border-bottom: 1px dotted #000000;
99 | margin-bottom: 20px;
100 | }
101 | .addcomment {
102 | margin-bottom:20px;
103 | }
104 | .comment {
105 | margin-top:20px;
106 | }
107 | .comment .body {
108 | margin: 0 0 5px 0;
109 | padding:0px;
110 | }
111 | .comment .permalink {
112 |
113 | }
114 | .comment .like {
115 | padding-left:10px;
116 | }
117 |
118 |
119 | /* Registration */
120 | @media(min-width: 768px) {
121 | .field-label-responsive {
122 | padding-top: .5rem;
123 | text-align: right;
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/main/resources/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danvega/springit/b1c1301740b1d11d7da4c221b774165c6d8f7648/src/main/resources/static/favicon.ico
--------------------------------------------------------------------------------
/src/main/resources/static/images/profile_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danvega/springit/b1c1301740b1d11d7da4c221b774165c6d8f7648/src/main/resources/static/images/profile_small.png
--------------------------------------------------------------------------------
/src/main/resources/static/images/spring-boot-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danvega/springit/b1c1301740b1d11d7da4c221b774165c6d8f7648/src/main/resources/static/images/spring-boot-logo.png
--------------------------------------------------------------------------------
/src/main/resources/static/libs/bootstrap/css/bootstrap-grid.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Grid v4.0.0 (https://getbootstrap.com)
3 | * Copyright 2011-2018 The Bootstrap Authors
4 | * Copyright 2011-2018 Twitter, Inc.
5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
6 | */@-ms-viewport{width:device-width}html{box-sizing:border-box;-ms-overflow-style:scrollbar}*,::after,::before{box-sizing:inherit}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1140px}}.container-fluid{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;min-height:1px;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-sm-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-sm-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-sm-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-sm-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-sm-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-sm-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-sm-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-sm-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-sm-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-sm-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-sm-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-sm-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-sm-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-sm-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-sm-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-sm-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-md-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-md-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-md-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-md-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-md-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-md-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-md-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-md-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-md-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-md-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-md-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-md-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-md-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-md-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-md-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-md-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-lg-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-lg-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-lg-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-lg-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-lg-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-lg-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-lg-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-lg-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-lg-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-lg-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-lg-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-lg-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-lg-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-lg-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-lg-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-lg-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-xl-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-xl-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-xl-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-xl-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-xl-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-xl-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-xl-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-xl-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-xl-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-xl-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-xl-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-xl-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-xl-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-xl-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-xl-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-xl-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}}.flex-row{-webkit-box-orient:horizontal!important;-webkit-box-direction:normal!important;-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-webkit-box-orient:horizontal!important;-webkit-box-direction:reverse!important;-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-webkit-box-orient:vertical!important;-webkit-box-direction:reverse!important;-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.justify-content-start{-webkit-box-pack:start!important;-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-webkit-box-pack:end!important;-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-webkit-box-pack:center!important;-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-webkit-box-pack:justify!important;-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-webkit-box-align:start!important;-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-webkit-box-align:end!important;-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-webkit-box-align:center!important;-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-webkit-box-align:baseline!important;-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-webkit-box-align:stretch!important;-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-webkit-box-orient:horizontal!important;-webkit-box-direction:normal!important;-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-webkit-box-orient:horizontal!important;-webkit-box-direction:reverse!important;-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-webkit-box-orient:vertical!important;-webkit-box-direction:reverse!important;-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.justify-content-sm-start{-webkit-box-pack:start!important;-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-webkit-box-pack:end!important;-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-webkit-box-pack:center!important;-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-webkit-box-pack:justify!important;-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-webkit-box-align:start!important;-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-webkit-box-align:end!important;-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-webkit-box-align:center!important;-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-webkit-box-align:baseline!important;-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-webkit-box-align:stretch!important;-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-webkit-box-orient:horizontal!important;-webkit-box-direction:normal!important;-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-webkit-box-orient:horizontal!important;-webkit-box-direction:reverse!important;-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-webkit-box-orient:vertical!important;-webkit-box-direction:reverse!important;-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.justify-content-md-start{-webkit-box-pack:start!important;-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-webkit-box-pack:end!important;-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-webkit-box-pack:center!important;-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-webkit-box-pack:justify!important;-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-webkit-box-align:start!important;-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-webkit-box-align:end!important;-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-webkit-box-align:center!important;-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-webkit-box-align:baseline!important;-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-webkit-box-align:stretch!important;-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-webkit-box-orient:horizontal!important;-webkit-box-direction:normal!important;-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-webkit-box-orient:horizontal!important;-webkit-box-direction:reverse!important;-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-webkit-box-orient:vertical!important;-webkit-box-direction:reverse!important;-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.justify-content-lg-start{-webkit-box-pack:start!important;-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-webkit-box-pack:end!important;-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-webkit-box-pack:center!important;-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-webkit-box-pack:justify!important;-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-webkit-box-align:start!important;-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-webkit-box-align:end!important;-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-webkit-box-align:center!important;-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-webkit-box-align:baseline!important;-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-webkit-box-align:stretch!important;-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-webkit-box-orient:horizontal!important;-webkit-box-direction:normal!important;-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-webkit-box-orient:horizontal!important;-webkit-box-direction:reverse!important;-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-webkit-box-orient:vertical!important;-webkit-box-direction:reverse!important;-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.justify-content-xl-start{-webkit-box-pack:start!important;-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-webkit-box-pack:end!important;-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-webkit-box-pack:center!important;-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-webkit-box-pack:justify!important;-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-webkit-box-align:start!important;-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-webkit-box-align:end!important;-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-webkit-box-align:center!important;-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-webkit-box-align:baseline!important;-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-webkit-box-align:stretch!important;-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}
7 | /*# sourceMappingURL=bootstrap-grid.min.css.map */
--------------------------------------------------------------------------------
/src/main/resources/static/libs/bootstrap/css/bootstrap-reboot.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Reboot v4.0.0 (https://getbootstrap.com)
3 | * Copyright 2011-2018 The Bootstrap Authors
4 | * Copyright 2011-2018 Twitter, Inc.
5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
7 | */
8 | *,
9 | *::before,
10 | *::after {
11 | box-sizing: border-box;
12 | }
13 |
14 | html {
15 | font-family: sans-serif;
16 | line-height: 1.15;
17 | -webkit-text-size-adjust: 100%;
18 | -ms-text-size-adjust: 100%;
19 | -ms-overflow-style: scrollbar;
20 | -webkit-tap-highlight-color: transparent;
21 | }
22 |
23 | @-ms-viewport {
24 | width: device-width;
25 | }
26 |
27 | article, aside, dialog, figcaption, figure, footer, header, hgroup, main, nav, section {
28 | display: block;
29 | }
30 |
31 | body {
32 | margin: 0;
33 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
34 | font-size: 1rem;
35 | font-weight: 400;
36 | line-height: 1.5;
37 | color: #212529;
38 | text-align: left;
39 | background-color: #fff;
40 | }
41 |
42 | [tabindex="-1"]:focus {
43 | outline: 0 !important;
44 | }
45 |
46 | hr {
47 | box-sizing: content-box;
48 | height: 0;
49 | overflow: visible;
50 | }
51 |
52 | h1, h2, h3, h4, h5, h6 {
53 | margin-top: 0;
54 | margin-bottom: 0.5rem;
55 | }
56 |
57 | p {
58 | margin-top: 0;
59 | margin-bottom: 1rem;
60 | }
61 |
62 | abbr[title],
63 | abbr[data-original-title] {
64 | text-decoration: underline;
65 | -webkit-text-decoration: underline dotted;
66 | text-decoration: underline dotted;
67 | cursor: help;
68 | border-bottom: 0;
69 | }
70 |
71 | address {
72 | margin-bottom: 1rem;
73 | font-style: normal;
74 | line-height: inherit;
75 | }
76 |
77 | ol,
78 | ul,
79 | dl {
80 | margin-top: 0;
81 | margin-bottom: 1rem;
82 | }
83 |
84 | ol ol,
85 | ul ul,
86 | ol ul,
87 | ul ol {
88 | margin-bottom: 0;
89 | }
90 |
91 | dt {
92 | font-weight: 700;
93 | }
94 |
95 | dd {
96 | margin-bottom: .5rem;
97 | margin-left: 0;
98 | }
99 |
100 | blockquote {
101 | margin: 0 0 1rem;
102 | }
103 |
104 | dfn {
105 | font-style: italic;
106 | }
107 |
108 | b,
109 | strong {
110 | font-weight: bolder;
111 | }
112 |
113 | small {
114 | font-size: 80%;
115 | }
116 |
117 | sub,
118 | sup {
119 | position: relative;
120 | font-size: 75%;
121 | line-height: 0;
122 | vertical-align: baseline;
123 | }
124 |
125 | sub {
126 | bottom: -.25em;
127 | }
128 |
129 | sup {
130 | top: -.5em;
131 | }
132 |
133 | a {
134 | color: #007bff;
135 | text-decoration: none;
136 | background-color: transparent;
137 | -webkit-text-decoration-skip: objects;
138 | }
139 |
140 | a:hover {
141 | color: #0056b3;
142 | text-decoration: underline;
143 | }
144 |
145 | a:not([href]):not([tabindex]) {
146 | color: inherit;
147 | text-decoration: none;
148 | }
149 |
150 | a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus {
151 | color: inherit;
152 | text-decoration: none;
153 | }
154 |
155 | a:not([href]):not([tabindex]):focus {
156 | outline: 0;
157 | }
158 |
159 | pre,
160 | code,
161 | kbd,
162 | samp {
163 | font-family: monospace, monospace;
164 | font-size: 1em;
165 | }
166 |
167 | pre {
168 | margin-top: 0;
169 | margin-bottom: 1rem;
170 | overflow: auto;
171 | -ms-overflow-style: scrollbar;
172 | }
173 |
174 | figure {
175 | margin: 0 0 1rem;
176 | }
177 |
178 | img {
179 | vertical-align: middle;
180 | border-style: none;
181 | }
182 |
183 | svg:not(:root) {
184 | overflow: hidden;
185 | }
186 |
187 | table {
188 | border-collapse: collapse;
189 | }
190 |
191 | caption {
192 | padding-top: 0.75rem;
193 | padding-bottom: 0.75rem;
194 | color: #6c757d;
195 | text-align: left;
196 | caption-side: bottom;
197 | }
198 |
199 | th {
200 | text-align: inherit;
201 | }
202 |
203 | label {
204 | display: inline-block;
205 | margin-bottom: .5rem;
206 | }
207 |
208 | button {
209 | border-radius: 0;
210 | }
211 |
212 | button:focus {
213 | outline: 1px dotted;
214 | outline: 5px auto -webkit-focus-ring-color;
215 | }
216 |
217 | input,
218 | button,
219 | select,
220 | optgroup,
221 | textarea {
222 | margin: 0;
223 | font-family: inherit;
224 | font-size: inherit;
225 | line-height: inherit;
226 | }
227 |
228 | button,
229 | input {
230 | overflow: visible;
231 | }
232 |
233 | button,
234 | select {
235 | text-transform: none;
236 | }
237 |
238 | button,
239 | html [type="button"],
240 | [type="reset"],
241 | [type="submit"] {
242 | -webkit-appearance: button;
243 | }
244 |
245 | button::-moz-focus-inner,
246 | [type="button"]::-moz-focus-inner,
247 | [type="reset"]::-moz-focus-inner,
248 | [type="submit"]::-moz-focus-inner {
249 | padding: 0;
250 | border-style: none;
251 | }
252 |
253 | input[type="radio"],
254 | input[type="checkbox"] {
255 | box-sizing: border-box;
256 | padding: 0;
257 | }
258 |
259 | input[type="date"],
260 | input[type="time"],
261 | input[type="datetime-local"],
262 | input[type="month"] {
263 | -webkit-appearance: listbox;
264 | }
265 |
266 | textarea {
267 | overflow: auto;
268 | resize: vertical;
269 | }
270 |
271 | fieldset {
272 | min-width: 0;
273 | padding: 0;
274 | margin: 0;
275 | border: 0;
276 | }
277 |
278 | legend {
279 | display: block;
280 | width: 100%;
281 | max-width: 100%;
282 | padding: 0;
283 | margin-bottom: .5rem;
284 | font-size: 1.5rem;
285 | line-height: inherit;
286 | color: inherit;
287 | white-space: normal;
288 | }
289 |
290 | progress {
291 | vertical-align: baseline;
292 | }
293 |
294 | [type="number"]::-webkit-inner-spin-button,
295 | [type="number"]::-webkit-outer-spin-button {
296 | height: auto;
297 | }
298 |
299 | [type="search"] {
300 | outline-offset: -2px;
301 | -webkit-appearance: none;
302 | }
303 |
304 | [type="search"]::-webkit-search-cancel-button,
305 | [type="search"]::-webkit-search-decoration {
306 | -webkit-appearance: none;
307 | }
308 |
309 | ::-webkit-file-upload-button {
310 | font: inherit;
311 | -webkit-appearance: button;
312 | }
313 |
314 | output {
315 | display: inline-block;
316 | }
317 |
318 | summary {
319 | display: list-item;
320 | cursor: pointer;
321 | }
322 |
323 | template {
324 | display: none;
325 | }
326 |
327 | [hidden] {
328 | display: none !important;
329 | }
330 | /*# sourceMappingURL=bootstrap-reboot.css.map */
--------------------------------------------------------------------------------
/src/main/resources/static/libs/bootstrap/css/bootstrap-reboot.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Reboot v4.0.0 (https://getbootstrap.com)
3 | * Copyright 2011-2018 The Bootstrap Authors
4 | * Copyright 2011-2018 Twitter, Inc.
5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
7 | */*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}@-ms-viewport{width:device-width}article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent;-webkit-text-decoration-skip:objects}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg:not(:root){overflow:hidden}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
8 | /*# sourceMappingURL=bootstrap-reboot.min.css.map */
--------------------------------------------------------------------------------
/src/main/resources/static/libs/bootstrap/css/bootstrap-reboot.min.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["../../scss/bootstrap-reboot.scss","../../scss/_reboot.scss","dist/css/bootstrap-reboot.css","bootstrap-reboot.css","../../scss/mixins/_hover.scss"],"names":[],"mappings":"AAAA;;;;;;ACoBA,ECXA,QADA,SDeE,WAAA,WAGF,KACE,YAAA,WACA,YAAA,KACA,yBAAA,KACA,qBAAA,KACA,mBAAA,UACA,4BAAA,YAKA,cACE,MAAA,aAMJ,QAAA,MAAA,OAAA,WAAA,OAAA,OAAA,OAAA,OAAA,KAAA,IAAA,QACE,QAAA,MAWF,KACE,OAAA,EACA,YAAA,aAAA,CAAA,kBAAA,CAAA,UAAA,CAAA,MAAA,CAAA,gBAAA,CAAA,KAAA,CAAA,UAAA,CAAA,mBAAA,CAAA,gBAAA,CAAA,kBACA,UAAA,KACA,YAAA,IACA,YAAA,IACA,MAAA,QACA,WAAA,KACA,iBAAA,KEvBF,sBFgCE,QAAA,YASF,GACE,WAAA,YACA,OAAA,EACA,SAAA,QAaF,GAAA,GAAA,GAAA,GAAA,GAAA,GACE,WAAA,EACA,cAAA,MAQF,EACE,WAAA,EACA,cAAA,KChDF,0BD0DA,YAEE,gBAAA,UACA,wBAAA,UAAA,OAAA,gBAAA,UAAA,OACA,OAAA,KACA,cAAA,EAGF,QACE,cAAA,KACA,WAAA,OACA,YAAA,QCrDF,GDwDA,GCzDA,GD4DE,WAAA,EACA,cAAA,KAGF,MCxDA,MACA,MAFA,MD6DE,cAAA,EAGF,GACE,YAAA,IAGF,GACE,cAAA,MACA,YAAA,EAGF,WACE,OAAA,EAAA,EAAA,KAGF,IACE,WAAA,OAIF,EC1DA,OD4DE,YAAA,OAIF,MACE,UAAA,IAQF,IChEA,IDkEE,SAAA,SACA,UAAA,IACA,YAAA,EACA,eAAA,SAGF,IAAM,OAAA,OACN,IAAM,IAAA,MAON,EACE,MAAA,QACA,gBAAA,KACA,iBAAA,YACA,6BAAA,QG3LA,QH8LE,MAAA,QACA,gBAAA,UAUJ,8BACE,MAAA,QACA,gBAAA,KGvMA,oCAAA,oCH0ME,MAAA,QACA,gBAAA,KANJ,oCAUI,QAAA,EClEJ,KACA,ID2EA,IC1EA,KD8EE,YAAA,SAAA,CAAA,UACA,UAAA,IAIF,IAEE,WAAA,EAEA,cAAA,KAEA,SAAA,KAGA,mBAAA,UAQF,OAEE,OAAA,EAAA,EAAA,KAQF,IACE,eAAA,OACA,aAAA,KAGF,eACE,SAAA,OAQF,MACE,gBAAA,SAGF,QACE,YAAA,OACA,eAAA,OACA,MAAA,QACA,WAAA,KACA,aAAA,OAGF,GAGE,WAAA,QAQF,MAEE,QAAA,aACA,cAAA,MAMF,OACE,cAAA,EAOF,aACE,QAAA,IAAA,OACA,QAAA,IAAA,KAAA,yBC9GF,ODiHA,MC/GA,SADA,OAEA,SDmHE,OAAA,EACA,YAAA,QACA,UAAA,QACA,YAAA,QAGF,OCjHA,MDmHE,SAAA,QAGF,OCjHA,ODmHE,eAAA,KC7GF,aACA,cDkHA,OCpHA,mBDwHE,mBAAA,OCjHF,gCACA,+BACA,gCDmHA,yBAIE,QAAA,EACA,aAAA,KClHF,qBDqHA,kBAEE,WAAA,WACA,QAAA,EAIF,iBCrHA,2BACA,kBAFA,iBD+HE,mBAAA,QAGF,SACE,SAAA,KAEA,OAAA,SAGF,SAME,UAAA,EAEA,QAAA,EACA,OAAA,EACA,OAAA,EAKF,OACE,QAAA,MACA,MAAA,KACA,UAAA,KACA,QAAA,EACA,cAAA,MACA,UAAA,OACA,YAAA,QACA,MAAA,QACA,YAAA,OAGF,SACE,eAAA,SEnIF,yCDEA,yCDuIE,OAAA,KEpIF,cF4IE,eAAA,KACA,mBAAA,KExIF,4CDEA,yCD+IE,mBAAA,KAQF,6BACE,KAAA,QACA,mBAAA,OAOF,OACE,QAAA,aAGF,QACE,QAAA,UACA,OAAA,QAGF,SACE,QAAA,KErJF,SF2JE,QAAA","sourcesContent":["/*!\n * Bootstrap Reboot v4.0.0 (https://getbootstrap.com)\n * Copyright 2011-2018 The Bootstrap Authors\n * Copyright 2011-2018 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)\n */\n\n@import \"functions\";\n@import \"variables\";\n@import \"mixins\";\n@import \"reboot\";\n","// stylelint-disable at-rule-no-vendor-prefix, declaration-no-important, selector-no-qualifying-type, property-no-vendor-prefix\n\n// Reboot\n//\n// Normalization of HTML elements, manually forked from Normalize.css to remove\n// styles targeting irrelevant browsers while applying new styles.\n//\n// Normalize is licensed MIT. https://github.com/necolas/normalize.css\n\n\n// Document\n//\n// 1. Change from `box-sizing: content-box` so that `width` is not affected by `padding` or `border`.\n// 2. Change the default font family in all browsers.\n// 3. Correct the line height in all browsers.\n// 4. Prevent adjustments of font size after orientation changes in IE on Windows Phone and in iOS.\n// 5. Setting @viewport causes scrollbars to overlap content in IE11 and Edge, so\n// we force a non-overlapping, non-auto-hiding scrollbar to counteract.\n// 6. Change the default tap highlight to be completely transparent in iOS.\n\n*,\n*::before,\n*::after {\n box-sizing: border-box; // 1\n}\n\nhtml {\n font-family: sans-serif; // 2\n line-height: 1.15; // 3\n -webkit-text-size-adjust: 100%; // 4\n -ms-text-size-adjust: 100%; // 4\n -ms-overflow-style: scrollbar; // 5\n -webkit-tap-highlight-color: rgba(0, 0, 0, 0); // 6\n}\n\n// IE10+ doesn't honor `` in some cases.\n@at-root {\n @-ms-viewport {\n width: device-width;\n }\n}\n\n// stylelint-disable selector-list-comma-newline-after\n// Shim for \"new\" HTML5 structural elements to display correctly (IE10, older browsers)\narticle, aside, dialog, figcaption, figure, footer, header, hgroup, main, nav, section {\n display: block;\n}\n// stylelint-enable selector-list-comma-newline-after\n\n// Body\n//\n// 1. Remove the margin in all browsers.\n// 2. As a best practice, apply a default `background-color`.\n// 3. Set an explicit initial text-align value so that we can later use the\n// the `inherit` value on things like `
` elements.\n\nbody {\n margin: 0; // 1\n font-family: $font-family-base;\n font-size: $font-size-base;\n font-weight: $font-weight-base;\n line-height: $line-height-base;\n color: $body-color;\n text-align: left; // 3\n background-color: $body-bg; // 2\n}\n\n// Suppress the focus outline on elements that cannot be accessed via keyboard.\n// This prevents an unwanted focus outline from appearing around elements that\n// might still respond to pointer events.\n//\n// Credit: https://github.com/suitcss/base\n[tabindex=\"-1\"]:focus {\n outline: 0 !important;\n}\n\n\n// Content grouping\n//\n// 1. Add the correct box sizing in Firefox.\n// 2. Show the overflow in Edge and IE.\n\nhr {\n box-sizing: content-box; // 1\n height: 0; // 1\n overflow: visible; // 2\n}\n\n\n//\n// Typography\n//\n\n// Remove top margins from headings\n//\n// By default, `
`-`
` all receive top and bottom margins. We nuke the top\n// margin for easier control within type scales as it avoids margin collapsing.\n// stylelint-disable selector-list-comma-newline-after\nh1, h2, h3, h4, h5, h6 {\n margin-top: 0;\n margin-bottom: $headings-margin-bottom;\n}\n// stylelint-enable selector-list-comma-newline-after\n\n// Reset margins on paragraphs\n//\n// Similarly, the top margin on `
`s get reset. However, we also reset the\n// bottom margin to use `rem` units instead of `em`.\np {\n margin-top: 0;\n margin-bottom: $paragraph-margin-bottom;\n}\n\n// Abbreviations\n//\n// 1. Remove the bottom border in Firefox 39-.\n// 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.\n// 3. Add explicit cursor to indicate changed behavior.\n// 4. Duplicate behavior to the data-* attribute for our tooltip plugin\n\nabbr[title],\nabbr[data-original-title] { // 4\n text-decoration: underline; // 2\n text-decoration: underline dotted; // 2\n cursor: help; // 3\n border-bottom: 0; // 1\n}\n\naddress {\n margin-bottom: 1rem;\n font-style: normal;\n line-height: inherit;\n}\n\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\n\ndt {\n font-weight: $dt-font-weight;\n}\n\ndd {\n margin-bottom: .5rem;\n margin-left: 0; // Undo browser default\n}\n\nblockquote {\n margin: 0 0 1rem;\n}\n\ndfn {\n font-style: italic; // Add the correct font style in Android 4.3-\n}\n\n// stylelint-disable font-weight-notation\nb,\nstrong {\n font-weight: bolder; // Add the correct font weight in Chrome, Edge, and Safari\n}\n// stylelint-enable font-weight-notation\n\nsmall {\n font-size: 80%; // Add the correct font size in all browsers\n}\n\n//\n// Prevent `sub` and `sup` elements from affecting the line height in\n// all browsers.\n//\n\nsub,\nsup {\n position: relative;\n font-size: 75%;\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub { bottom: -.25em; }\nsup { top: -.5em; }\n\n\n//\n// Links\n//\n\na {\n color: $link-color;\n text-decoration: $link-decoration;\n background-color: transparent; // Remove the gray background on active links in IE 10.\n -webkit-text-decoration-skip: objects; // Remove gaps in links underline in iOS 8+ and Safari 8+.\n\n @include hover {\n color: $link-hover-color;\n text-decoration: $link-hover-decoration;\n }\n}\n\n// And undo these styles for placeholder links/named anchors (without href)\n// which have not been made explicitly keyboard-focusable (without tabindex).\n// It would be more straightforward to just use a[href] in previous block, but that\n// causes specificity issues in many other styles that are too complex to fix.\n// See https://github.com/twbs/bootstrap/issues/19402\n\na:not([href]):not([tabindex]) {\n color: inherit;\n text-decoration: none;\n\n @include hover-focus {\n color: inherit;\n text-decoration: none;\n }\n\n &:focus {\n outline: 0;\n }\n}\n\n\n//\n// Code\n//\n\n// stylelint-disable font-family-no-duplicate-names\npre,\ncode,\nkbd,\nsamp {\n font-family: monospace, monospace; // Correct the inheritance and scaling of font size in all browsers.\n font-size: 1em; // Correct the odd `em` font sizing in all browsers.\n}\n// stylelint-enable font-family-no-duplicate-names\n\npre {\n // Remove browser default top margin\n margin-top: 0;\n // Reset browser default of `1em` to use `rem`s\n margin-bottom: 1rem;\n // Don't allow content to break outside\n overflow: auto;\n // We have @viewport set which causes scrollbars to overlap content in IE11 and Edge, so\n // we force a non-overlapping, non-auto-hiding scrollbar to counteract.\n -ms-overflow-style: scrollbar;\n}\n\n\n//\n// Figures\n//\n\nfigure {\n // Apply a consistent margin strategy (matches our type styles).\n margin: 0 0 1rem;\n}\n\n\n//\n// Images and content\n//\n\nimg {\n vertical-align: middle;\n border-style: none; // Remove the border on images inside links in IE 10-.\n}\n\nsvg:not(:root) {\n overflow: hidden; // Hide the overflow in IE\n}\n\n\n//\n// Tables\n//\n\ntable {\n border-collapse: collapse; // Prevent double borders\n}\n\ncaption {\n padding-top: $table-cell-padding;\n padding-bottom: $table-cell-padding;\n color: $text-muted;\n text-align: left;\n caption-side: bottom;\n}\n\nth {\n // Matches default `
` alignment by inheriting from the ``, or the\n // closest parent with a set `text-align`.\n text-align: inherit;\n}\n\n\n//\n// Forms\n//\n\nlabel {\n // Allow labels to use `margin` for spacing.\n display: inline-block;\n margin-bottom: .5rem;\n}\n\n// Remove the default `border-radius` that macOS Chrome adds.\n//\n// Details at https://github.com/twbs/bootstrap/issues/24093\nbutton {\n border-radius: 0;\n}\n\n// Work around a Firefox/IE bug where the transparent `button` background\n// results in a loss of the default `button` focus styles.\n//\n// Credit: https://github.com/suitcss/base/\nbutton:focus {\n outline: 1px dotted;\n outline: 5px auto -webkit-focus-ring-color;\n}\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n margin: 0; // Remove the margin in Firefox and Safari\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\nbutton,\ninput {\n overflow: visible; // Show the overflow in Edge\n}\n\nbutton,\nselect {\n text-transform: none; // Remove the inheritance of text transform in Firefox\n}\n\n// 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`\n// controls in Android 4.\n// 2. Correct the inability to style clickable types in iOS and Safari.\nbutton,\nhtml [type=\"button\"], // 1\n[type=\"reset\"],\n[type=\"submit\"] {\n -webkit-appearance: button; // 2\n}\n\n// Remove inner border and padding from Firefox, but don't restore the outline like Normalize.\nbutton::-moz-focus-inner,\n[type=\"button\"]::-moz-focus-inner,\n[type=\"reset\"]::-moz-focus-inner,\n[type=\"submit\"]::-moz-focus-inner {\n padding: 0;\n border-style: none;\n}\n\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n box-sizing: border-box; // 1. Add the correct box sizing in IE 10-\n padding: 0; // 2. Remove the padding in IE 10-\n}\n\n\ninput[type=\"date\"],\ninput[type=\"time\"],\ninput[type=\"datetime-local\"],\ninput[type=\"month\"] {\n // Remove the default appearance of temporal inputs to avoid a Mobile Safari\n // bug where setting a custom line-height prevents text from being vertically\n // centered within the input.\n // See https://bugs.webkit.org/show_bug.cgi?id=139848\n // and https://github.com/twbs/bootstrap/issues/11266\n -webkit-appearance: listbox;\n}\n\ntextarea {\n overflow: auto; // Remove the default vertical scrollbar in IE.\n // Textareas should really only resize vertically so they don't break their (horizontal) containers.\n resize: vertical;\n}\n\nfieldset {\n // Browsers set a default `min-width: min-content;` on fieldsets,\n // unlike e.g. `
This is a reddit clone built using Spring Boot 2, Spring Framework 5 & so many other great projects! If you would like to learn how to build this
72 | application you can check out my course, Getting Started with Spring Boot 2.
It’s one thing I never care about, new releases of maven. Yet I do for most other things.. I really should take a look at any features released in the last while! Pull my dependencies, run my tests and upload to nexus. I don’t care for much else, I wonder what if any I’m missing.
It’s one thing I never care about, new releases of maven. Yet I do for most other things.. I really should take a look at any features released in the last while! Pull my dependencies, run my tests and upload to nexus. I don’t care for much else, I wonder what if any I’m missing.
70 | permalink 71 | Like 72 |