Initial commit.
This commit is contained in:
parent
c70b66057a
commit
373708f991
57 changed files with 2408 additions and 2 deletions
50
.classpath
Normal file
50
.classpath
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="optional" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="test" value="true"/>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" path="target/generated-sources/annotations">
|
||||
<attributes>
|
||||
<attribute name="ignore_optional_problems" value="true"/>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="m2e-apt" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
|
||||
<attributes>
|
||||
<attribute name="test" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="optional" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="target/generated-test-sources/test-annotations">
|
||||
<attributes>
|
||||
<attribute name="test" value="true"/>
|
||||
<attribute name="optional" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
74
.factorypath
Normal file
74
.factorypath
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
<factorypath>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-web/2.1.12.RELEASE/spring-boot-starter-web-2.1.12.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter/2.1.12.RELEASE/spring-boot-starter-2.1.12.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot/2.1.12.RELEASE/spring-boot-2.1.12.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-autoconfigure/2.1.12.RELEASE/spring-boot-autoconfigure-2.1.12.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-logging/2.1.12.RELEASE/spring-boot-starter-logging-2.1.12.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/logging/log4j/log4j-to-slf4j/2.11.2/log4j-to-slf4j-2.11.2.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/logging/log4j/log4j-api/2.11.2/log4j-api-2.11.2.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/slf4j/jul-to-slf4j/1.7.30/jul-to-slf4j-1.7.30.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/javax/annotation/javax.annotation-api/1.3.2/javax.annotation-api-1.3.2.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-core/5.1.13.RELEASE/spring-core-5.1.13.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-jcl/5.1.13.RELEASE/spring-jcl-5.1.13.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/yaml/snakeyaml/1.23/snakeyaml-1.23.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-json/2.1.12.RELEASE/spring-boot-starter-json-2.1.12.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.9.10/jackson-datatype-jdk8-2.9.10.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.9.10/jackson-datatype-jsr310-2.9.10.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/module/jackson-module-parameter-names/2.9.10/jackson-module-parameter-names-2.9.10.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-tomcat/2.1.12.RELEASE/spring-boot-starter-tomcat-2.1.12.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/tomcat/embed/tomcat-embed-core/9.0.30/tomcat-embed-core-9.0.30.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/tomcat/embed/tomcat-embed-el/9.0.30/tomcat-embed-el-9.0.30.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/tomcat/embed/tomcat-embed-websocket/9.0.30/tomcat-embed-websocket-9.0.30.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/hibernate/validator/hibernate-validator/6.0.18.Final/hibernate-validator-6.0.18.Final.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/javax/validation/validation-api/2.0.1.Final/validation-api-2.0.1.Final.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/jboss/logging/jboss-logging/3.3.3.Final/jboss-logging-3.3.3.Final.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/classmate/1.4.0/classmate-1.4.0.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-web/5.1.13.RELEASE/spring-web-5.1.13.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-beans/5.1.13.RELEASE/spring-beans-5.1.13.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-webmvc/5.1.13.RELEASE/spring-webmvc-5.1.13.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-context/5.1.13.RELEASE/spring-context-5.1.13.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-expression/5.1.13.RELEASE/spring-expression-5.1.13.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-data-jpa/2.1.12.RELEASE/spring-boot-starter-data-jpa-2.1.12.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-aop/2.1.12.RELEASE/spring-boot-starter-aop-2.1.12.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/aspectj/aspectjweaver/1.9.5/aspectjweaver-1.9.5.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/javax/transaction/javax.transaction-api/1.3/javax.transaction-api-1.3.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/javax/xml/bind/jaxb-api/2.3.1/jaxb-api-2.3.1.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/javax/activation/javax.activation-api/1.2.0/javax.activation-api-1.2.0.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/hibernate/hibernate-core/5.3.15.Final/hibernate-core-5.3.15.Final.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/javax/persistence/javax.persistence-api/2.2/javax.persistence-api-2.2.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/javassist/javassist/3.23.2-GA/javassist-3.23.2-GA.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/net/bytebuddy/byte-buddy/1.9.16/byte-buddy-1.9.16.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/antlr/antlr/2.7.7/antlr-2.7.7.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/jboss/jandex/2.0.5.Final/jandex-2.0.5.Final.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/dom4j/dom4j/2.1.1/dom4j-2.1.1.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/hibernate/common/hibernate-commons-annotations/5.0.4.Final/hibernate-commons-annotations-5.0.4.Final.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/glassfish/jaxb/jaxb-runtime/2.3.1/jaxb-runtime-2.3.1.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/glassfish/jaxb/txw2/2.3.1/txw2-2.3.1.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/com/sun/istack/istack-commons-runtime/3.0.7/istack-commons-runtime-3.0.7.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/jvnet/staxex/stax-ex/1.8/stax-ex-1.8.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/com/sun/xml/fastinfoset/FastInfoset/1.2.15/FastInfoset-1.2.15.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/data/spring-data-jpa/2.1.15.RELEASE/spring-data-jpa-2.1.15.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/data/spring-data-commons/2.1.15.RELEASE/spring-data-commons-2.1.15.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-orm/5.1.13.RELEASE/spring-orm-5.1.13.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-tx/5.1.13.RELEASE/spring-tx-5.1.13.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-aspects/5.1.13.RELEASE/spring-aspects-5.1.13.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-jdbc/2.1.12.RELEASE/spring-boot-starter-jdbc-2.1.12.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/com/zaxxer/HikariCP/3.2.0/HikariCP-3.2.0.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-jdbc/5.1.13.RELEASE/spring-jdbc-5.1.13.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/boot/spring-boot-starter-security/2.1.12.RELEASE/spring-boot-starter-security-2.1.12.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/spring-aop/5.1.13.RELEASE/spring-aop-5.1.13.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/security/spring-security-config/5.1.7.RELEASE/spring-security-config-5.1.7.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/security/spring-security-core/5.1.7.RELEASE/spring-security-core-5.1.7.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/springframework/security/spring-security-web/5.1.7.RELEASE/spring-security-web-5.1.7.RELEASE.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/projectlombok/lombok/1.18.16/lombok-1.18.16.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/io/jsonwebtoken/jjwt/0.9.0/jjwt-0.9.0.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/core/jackson-databind/2.9.10.2/jackson-databind-2.9.10.2.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/core/jackson-annotations/2.9.10/jackson-annotations-2.9.10.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/com/fasterxml/jackson/core/jackson-core/2.9.10/jackson-core-2.9.10.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/mysql/mysql-connector-java/8.0.19/mysql-connector-java-8.0.19.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/joda-time/joda-time/2.10.5/joda-time-2.10.5.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="M2_REPO/org/apache/commons/commons-csv/1.5/commons-csv-1.5.jar" enabled="true" runInBatchMode="false"/>
|
||||
</factorypath>
|
||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
/logs/
|
||||
/run-local
|
||||
*.log
|
||||
/target/
|
||||
39
.project
Normal file
39
.project
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>budget-api</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.springframework.ide.eclipse.boot.validation.springbootbuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
<filteredResources>
|
||||
<filter>
|
||||
<id>1626184189329</id>
|
||||
<name></name>
|
||||
<type>30</type>
|
||||
<matcher>
|
||||
<id>org.eclipse.core.resources.regexFilterMatcher</id>
|
||||
<arguments>node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
|
||||
</matcher>
|
||||
</filter>
|
||||
</filteredResources>
|
||||
</projectDescription>
|
||||
4
.settings/org.eclipse.core.resources.prefs
Normal file
4
.settings/org.eclipse.core.resources.prefs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
eclipse.preferences.version=1
|
||||
encoding//src/main/java=UTF-8
|
||||
encoding//src/main/resources=UTF-8
|
||||
encoding/<project>=UTF-8
|
||||
4
.settings/org.eclipse.jdt.apt.core.prefs
Normal file
4
.settings/org.eclipse.jdt.apt.core.prefs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.apt.aptEnabled=true
|
||||
org.eclipse.jdt.apt.genSrcDir=target/generated-sources/annotations
|
||||
org.eclipse.jdt.apt.genTestSrcDir=target/generated-test-sources/test-annotations
|
||||
14
.settings/org.eclipse.jdt.core.prefs
Normal file
14
.settings/org.eclipse.jdt.core.prefs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.methodParameters=generate
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=17
|
||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||
org.eclipse.jdt.core.compiler.compliance=17
|
||||
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
|
||||
org.eclipse.jdt.core.compiler.processAnnotations=enabled
|
||||
org.eclipse.jdt.core.compiler.release=disabled
|
||||
org.eclipse.jdt.core.compiler.source=17
|
||||
4
.settings/org.eclipse.m2e.core.prefs
Normal file
4
.settings/org.eclipse.m2e.core.prefs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
||||
2
.settings/org.springframework.ide.eclipse.prefs
Normal file
2
.settings/org.springframework.ide.eclipse.prefs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
boot.validation.initialized=true
|
||||
eclipse.preferences.version=1
|
||||
|
|
@ -1,3 +1,6 @@
|
|||
# com.stephenschafer.budget.api
|
||||
# Budget REST API
|
||||
|
||||
Provides a REST API to support my React Budget Application.
|
||||
|
||||
Built from this example as a starting point: [Spring Boot Security JWT Authentication](http://www.devglan.com/spring-security/spring-boot-jwt-auth)
|
||||
|
||||
Back-end to budget react app
|
||||
11
build
Executable file
11
build
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
#!/bin/sh
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
ROOT=$(pwd)
|
||||
./stop
|
||||
# export JAVA_HOME="/usr/lib/jvm/java-11-openjdk"
|
||||
mkdir -p logs
|
||||
if ! mvn clean package > logs/build.log 2> logs/build.err.log; then
|
||||
echo "build failed"
|
||||
exit 1
|
||||
fi
|
||||
echo "success"
|
||||
5
deploy
Executable file
5
deploy
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
#!/bin/sh
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
ssh pi@raspi "./budget/backup"
|
||||
scp $(find target -name "*.jar") pi@raspi:~/budget
|
||||
ssh pi@raspi "./budget/start"
|
||||
6
eclipse
Executable file
6
eclipse
Executable file
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/sh
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
LOG=./logs
|
||||
mkdir -p $LOG
|
||||
/home/eclipse/sts-4.30.0.RELEASE/SpringToolSuite4 -data .. \
|
||||
>$LOG/eclipse-sts.log 2>$LOG/eclipse-sts.err.log &
|
||||
98
pom.xml
Normal file
98
pom.xml
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.stephenschafer</groupId>
|
||||
<artifactId>budget</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.4.7</version>
|
||||
</parent>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>joda-time</groupId>
|
||||
<artifactId>joda-time</artifactId>
|
||||
<version>2.10.9</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-jdbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.thymeleaf.extras</groupId>
|
||||
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
|
||||
<!-- Temporary explicit version to fix Thymeleaf bug -->
|
||||
<version>3.1.1.RELEASE</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt</artifactId>
|
||||
<version>0.9.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>joda-time</groupId>
|
||||
<artifactId>joda-time</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-csv</artifactId>
|
||||
<version>1.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.xml.bind</groupId>
|
||||
<artifactId>jaxb-api</artifactId>
|
||||
<version>2.3.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
33
run
Executable file
33
run
Executable file
|
|
@ -0,0 +1,33 @@
|
|||
#!/bin/bash
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
ROOT=$(pwd)
|
||||
./stop
|
||||
mkdir -p logs
|
||||
rm -f $ROOT/logs/run-*.log
|
||||
SUSPEND="n"
|
||||
ARGS=""
|
||||
while (( "$#" )); do
|
||||
case $1 in
|
||||
suspend)
|
||||
SUSPEND="y"
|
||||
;;
|
||||
init)
|
||||
ARGS="init"
|
||||
;;
|
||||
*)
|
||||
echo "Unrecognized argument"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
JVM_ARGS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=$SUSPEND,address=8004"
|
||||
# JAVA_HOME="/usr/lib/jvm/java-11-openjdk"
|
||||
$JAVA_HOME/bin/java $JVM_ARGS -jar $(find target -name "*.jar") $ARGS\
|
||||
--server.port=$PORT\
|
||||
--spring.datasource.url=$DB_URL\
|
||||
--spring.datasource.username=$DB_USERNAME\
|
||||
--spring.datasource.password=$DB_PASSWORD\
|
||||
> $ROOT/logs/run-budget.log 2> $ROOT/logs/run-budget.err.log &
|
||||
echo "$!" > $ROOT/logs/run-budget.pid
|
||||
echo "running"
|
||||
87
spring-boot-jwt.iml
Normal file
87
spring-boot-jwt.iml
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="web" name="Web">
|
||||
<configuration>
|
||||
<webroots>
|
||||
<root url="file://$MODULE_DIR$/src/main/webapp" relative="/" />
|
||||
</webroots>
|
||||
</configuration>
|
||||
</facet>
|
||||
<facet type="Spring" name="Spring">
|
||||
<configuration />
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
|
||||
<output url="file://$MODULE_DIR$/target/classes" />
|
||||
<output-test url="file://$MODULE_DIR$/target/test-classes" />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-web:2.0.1.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter:2.0.1.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot:2.0.1.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-autoconfigure:2.0.1.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-logging:2.0.1.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: ch.qos.logback:logback-classic:1.2.3" level="project" />
|
||||
<orderEntry type="library" name="Maven: ch.qos.logback:logback-core:1.2.3" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-to-slf4j:2.10.0" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-api:2.10.0" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.slf4j:jul-to-slf4j:1.7.25" level="project" />
|
||||
<orderEntry type="library" name="Maven: javax.annotation:javax.annotation-api:1.3.2" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-core:5.0.5.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-jcl:5.0.5.RELEASE" level="project" />
|
||||
<orderEntry type="library" scope="RUNTIME" name="Maven: org.yaml:snakeyaml:1.19" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-json:2.0.1.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.9.5" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.5" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.fasterxml.jackson.module:jackson-module-parameter-names:2.9.5" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-tomcat:2.0.1.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-core:8.5.29" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-el:8.5.29" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-websocket:8.5.29" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.hibernate.validator:hibernate-validator:6.0.9.Final" level="project" />
|
||||
<orderEntry type="library" name="Maven: javax.validation:validation-api:2.0.1.Final" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.jboss.logging:jboss-logging:3.3.2.Final" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.fasterxml:classmate:1.3.4" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-web:5.0.5.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-beans:5.0.5.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-webmvc:5.0.5.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-context:5.0.5.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-expression:5.0.5.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-data-jpa:2.0.1.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-aop:2.0.1.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.aspectj:aspectjweaver:1.8.13" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-jdbc:2.0.1.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.zaxxer:HikariCP:2.7.8" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-jdbc:5.0.5.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.hibernate:hibernate-core:5.2.16.Final" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.javassist:javassist:3.22.0-GA" level="project" />
|
||||
<orderEntry type="library" name="Maven: antlr:antlr:2.7.7" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.jboss:jandex:2.0.3.Final" level="project" />
|
||||
<orderEntry type="library" name="Maven: dom4j:dom4j:1.6.1" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.hibernate.common:hibernate-commons-annotations:5.0.1.Final" level="project" />
|
||||
<orderEntry type="library" name="Maven: javax.transaction:javax.transaction-api:1.2" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.data:spring-data-jpa:2.0.6.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.data:spring-data-commons:2.0.6.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-orm:5.0.5.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-tx:5.0.5.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.25" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-aspects:5.0.5.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-security:2.0.1.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-aop:5.0.5.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-config:5.0.4.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-core:5.0.4.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework.security:spring-security-web:5.0.4.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: io.jsonwebtoken:jjwt:0.9.0" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.9.5" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.9.0" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-core:2.9.5" level="project" />
|
||||
<orderEntry type="library" name="Maven: mysql:mysql-connector-java:5.1.46" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
14
src/main/java/com/stephenschafer/budget/ApiResponse.java
Normal file
14
src/main/java/com/stephenschafer/budget/ApiResponse.java
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
public class ApiResponse<T> {
|
||||
private int status;
|
||||
private String message;
|
||||
private T result;
|
||||
}
|
||||
55
src/main/java/com/stephenschafer/budget/Application.java
Normal file
55
src/main/java/com/stephenschafer/budget/Application.java
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
@Autowired
|
||||
private BCryptPasswordEncoder passwordEncoder;
|
||||
|
||||
public static void main(final String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
CommandLineRunner init(final UserDao userDao) {
|
||||
return args -> {
|
||||
if (args.length >= 1 && args[0].equals("init")) {
|
||||
final UserEntity user1 = new UserEntity();
|
||||
user1.setFirstName("Devglan");
|
||||
user1.setLastName("Devglan");
|
||||
user1.setUsername("devglan");
|
||||
user1.setPassword(passwordEncoder.encode("devglan"));
|
||||
userDao.save(user1);
|
||||
final UserEntity user2 = new UserEntity();
|
||||
user2.setFirstName("John");
|
||||
user2.setLastName("Doe");
|
||||
user2.setUsername("john");
|
||||
user2.setPassword(passwordEncoder.encode("john"));
|
||||
userDao.save(user2);
|
||||
}
|
||||
else {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
RegexDao getRegexDao() {
|
||||
return new RegexDaoImpl();
|
||||
}
|
||||
|
||||
@Bean
|
||||
CategoryDao getCategoryDao() {
|
||||
return new CategoryDaoImpl();
|
||||
}
|
||||
|
||||
@Bean
|
||||
TransactionDao getTransactionDao() {
|
||||
return new TransactionDaoImpl();
|
||||
}
|
||||
}
|
||||
15
src/main/java/com/stephenschafer/budget/AuthToken.java
Normal file
15
src/main/java/com/stephenschafer/budget/AuthToken.java
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class AuthToken {
|
||||
private String token;
|
||||
private String username;
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@CrossOrigin
|
||||
@RestController
|
||||
@RequestMapping("/token")
|
||||
public class AuthenticationController {
|
||||
@Autowired
|
||||
private AuthenticationManager authenticationManager;
|
||||
@Autowired
|
||||
private JwtTokenUtil jwtTokenUtil;
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@CrossOrigin
|
||||
@PostMapping("/generate-token")
|
||||
public ApiResponse<AuthToken> register(@RequestBody final LoginUser loginUser)
|
||||
throws AuthenticationException {
|
||||
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
|
||||
loginUser.getUsername(), loginUser.getPassword()));
|
||||
final UserEntity user = userService.findByUsername(loginUser.getUsername());
|
||||
final String token = jwtTokenUtil.generateToken(user);
|
||||
return new ApiResponse<>(200, "success", new AuthToken(token, user.getUsername()));
|
||||
}
|
||||
|
||||
@CrossOrigin
|
||||
@PostMapping("/register")
|
||||
public ApiResponse<AuthToken> saveUser(@RequestBody final UserDto loginUser) {
|
||||
final UserEntity userEntity = userService.findByUsername(loginUser.getUsername());
|
||||
if (userEntity != null) {
|
||||
return new ApiResponse<>(400, "username already exists", null);
|
||||
}
|
||||
userService.save(loginUser);
|
||||
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
|
||||
loginUser.getUsername(), loginUser.getPassword()));
|
||||
final UserEntity user = userService.findByUsername(loginUser.getUsername());
|
||||
final String token = jwtTokenUtil.generateToken(user);
|
||||
return new ApiResponse<>(200, "success", new AuthToken(token, user.getUsername()));
|
||||
}
|
||||
}
|
||||
23
src/main/java/com/stephenschafer/budget/Category.java
Normal file
23
src/main/java/com/stephenschafer/budget/Category.java
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@ToString
|
||||
public class Category {
|
||||
public Category() {
|
||||
}
|
||||
|
||||
public Category(final int intValue, final Category category) {
|
||||
this(intValue, category.getParentCategoryId(), category.getName());
|
||||
}
|
||||
|
||||
Integer id;
|
||||
Integer parentCategoryId;
|
||||
String name;
|
||||
}
|
||||
124
src/main/java/com/stephenschafer/budget/CategoryController.java
Normal file
124
src/main/java/com/stephenschafer/budget/CategoryController.java
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@CrossOrigin(origins = "*", maxAge = 3600)
|
||||
@RestController
|
||||
public class CategoryController {
|
||||
@Autowired
|
||||
private CategoryDao categoryDao;
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@PostMapping("/categories")
|
||||
@ResponseBody
|
||||
public ApiResponse<Category> postCategory(@RequestBody final Category category,
|
||||
final HttpServletRequest req) {
|
||||
if (!userService.isAuthorized(req)) {
|
||||
return new ApiResponse<>(HttpStatus.UNAUTHORIZED.value(),
|
||||
"You are not authorized to do this", null);
|
||||
}
|
||||
return new ApiResponse<>(HttpStatus.OK.value(), "Category inserted successfully",
|
||||
categoryDao.add(category));
|
||||
}
|
||||
|
||||
@PutMapping("/categories")
|
||||
@ResponseBody
|
||||
public ApiResponse<Category> putCategory(@RequestBody final Category category,
|
||||
final HttpServletRequest req) {
|
||||
if (!userService.isAuthorized(req)) {
|
||||
return new ApiResponse<>(HttpStatus.UNAUTHORIZED.value(),
|
||||
"You are not authorized to do this", null);
|
||||
}
|
||||
categoryDao.update(category);
|
||||
return new ApiResponse<>(HttpStatus.OK.value(), "Category updated successfully", category);
|
||||
}
|
||||
|
||||
@GetMapping("/categories")
|
||||
@ResponseBody
|
||||
public ApiResponse<List<Category>> getCategories(final HttpServletRequest request) {
|
||||
log.info("GET /categories");
|
||||
if (!userService.isAuthorized(request)) {
|
||||
return new ApiResponse<>(HttpStatus.UNAUTHORIZED.value(),
|
||||
"You are not authorized to do this", null);
|
||||
}
|
||||
final List<Category> categories = new ArrayList<>();
|
||||
categoryDao.getAll(category -> {
|
||||
categories.add(category);
|
||||
});
|
||||
return new ApiResponse<>(HttpStatus.OK.value(), "Category list fetched successfully",
|
||||
categories);
|
||||
}
|
||||
|
||||
@GetMapping("/categories/parent/{parentCategoryId}")
|
||||
@ResponseBody
|
||||
public ApiResponse<List<Category>> getCategoriesByCategory(
|
||||
@PathVariable(required = true) final Integer parentCategoryId,
|
||||
final HttpServletRequest request) {
|
||||
log.info("GET /categories/parent/" + parentCategoryId);
|
||||
if (!userService.isAuthorized(request)) {
|
||||
return new ApiResponse<>(HttpStatus.UNAUTHORIZED.value(),
|
||||
"You are not authorized to do this", null);
|
||||
}
|
||||
final Optional<Category> parentCategory = categoryDao.getById(parentCategoryId);
|
||||
if (!parentCategory.isPresent()) {
|
||||
return new ApiResponse<>(HttpStatus.NOT_FOUND.value(), "Parent category not found",
|
||||
null);
|
||||
}
|
||||
final int categoryId = parentCategory.get().getId();
|
||||
final List<Category> categories = new ArrayList<>();
|
||||
categoryDao.getByParent(categoryId, category -> {
|
||||
categories.add(category);
|
||||
});
|
||||
return new ApiResponse<>(HttpStatus.OK.value(), "Category list fetched successfully",
|
||||
categories);
|
||||
}
|
||||
|
||||
@GetMapping("/categories/root")
|
||||
@ResponseBody
|
||||
public ApiResponse<List<Category>> getRootCategories(final HttpServletRequest request) {
|
||||
log.info("GET /categories/root");
|
||||
if (!userService.isAuthorized(request)) {
|
||||
return new ApiResponse<>(HttpStatus.UNAUTHORIZED.value(),
|
||||
"You are not authorized to do this", null);
|
||||
}
|
||||
final List<Category> categories = new ArrayList<>();
|
||||
categoryDao.getRoot(category -> {
|
||||
categories.add(category);
|
||||
});
|
||||
return new ApiResponse<>(HttpStatus.OK.value(), "Category list fetched successfully",
|
||||
categories);
|
||||
}
|
||||
|
||||
@GetMapping("/categories/ancestry/{categoryId}")
|
||||
@ResponseBody
|
||||
public ApiResponse<List<Category>> getCategoryAncestry(
|
||||
@PathVariable(required = true) final Integer categoryId,
|
||||
final HttpServletRequest request) {
|
||||
log.info("GET /categories/ancestry/" + categoryId);
|
||||
if (!userService.isAuthorized(request)) {
|
||||
return new ApiResponse<>(HttpStatus.UNAUTHORIZED.value(),
|
||||
"You are not authorized to do this", null);
|
||||
}
|
||||
final List<Category> ancestry = categoryDao.getCategoryAncestry(categoryId);
|
||||
return new ApiResponse<>(HttpStatus.OK.value(), "Category ancestry fetched successfully",
|
||||
ancestry);
|
||||
}
|
||||
}
|
||||
25
src/main/java/com/stephenschafer/budget/CategoryDao.java
Normal file
25
src/main/java/com/stephenschafer/budget/CategoryDao.java
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface CategoryDao {
|
||||
int deleteById(int id);
|
||||
|
||||
Optional<Category> getById(int id);
|
||||
|
||||
Optional<Category> getByName(String name);
|
||||
|
||||
List<Category> getCategoryAncestry(int categoryId);
|
||||
|
||||
Category add(Category category);
|
||||
|
||||
void update(Category category);
|
||||
|
||||
void getAll(Consumer<Category> consumer);
|
||||
|
||||
void getByParent(int parentCategoryId, Consumer<Category> consumer);
|
||||
|
||||
void getRoot(Consumer<Category> consumer);
|
||||
}
|
||||
230
src/main/java/com/stephenschafer/budget/CategoryDaoImpl.java
Normal file
230
src/main/java/com/stephenschafer/budget/CategoryDaoImpl.java
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.PreparedStatementCreator;
|
||||
import org.springframework.jdbc.core.PreparedStatementCreatorFactory;
|
||||
import org.springframework.jdbc.support.GeneratedKeyHolder;
|
||||
import org.springframework.jdbc.support.KeyHolder;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class CategoryDaoImpl implements CategoryDao {
|
||||
@Autowired
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
@Override
|
||||
public int deleteById(final int id) {
|
||||
return jdbcTemplate.update("delete from budget.category where id = ?", id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Category> getById(final int id) {
|
||||
final String sql = "select coalesce(parent_category_id, 0), name"
|
||||
+ " from budget.category where id = ?";
|
||||
try {
|
||||
return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> {
|
||||
final int parentCategoryId = rs.getInt(1);
|
||||
final boolean parentCategoryIdWasNull = rs.wasNull();
|
||||
final String name = rs.getString(2);
|
||||
return Optional.of(new Category(id,
|
||||
parentCategoryIdWasNull ? null : Integer.valueOf(parentCategoryId), name));
|
||||
}, id);
|
||||
}
|
||||
catch (final DataAccessException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Category> getByName(final String name) {
|
||||
final String[] parts = name.split("\\.");
|
||||
Category category = null;
|
||||
for (final String part : parts) {
|
||||
Optional<Category> optional;
|
||||
if (category == null) {
|
||||
final String sql = "select id"
|
||||
+ " from budget.category where coalesce(parent_category_id, 0) = 0 and name = ?";
|
||||
optional = jdbcTemplate.queryForObject(sql, (rs, rowNum) -> {
|
||||
final int id = rs.getInt(1);
|
||||
return Optional.of(new Category(id, null, part));
|
||||
}, part);
|
||||
}
|
||||
else {
|
||||
final Integer parentId = category.getId();
|
||||
final String sql = "select id"
|
||||
+ " from budget.category where coalesce(parent_category_id, 0) = ? and name = ?";
|
||||
optional = jdbcTemplate.queryForObject(sql, (rs, rowNum) -> {
|
||||
final int id = rs.getInt(1);
|
||||
return Optional.of(new Category(id, parentId, part));
|
||||
}, parentId, part);
|
||||
}
|
||||
if (!optional.isPresent()) {
|
||||
return optional;
|
||||
}
|
||||
category = optional.get();
|
||||
}
|
||||
return Optional.of(category);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Category> getCategoryAncestry(final int categoryId) {
|
||||
return getCategoryAncestry(null, categoryId);
|
||||
}
|
||||
|
||||
private List<Category> getCategoryAncestry(final List<Category> ancestry,
|
||||
final int categoryId) {
|
||||
final Optional<Category> optionalCategory = getById(categoryId);
|
||||
if (!optionalCategory.isPresent()) {
|
||||
return ancestry;
|
||||
}
|
||||
final Category category = optionalCategory.get();
|
||||
final List<Category> result = new ArrayList<>();
|
||||
result.add(category);
|
||||
if (ancestry != null) {
|
||||
result.addAll(ancestry);
|
||||
}
|
||||
if (category.parentCategoryId == null) {
|
||||
return result;
|
||||
}
|
||||
return getCategoryAncestry(result, category.parentCategoryId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Category add(final Category category) {
|
||||
log.info("CategoryDaoImpl.add " + category);
|
||||
final String sql = "insert into budget.category" + " (parent_category_id, name)"
|
||||
+ " values (?, ?)";
|
||||
final PreparedStatementCreatorFactory factory = new PreparedStatementCreatorFactory(sql,
|
||||
Types.INTEGER, Types.VARCHAR);
|
||||
factory.setReturnGeneratedKeys(true);
|
||||
factory.setGeneratedKeysColumnNames("id");
|
||||
final PreparedStatementCreator creator = factory.newPreparedStatementCreator(
|
||||
new Object[] { category.getParentCategoryId(), category.getName() });
|
||||
final KeyHolder keyHolder = new GeneratedKeyHolder();
|
||||
final int rowCount = jdbcTemplate.update(creator, keyHolder);
|
||||
if (rowCount == 0) {
|
||||
return null;
|
||||
}
|
||||
final Number generatedId = keyHolder.getKey();
|
||||
return new Category(generatedId.intValue(), category);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(final Category category) {
|
||||
log.info("CategoryDaoImpl.update " + category);
|
||||
final String sql = "update budget.category" + " set parent_category_id = ?, name = ?"
|
||||
+ " where id = ?";
|
||||
final PreparedStatementCreatorFactory factory = new PreparedStatementCreatorFactory(sql,
|
||||
Types.INTEGER, Types.VARCHAR, Types.INTEGER);
|
||||
PreparedStatementCreator creator;
|
||||
try {
|
||||
creator = factory.newPreparedStatementCreator(new Object[] {
|
||||
category.getParentCategoryId(), category.getName(), category.getId() });
|
||||
}
|
||||
catch (final Exception e) {
|
||||
log.error("failed to get creator");
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
log.info("update creator = " + creator);
|
||||
jdbcTemplate.update(creator);
|
||||
}
|
||||
|
||||
private static class PreparedStatementHolder {
|
||||
PreparedStatement statement;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getAll(final Consumer<Category> consumer) {
|
||||
final PreparedStatementHolder holder = new PreparedStatementHolder();
|
||||
final String sql = "select" + " id, coalesce(parent_category_id, 0), name"
|
||||
+ " from budget.category" + " order by id";
|
||||
final PreparedStatementCreator creator = connection -> {
|
||||
holder.statement = connection.prepareStatement(sql);
|
||||
return holder.statement;
|
||||
};
|
||||
try {
|
||||
jdbcTemplate.query(creator, rs -> {
|
||||
int i = 0;
|
||||
final int id = rs.getInt(++i);
|
||||
final int parentCategoryId = rs.getInt(++i);
|
||||
final String name = rs.getString(++i);
|
||||
consumer.accept(new Category(id, parentCategoryId, name));
|
||||
});
|
||||
}
|
||||
catch (final StopException e) {
|
||||
try {
|
||||
holder.statement.cancel();
|
||||
}
|
||||
catch (final SQLException e1) {
|
||||
log.error("getByCategory failed", e1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getByParent(final int parentCategoryId, final Consumer<Category> consumer) {
|
||||
final PreparedStatementHolder holder = new PreparedStatementHolder();
|
||||
final String sql = "select" + " id, name" + " from budget.category"
|
||||
+ " where coalesce(parent_category_id, 0) = ?" + " order by id";
|
||||
final PreparedStatementCreator creator = connection -> {
|
||||
holder.statement = connection.prepareStatement(sql);
|
||||
holder.statement.setInt(1, parentCategoryId);
|
||||
return holder.statement;
|
||||
};
|
||||
try {
|
||||
jdbcTemplate.query(creator, rs -> {
|
||||
int i = 0;
|
||||
final int id = rs.getInt(++i);
|
||||
final String name = rs.getString(++i);
|
||||
consumer.accept(new Category(id, parentCategoryId, name));
|
||||
});
|
||||
}
|
||||
catch (final StopException e) {
|
||||
try {
|
||||
holder.statement.cancel();
|
||||
}
|
||||
catch (final SQLException e1) {
|
||||
log.error("getByCategory failed", e1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getRoot(final Consumer<Category> consumer) {
|
||||
final PreparedStatementHolder holder = new PreparedStatementHolder();
|
||||
final String sql = "select id, name from budget.category"
|
||||
+ " where coalesce(parent_category_id, 0) = 0 order by id";
|
||||
final PreparedStatementCreator creator = connection -> {
|
||||
holder.statement = connection.prepareStatement(sql);
|
||||
return holder.statement;
|
||||
};
|
||||
try {
|
||||
jdbcTemplate.query(creator, rs -> {
|
||||
int i = 0;
|
||||
final int id = rs.getInt(++i);
|
||||
final String name = rs.getString(++i);
|
||||
consumer.accept(new Category(id, null, name));
|
||||
});
|
||||
}
|
||||
catch (final StopException e) {
|
||||
try {
|
||||
holder.statement.cancel();
|
||||
}
|
||||
catch (final SQLException e1) {
|
||||
log.error("getRoot failed", e1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/main/java/com/stephenschafer/budget/Constants.java
Normal file
8
src/main/java/com/stephenschafer/budget/Constants.java
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
public class Constants {
|
||||
public static final long ACCESS_TOKEN_VALIDITY_SECONDS = 24L * 60L * 60L;
|
||||
public static final String SIGNING_KEY = "sschafer123r";
|
||||
public static final String TOKEN_PREFIX = "Bearer ";
|
||||
public static final String HEADER_STRING = "Authorization";
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
@Component
|
||||
public class CustomCorsConfiguration implements CorsConfigurationSource {
|
||||
@Override
|
||||
public CorsConfiguration getCorsConfiguration(final HttpServletRequest request) {
|
||||
final CorsConfiguration config = new CorsConfiguration();
|
||||
config.setAllowedOrigins(List.of("http://localhost:3000"));
|
||||
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
|
||||
config.setAllowedHeaders(List.of("*"));
|
||||
return config;
|
||||
}
|
||||
}
|
||||
13
src/main/java/com/stephenschafer/budget/ExceptionAdvice.java
Normal file
13
src/main/java/com/stephenschafer/budget/ExceptionAdvice.java
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
@RestControllerAdvice
|
||||
public class ExceptionAdvice {
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
public ApiResponse<Void> handleNotFoundException(final RuntimeException ex) {
|
||||
final ApiResponse<Void> apiResponse = new ApiResponse<>(400, "Bad request", null);
|
||||
return apiResponse;
|
||||
}
|
||||
}
|
||||
9
src/main/java/com/stephenschafer/budget/FileDao.java
Normal file
9
src/main/java/com/stephenschafer/budget/FileDao.java
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
public interface FileDao extends CrudRepository<FileEntity, Integer> {
|
||||
Optional<FileEntity> findByName(String filename);
|
||||
}
|
||||
29
src/main/java/com/stephenschafer/budget/FileEntity.java
Normal file
29
src/main/java/com/stephenschafer/budget/FileEntity.java
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@Entity
|
||||
@Table(name = "file")
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@ToString
|
||||
public class FileEntity {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Integer id;
|
||||
@Column
|
||||
private String name;
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class FindProjectResult {
|
||||
private int id;
|
||||
private String code;
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public void commence(final HttpServletRequest request, final HttpServletResponse response,
|
||||
final AuthenticationException authException) throws IOException {
|
||||
// This is invoked when user tries to access a secured REST resource without supplying any credentials
|
||||
// We should just send a 401 Unauthorized response because there is no 'login page' to redirect to
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import static com.stephenschafer.budget.Constants.HEADER_STRING;
|
||||
import static com.stephenschafer.budget.Constants.TOKEN_PREFIX;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.SignatureException;
|
||||
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
@Autowired
|
||||
private UserDetailsService userDetailsService;
|
||||
@Autowired
|
||||
private JwtTokenUtil jwtTokenUtil;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(final HttpServletRequest req, final HttpServletResponse res,
|
||||
final FilterChain chain) throws IOException, ServletException {
|
||||
final String header = req.getHeader(HEADER_STRING);
|
||||
String username = null;
|
||||
String authToken = null;
|
||||
if (header != null && header.startsWith(TOKEN_PREFIX)) {
|
||||
authToken = header.replace(TOKEN_PREFIX, "");
|
||||
try {
|
||||
username = jwtTokenUtil.getUsernameFromToken(authToken);
|
||||
}
|
||||
catch (final IllegalArgumentException e) {
|
||||
logger.error("an error occured during getting username from token", e);
|
||||
}
|
||||
catch (final ExpiredJwtException e) {
|
||||
logger.warn("the token is expired and not valid anymore", e);
|
||||
}
|
||||
catch (final SignatureException e) {
|
||||
logger.error("Authentication Failed. Username or Password not valid.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
logger.warn("couldn't find bearer string, will ignore the header");
|
||||
}
|
||||
req.setAttribute("username", username);
|
||||
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||
final UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
||||
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
|
||||
final UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
|
||||
userDetails, null, Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN")));
|
||||
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(req));
|
||||
logger.info("authenticated user " + username + ", setting security context");
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
}
|
||||
chain.doFilter(req, res);
|
||||
}
|
||||
}
|
||||
65
src/main/java/com/stephenschafer/budget/JwtTokenUtil.java
Normal file
65
src/main/java/com/stephenschafer/budget/JwtTokenUtil.java
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import static com.stephenschafer.budget.Constants.ACCESS_TOKEN_VALIDITY_SECONDS;
|
||||
import static com.stephenschafer.budget.Constants.SIGNING_KEY;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
|
||||
@Component
|
||||
public class JwtTokenUtil implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public String getUsernameFromToken(final String token) {
|
||||
return getClaimFromToken(token, Claims::getSubject);
|
||||
}
|
||||
|
||||
public Date getExpirationDateFromToken(final String token) {
|
||||
return getClaimFromToken(token, Claims::getExpiration);
|
||||
}
|
||||
|
||||
public <T> T getClaimFromToken(final String token, final Function<Claims, T> claimsResolver) {
|
||||
final Claims claims = getAllClaimsFromToken(token);
|
||||
return claimsResolver.apply(claims);
|
||||
}
|
||||
|
||||
private Claims getAllClaimsFromToken(final String token) {
|
||||
return Jwts.parser().setSigningKey(SIGNING_KEY).parseClaimsJws(token).getBody();
|
||||
}
|
||||
|
||||
private Boolean isTokenExpired(final String token) {
|
||||
final Date expiration = getExpirationDateFromToken(token);
|
||||
return expiration.before(new Date());
|
||||
}
|
||||
|
||||
public String generateToken(final UserEntity user) {
|
||||
return doGenerateToken(user.getUsername());
|
||||
}
|
||||
|
||||
private String doGenerateToken(final String subject) {
|
||||
final Claims claims = Jwts.claims().setSubject(subject);
|
||||
claims.put("scopes", Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN")));
|
||||
return Jwts.builder().setClaims(claims).setIssuer("http://stephenschafer.com").setIssuedAt(
|
||||
new Date(System.currentTimeMillis())).setExpiration(
|
||||
new Date(System.currentTimeMillis()
|
||||
+ ACCESS_TOKEN_VALIDITY_SECONDS * 1000L)).signWith(SignatureAlgorithm.HS256,
|
||||
SIGNING_KEY).compact();
|
||||
}
|
||||
|
||||
public Boolean validateToken(final String token, final UserDetails userDetails) {
|
||||
final String username = getUsernameFromToken(token);
|
||||
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
|
||||
}
|
||||
}
|
||||
11
src/main/java/com/stephenschafer/budget/LoginUser.java
Normal file
11
src/main/java/com/stephenschafer/budget/LoginUser.java
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class LoginUser {
|
||||
private String username;
|
||||
private String password;
|
||||
}
|
||||
33
src/main/java/com/stephenschafer/budget/MyUserDetails.java
Normal file
33
src/main/java/com/stephenschafer/budget/MyUserDetails.java
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
public class MyUserDetails implements UserDetails {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final UserEntity user;
|
||||
|
||||
public MyUserDetails(final UserEntity user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
final SimpleGrantedAuthority authority = new SimpleGrantedAuthority(user.getRole());
|
||||
return Arrays.asList(authority);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return user.getPassword();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return user.getUsername();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
|
||||
public class PreparedStatementHolder {
|
||||
PreparedStatement statement;
|
||||
}
|
||||
29
src/main/java/com/stephenschafer/budget/Regex.java
Normal file
29
src/main/java/com/stephenschafer/budget/Regex.java
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@ToString
|
||||
public class Regex {
|
||||
public Regex() {
|
||||
}
|
||||
|
||||
public Regex(final int id, final Regex regex) {
|
||||
this(id, regex.categoryId, regex.regex, regex.flags, regex.source, regex.priority,
|
||||
regex.description, regex.year);
|
||||
}
|
||||
|
||||
Integer id;
|
||||
Integer categoryId;
|
||||
String regex;
|
||||
int flags;
|
||||
String source;
|
||||
int priority;
|
||||
String description;
|
||||
Integer year;
|
||||
}
|
||||
130
src/main/java/com/stephenschafer/budget/RegexController.java
Normal file
130
src/main/java/com/stephenschafer/budget/RegexController.java
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@CrossOrigin(origins = "*", maxAge = 3600)
|
||||
@RestController
|
||||
public class RegexController {
|
||||
@Autowired
|
||||
private RegexDao regexDao;
|
||||
@Autowired
|
||||
private CategoryDao categoryDao;
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@PostMapping("/regexes")
|
||||
@ResponseBody
|
||||
public ApiResponse<Regex> postRegex(@RequestBody final Regex regex,
|
||||
final HttpServletRequest req) {
|
||||
if (!userService.isAuthorized(req)) {
|
||||
return new ApiResponse<>(HttpStatus.UNAUTHORIZED.value(),
|
||||
"You are not authorized to do this", null);
|
||||
}
|
||||
return new ApiResponse<>(HttpStatus.OK.value(), "Regex inserted successfully",
|
||||
regexDao.add(regex));
|
||||
}
|
||||
|
||||
@PutMapping("/regexes")
|
||||
@ResponseBody
|
||||
public ApiResponse<Regex> putRegex(@RequestBody final Regex regex,
|
||||
final HttpServletRequest req) {
|
||||
if (!userService.isAuthorized(req)) {
|
||||
return new ApiResponse<>(HttpStatus.UNAUTHORIZED.value(),
|
||||
"You are not authorized to do this", null);
|
||||
}
|
||||
regexDao.update(regex);
|
||||
return new ApiResponse<>(HttpStatus.OK.value(), "Regex updated successfully", regex);
|
||||
}
|
||||
|
||||
@GetMapping("/regexes")
|
||||
@ResponseBody
|
||||
public ApiResponse<List<RegexDisplay>> getRegexes(final HttpServletRequest request) {
|
||||
log.info("GET /regexes");
|
||||
if (!userService.isAuthorized(request)) {
|
||||
return new ApiResponse<>(HttpStatus.UNAUTHORIZED.value(),
|
||||
"You are not authorized to do this", null);
|
||||
}
|
||||
final List<RegexDisplay> regexDisplays = new ArrayList<>();
|
||||
regexDao.getAllDisplay(regexDisplay -> {
|
||||
regexDisplays.add(regexDisplay);
|
||||
});
|
||||
return new ApiResponse<>(HttpStatus.OK.value(), "Regex list fetched successfully",
|
||||
regexDisplays);
|
||||
}
|
||||
|
||||
@GetMapping("/regex/{regexId}")
|
||||
@ResponseBody
|
||||
public ApiResponse<Regex> getRegex(@PathVariable(required = true) final Integer regexId,
|
||||
final HttpServletRequest request) {
|
||||
log.info("GET /regex/" + regexId);
|
||||
if (!userService.isAuthorized(request)) {
|
||||
return new ApiResponse<>(HttpStatus.UNAUTHORIZED.value(),
|
||||
"You are not authorized to do this", null);
|
||||
}
|
||||
if (regexId == null) {
|
||||
return new ApiResponse<>(HttpStatus.BAD_REQUEST.value(), "Regex ID not specified",
|
||||
null);
|
||||
}
|
||||
final Optional<Regex> optionalRegex = regexDao.getById(regexId.intValue());
|
||||
if (!optionalRegex.isPresent()) {
|
||||
return new ApiResponse<>(HttpStatus.NOT_FOUND.value(), "Regex ID not found", null);
|
||||
}
|
||||
final Regex regex = optionalRegex.get();
|
||||
return new ApiResponse<>(HttpStatus.OK.value(), "Regex retrieved successfully", regex);
|
||||
}
|
||||
|
||||
@GetMapping("/regexes/category/{categoryName}")
|
||||
@ResponseBody
|
||||
public ApiResponse<List<Regex>> getRegexesByCategory(
|
||||
@PathVariable(required = true) final String categoryName,
|
||||
final HttpServletRequest request) {
|
||||
log.info("GET /regexes/" + categoryName);
|
||||
if (!userService.isAuthorized(request)) {
|
||||
return new ApiResponse<>(HttpStatus.UNAUTHORIZED.value(),
|
||||
"You are not authorized to do this", null);
|
||||
}
|
||||
final Optional<Category> category = categoryDao.getByName(categoryName);
|
||||
if (!category.isPresent()) {
|
||||
return new ApiResponse<>(HttpStatus.NOT_FOUND.value(), "Category not found", null);
|
||||
}
|
||||
final int categoryId = category.get().getId();
|
||||
final List<Regex> regexes = new ArrayList<>();
|
||||
regexDao.getByCategory(categoryId, regex -> {
|
||||
regexes.add(regex);
|
||||
});
|
||||
return new ApiResponse<>(HttpStatus.OK.value(), "Regex list fetched successfully", regexes);
|
||||
}
|
||||
|
||||
@GetMapping("/regexes/source/{source}")
|
||||
@ResponseBody
|
||||
public ApiResponse<List<Regex>> getRegexesBySource(
|
||||
@PathVariable(required = true) final String source, final HttpServletRequest request) {
|
||||
log.info("GET /regexes/source/" + source);
|
||||
if (!userService.isAuthorized(request)) {
|
||||
return new ApiResponse<>(HttpStatus.UNAUTHORIZED.value(),
|
||||
"You are not authorized to do this", null);
|
||||
}
|
||||
final List<Regex> regexes = new ArrayList<>();
|
||||
regexDao.getBySource(source, regex -> {
|
||||
regexes.add(regex);
|
||||
});
|
||||
return new ApiResponse<>(HttpStatus.OK.value(), "Regex list fetched successfully", regexes);
|
||||
}
|
||||
}
|
||||
22
src/main/java/com/stephenschafer/budget/RegexDao.java
Normal file
22
src/main/java/com/stephenschafer/budget/RegexDao.java
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface RegexDao {
|
||||
int deleteById(int id);
|
||||
|
||||
Optional<Regex> getById(int id);
|
||||
|
||||
Regex add(Regex regex);
|
||||
|
||||
void update(Regex regex);
|
||||
|
||||
void getAll(Consumer<Regex> consumer);
|
||||
|
||||
void getAllDisplay(Consumer<RegexDisplay> consumer);
|
||||
|
||||
void getByCategory(int categoryId, Consumer<Regex> consumer);
|
||||
|
||||
void getBySource(String source, Consumer<Regex> consumer);
|
||||
}
|
||||
228
src/main/java/com/stephenschafer/budget/RegexDaoImpl.java
Normal file
228
src/main/java/com/stephenschafer/budget/RegexDaoImpl.java
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.PreparedStatementCreator;
|
||||
import org.springframework.jdbc.core.PreparedStatementCreatorFactory;
|
||||
import org.springframework.jdbc.support.GeneratedKeyHolder;
|
||||
import org.springframework.jdbc.support.KeyHolder;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class RegexDaoImpl implements RegexDao {
|
||||
@Autowired
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
@Autowired
|
||||
private CategoryDao categoryDao;
|
||||
|
||||
@Override
|
||||
public int deleteById(final int id) {
|
||||
return jdbcTemplate.update("delete from budget.regex where id = ?", id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Regex> getById(final int id) {
|
||||
final String sql = "select"
|
||||
+ " category_id, regex, flags, source, priority, description, year"
|
||||
+ " from budget.regex where id = ?";
|
||||
return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> {
|
||||
int i = 0;
|
||||
final int categoryId = rs.getInt(++i);
|
||||
final String regex = rs.getString(++i);
|
||||
final int flags = rs.getInt(++i);
|
||||
final String source = rs.getString(++i);
|
||||
final int priority = rs.getInt(++i);
|
||||
final String description = rs.getString(++i);
|
||||
final int yearInt = rs.getInt(++i);
|
||||
final Integer year = rs.wasNull() ? null : Integer.valueOf(yearInt);
|
||||
return Optional.of(
|
||||
new Regex(id, categoryId, regex, flags, source, priority, description, year));
|
||||
}, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Regex add(final Regex regex) {
|
||||
log.info("RegexDaoImpl.add " + regex);
|
||||
final String sql = "insert into budget.regex"
|
||||
+ " (category_id, regex, flags, source, priority, description, year)"
|
||||
+ " values (?, ?, ?, ?, ?, ?, ?)";
|
||||
final PreparedStatementCreatorFactory factory = new PreparedStatementCreatorFactory(sql,
|
||||
Types.INTEGER, Types.VARCHAR, Types.INTEGER, Types.VARCHAR, Types.INTEGER,
|
||||
Types.VARCHAR, Types.INTEGER);
|
||||
factory.setReturnGeneratedKeys(true);
|
||||
factory.setGeneratedKeysColumnNames("id");
|
||||
final PreparedStatementCreator creator = factory.newPreparedStatementCreator(
|
||||
new Object[] { regex.getCategoryId(), regex.getRegex(), regex.getFlags(),
|
||||
regex.getSource(), regex.getPriority(), regex.getDescription(), regex.getYear() });
|
||||
final KeyHolder keyHolder = new GeneratedKeyHolder();
|
||||
final int rowCount = jdbcTemplate.update(creator, keyHolder);
|
||||
if (rowCount == 0) {
|
||||
return null;
|
||||
}
|
||||
final Number generatedId = keyHolder.getKey();
|
||||
return new Regex(generatedId.intValue(), regex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(final Regex regex) {
|
||||
log.info("RegexDaoImpl.update " + regex);
|
||||
final String sql = "update budget.regex"
|
||||
+ " set category_id = ?, regex = ?, flags = ?, source = ?, priority = ?, description = ?, year = ?"
|
||||
+ " where id = ?";
|
||||
final PreparedStatementCreatorFactory factory = new PreparedStatementCreatorFactory(sql,
|
||||
Types.INTEGER, Types.VARCHAR, Types.INTEGER, Types.VARCHAR, Types.INTEGER,
|
||||
Types.VARCHAR, Types.INTEGER, Types.INTEGER);
|
||||
PreparedStatementCreator creator;
|
||||
try {
|
||||
creator = factory.newPreparedStatementCreator(new Object[] { regex.getCategoryId(),
|
||||
regex.getRegex(), regex.getFlags(), regex.getSource(), regex.getPriority(),
|
||||
regex.getDescription(), regex.getYear(), regex.getId() });
|
||||
}
|
||||
catch (final Exception e) {
|
||||
log.error("failed to get creator");
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
log.info("update creator = " + creator);
|
||||
jdbcTemplate.update(creator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getAll(final Consumer<Regex> consumer) {
|
||||
final PreparedStatementHolder holder = new PreparedStatementHolder();
|
||||
final String sql = "select"
|
||||
+ " id, category_id, regex, flags, source, priority, description, year"
|
||||
+ " from budget.regex" + " order by id";
|
||||
final PreparedStatementCreator creator = connection -> {
|
||||
holder.statement = connection.prepareStatement(sql);
|
||||
return holder.statement;
|
||||
};
|
||||
try {
|
||||
jdbcTemplate.query(creator, rs -> {
|
||||
int i = 0;
|
||||
final int id = rs.getInt(++i);
|
||||
final int categoryId = rs.getInt(++i);
|
||||
final String regex = rs.getString(++i);
|
||||
final int flags = rs.getInt(++i);
|
||||
final String source = rs.getString(++i);
|
||||
final int priority = rs.getInt(++i);
|
||||
final String description = rs.getString(++i);
|
||||
final int yearInt = rs.getInt(++i);
|
||||
final Integer year = rs.wasNull() ? null : Integer.valueOf(yearInt);
|
||||
consumer.accept(
|
||||
new Regex(id, categoryId, regex, flags, source, priority, description, year));
|
||||
});
|
||||
}
|
||||
catch (final StopException e) {
|
||||
try {
|
||||
holder.statement.cancel();
|
||||
}
|
||||
catch (final SQLException e1) {
|
||||
log.error("getAll failed", e1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getAllDisplay(final Consumer<RegexDisplay> consumer) {
|
||||
final List<Regex> regexes = new ArrayList<>();
|
||||
getAll(regex -> {
|
||||
regexes.add(regex);
|
||||
});
|
||||
for (final Regex regex : regexes) {
|
||||
final List<Category> categoryAncestry = categoryDao.getCategoryAncestry(
|
||||
regex.categoryId);
|
||||
final String fqCategoryName = getFQCategoryName(categoryAncestry);
|
||||
consumer.accept(new RegexDisplay(regex, fqCategoryName));
|
||||
}
|
||||
}
|
||||
|
||||
private String getFQCategoryName(final List<Category> categoryAncestry) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
String sep = "";
|
||||
for (final Category category : categoryAncestry) {
|
||||
sb.append(sep);
|
||||
sep = ".";
|
||||
sb.append(category.name);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getByCategory(final int categoryId, final Consumer<Regex> consumer) {
|
||||
final PreparedStatementHolder holder = new PreparedStatementHolder();
|
||||
final String sql = "select" + " id, regex, flags, source, priority, description, year"
|
||||
+ " from budget.regex" + " where category_id = ?" + " order by id";
|
||||
final PreparedStatementCreator creator = connection -> {
|
||||
holder.statement = connection.prepareStatement(sql);
|
||||
holder.statement.setInt(1, categoryId);
|
||||
return holder.statement;
|
||||
};
|
||||
try {
|
||||
jdbcTemplate.query(creator, rs -> {
|
||||
int i = 0;
|
||||
final int id = rs.getInt(++i);
|
||||
final String regex = rs.getString(++i);
|
||||
final int flags = rs.getInt(++i);
|
||||
final String source = rs.getString(++i);
|
||||
final int priority = rs.getInt(++i);
|
||||
final String description = rs.getString(++i);
|
||||
final int yearInt = rs.getInt(++i);
|
||||
final Integer year = rs.wasNull() ? null : Integer.valueOf(yearInt);
|
||||
consumer.accept(
|
||||
new Regex(id, categoryId, regex, flags, source, priority, description, year));
|
||||
});
|
||||
}
|
||||
catch (final StopException e) {
|
||||
try {
|
||||
holder.statement.cancel();
|
||||
}
|
||||
catch (final SQLException e1) {
|
||||
log.error("getByCategory failed", e1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getBySource(final String source, final Consumer<Regex> consumer) {
|
||||
final PreparedStatementHolder holder = new PreparedStatementHolder();
|
||||
final String sql = "select" + " id, category_id, regex, flags, priority, description, year"
|
||||
+ " from budget.regex" + " where source = ?" + " order by id";
|
||||
final PreparedStatementCreator creator = connection -> {
|
||||
holder.statement = connection.prepareStatement(sql);
|
||||
holder.statement.setString(1, source);
|
||||
return holder.statement;
|
||||
};
|
||||
try {
|
||||
jdbcTemplate.query(creator, rs -> {
|
||||
int i = 0;
|
||||
final int id = rs.getInt(++i);
|
||||
final int categoryId = rs.getInt(++i);
|
||||
final String regex = rs.getString(++i);
|
||||
final int flags = rs.getInt(++i);
|
||||
final int priority = rs.getInt(++i);
|
||||
final String description = rs.getString(++i);
|
||||
final int yearInt = rs.getInt(++i);
|
||||
final Integer year = rs.wasNull() ? null : Integer.valueOf(yearInt);
|
||||
consumer.accept(
|
||||
new Regex(id, categoryId, regex, flags, source, priority, description, year));
|
||||
});
|
||||
}
|
||||
catch (final StopException e) {
|
||||
try {
|
||||
holder.statement.cancel();
|
||||
}
|
||||
catch (final SQLException e1) {
|
||||
log.error("getBySource failed", e1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
src/main/java/com/stephenschafer/budget/RegexDisplay.java
Normal file
27
src/main/java/com/stephenschafer/budget/RegexDisplay.java
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@ToString
|
||||
public class RegexDisplay {
|
||||
public RegexDisplay(final Regex regex, final String fqCategoryName) {
|
||||
this(regex.id, regex.categoryId, fqCategoryName, regex.regex, regex.flags, regex.source,
|
||||
regex.priority, regex.description, regex.year);
|
||||
}
|
||||
|
||||
Integer id;
|
||||
Integer categoryId;
|
||||
String fqCategoryName;
|
||||
String regex;
|
||||
int flags;
|
||||
String source;
|
||||
int priority;
|
||||
String description;
|
||||
Integer year;
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import java.text.ParseException;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.multipart.MultipartException;
|
||||
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
public class RestExceptionHandler {
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "Unknown error")
|
||||
public ApiResponse<Void> handleNotFoundException(final RuntimeException ex) {
|
||||
log.info("Exception: " + ex);
|
||||
final ApiResponse<Void> apiResponse = new ApiResponse<>(400, "Unknown error", null);
|
||||
return apiResponse;
|
||||
}
|
||||
|
||||
@ExceptionHandler(MultipartException.class)
|
||||
@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "Multipart exception")
|
||||
public ApiResponse<Void> handleError1(final MultipartException e,
|
||||
final RedirectAttributes redirectAttributes) {
|
||||
redirectAttributes.addFlashAttribute("message", e.getCause().getMessage());
|
||||
log.info("Exception: " + e);
|
||||
final ApiResponse<Void> apiResponse = new ApiResponse<>(400, "Multipart Exception", null);
|
||||
return apiResponse;
|
||||
}
|
||||
|
||||
@ExceptionHandler(ParseException.class)
|
||||
@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "Bad date format")
|
||||
public ApiResponse<Void> handleParseException(final ParseException ex) {
|
||||
log.info("Exception: " + ex);
|
||||
final ApiResponse<Void> apiResponse = new ApiResponse<>(400, "Bad date format", null);
|
||||
return apiResponse;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
public class StopException extends RuntimeException {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
||||
26
src/main/java/com/stephenschafer/budget/Transaction.java
Normal file
26
src/main/java/com/stephenschafer/budget/Transaction.java
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Date;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@ToString
|
||||
public class Transaction {
|
||||
Integer id;
|
||||
String source;
|
||||
String uniqueIdentifier;
|
||||
String type;
|
||||
String description;
|
||||
String extraDescription;
|
||||
Date date;
|
||||
BigDecimal amount;
|
||||
Integer optional;
|
||||
Integer regexId;
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@CrossOrigin(origins = "*", maxAge = 3600)
|
||||
@RestController
|
||||
public class TransactionController {
|
||||
@Autowired
|
||||
private TransactionDao transactionDao;
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@GetMapping("/sources/{year}")
|
||||
@ResponseBody
|
||||
public ApiResponse<List<String>> geSources(@PathVariable(required = true) final String year,
|
||||
final HttpServletRequest request) {
|
||||
log.info("GET /sources/" + year);
|
||||
if (!userService.isAuthorized(request)) {
|
||||
return new ApiResponse<>(HttpStatus.UNAUTHORIZED.value(),
|
||||
"You are not authorized to do this", null);
|
||||
}
|
||||
if (year == null) {
|
||||
return new ApiResponse<>(HttpStatus.BAD_REQUEST.value(), "Year not specified", null);
|
||||
}
|
||||
final List<String> sources = new ArrayList<>();
|
||||
transactionDao.getSources(year, source -> {
|
||||
sources.add(source);
|
||||
});
|
||||
return new ApiResponse<>(HttpStatus.OK.value(), "Sources retrieved successfully", sources);
|
||||
}
|
||||
|
||||
@GetMapping("/transactions/{year}")
|
||||
@ResponseBody
|
||||
public ApiResponse<List<Transaction>> getTransactions(
|
||||
@PathVariable(required = true) final String year, final HttpServletRequest request) {
|
||||
log.info("GET /transactions/" + year);
|
||||
if (!userService.isAuthorized(request)) {
|
||||
return new ApiResponse<>(HttpStatus.UNAUTHORIZED.value(),
|
||||
"You are not authorized to do this", null);
|
||||
}
|
||||
if (year == null) {
|
||||
return new ApiResponse<>(HttpStatus.BAD_REQUEST.value(), "Year not specified", null);
|
||||
}
|
||||
final List<Transaction> transactions = new ArrayList<>();
|
||||
transactionDao.getAll(year, transaction -> {
|
||||
transactions.add(transaction);
|
||||
});
|
||||
return new ApiResponse<>(HttpStatus.OK.value(), "Transactions retrieved successfully",
|
||||
transactions);
|
||||
}
|
||||
|
||||
@GetMapping("/transactionsByRegexId/{year}/{regexId}")
|
||||
@ResponseBody
|
||||
public ApiResponse<List<Transaction>> getTransactionsByRegexId(
|
||||
@PathVariable(required = true) final String year,
|
||||
@PathVariable(required = true) final Integer regexId,
|
||||
final HttpServletRequest request) {
|
||||
log.info("GET /transactions/" + year);
|
||||
if (!userService.isAuthorized(request)) {
|
||||
return new ApiResponse<>(HttpStatus.UNAUTHORIZED.value(),
|
||||
"You are not authorized to do this", null);
|
||||
}
|
||||
if (year == null) {
|
||||
return new ApiResponse<>(HttpStatus.BAD_REQUEST.value(), "Year not specified", null);
|
||||
}
|
||||
final List<Transaction> transactions = new ArrayList<>();
|
||||
transactionDao.getByRegexId(year, regexId, transaction -> {
|
||||
transactions.add(transaction);
|
||||
});
|
||||
return new ApiResponse<>(HttpStatus.OK.value(), "Transactions retrieved successfully",
|
||||
transactions);
|
||||
}
|
||||
}
|
||||
20
src/main/java/com/stephenschafer/budget/TransactionDao.java
Normal file
20
src/main/java/com/stephenschafer/budget/TransactionDao.java
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface TransactionDao {
|
||||
Optional<Transaction> getById(String year, int id);
|
||||
|
||||
void getAll(String year, Consumer<Transaction> consumer);
|
||||
|
||||
void getByCategory(String year, int categoryId, Consumer<Transaction> consumer);
|
||||
|
||||
void getBySource(String year, String source, Consumer<Transaction> consumer);
|
||||
|
||||
void getByRegexId(String year, Integer regexId, Consumer<Transaction> consumer);
|
||||
|
||||
void getSources(String year, Consumer<String> consumer);
|
||||
|
||||
void update(String year, Transaction transaction);
|
||||
}
|
||||
159
src/main/java/com/stephenschafer/budget/TransactionDaoImpl.java
Normal file
159
src/main/java/com/stephenschafer/budget/TransactionDaoImpl.java
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Date;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.PreparedStatementCreator;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class TransactionDaoImpl implements TransactionDao {
|
||||
@Autowired
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
@Override
|
||||
public Optional<Transaction> getById(final String year, final int id) {
|
||||
final String sql = ("select"
|
||||
+ " source, unique_identifier, type, description, extra_description, date, amount, optional, regex_id"
|
||||
+ " from budget_${year}.transaction where id = ?").replace("${year}", year);
|
||||
return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> {
|
||||
int i = 0;
|
||||
final String source = rs.getString(++i);
|
||||
final String uniqueIdentifier = rs.getString(++i);
|
||||
final String type = rs.getString(++i);
|
||||
final String description = rs.getString(++i);
|
||||
final String extraDescription = rs.getString(++i);
|
||||
final Date date = rs.getDate(++i);
|
||||
final BigDecimal amount = rs.getBigDecimal(++i);
|
||||
final int optional = rs.getInt(++i);
|
||||
final int regexId = rs.getInt(++i);
|
||||
return Optional.of(new Transaction(id, source, uniqueIdentifier, type, description,
|
||||
extraDescription, date, amount, optional, regexId));
|
||||
}, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(final String year, final Transaction transaction) {
|
||||
// TODO Auto-generated method stub
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getAll(final String year, final Consumer<Transaction> consumer) {
|
||||
final PreparedStatementHolder holder = new PreparedStatementHolder();
|
||||
final String sql = ("select"
|
||||
+ " id, source, unique_identifier, type, description, extra_description, date, amount, optional, regex_id"
|
||||
+ " from budget_${year}.transaction" + " order by id").replace("${year}", year);
|
||||
final PreparedStatementCreator creator = connection -> {
|
||||
holder.statement = connection.prepareStatement(sql);
|
||||
return holder.statement;
|
||||
};
|
||||
try {
|
||||
jdbcTemplate.query(creator, rs -> {
|
||||
int i = 0;
|
||||
final int id = rs.getInt(++i);
|
||||
final String source = rs.getString(++i);
|
||||
final String uniqueIdentifier = rs.getString(++i);
|
||||
final String type = rs.getString(++i);
|
||||
final String description = rs.getString(++i);
|
||||
final String extraDescription = rs.getString(++i);
|
||||
final Date date = rs.getDate(++i);
|
||||
final BigDecimal amount = rs.getBigDecimal(++i);
|
||||
final int optional = rs.getInt(++i);
|
||||
final int regexId = rs.getInt(++i);
|
||||
consumer.accept(new Transaction(id, source, uniqueIdentifier, type, description,
|
||||
extraDescription, date, amount, optional, regexId));
|
||||
});
|
||||
}
|
||||
catch (final StopException e) {
|
||||
try {
|
||||
holder.statement.cancel();
|
||||
}
|
||||
catch (final SQLException e1) {
|
||||
log.error("getByCategory failed", e1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getByCategory(final String year, final int categoryId,
|
||||
final Consumer<Transaction> consumer) {
|
||||
// TODO Auto-generated method stub
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getBySource(final String year, final String source,
|
||||
final Consumer<Transaction> consumer) {
|
||||
// TODO Auto-generated method stub
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getByRegexId(final String year, final Integer regexId,
|
||||
final Consumer<Transaction> consumer) {
|
||||
final PreparedStatementHolder holder = new PreparedStatementHolder();
|
||||
final String sql = ("select"
|
||||
+ " id, source, unique_identifier, type, description, extra_description, date, amount, optional"
|
||||
+ " from budget_${year}.transaction where regex_id = ?" + " order by id").replace(
|
||||
"${year}", year);
|
||||
final PreparedStatementCreator creator = connection -> {
|
||||
holder.statement = connection.prepareStatement(sql);
|
||||
holder.statement.setInt(1, regexId.intValue());
|
||||
return holder.statement;
|
||||
};
|
||||
try {
|
||||
jdbcTemplate.query(creator, rs -> {
|
||||
int i = 0;
|
||||
final int id = rs.getInt(++i);
|
||||
final String source = rs.getString(++i);
|
||||
final String uniqueIdentifier = rs.getString(++i);
|
||||
final String type = rs.getString(++i);
|
||||
final String description = rs.getString(++i);
|
||||
final String extraDescription = rs.getString(++i);
|
||||
final Date date = rs.getDate(++i);
|
||||
final BigDecimal amount = rs.getBigDecimal(++i);
|
||||
final int optional = rs.getInt(++i);
|
||||
consumer.accept(new Transaction(id, source, uniqueIdentifier, type, description,
|
||||
extraDescription, date, amount, optional, regexId));
|
||||
});
|
||||
}
|
||||
catch (final StopException e) {
|
||||
try {
|
||||
holder.statement.cancel();
|
||||
}
|
||||
catch (final SQLException e1) {
|
||||
log.error("getByCategory failed", e1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getSources(final String year, final Consumer<String> consumer) {
|
||||
final PreparedStatementHolder holder = new PreparedStatementHolder();
|
||||
final String sql = "select source from budget_${year}.transaction group by source order by source".replace(
|
||||
"${year}", year);
|
||||
final PreparedStatementCreator creator = connection -> {
|
||||
holder.statement = connection.prepareStatement(sql);
|
||||
return holder.statement;
|
||||
};
|
||||
try {
|
||||
jdbcTemplate.query(creator, rs -> {
|
||||
int i = 0;
|
||||
final String source = rs.getString(++i);
|
||||
consumer.accept(source);
|
||||
});
|
||||
}
|
||||
catch (final StopException e) {
|
||||
try {
|
||||
holder.statement.cancel();
|
||||
}
|
||||
catch (final SQLException e1) {
|
||||
log.error("getSources failed", e1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
53
src/main/java/com/stephenschafer/budget/UserController.java
Normal file
53
src/main/java/com/stephenschafer/budget/UserController.java
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@CrossOrigin(origins = "*", maxAge = 3600)
|
||||
@RestController
|
||||
@RequestMapping("/users")
|
||||
public class UserController {
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@PostMapping
|
||||
public ApiResponse<UserEntity> saveUser(@RequestBody final UserDto user) {
|
||||
return new ApiResponse<>(HttpStatus.OK.value(), "User saved successfully.",
|
||||
userService.save(user));
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<List<UserEntity>> listUser() {
|
||||
return new ApiResponse<>(HttpStatus.OK.value(), "User list fetched successfully.",
|
||||
userService.findAll());
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ApiResponse<UserEntity> getOne(@PathVariable final int id) {
|
||||
return new ApiResponse<>(HttpStatus.OK.value(), "User fetched successfully.",
|
||||
userService.findById(id));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ApiResponse<UserDto> update(@RequestBody final UserDto userDto) {
|
||||
return new ApiResponse<>(HttpStatus.OK.value(), "User updated successfully.",
|
||||
userService.update(userDto));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ApiResponse<Void> delete(@PathVariable final int id) {
|
||||
userService.delete(id);
|
||||
return new ApiResponse<>(HttpStatus.OK.value(), "User deleted successfully.", null);
|
||||
}
|
||||
}
|
||||
9
src/main/java/com/stephenschafer/budget/UserDao.java
Normal file
9
src/main/java/com/stephenschafer/budget/UserDao.java
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface UserDao extends CrudRepository<UserEntity, Integer> {
|
||||
UserEntity findByUsername(String username);
|
||||
}
|
||||
15
src/main/java/com/stephenschafer/budget/UserDto.java
Normal file
15
src/main/java/com/stephenschafer/budget/UserDto.java
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class UserDto {
|
||||
private int id;
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
private String username;
|
||||
private String password;
|
||||
private String role;
|
||||
}
|
||||
33
src/main/java/com/stephenschafer/budget/UserEntity.java
Normal file
33
src/main/java/com/stephenschafer/budget/UserEntity.java
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "user_1")
|
||||
@Getter
|
||||
@Setter
|
||||
public class UserEntity {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private int id;
|
||||
@Column
|
||||
private String firstName;
|
||||
@Column
|
||||
private String lastName;
|
||||
@Column
|
||||
private String username;
|
||||
@Column
|
||||
private String role;
|
||||
@Column
|
||||
@JsonIgnore
|
||||
private String password;
|
||||
}
|
||||
23
src/main/java/com/stephenschafer/budget/UserService.java
Normal file
23
src/main/java/com/stephenschafer/budget/UserService.java
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
public interface UserService extends UserDetailsService {
|
||||
UserEntity save(UserDto user);
|
||||
|
||||
List<UserEntity> findAll();
|
||||
|
||||
void delete(int id);
|
||||
|
||||
UserEntity findByUsername(String username);
|
||||
|
||||
UserEntity findById(int id);
|
||||
|
||||
UserDto update(UserDto userDto);
|
||||
|
||||
boolean isAuthorized(final HttpServletRequest request);
|
||||
}
|
||||
98
src/main/java/com/stephenschafer/budget/UserServiceImpl.java
Normal file
98
src/main/java/com/stephenschafer/budget/UserServiceImpl.java
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.transaction.Transactional;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Transactional
|
||||
@Service(value = "userService")
|
||||
public class UserServiceImpl implements UserDetailsService, UserService {
|
||||
@Autowired
|
||||
private UserDao userDao;
|
||||
@Autowired
|
||||
private BCryptPasswordEncoder bcryptEncoder;
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException {
|
||||
final UserEntity user = userDao.findByUsername(username);
|
||||
if (user == null) {
|
||||
throw new UsernameNotFoundException("Invalid username or password.");
|
||||
}
|
||||
return new org.springframework.security.core.userdetails.User(user.getUsername(),
|
||||
user.getPassword(), getAuthority());
|
||||
}
|
||||
|
||||
private List<SimpleGrantedAuthority> getAuthority() {
|
||||
return Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserEntity> findAll() {
|
||||
final List<UserEntity> list = new ArrayList<>();
|
||||
userDao.findAll().iterator().forEachRemaining(list::add);
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(final int id) {
|
||||
userDao.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserEntity findByUsername(final String username) {
|
||||
return userDao.findByUsername(username);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserEntity findById(final int id) {
|
||||
final Optional<UserEntity> optionalUser = userDao.findById(id);
|
||||
return optionalUser.isPresent() ? optionalUser.get() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserDto update(final UserDto userDto) {
|
||||
final UserEntity user = findById(userDto.getId());
|
||||
if (user != null) {
|
||||
BeanUtils.copyProperties(userDto, user, "password", "username");
|
||||
userDao.save(user);
|
||||
}
|
||||
return userDto;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserEntity save(final UserDto user) {
|
||||
final UserEntity newUser = new UserEntity();
|
||||
newUser.setUsername(user.getUsername());
|
||||
newUser.setFirstName(user.getFirstName());
|
||||
newUser.setLastName(user.getLastName());
|
||||
newUser.setPassword(bcryptEncoder.encode(user.getPassword()));
|
||||
newUser.setRole(user.getRole());
|
||||
return userDao.save(newUser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAuthorized(final HttpServletRequest request) {
|
||||
final String username = (String) request.getAttribute("username");
|
||||
log.info("username = " + username);
|
||||
final UserEntity userEntity = findByUsername(username);
|
||||
if (userEntity == null || !"administrator".equals(userEntity.getRole())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
4
src/main/java/com/stephenschafer/budget/Util.java
Normal file
4
src/main/java/com/stephenschafer/budget/Util.java
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
public class Util {
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package com.stephenschafer.budget;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.ProviderManager;
|
||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class WebSecurityConfig {
|
||||
@Autowired
|
||||
private JwtAuthenticationEntryPoint unauthorizedHandler;
|
||||
@Autowired
|
||||
CustomCorsConfiguration customCorsConfiguration;
|
||||
|
||||
@Bean
|
||||
AuthenticationManager authenticationManager(final UserDetailsService userDetailsService,
|
||||
final PasswordEncoder passwordEncoder) {
|
||||
final var provider = new DaoAuthenticationProvider();
|
||||
provider.setUserDetailsService(userDetailsService);
|
||||
provider.setPasswordEncoder(passwordEncoder);
|
||||
return new ProviderManager(provider);
|
||||
}
|
||||
|
||||
@Bean
|
||||
BCryptPasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
JwtAuthenticationFilter authenticationTokenFilterBean() throws Exception {
|
||||
return new JwtAuthenticationFilter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(final HttpSecurity http) throws Exception {
|
||||
http.cors(c -> c.configurationSource(customCorsConfiguration)) //
|
||||
.csrf(AbstractHttpConfigurer::disable) //
|
||||
.authorizeHttpRequests(requests -> {
|
||||
requests.requestMatchers("/token/*",
|
||||
"/signup").permitAll().anyRequest().authenticated();
|
||||
}) //
|
||||
.exceptionHandling(
|
||||
configurer -> configurer.authenticationEntryPoint(unauthorizedHandler)) //
|
||||
.sessionManagement(configurer -> configurer.sessionCreationPolicy(
|
||||
SessionCreationPolicy.STATELESS));
|
||||
http.addFilterBefore(authenticationTokenFilterBean(),
|
||||
UsernamePasswordAuthenticationFilter.class);
|
||||
/*
|
||||
requests.requestMatchers("/token/*",
|
||||
"/signup").permitAll().anyRequest().authenticated();
|
||||
*/
|
||||
/*
|
||||
http.cors().and().csrf().disable().authorizeRequests().antMatchers("/token/*",
|
||||
"/signup").permitAll().anyRequest().authenticated().and().exceptionHandling().authenticationEntryPoint(
|
||||
unauthorizedHandler).and().sessionManagement().sessionCreationPolicy(
|
||||
SessionCreationPolicy.STATELESS);
|
||||
// @formatter:on
|
||||
http.addFilterBefore(authenticationTokenFilterBean(),
|
||||
UsernamePasswordAuthenticationFilter.class);
|
||||
*/
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
20
src/main/resources/application.properties
Normal file
20
src/main/resources/application.properties
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
|
||||
spring.datasource.url=jdbc:mysql://localhost:3306?serverTimezone=UTC&useSSL=false
|
||||
spring.datasource.username=elephant
|
||||
# spring.datasource.password=CHANGEME
|
||||
|
||||
spring.jpa.show-sql=true
|
||||
# this unconditionally re-creates the table even if it's already populated
|
||||
#spring.jpa.hibernate.ddl-auto=create-drop
|
||||
# this will add columns to the table if they are missing but doesn't remove any
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
# spring.user.datasource.driver-class-name=com.mysql.jdbc.Driver
|
||||
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
|
||||
|
||||
#server.port=8443
|
||||
#server.ssl.key-store=classpath:keystore.p12
|
||||
#server.ssl.key-store-password=foobar
|
||||
#server.ssl.key-store-type=PKCS12
|
||||
#server.ssl.key-alias=timesheet
|
||||
#server.ssl.key-password=foobar
|
||||
#server.ssl.enabled=true
|
||||
24
stop
Executable file
24
stop
Executable file
|
|
@ -0,0 +1,24 @@
|
|||
#!/bin/sh
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
ROOT=$(pwd)
|
||||
echo "$ROOT"
|
||||
function k() {
|
||||
if ! test -f "$1"; then
|
||||
echo "nothing to stop"
|
||||
return 1
|
||||
fi
|
||||
PID=$(cat "$1")
|
||||
if kill -9 $PID; then
|
||||
echo "process $PID stopped"
|
||||
else
|
||||
echo "no such process"
|
||||
fi
|
||||
rm "$1"
|
||||
}
|
||||
if [ -z "$1" ]; then
|
||||
for file in $ROOT/logs/run-*.pid; do
|
||||
k $file
|
||||
done
|
||||
else
|
||||
k "$ROOT/logs/run-$1.pid"
|
||||
fi
|
||||
Loading…
Reference in a new issue