Initial commit with basic working API
Signed-off-by: Billy Brawner <billy@wbrawner.com>
This commit is contained in:
commit
e2a35ec249
29 changed files with 1477 additions and 0 deletions
29
.gitignore
vendored
Normal file
29
.gitignore
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
HELP.md
|
||||
/target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
114
.mvn/wrapper/MavenWrapperDownloader.java
vendored
Normal file
114
.mvn/wrapper/MavenWrapperDownloader.java
vendored
Normal file
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.util.Properties;
|
||||
|
||||
public class MavenWrapperDownloader {
|
||||
|
||||
/**
|
||||
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
|
||||
*/
|
||||
private static final String DEFAULT_DOWNLOAD_URL =
|
||||
"https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar";
|
||||
|
||||
/**
|
||||
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
|
||||
* use instead of the default one.
|
||||
*/
|
||||
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
|
||||
".mvn/wrapper/maven-wrapper.properties";
|
||||
|
||||
/**
|
||||
* Path where the maven-wrapper.jar will be saved to.
|
||||
*/
|
||||
private static final String MAVEN_WRAPPER_JAR_PATH =
|
||||
".mvn/wrapper/maven-wrapper.jar";
|
||||
|
||||
/**
|
||||
* Name of the property which should be used to override the default download url for the wrapper.
|
||||
*/
|
||||
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
|
||||
|
||||
public static void main(String args[]) {
|
||||
System.out.println("- Downloader started");
|
||||
File baseDirectory = new File(args[0]);
|
||||
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
|
||||
|
||||
// If the maven-wrapper.properties exists, read it and check if it contains a custom
|
||||
// wrapperUrl parameter.
|
||||
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
|
||||
String url = DEFAULT_DOWNLOAD_URL;
|
||||
if (mavenWrapperPropertyFile.exists()) {
|
||||
FileInputStream mavenWrapperPropertyFileInputStream = null;
|
||||
try {
|
||||
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
|
||||
Properties mavenWrapperProperties = new Properties();
|
||||
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
|
||||
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
|
||||
} catch (IOException e) {
|
||||
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
|
||||
} finally {
|
||||
try {
|
||||
if (mavenWrapperPropertyFileInputStream != null) {
|
||||
mavenWrapperPropertyFileInputStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Ignore ...
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.println("- Downloading from: : " + url);
|
||||
|
||||
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
|
||||
if (!outputFile.getParentFile().exists()) {
|
||||
if (!outputFile.getParentFile().mkdirs()) {
|
||||
System.out.println(
|
||||
"- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'");
|
||||
}
|
||||
}
|
||||
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
|
||||
try {
|
||||
downloadFileFromURL(url, outputFile);
|
||||
System.out.println("Done");
|
||||
System.exit(0);
|
||||
} catch (Throwable e) {
|
||||
System.out.println("- Error downloading");
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
|
||||
URL website = new URL(urlString);
|
||||
ReadableByteChannel rbc;
|
||||
rbc = Channels.newChannel(website.openStream());
|
||||
FileOutputStream fos = new FileOutputStream(destination);
|
||||
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
|
||||
fos.close();
|
||||
rbc.close();
|
||||
}
|
||||
|
||||
}
|
BIN
.mvn/wrapper/maven-wrapper.jar
vendored
Normal file
BIN
.mvn/wrapper/maven-wrapper.jar
vendored
Normal file
Binary file not shown.
1
.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
1
.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip
|
286
mvnw
vendored
Executable file
286
mvnw
vendored
Executable file
|
@ -0,0 +1,286 @@
|
|||
#!/bin/sh
|
||||
# ----------------------------------------------------------------------------
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Maven2 Start Up Batch script
|
||||
#
|
||||
# Required ENV vars:
|
||||
# ------------------
|
||||
# JAVA_HOME - location of a JDK home dir
|
||||
#
|
||||
# Optional ENV vars
|
||||
# -----------------
|
||||
# M2_HOME - location of maven2's installed home dir
|
||||
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
# e.g. to debug Maven itself, use
|
||||
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
if [ -z "$MAVEN_SKIP_RC" ] ; then
|
||||
|
||||
if [ -f /etc/mavenrc ] ; then
|
||||
. /etc/mavenrc
|
||||
fi
|
||||
|
||||
if [ -f "$HOME/.mavenrc" ] ; then
|
||||
. "$HOME/.mavenrc"
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
# OS specific support. $var _must_ be set to either true or false.
|
||||
cygwin=false;
|
||||
darwin=false;
|
||||
mingw=false
|
||||
case "`uname`" in
|
||||
CYGWIN*) cygwin=true ;;
|
||||
MINGW*) mingw=true;;
|
||||
Darwin*) darwin=true
|
||||
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
|
||||
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
if [ -x "/usr/libexec/java_home" ]; then
|
||||
export JAVA_HOME="`/usr/libexec/java_home`"
|
||||
else
|
||||
export JAVA_HOME="/Library/Java/Home"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then
|
||||
if [ -r /etc/gentoo-release ] ; then
|
||||
JAVA_HOME=`java-config --jre-home`
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$M2_HOME" ] ; then
|
||||
## resolve links - $0 may be a link to maven's home
|
||||
PRG="$0"
|
||||
|
||||
# need this for relative symlinks
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG="`dirname "$PRG"`/$link"
|
||||
fi
|
||||
done
|
||||
|
||||
saveddir=`pwd`
|
||||
|
||||
M2_HOME=`dirname "$PRG"`/..
|
||||
|
||||
# make it fully qualified
|
||||
M2_HOME=`cd "$M2_HOME" && pwd`
|
||||
|
||||
cd "$saveddir"
|
||||
# echo Using m2 at $M2_HOME
|
||||
fi
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched
|
||||
if $cygwin ; then
|
||||
[ -n "$M2_HOME" ] &&
|
||||
M2_HOME=`cygpath --unix "$M2_HOME"`
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
[ -n "$CLASSPATH" ] &&
|
||||
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
|
||||
fi
|
||||
|
||||
# For Mingw, ensure paths are in UNIX format before anything is touched
|
||||
if $mingw ; then
|
||||
[ -n "$M2_HOME" ] &&
|
||||
M2_HOME="`(cd "$M2_HOME"; pwd)`"
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
|
||||
# TODO classpath?
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
javaExecutable="`which javac`"
|
||||
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
|
||||
# readlink(1) is not available as standard on Solaris 10.
|
||||
readLink=`which readlink`
|
||||
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
|
||||
if $darwin ; then
|
||||
javaHome="`dirname \"$javaExecutable\"`"
|
||||
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
|
||||
else
|
||||
javaExecutable="`readlink -f \"$javaExecutable\"`"
|
||||
fi
|
||||
javaHome="`dirname \"$javaExecutable\"`"
|
||||
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
|
||||
JAVA_HOME="$javaHome"
|
||||
export JAVA_HOME
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$JAVACMD" ] ; then
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
else
|
||||
JAVACMD="`which java`"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
echo "Error: JAVA_HOME is not defined correctly." >&2
|
||||
echo " We cannot execute $JAVACMD" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then
|
||||
echo "Warning: JAVA_HOME environment variable is not set."
|
||||
fi
|
||||
|
||||
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
|
||||
|
||||
# traverses directory structure from process work directory to filesystem root
|
||||
# first directory with .mvn subdirectory is considered project base directory
|
||||
find_maven_basedir() {
|
||||
|
||||
if [ -z "$1" ]
|
||||
then
|
||||
echo "Path not specified to find_maven_basedir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
basedir="$1"
|
||||
wdir="$1"
|
||||
while [ "$wdir" != '/' ] ; do
|
||||
if [ -d "$wdir"/.mvn ] ; then
|
||||
basedir=$wdir
|
||||
break
|
||||
fi
|
||||
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
|
||||
if [ -d "${wdir}" ]; then
|
||||
wdir=`cd "$wdir/.."; pwd`
|
||||
fi
|
||||
# end of workaround
|
||||
done
|
||||
echo "${basedir}"
|
||||
}
|
||||
|
||||
# concatenates all lines of a file
|
||||
concat_lines() {
|
||||
if [ -f "$1" ]; then
|
||||
echo "$(tr -s '\n' ' ' < "$1")"
|
||||
fi
|
||||
}
|
||||
|
||||
BASE_DIR=`find_maven_basedir "$(pwd)"`
|
||||
if [ -z "$BASE_DIR" ]; then
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
##########################################################################################
|
||||
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||
# This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||
##########################################################################################
|
||||
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found .mvn/wrapper/maven-wrapper.jar"
|
||||
fi
|
||||
else
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
|
||||
fi
|
||||
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
|
||||
while IFS="=" read key value; do
|
||||
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
|
||||
esac
|
||||
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Downloading from: $jarUrl"
|
||||
fi
|
||||
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
|
||||
|
||||
if command -v wget > /dev/null; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found wget ... using wget"
|
||||
fi
|
||||
wget "$jarUrl" -O "$wrapperJarPath"
|
||||
elif command -v curl > /dev/null; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found curl ... using curl"
|
||||
fi
|
||||
curl -o "$wrapperJarPath" "$jarUrl"
|
||||
else
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Falling back to using Java to download"
|
||||
fi
|
||||
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
|
||||
if [ -e "$javaClass" ]; then
|
||||
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo " - Compiling MavenWrapperDownloader.java ..."
|
||||
fi
|
||||
# Compiling the Java class
|
||||
("$JAVA_HOME/bin/javac" "$javaClass")
|
||||
fi
|
||||
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
|
||||
# Running the downloader
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo " - Running MavenWrapperDownloader.java ..."
|
||||
fi
|
||||
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
##########################################################################################
|
||||
# End of extension
|
||||
##########################################################################################
|
||||
|
||||
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo $MAVEN_PROJECTBASEDIR
|
||||
fi
|
||||
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin; then
|
||||
[ -n "$M2_HOME" ] &&
|
||||
M2_HOME=`cygpath --path --windows "$M2_HOME"`
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
|
||||
[ -n "$CLASSPATH" ] &&
|
||||
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
|
||||
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
|
||||
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
|
||||
fi
|
||||
|
||||
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
exec "$JAVACMD" \
|
||||
$MAVEN_OPTS \
|
||||
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
|
||||
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
|
||||
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
|
161
mvnw.cmd
vendored
Normal file
161
mvnw.cmd
vendored
Normal file
|
@ -0,0 +1,161 @@
|
|||
@REM ----------------------------------------------------------------------------
|
||||
@REM Licensed to the Apache Software Foundation (ASF) under one
|
||||
@REM or more contributor license agreements. See the NOTICE file
|
||||
@REM distributed with this work for additional information
|
||||
@REM regarding copyright ownership. The ASF licenses this file
|
||||
@REM to you under the Apache License, Version 2.0 (the
|
||||
@REM "License"); you may not use this file except in compliance
|
||||
@REM with the License. You may obtain a copy of the License at
|
||||
@REM
|
||||
@REM https://www.apache.org/licenses/LICENSE-2.0
|
||||
@REM
|
||||
@REM Unless required by applicable law or agreed to in writing,
|
||||
@REM software distributed under the License is distributed on an
|
||||
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
@REM KIND, either express or implied. See the License for the
|
||||
@REM specific language governing permissions and limitations
|
||||
@REM under the License.
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Maven2 Start Up Batch script
|
||||
@REM
|
||||
@REM Required ENV vars:
|
||||
@REM JAVA_HOME - location of a JDK home dir
|
||||
@REM
|
||||
@REM Optional ENV vars
|
||||
@REM M2_HOME - location of maven2's installed home dir
|
||||
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
|
||||
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
|
||||
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
@REM e.g. to debug Maven itself, use
|
||||
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
|
||||
@echo off
|
||||
@REM set title of command window
|
||||
title %0
|
||||
@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
|
||||
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
|
||||
|
||||
@REM set %HOME% to equivalent of $HOME
|
||||
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
|
||||
|
||||
@REM Execute a user defined script before this one
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
|
||||
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
|
||||
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
|
||||
:skipRcPre
|
||||
|
||||
@setlocal
|
||||
|
||||
set ERROR_CODE=0
|
||||
|
||||
@REM To isolate internal variables from possible post scripts, we use another setlocal
|
||||
@setlocal
|
||||
|
||||
@REM ==== START VALIDATION ====
|
||||
if not "%JAVA_HOME%" == "" goto OkJHome
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME not found in your environment. >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
:OkJHome
|
||||
if exist "%JAVA_HOME%\bin\java.exe" goto init
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME is set to an invalid directory. >&2
|
||||
echo JAVA_HOME = "%JAVA_HOME%" >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
@REM ==== END VALIDATION ====
|
||||
|
||||
:init
|
||||
|
||||
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
|
||||
@REM Fallback to current working directory if not found.
|
||||
|
||||
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
|
||||
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
|
||||
|
||||
set EXEC_DIR=%CD%
|
||||
set WDIR=%EXEC_DIR%
|
||||
:findBaseDir
|
||||
IF EXIST "%WDIR%"\.mvn goto baseDirFound
|
||||
cd ..
|
||||
IF "%WDIR%"=="%CD%" goto baseDirNotFound
|
||||
set WDIR=%CD%
|
||||
goto findBaseDir
|
||||
|
||||
:baseDirFound
|
||||
set MAVEN_PROJECTBASEDIR=%WDIR%
|
||||
cd "%EXEC_DIR%"
|
||||
goto endDetectBaseDir
|
||||
|
||||
:baseDirNotFound
|
||||
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
|
||||
cd "%EXEC_DIR%"
|
||||
|
||||
:endDetectBaseDir
|
||||
|
||||
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
|
||||
|
||||
@setlocal EnableExtensions EnableDelayedExpansion
|
||||
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
|
||||
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
|
||||
|
||||
:endReadAdditionalConfig
|
||||
|
||||
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
|
||||
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
|
||||
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
|
||||
FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO (
|
||||
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
|
||||
)
|
||||
|
||||
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||
if exist %WRAPPER_JAR% (
|
||||
echo Found %WRAPPER_JAR%
|
||||
) else (
|
||||
echo Couldn't find %WRAPPER_JAR%, downloading it ...
|
||||
echo Downloading from: %DOWNLOAD_URL%
|
||||
powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"
|
||||
echo Finished downloading %WRAPPER_JAR%
|
||||
)
|
||||
@REM End of extension
|
||||
|
||||
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
|
||||
if ERRORLEVEL 1 goto error
|
||||
goto end
|
||||
|
||||
:error
|
||||
set ERROR_CODE=1
|
||||
|
||||
:end
|
||||
@endlocal & set ERROR_CODE=%ERROR_CODE%
|
||||
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
|
||||
@REM check for post script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
|
||||
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
|
||||
:skipRcPost
|
||||
|
||||
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
|
||||
if "%MAVEN_BATCH_PAUSE%" == "on" pause
|
||||
|
||||
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
|
||||
|
||||
exit /B %ERROR_CODE%
|
113
pom.xml
Normal file
113
pom.xml
Normal file
|
@ -0,0 +1,113 @@
|
|||
<?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>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.1.4.RELEASE</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>com.wbrawner</groupId>
|
||||
<artifactId>budget-server</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>budget-server</name>
|
||||
<description>The server-side component for the budgeting apps</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<kotlin.version>1.2.71</kotlin.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.module</groupId>
|
||||
<artifactId>jackson-module-kotlin</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-reflect</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib-jdk8</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger2</artifactId>
|
||||
<version>2.8.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger-ui</artifactId>
|
||||
<version>2.8.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
|
||||
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<compilerPlugins>
|
||||
<plugin>jpa</plugin>
|
||||
<plugin>spring</plugin>
|
||||
</compilerPlugins>
|
||||
<args>
|
||||
<arg>-Xjsr305=strict</arg>
|
||||
</args>
|
||||
</configuration>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-maven-noarg</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-maven-allopen</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,11 @@
|
|||
package com.wbrawner.budgetserver
|
||||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
import org.springframework.boot.runApplication
|
||||
|
||||
@SpringBootApplication
|
||||
class BudgetServerApplication
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
runApplication<BudgetServerApplication>(*args)
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package com.wbrawner.budgetserver
|
||||
|
||||
data class ErrorResponse(val message: String)
|
9
src/main/kotlin/com/wbrawner/budgetserver/Utils.kt
Normal file
9
src/main/kotlin/com/wbrawner/budgetserver/Utils.kt
Normal file
|
@ -0,0 +1,9 @@
|
|||
package com.wbrawner.budgetserver
|
||||
|
||||
import com.wbrawner.budgetserver.user.User
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
|
||||
fun getCurrentUser(): User? {
|
||||
val user = SecurityContextHolder.getContext().authentication.principal
|
||||
return if (user is User) user else null
|
||||
}
|
27
src/main/kotlin/com/wbrawner/budgetserver/account/Account.kt
Normal file
27
src/main/kotlin/com/wbrawner/budgetserver/account/Account.kt
Normal file
|
@ -0,0 +1,27 @@
|
|||
package com.wbrawner.budgetserver.account
|
||||
|
||||
import com.wbrawner.budgetserver.category.Category
|
||||
import com.wbrawner.budgetserver.transaction.Transaction
|
||||
import com.wbrawner.budgetserver.user.User
|
||||
import com.wbrawner.budgetserver.user.UserResponse
|
||||
import java.util.*
|
||||
import javax.persistence.*
|
||||
|
||||
@Entity
|
||||
data class Account(
|
||||
@Id @GeneratedValue(strategy = GenerationType.AUTO) val id: Long? = null,
|
||||
val name: String = "",
|
||||
val description: String? = null,
|
||||
@OneToMany(mappedBy = "account") val transactions: Set<Transaction> = TreeSet(),
|
||||
@OneToMany(mappedBy = "account") val categories: Set<Category> = TreeSet(),
|
||||
@ManyToMany val users: Set<User> = mutableSetOf(),
|
||||
@ManyToOne val owner: User
|
||||
)
|
||||
|
||||
data class NewAccountRequest(val name: String, val description: String?, val userIds: List<Long>)
|
||||
|
||||
data class UpdateAccountRequest(val name: String?, val description: String?, val userIds: List<Long>?)
|
||||
|
||||
data class AccountResponse(val id: Long, val name: String, val description: String?, val users: List<Long>) {
|
||||
constructor(account: Account) : this(account.id!!, account.name, account.description, account.users.map { it.id!! })
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package com.wbrawner.budgetserver.account
|
||||
|
||||
import com.wbrawner.budgetserver.getCurrentUser
|
||||
import com.wbrawner.budgetserver.user.UserRepository
|
||||
import io.swagger.annotations.Api
|
||||
import io.swagger.annotations.ApiOperation
|
||||
import org.hibernate.Hibernate
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import javax.transaction.Transactional
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/accounts")
|
||||
@Api(value = "Accounts", tags = ["Accounts"])
|
||||
class AccountController @Autowired constructor(private val accountRepository: AccountRepository, private val userRepository: UserRepository) {
|
||||
@Transactional
|
||||
@GetMapping("", produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@ApiOperation(value = "getAccounts", nickname = "getAccounts", tags = ["Accounts"])
|
||||
fun getAccounts(): ResponseEntity<List<AccountResponse>> = ResponseEntity.ok(
|
||||
accountRepository.findAllByUsersContainsOrOwner(getCurrentUser()!!).map {
|
||||
Hibernate.initialize(it.users)
|
||||
AccountResponse(it)
|
||||
}
|
||||
)
|
||||
|
||||
@Transactional
|
||||
@GetMapping("/{id}", produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@ApiOperation(value = "getAccount", nickname = "getAccount", tags = ["Accounts"])
|
||||
fun getAccount(@PathVariable id: Long): ResponseEntity<AccountResponse> = accountRepository.findByUsersContainsAndId(getCurrentUser()!!, id)
|
||||
.orElse(null)
|
||||
?.let {
|
||||
Hibernate.initialize(it.users)
|
||||
ResponseEntity.ok(AccountResponse(it))
|
||||
} ?: ResponseEntity.notFound().build()
|
||||
|
||||
@PostMapping("/new", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@ApiOperation(value = "newAccount", nickname = "newAccount", tags = ["Accounts"])
|
||||
fun newAccount(@RequestBody request: NewAccountRequest): ResponseEntity<AccountResponse> {
|
||||
val users = request.userIds
|
||||
.map { id -> userRepository.findById(id).orElse(null) }
|
||||
.filter { user -> user != null }
|
||||
.toMutableSet()
|
||||
.apply { this.add(getCurrentUser()) }
|
||||
val account = accountRepository.save(Account(name = request.name, description = request.description, users = users, owner = getCurrentUser()!!))
|
||||
return ResponseEntity.ok(AccountResponse(account))
|
||||
}
|
||||
|
||||
@PutMapping("/{id}", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@ApiOperation(value = "updateAccount", nickname = "updateAccount", tags = ["Accounts"])
|
||||
fun updateAccount(@PathVariable id: Long, request: UpdateAccountRequest): ResponseEntity<AccountResponse> {
|
||||
var account = accountRepository.findByUsersContainsAndId(getCurrentUser()!!, id).orElse(null) ?: return ResponseEntity.notFound().build()
|
||||
if (request.name != null) account = account.copy(name = request.name)
|
||||
if (request.description != null) account = account.copy(description = request.description)
|
||||
if (request.userIds != null) account = account.copy(users = userRepository.findAllById(request.userIds).toSet())
|
||||
return ResponseEntity.ok(AccountResponse(accountRepository.save(account)))
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}", produces = [MediaType.TEXT_PLAIN_VALUE])
|
||||
@ApiOperation(value = "deleteAccount", nickname = "deleteAccount", tags = ["Accounts"])
|
||||
fun deleteAccount(@PathVariable id: Long): ResponseEntity<Unit> {
|
||||
val account = accountRepository.findByUsersContainsAndId(getCurrentUser()!!, id).orElse(null) ?: return ResponseEntity.notFound().build()
|
||||
accountRepository.delete(account)
|
||||
return ResponseEntity.ok().build()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package com.wbrawner.budgetserver.account
|
||||
|
||||
import com.wbrawner.budgetserver.category.Category
|
||||
import com.wbrawner.budgetserver.transaction.Transaction
|
||||
import com.wbrawner.budgetserver.user.User
|
||||
import org.springframework.data.repository.PagingAndSortingRepository
|
||||
import java.util.*
|
||||
|
||||
interface AccountRepository: PagingAndSortingRepository<Account, Long> {
|
||||
fun findAllByUsersContainsOrOwner(user: User, owner: User = user): List<Account>
|
||||
fun findByUsersContainsAndId(user: User, id: Long): Optional<Account>
|
||||
fun findByUsersContainsAndTransactionsContains(user: User, transaction: Transaction): Optional<Account>
|
||||
fun findByUsersContainsAndCategoriesContains(user: User, category: Category): Optional<Account>
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package com.wbrawner.budgetserver.category
|
||||
|
||||
import com.wbrawner.budgetserver.account.Account
|
||||
import com.wbrawner.budgetserver.account.AccountResponse
|
||||
import com.wbrawner.budgetserver.transaction.Transaction
|
||||
import javax.persistence.*
|
||||
|
||||
@Entity
|
||||
data class Category(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO) val id: Long? = null,
|
||||
val title: String = "",
|
||||
val description: String? = null,
|
||||
val amount: Long = 0,
|
||||
@ManyToOne val account: Account,
|
||||
@OneToMany(mappedBy = "category") val transactions: List<Transaction> = emptyList()
|
||||
) : Comparable<Category> {
|
||||
override fun compareTo(other: Category): Int = title.compareTo(other.title)
|
||||
}
|
||||
|
||||
data class CategoryResponse(
|
||||
val id: Long,
|
||||
val title: String,
|
||||
val description: String?,
|
||||
val amount: Long,
|
||||
val accountId: Long
|
||||
) {
|
||||
constructor(category: Category) : this(
|
||||
category.id!!,
|
||||
category.title,
|
||||
category.description,
|
||||
category.amount,
|
||||
category.account.id!!
|
||||
)
|
||||
}
|
||||
|
||||
data class NewCategoryRequest(
|
||||
val title: String,
|
||||
val description: String?,
|
||||
val amount: Long,
|
||||
val accountId: Long
|
||||
)
|
||||
|
||||
data class UpdateCategoryRequest(
|
||||
val title: String?,
|
||||
val description: String?,
|
||||
val amount: Long?
|
||||
)
|
|
@ -0,0 +1,86 @@
|
|||
package com.wbrawner.budgetserver.category
|
||||
|
||||
import com.wbrawner.budgetserver.ErrorResponse
|
||||
import com.wbrawner.budgetserver.account.AccountRepository
|
||||
import com.wbrawner.budgetserver.getCurrentUser
|
||||
import com.wbrawner.budgetserver.transaction.TransactionRepository
|
||||
import io.swagger.annotations.Api
|
||||
import io.swagger.annotations.ApiOperation
|
||||
import org.hibernate.Hibernate
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.data.domain.PageRequest
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import java.lang.Integer.min
|
||||
import javax.transaction.Transactional
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/categories")
|
||||
@Api(value = "Categories", tags = ["Categories"])
|
||||
class CategoryController @Autowired constructor(
|
||||
private val accountRepository: AccountRepository,
|
||||
private val categoryRepository: CategoryRepository,
|
||||
private val transactionRepository: TransactionRepository
|
||||
) {
|
||||
@Transactional
|
||||
@GetMapping("", produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@ApiOperation(value = "getCategories", nickname = "getCategories", tags = ["Categories"])
|
||||
fun getCategories(accountId: Long, count: Int?, page: Int?): ResponseEntity<List<CategoryResponse>> {
|
||||
val account = accountRepository.findByUsersContainsAndId(getCurrentUser()!!, accountId).orElse(null)
|
||||
?: return ResponseEntity.notFound().build()
|
||||
Hibernate.initialize(account.users)
|
||||
val pageRequest = PageRequest.of(min(0, page?.minus(1)?: 0), count?: 1000)
|
||||
return ResponseEntity.ok(categoryRepository.findAllByAccount(account, pageRequest).map { CategoryResponse(it) })
|
||||
}
|
||||
|
||||
@GetMapping("/{id}", produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@ApiOperation(value = "getCategory", nickname = "getCategory", tags = ["Categories"])
|
||||
fun getCategory(@PathVariable id: Long): ResponseEntity<CategoryResponse> {
|
||||
val category = categoryRepository.findById(id).orElse(null) ?: return ResponseEntity.notFound().build()
|
||||
accountRepository.findByUsersContainsAndCategoriesContains(getCurrentUser()!!, category).orElse(null)
|
||||
?: return ResponseEntity.notFound().build()
|
||||
return ResponseEntity.ok(CategoryResponse(category))
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@PostMapping("/new", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@ApiOperation(value = "newCategory", nickname = "newCategory", tags = ["Categories"])
|
||||
fun newCategory(@RequestBody request: NewCategoryRequest): ResponseEntity<Any> {
|
||||
val account = accountRepository.findByUsersContainsAndId(getCurrentUser()!!, request.accountId).orElse(null)
|
||||
?: return ResponseEntity.badRequest().body(ErrorResponse("Invalid account ID"))
|
||||
Hibernate.initialize(account.users)
|
||||
return ResponseEntity.ok(CategoryResponse(categoryRepository.save(Category(
|
||||
title = request.title,
|
||||
description = request.description,
|
||||
amount = request.amount,
|
||||
account = account
|
||||
))))
|
||||
}
|
||||
|
||||
@PutMapping("/{id}", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@ApiOperation(value = "updateCategory", nickname = "updateCategory", tags = ["Categories"])
|
||||
fun updateCategory(@PathVariable id: Long, @RequestBody request: UpdateCategoryRequest): ResponseEntity<CategoryResponse> {
|
||||
var category = categoryRepository.findById(id).orElse(null) ?: return ResponseEntity.notFound().build()
|
||||
accountRepository.findByUsersContainsAndCategoriesContains(getCurrentUser()!!, category).orElse(null)
|
||||
?: return ResponseEntity.notFound().build()
|
||||
request.title?.let { category = category.copy(title = it) }
|
||||
request.description?.let { category = category.copy(description = it) }
|
||||
request.amount?.let { category = category.copy(amount = it) }
|
||||
return ResponseEntity.ok(CategoryResponse(categoryRepository.save(category)))
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}", produces = [MediaType.TEXT_PLAIN_VALUE])
|
||||
@ApiOperation(value = "deleteCategory", nickname = "deleteCategory", tags = ["Categories"])
|
||||
fun deleteCategory(@PathVariable id: Long): ResponseEntity<Unit> {
|
||||
val category = categoryRepository.findById(id).orElse(null) ?: return ResponseEntity.notFound().build()
|
||||
val account = accountRepository.findByUsersContainsAndCategoriesContains(getCurrentUser()!!, category).orElse(null)
|
||||
?: return ResponseEntity.notFound().build()
|
||||
categoryRepository.delete(category)
|
||||
transactionRepository.findAllByAccountAndCategory(account, category).forEach { transaction ->
|
||||
transactionRepository.save(transaction.copy(category = null))
|
||||
}
|
||||
return ResponseEntity.ok().build()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package com.wbrawner.budgetserver.category
|
||||
|
||||
import com.wbrawner.budgetserver.account.Account
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.data.repository.PagingAndSortingRepository
|
||||
import java.util.*
|
||||
|
||||
interface CategoryRepository: PagingAndSortingRepository<Category, Long> {
|
||||
fun findAllByAccount(account: Account, pageable: Pageable): List<Category>
|
||||
fun findByAccountAndId(account: Account, id: Long): Optional<Category>
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package com.wbrawner.budgetserver.config
|
||||
|
||||
import com.wbrawner.budgetserver.user.UserRepository
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.security.core.userdetails.UserDetails
|
||||
import org.springframework.security.core.userdetails.UserDetailsService
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
@Component
|
||||
class JdbcUserDetailsService @Autowired
|
||||
constructor(private val userRepository: UserRepository) : UserDetailsService {
|
||||
|
||||
@Throws(UsernameNotFoundException::class)
|
||||
override fun loadUserByUsername(username: String): UserDetails {
|
||||
return userRepository.findByName(username).orElse(null)
|
||||
?: throw UsernameNotFoundException("Unable to find user with username $username")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package com.wbrawner.budgetserver.config
|
||||
|
||||
import com.wbrawner.budgetserver.passwordresetrequest.PasswordResetRequestRepository
|
||||
import com.wbrawner.budgetserver.user.UserRepository
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.core.env.Environment
|
||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity
|
||||
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.builders.WebSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
|
||||
import org.springframework.security.crypto.password.PasswordEncoder
|
||||
import org.springframework.security.provisioning.JdbcUserDetailsManager
|
||||
import javax.sql.DataSource
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
class SecurityConfig @Autowired
|
||||
constructor(
|
||||
private val env: Environment,
|
||||
private val datasource: DataSource,
|
||||
private val userRepository: UserRepository,
|
||||
private val passwordResetRequestRepository: PasswordResetRequestRepository,
|
||||
private val userDetailsService: JdbcUserDetailsService) : WebSecurityConfigurerAdapter() {
|
||||
|
||||
val userDetailsManager: JdbcUserDetailsManager
|
||||
@Bean
|
||||
get() {
|
||||
val userDetailsManager = JdbcUserDetailsManager()
|
||||
userDetailsManager.setDataSource(datasource)
|
||||
return userDetailsManager
|
||||
}
|
||||
|
||||
val authenticationProvider: DaoAuthenticationProvider
|
||||
@Bean
|
||||
get() = DaoAuthenticationProvider().apply {
|
||||
this.setPasswordEncoder(passwordEncoder)
|
||||
this.setUserDetailsService(userDetailsService)
|
||||
}
|
||||
|
||||
val passwordEncoder: PasswordEncoder
|
||||
@Bean
|
||||
get() = BCryptPasswordEncoder()
|
||||
|
||||
public override fun configure(auth: AuthenticationManagerBuilder?) {
|
||||
auth!!.authenticationProvider(authenticationProvider)
|
||||
}
|
||||
|
||||
override fun configure(web: WebSecurity?) {
|
||||
web?.ignoring()?.antMatchers("/users/new", "/v2/api-docs")
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
public override fun configure(http: HttpSecurity) {
|
||||
http.authorizeRequests()
|
||||
.antMatchers("/users/new", "/login")
|
||||
.permitAll()
|
||||
.anyRequest()
|
||||
.authenticated()
|
||||
.and()
|
||||
.httpBasic()
|
||||
.and()
|
||||
.cors()
|
||||
.and()
|
||||
.csrf()
|
||||
.disable()
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||
class MethodSecurity : GlobalMethodSecurityConfiguration()
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.wbrawner.budgetserver.config
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport
|
||||
import springfox.documentation.builders.RequestHandlerSelectors
|
||||
import springfox.documentation.spi.DocumentationType
|
||||
import springfox.documentation.spring.web.plugins.Docket
|
||||
import springfox.documentation.swagger2.annotations.EnableSwagger2
|
||||
|
||||
@Configuration
|
||||
@EnableSwagger2
|
||||
class SwaggerConfig : WebMvcConfigurationSupport() {
|
||||
@Bean
|
||||
fun budgetApi(): Docket = Docket(DocumentationType.SWAGGER_2)
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.basePackage("com.wbrawner.budgetserver"))
|
||||
.build()
|
||||
|
||||
override fun addResourceHandlers(registry: ResourceHandlerRegistry) {
|
||||
registry.addResourceHandler("swagger-ui.html")
|
||||
.addResourceLocations("classpath:/META-INF/resources/")
|
||||
|
||||
registry.addResourceHandler("/webjars/**")
|
||||
.addResourceLocations("classpath:/META-INF/resources/webjars/")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package com.wbrawner.budgetserver.passwordresetrequest
|
||||
|
||||
import com.wbrawner.budgetserver.user.User
|
||||
import java.util.*
|
||||
import javax.persistence.Entity
|
||||
import javax.persistence.GeneratedValue
|
||||
import javax.persistence.GenerationType
|
||||
import javax.persistence.Id
|
||||
|
||||
@Entity
|
||||
data class PasswordResetRequest(
|
||||
@Id @GeneratedValue(strategy = GenerationType.AUTO) val id: Long? = null,
|
||||
val user: User? = null,
|
||||
val date: Calendar = GregorianCalendar(),
|
||||
val token: String = UUID.randomUUID().toString().replace("-", "")
|
||||
)
|
|
@ -0,0 +1,5 @@
|
|||
package com.wbrawner.budgetserver.passwordresetrequest
|
||||
|
||||
import org.springframework.data.repository.PagingAndSortingRepository
|
||||
|
||||
interface PasswordResetRequestRepository: PagingAndSortingRepository<PasswordResetRequest, Long>
|
|
@ -0,0 +1,67 @@
|
|||
package com.wbrawner.budgetserver.transaction
|
||||
|
||||
import com.wbrawner.budgetserver.account.Account
|
||||
import com.wbrawner.budgetserver.category.Category
|
||||
import com.wbrawner.budgetserver.user.User
|
||||
import java.time.Instant
|
||||
import javax.persistence.*
|
||||
|
||||
@Entity
|
||||
data class Transaction(
|
||||
@Id @GeneratedValue(strategy = GenerationType.AUTO) val id: Long? = null,
|
||||
val title: String = "",
|
||||
val description: String? = null,
|
||||
val date: Instant = Instant.now(),
|
||||
val amount: Long = 0,
|
||||
@ManyToOne val category: Category? = null,
|
||||
val isExpense: Boolean = true,
|
||||
@ManyToOne val createdBy: User,
|
||||
@ManyToOne val account: Account
|
||||
) : Comparable<Transaction> {
|
||||
override fun compareTo(other: Transaction): Int = this.date.compareTo(other.date)
|
||||
}
|
||||
|
||||
data class TransactionResponse(
|
||||
val id: Long,
|
||||
val title: String,
|
||||
val description: String?,
|
||||
val date: String,
|
||||
val amount: Long,
|
||||
val isExpense: Boolean,
|
||||
val accountId: Long,
|
||||
val categoryId: Long?,
|
||||
val createdBy: Long
|
||||
) {
|
||||
constructor(transaction: Transaction) : this(
|
||||
transaction.id!!,
|
||||
transaction.title,
|
||||
transaction.description,
|
||||
transaction.date.toString(),
|
||||
transaction.amount,
|
||||
transaction.isExpense,
|
||||
transaction.account.id!!,
|
||||
if (transaction.category != null) transaction.category.id!! else null,
|
||||
transaction.createdBy.id!!
|
||||
)
|
||||
}
|
||||
|
||||
data class NewTransactionRequest(
|
||||
val title: String,
|
||||
val description: String?,
|
||||
val date: String,
|
||||
val amount: Long,
|
||||
val categoryId: Long?,
|
||||
val isExpense: Boolean,
|
||||
val accountId: Long
|
||||
)
|
||||
|
||||
data class UpdateTransactionRequest(
|
||||
val title: String?,
|
||||
val description: String?,
|
||||
val date: String?,
|
||||
val amount: Long?,
|
||||
val categoryId: Long?,
|
||||
val isExpense: Boolean?,
|
||||
val accountId: Long?,
|
||||
val createdBy: Long?
|
||||
)
|
|
@ -0,0 +1,108 @@
|
|||
package com.wbrawner.budgetserver.transaction
|
||||
|
||||
import com.wbrawner.budgetserver.ErrorResponse
|
||||
import com.wbrawner.budgetserver.account.AccountRepository
|
||||
import com.wbrawner.budgetserver.category.Category
|
||||
import com.wbrawner.budgetserver.category.CategoryRepository
|
||||
import com.wbrawner.budgetserver.getCurrentUser
|
||||
import io.swagger.annotations.Api
|
||||
import io.swagger.annotations.ApiOperation
|
||||
import org.hibernate.Hibernate
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.data.domain.PageRequest
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import java.lang.Integer.min
|
||||
import java.lang.RuntimeException
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import javax.transaction.Transactional
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/transactions")
|
||||
@Api(value = "Transactions", tags = ["Transactions"])
|
||||
class TransactionController @Autowired constructor(
|
||||
private val accountRepository: AccountRepository,
|
||||
private val categoryRepository: CategoryRepository,
|
||||
private val transactionRepository: TransactionRepository
|
||||
) {
|
||||
@Transactional
|
||||
@GetMapping("", produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@ApiOperation(value = "getTransactions", nickname = "getTransactions", tags = ["Transactions"])
|
||||
fun getTransactions(accountId: Long, count: Int?, page: Int?): ResponseEntity<List<TransactionResponse>> {
|
||||
val account = accountRepository.findByUsersContainsAndId(getCurrentUser()!!, accountId).orElse(null)
|
||||
?: return ResponseEntity.notFound().build()
|
||||
Hibernate.initialize(account.users)
|
||||
val pageRequest = PageRequest.of(min(0, page?.minus(1)?: 0), count?: 1000)
|
||||
return ResponseEntity.ok(transactionRepository.findAllByAccount(account, pageRequest).map { TransactionResponse(it) })
|
||||
}
|
||||
|
||||
@GetMapping("/{id}", produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@ApiOperation(value = "getTransaction", nickname = "getTransaction", tags = ["Transactions"])
|
||||
fun getTransaction(@PathVariable id: Long): ResponseEntity<TransactionResponse> {
|
||||
val transaction = transactionRepository.findById(id).orElse(null) ?: return ResponseEntity.notFound().build()
|
||||
accountRepository.findByUsersContainsAndTransactionsContains(getCurrentUser()!!, transaction).orElse(null)
|
||||
?: return ResponseEntity.notFound().build()
|
||||
return ResponseEntity.ok(TransactionResponse(transaction))
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@PostMapping("/new", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@ApiOperation(value = "newTransaction", nickname = "newTransaction", tags = ["Transactions"])
|
||||
fun newTransaction(@RequestBody request: NewTransactionRequest): ResponseEntity<Any> {
|
||||
val account = accountRepository.findByUsersContainsAndId(getCurrentUser()!!, request.accountId).orElse(null)
|
||||
?: return ResponseEntity.badRequest().body(ErrorResponse("Invalid account ID"))
|
||||
Hibernate.initialize(account.users)
|
||||
val category: Category? = request.categoryId?.let {
|
||||
categoryRepository.findByAccountAndId(account, request.categoryId).orElse(null)
|
||||
}
|
||||
return ResponseEntity.ok(TransactionResponse(transactionRepository.save(Transaction(
|
||||
title = request.title,
|
||||
description = request.description,
|
||||
date = Instant.parse(request.date),
|
||||
amount = request.amount,
|
||||
category = category,
|
||||
isExpense = request.isExpense,
|
||||
account = account,
|
||||
createdBy = getCurrentUser()!!
|
||||
))))
|
||||
}
|
||||
|
||||
@PutMapping("/{id}", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@ApiOperation(value = "updateTransaction", nickname = "updateTransaction", tags = ["Transactions"])
|
||||
fun updateTransaction(@PathVariable id: Long, @RequestBody request: UpdateTransactionRequest): ResponseEntity<TransactionResponse> {
|
||||
var transaction = transactionRepository.findById(id).orElse(null)?: return ResponseEntity.notFound().build()
|
||||
var account = accountRepository.findByUsersContainsAndTransactionsContains(getCurrentUser()!!, transaction)
|
||||
.orElse(null)?: return ResponseEntity.notFound().build()
|
||||
request.title?.let { transaction = transaction.copy(title = it) }
|
||||
request.description?.let { transaction = transaction.copy(description = it) }
|
||||
request.date?.let { transaction = transaction.copy(date = Instant.parse(it)) }
|
||||
request.amount?.let { transaction = transaction.copy(amount = it) }
|
||||
request.isExpense?.let { transaction = transaction.copy(isExpense = it) }
|
||||
request.accountId?.let { accountId ->
|
||||
accountRepository.findByUsersContainsAndId(getCurrentUser()!!, accountId).orElse(null)?.let {
|
||||
account = it
|
||||
transaction = transaction.copy(account = it, category = null)
|
||||
}
|
||||
}
|
||||
request.categoryId?.let {
|
||||
categoryRepository.findByAccountAndId(account, it).orElse(null)?.let { category ->
|
||||
transaction = transaction.copy(category = category)
|
||||
}
|
||||
}
|
||||
return ResponseEntity.ok(TransactionResponse(transactionRepository.save(transaction)))
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}", produces = [MediaType.TEXT_PLAIN_VALUE])
|
||||
@ApiOperation(value = "deleteTransaction", nickname = "deleteTransaction", tags = ["Transactions"])
|
||||
fun deleteTransaction(@PathVariable id: Long): ResponseEntity<Unit> {
|
||||
val transaction = transactionRepository.findById(id).orElse(null) ?: return ResponseEntity.notFound().build()
|
||||
// Check that the transaction belongs to an account that the user has access to before deleting it
|
||||
accountRepository.findByUsersContainsAndTransactionsContains(getCurrentUser()!!, transaction).orElse(null)
|
||||
?: return ResponseEntity.notFound().build()
|
||||
transactionRepository.delete(transaction)
|
||||
return ResponseEntity.ok().build()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package com.wbrawner.budgetserver.transaction
|
||||
|
||||
import com.wbrawner.budgetserver.account.Account
|
||||
import com.wbrawner.budgetserver.category.Category
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.data.repository.PagingAndSortingRepository
|
||||
import java.util.*
|
||||
|
||||
interface TransactionRepository: PagingAndSortingRepository<Transaction, Long> {
|
||||
fun findAllByAccount(account: Account, pageable: Pageable): List<Transaction>
|
||||
fun findByAccountAndId(account: Account, id: Long): Optional<Transaction>
|
||||
fun findAllByAccountAndCategory(account: Account, category: Category): List<Transaction>
|
||||
}
|
45
src/main/kotlin/com/wbrawner/budgetserver/user/User.kt
Normal file
45
src/main/kotlin/com/wbrawner/budgetserver/user/User.kt
Normal file
|
@ -0,0 +1,45 @@
|
|||
package com.wbrawner.budgetserver.user
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||
import org.springframework.security.core.userdetails.UserDetails
|
||||
import javax.persistence.Entity
|
||||
import javax.persistence.GeneratedValue
|
||||
import javax.persistence.GenerationType
|
||||
import javax.persistence.Id
|
||||
|
||||
@Entity
|
||||
data class User(
|
||||
@Id @GeneratedValue(strategy = GenerationType.AUTO) val id: Long? = null,
|
||||
val name: String = "",
|
||||
val passphrase: String = "",
|
||||
val email: String = "",
|
||||
val enabled: Boolean = true,
|
||||
val credentialsExpired: Boolean = false,
|
||||
val isExpired: Boolean = false,
|
||||
val isLocked: Boolean = false,
|
||||
@Transient val grantedAuthorities: MutableCollection<out GrantedAuthority>
|
||||
= mutableListOf<GrantedAuthority>(SimpleGrantedAuthority("USER"))
|
||||
) : UserDetails {
|
||||
override fun getUsername(): String = name
|
||||
|
||||
override fun getAuthorities(): MutableCollection<out GrantedAuthority> = grantedAuthorities
|
||||
|
||||
override fun isEnabled(): Boolean = enabled
|
||||
|
||||
override fun isCredentialsNonExpired(): Boolean = !credentialsExpired
|
||||
|
||||
override fun getPassword(): String = passphrase
|
||||
|
||||
override fun isAccountNonExpired(): Boolean = !isExpired
|
||||
|
||||
override fun isAccountNonLocked(): Boolean = !isLocked
|
||||
}
|
||||
|
||||
data class UserResponse(val id: Long, val username: String, val email: String) {
|
||||
constructor(user: User) : this(user.id!!, user.name, user.email)
|
||||
}
|
||||
|
||||
data class NewUserRequest(val username: String, val password: String, val email: String)
|
||||
|
||||
data class UpdateUserRequest(val username: String?, val password: String?, val email: String?)
|
|
@ -0,0 +1,86 @@
|
|||
package com.wbrawner.budgetserver.user
|
||||
|
||||
import com.wbrawner.budgetserver.ErrorResponse
|
||||
import com.wbrawner.budgetserver.account.AccountRepository
|
||||
import com.wbrawner.budgetserver.getCurrentUser
|
||||
import io.swagger.annotations.Api
|
||||
import io.swagger.annotations.ApiOperation
|
||||
import org.hibernate.Hibernate
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.security.crypto.password.PasswordEncoder
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import javax.transaction.Transactional
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/users")
|
||||
@Api(value = "Users", tags = ["Users"])
|
||||
class UserController @Autowired constructor(private val accountRepository: AccountRepository, private val userRepository: UserRepository, private val passwordEncoder: PasswordEncoder) {
|
||||
@Transactional
|
||||
@GetMapping("", produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@ApiOperation(value = "getUsers", nickname = "getUsers", tags = ["Users"])
|
||||
fun getUsers(accountId: Long): ResponseEntity<List<UserResponse>> {
|
||||
val account = accountRepository.findByUsersContainsAndId(getCurrentUser()!!, accountId).orElse(null)
|
||||
?: return ResponseEntity.notFound().build()
|
||||
Hibernate.initialize(account.users)
|
||||
return ResponseEntity.ok(account.users.map { UserResponse(it) })
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
@ApiOperation(value = "getUser", nickname = "getUser", tags = ["Users"])
|
||||
fun getUser(@PathVariable id: Long): ResponseEntity<UserResponse> = userRepository.findById(id).orElse(null)
|
||||
?.let {
|
||||
ResponseEntity.ok(UserResponse(it))
|
||||
}
|
||||
?: ResponseEntity.notFound().build()
|
||||
|
||||
@PostMapping("/new", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@ApiOperation(value = "newUser", nickname = "newUser", tags = ["Users"])
|
||||
fun newUser(@RequestBody request: NewUserRequest): ResponseEntity<Any> {
|
||||
if (userRepository.findByName(request.username).isPresent)
|
||||
return ResponseEntity.badRequest()
|
||||
.body(ErrorResponse("Username taken"))
|
||||
if (userRepository.findByEmail(request.email).isPresent)
|
||||
return ResponseEntity.badRequest()
|
||||
.body(ErrorResponse("Email taken"))
|
||||
if (request.password.isBlank())
|
||||
return ResponseEntity.badRequest()
|
||||
.body(ErrorResponse("Invalid password"))
|
||||
return ResponseEntity.ok(UserResponse(userRepository.save(User(
|
||||
name = request.username,
|
||||
passphrase = passwordEncoder.encode(request.password),
|
||||
email = request.email
|
||||
))))
|
||||
}
|
||||
|
||||
@PutMapping("/{id}", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@ApiOperation(value = "updateUser", nickname = "updateUser", tags = ["Users"])
|
||||
fun updateUser(@PathVariable id: Long, @RequestBody request: UpdateUserRequest): ResponseEntity<Any> {
|
||||
if (getCurrentUser()!!.id != id) return ResponseEntity.status(403)
|
||||
.body(ErrorResponse("Attempting to modify another user's account"))
|
||||
var user = userRepository.findById(getCurrentUser()!!.id!!).orElse(null)?: return ResponseEntity.notFound().build()
|
||||
if (request.username != null) {
|
||||
if (userRepository.findByName(request.username).isPresent) throw RuntimeException("Username taken")
|
||||
user = user.copy(name = request.username)
|
||||
}
|
||||
if (request.email != null) {
|
||||
if (userRepository.findByEmail(request.email).isPresent) throw RuntimeException("Email taken")
|
||||
user = user.copy(email = request.email)
|
||||
}
|
||||
if (request.password != null) {
|
||||
if (request.password.isBlank()) throw RuntimeException("Invalid password")
|
||||
user = user.copy(passphrase = passwordEncoder.encode(request.password))
|
||||
}
|
||||
return ResponseEntity.ok(UserResponse(userRepository.save(user)))
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}", produces = [MediaType.TEXT_PLAIN_VALUE])
|
||||
@ApiOperation(value = "deleteUser", nickname = "deleteUser", tags = ["Users"])
|
||||
fun deleteUser(@PathVariable id: Long): ResponseEntity<Unit> {
|
||||
if(getCurrentUser()!!.id != id) return ResponseEntity.status(403).build()
|
||||
userRepository.deleteById(id)
|
||||
return ResponseEntity.ok().build()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.wbrawner.budgetserver.user
|
||||
|
||||
import org.springframework.data.repository.PagingAndSortingRepository
|
||||
import java.util.*
|
||||
|
||||
interface UserRepository: PagingAndSortingRepository<User, Long> {
|
||||
fun findByName(username: String): Optional<User>
|
||||
fun findByEmail(email: String): Optional<User>
|
||||
}
|
6
src/main/resources/application.properties
Normal file
6
src/main/resources/application.properties
Normal file
|
@ -0,0 +1,6 @@
|
|||
spring.jpa.hibernate.ddl-auto=create-drop
|
||||
spring.datasource.url=jdbc:mysql://localhost:3306/budget
|
||||
spring.datasource.username=budget
|
||||
spring.datasource.password=budget
|
||||
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
|
||||
spring.profiles.active=dev
|
|
@ -0,0 +1,16 @@
|
|||
package com.wbrawner.budgetserver
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.test.context.junit4.SpringRunner
|
||||
|
||||
@RunWith(SpringRunner::class)
|
||||
@SpringBootTest
|
||||
class BudgetServerApplicationTests {
|
||||
|
||||
@Test
|
||||
fun contextLoads() {
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue