First runnable Version

master
Markus Kreth 4 years ago
parent fc085f8ba3
commit 903453bf41
  1. 32
      .classpath
  2. 4
      .gitignore
  3. 6
      .idea/workspace.xml
  4. 118
      .mvn/wrapper/MavenWrapperDownloader.java
  5. 2
      .mvn/wrapper/maven-wrapper.properties
  6. 6
      .npmrc
  7. 4
      .prettierrc.js
  8. 28
      .project
  9. 4
      .settings/org.eclipse.core.resources.prefs
  10. 9
      .settings/org.eclipse.jdt.core.prefs
  11. 4
      .settings/org.eclipse.m2e.core.prefs
  12. 2
      .settings/org.springframework.ide.eclipse.prefs
  13. 8
      .vscode/extensions.json
  14. 3
      .vscode/settings.json
  15. 34
      Dockerfile
  16. 310
      mvnw
  17. 182
      mvnw.cmd
  18. 25042
      package-lock.json
  19. 404
      package.json
  20. 275
      pom.xml
  21. 31
      src/main/java/de/kreth/invoice/Application.java
  22. 32
      src/main/java/de/kreth/invoice/components/InvoiceGrid.java
  23. 119
      src/main/java/de/kreth/invoice/components/InvoiceItemDialog.java
  24. 177
      src/main/java/de/kreth/invoice/components/InvoiceItemGrid.java
  25. 53
      src/main/java/de/kreth/invoice/components/InvoiceItemOverviewComponent.java
  26. 18
      src/main/java/de/kreth/invoice/components/InvoiceOverviewComponent.java
  27. 43
      src/main/java/de/kreth/invoice/config/ConfigureUIServiceInitListener.java
  28. 16
      src/main/java/de/kreth/invoice/config/KeycloakConfigResolverLocal.java
  29. 36
      src/main/java/de/kreth/invoice/config/SecurityUtils.java
  30. 81
      src/main/java/de/kreth/invoice/config/UiSecurityConfig.java
  31. 112
      src/main/java/de/kreth/invoice/data/Adress.java
  32. 104
      src/main/java/de/kreth/invoice/data/Article.java
  33. 83
      src/main/java/de/kreth/invoice/data/BankingConnection.java
  34. 90
      src/main/java/de/kreth/invoice/data/BaseEntity.java
  35. 77
      src/main/java/de/kreth/invoice/data/Invoice.java
  36. 147
      src/main/java/de/kreth/invoice/data/InvoiceItem.java
  37. 58
      src/main/java/de/kreth/invoice/data/User.java
  38. 24
      src/main/java/de/kreth/invoice/data/UserAdress.java
  39. 32
      src/main/java/de/kreth/invoice/data/UserBank.java
  40. 5
      src/main/java/de/kreth/invoice/data/package-info.java
  41. 12
      src/main/java/de/kreth/invoice/persistence/ArticleRepository.java
  42. 13
      src/main/java/de/kreth/invoice/persistence/InvoiceItemRepository.java
  43. 11
      src/main/java/de/kreth/invoice/persistence/UserAdressRepository.java
  44. 11
      src/main/java/de/kreth/invoice/persistence/UserBankRepository.java
  45. 10
      src/main/java/de/kreth/invoice/persistence/UserRepository.java
  46. 55
      src/main/java/de/kreth/invoice/security/UserManager.java
  47. 144
      src/main/java/de/kreth/invoice/views/View.java
  48. BIN
      src/main/resources/META-INF/resources/icons/icon.png
  49. 9
      src/main/resources/application.properties
  50. 6
      src/main/resources/banner.txt
  51. 34
      tsconfig.json
  52. 5
      types.d.ts
  53. 37
      webpack.config.js
  54. 379
      webpack.generated.js

@ -0,0 +1,32 @@
<?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"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<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="output" path="target/classes"/>
</classpath>

4
.gitignore vendored

@ -8,3 +8,7 @@ dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar
frontend/
node_modules/

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PackageJsonUpdateNotifier">
<dismissed value="$PROJECT_DIR$/package.json" />
</component>
</project>

@ -0,0 +1,118 @@
/*
* Copyright 2007-present the original author or authors.
*
* Licensed 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
*
* http://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.*;
import java.net.*;
import java.nio.channels.*;
import java.util.Properties;
public class MavenWrapperDownloader {
private static final String WRAPPER_VERSION = "0.5.6";
/**
* 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/"
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".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 directory '" + 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 {
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
String username = System.getenv("MVNW_USERNAME");
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
}
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();
}
}

@ -0,0 +1,2 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar

@ -0,0 +1,6 @@
#
# NOTICE: this is an auto-generated file
#
# This file sets the default parameters for manual `pnpm install`.
#
shamefully-hoist=true

@ -0,0 +1,4 @@
module.exports = {
singleQuote: true,
printWidth: 120,
};

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>trainerinvoice</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.springframework.ide.eclipse.boot.validation.springbootbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

@ -0,0 +1,4 @@
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding//src/main/resources=UTF-8
encoding/<project>=UTF-8

@ -0,0 +1,9 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.methodParameters=generate
org.eclipse.jdt.core.compiler.codegen.targetPlatform=11
org.eclipse.jdt.core.compiler.compliance=11
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore
org.eclipse.jdt.core.compiler.release=disabled
org.eclipse.jdt.core.compiler.source=11

@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

@ -0,0 +1,2 @@
boot.validation.initialized=true
eclipse.preferences.version=1

@ -0,0 +1,8 @@
{
"recommendations": [
"runem.lit-plugin",
"vscjava.vscode-java-pack",
"pivotal.vscode-spring-boot"
],
"unwantedRecommendations": []
}

@ -0,0 +1,3 @@
{
"lit-plugin.strict": true
}

@ -0,0 +1,34 @@
# Stage that builds the application, a prerequisite for the running stage
FROM maven:3-openjdk-17-slim as build
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
RUN apt-get update -qq && apt-get install -qq --no-install-recommends nodejs
# Stop running as root at this point
RUN useradd -m myuser
WORKDIR /usr/src/app/
RUN chown myuser:myuser /usr/src/app/
USER myuser
# Copy pom.xml and prefetch dependencies so a repeated build can continue from the next step with existing dependencies
COPY --chown=myuser pom.xml ./
RUN mvn dependency:go-offline -Pproduction
# Copy all needed project files to a folder
COPY --chown=myuser:myuser src src
COPY --chown=myuser:myuser frontend frontend
COPY --chown=myuser:myuser package.json ./
# Using * after the files that are autogenerated so that so build won't fail if they are not yet created
COPY --chown=myuser:myuser package-lock.json* pnpm-lock.yaml* webpack.config.js* ./
# Build the production package, assuming that we validated the version before so no need for running tests again
RUN mvn clean package -DskipTests -Pproduction
# Running stage: the part that is used for running the application
FROM openjdk:17-jdk-slim
COPY --from=build /usr/src/app/target/*.jar /usr/app/app.jar
RUN useradd -m myuser
USER myuser
EXPOSE 8080
CMD java -jar /usr/app/app.jar

310
mvnw vendored

@ -0,0 +1,310 @@
#!/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
#
# http://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.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven 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)`"
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
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
fi
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 $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
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
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
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 "$@"

182
mvnw.cmd vendored

@ -0,0 +1,182 @@
@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 http://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 Maven 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 keystroke 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 by 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.5.6/maven-wrapper-0.5.6.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% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%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%

25042
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,404 @@
{
"name": "no-name",
"license": "UNLICENSED",
"dependencies": {
"@polymer/iron-icon": "3.0.1",
"@polymer/iron-iconset-svg": "3.0.1",
"@polymer/iron-list": "3.1.0",
"@polymer/iron-meta": "3.0.1",
"@polymer/iron-resizable-behavior": "3.0.1",
"@polymer/polymer": "3.4.1",
"@vaadin/accordion": "23.0.7",
"@vaadin/app-layout": "23.0.7",
"@vaadin/avatar": "23.0.7",
"@vaadin/avatar-group": "23.0.7",
"@vaadin/board": "23.0.7",
"@vaadin/bundles": "23.0.7",
"@vaadin/button": "23.0.7",
"@vaadin/charts": "23.0.7",
"@vaadin/checkbox": "23.0.7",
"@vaadin/checkbox-group": "23.0.7",
"@vaadin/combo-box": "23.0.7",
"@vaadin/common-frontend": "0.0.17",
"@vaadin/component-base": "23.0.7",
"@vaadin/confirm-dialog": "23.0.7",
"@vaadin/context-menu": "23.0.7",
"@vaadin/cookie-consent": "23.0.7",
"@vaadin/crud": "23.0.7",
"@vaadin/custom-field": "23.0.7",
"@vaadin/date-picker": "23.0.7",
"@vaadin/date-time-picker": "23.0.7",
"@vaadin/details": "23.0.7",
"@vaadin/dialog": "23.0.7",
"@vaadin/email-field": "23.0.7",
"@vaadin/field-base": "23.0.7",
"@vaadin/field-highlighter": "23.0.7",
"@vaadin/flow-frontend": "./target/flow-frontend",
"@vaadin/form-layout": "23.0.7",
"@vaadin/grid": "23.0.7",
"@vaadin/grid-pro": "23.0.7",
"@vaadin/horizontal-layout": "23.0.7",
"@vaadin/icon": "23.0.7",
"@vaadin/icons": "23.0.7",
"@vaadin/input-container": "23.0.7",
"@vaadin/integer-field": "23.0.7",
"@vaadin/item": "23.0.7",
"@vaadin/list-box": "23.0.7",
"@vaadin/login": "23.0.7",
"@vaadin/map": "23.0.7",
"@vaadin/menu-bar": "23.0.7",
"@vaadin/message-input": "23.0.7",
"@vaadin/message-list": "23.0.7",
"@vaadin/notification": "23.0.7",
"@vaadin/number-field": "23.0.7",
"@vaadin/password-field": "23.0.7",
"@vaadin/polymer-legacy-adapter": "23.0.7",
"@vaadin/progress-bar": "23.0.7",
"@vaadin/radio-group": "23.0.7",
"@vaadin/rich-text-editor": "23.0.7",
"@vaadin/router": "1.7.4",
"@vaadin/scroller": "23.0.7",
"@vaadin/select": "23.0.7",
"@vaadin/split-layout": "23.0.7",
"@vaadin/tabs": "23.0.7",
"@vaadin/text-area": "23.0.7",
"@vaadin/text-field": "23.0.7",
"@vaadin/time-picker": "23.0.7",
"@vaadin/upload": "23.0.7",
"@vaadin/vaadin-accordion": "23.0.7",
"@vaadin/vaadin-app-layout": "23.0.7",
"@vaadin/vaadin-avatar": "23.0.7",
"@vaadin/vaadin-board": "23.0.7",
"@vaadin/vaadin-button": "23.0.7",
"@vaadin/vaadin-charts": "23.0.7",
"@vaadin/vaadin-checkbox": "23.0.7",
"@vaadin/vaadin-combo-box": "23.0.7",
"@vaadin/vaadin-confirm-dialog": "23.0.7",
"@vaadin/vaadin-context-menu": "23.0.7",
"@vaadin/vaadin-cookie-consent": "23.0.7",
"@vaadin/vaadin-crud": "23.0.7",
"@vaadin/vaadin-custom-field": "23.0.7",
"@vaadin/vaadin-date-picker": "23.0.7",
"@vaadin/vaadin-date-time-picker": "23.0.7",
"@vaadin/vaadin-details": "23.0.7",
"@vaadin/vaadin-development-mode-detector": "2.0.5",
"@vaadin/vaadin-dialog": "23.0.7",
"@vaadin/vaadin-form-layout": "23.0.7",
"@vaadin/vaadin-grid": "23.0.7",
"@vaadin/vaadin-grid-pro": "23.0.7",
"@vaadin/vaadin-icon": "23.0.7",
"@vaadin/vaadin-icons": "23.0.7",
"@vaadin/vaadin-item": "23.0.7",
"@vaadin/vaadin-list-box": "23.0.7",
"@vaadin/vaadin-list-mixin": "23.0.7",
"@vaadin/vaadin-login": "23.0.7",
"@vaadin/vaadin-lumo-styles": "23.0.7",
"@vaadin/vaadin-material-styles": "23.0.7",
"@vaadin/vaadin-menu-bar": "23.0.7",
"@vaadin/vaadin-messages": "23.0.7",
"@vaadin/vaadin-notification": "23.0.7",
"@vaadin/vaadin-ordered-layout": "23.0.7",
"@vaadin/vaadin-overlay": "23.0.7",
"@vaadin/vaadin-progress-bar": "23.0.7",
"@vaadin/vaadin-radio-button": "23.0.7",
"@vaadin/vaadin-rich-text-editor": "23.0.7",
"@vaadin/vaadin-select": "23.0.7",
"@vaadin/vaadin-split-layout": "23.0.7",
"@vaadin/vaadin-tabs": "23.0.7",
"@vaadin/vaadin-template-renderer": "23.0.7",
"@vaadin/vaadin-text-field": "23.0.7",
"@vaadin/vaadin-themable-mixin": "23.0.7",
"@vaadin/vaadin-time-picker": "23.0.7",
"@vaadin/vaadin-upload": "23.0.7",
"@vaadin/vaadin-usage-statistics": "2.1.2",
"@vaadin/vaadin-virtual-list": "23.0.7",
"@vaadin/vertical-layout": "23.0.7",
"@vaadin/virtual-list": "23.0.7",
"construct-style-sheets-polyfill": "3.0.4",
"date-fns": "2.28.0",
"line-awesome": "1.3.0",
"lit": "2.1.4"
},
"devDependencies": {
"chokidar": "^3.5.0",
"compression-webpack-plugin": "4.0.1",
"css-loader": "5.2.7",
"esbuild-loader": "2.15.1",
"extra-watch-webpack-plugin": "1.0.3",
"extract-loader": "5.1.0",
"file-loader": "6.2.0",
"fork-ts-checker-webpack-plugin": "6.2.1",
"glob": "7.1.6",
"html-webpack-plugin": "4.5.1",
"lit-css-loader": "0.1.0",
"loader-utils": "2.0.0",
"typescript": "4.5.3",
"webpack": "4.46.0",
"webpack-cli": "4.9.2",
"webpack-dev-server": "4.8.1",
"webpack-merge": "4.2.2",
"workbox-core": "6.5.0",
"workbox-precaching": "6.5.0",
"workbox-webpack-plugin": "6.5.0"
},
"overrides": {
"@vaadin/accordion": "$@vaadin/accordion",
"@vaadin/app-layout": "$@vaadin/app-layout",
"@vaadin/avatar": "$@vaadin/avatar",
"@vaadin/avatar-group": "$@vaadin/avatar-group",
"@vaadin/button": "$@vaadin/button",
"@vaadin/checkbox": "$@vaadin/checkbox",
"@vaadin/checkbox-group": "$@vaadin/checkbox-group",
"@vaadin/combo-box": "$@vaadin/combo-box",
"@vaadin/component-base": "$@vaadin/component-base",
"@vaadin/context-menu": "$@vaadin/context-menu",
"@vaadin/custom-field": "$@vaadin/custom-field",
"@vaadin/date-picker": "$@vaadin/date-picker",
"@vaadin/date-time-picker": "$@vaadin/date-time-picker",
"@vaadin/details": "$@vaadin/details",
"@vaadin/dialog": "$@vaadin/dialog",
"@vaadin/email-field": "$@vaadin/email-field",
"@vaadin/field-base": "$@vaadin/field-base",
"@vaadin/field-highlighter": "$@vaadin/field-highlighter",
"@vaadin/form-layout": "$@vaadin/form-layout",
"@vaadin/grid": "$@vaadin/grid",
"@vaadin/horizontal-layout": "$@vaadin/horizontal-layout",
"@vaadin/icon": "$@vaadin/icon",
"@vaadin/icons": "$@vaadin/icons",
"@vaadin/input-container": "$@vaadin/input-container",
"@vaadin/integer-field": "$@vaadin/integer-field",
"@polymer/iron-icon": "$@polymer/iron-icon",
"@polymer/iron-iconset-svg": "$@polymer/iron-iconset-svg",
"@polymer/iron-list": "$@polymer/iron-list",
"@polymer/iron-meta": "$@polymer/iron-meta",
"@polymer/iron-resizable-behavior": "$@polymer/iron-resizable-behavior",
"@vaadin/item": "$@vaadin/item",
"@vaadin/list-box": "$@vaadin/list-box",
"@vaadin/login": "$@vaadin/login",
"@vaadin/menu-bar": "$@vaadin/menu-bar",
"@vaadin/message-input": "$@vaadin/message-input",
"@vaadin/message-list": "$@vaadin/message-list",
"@vaadin/notification": "$@vaadin/notification",
"@vaadin/number-field": "$@vaadin/number-field",
"@vaadin/password-field": "$@vaadin/password-field",
"@vaadin/polymer-legacy-adapter": "$@vaadin/polymer-legacy-adapter",
"@vaadin/progress-bar": "$@vaadin/progress-bar",
"@vaadin/radio-group": "$@vaadin/radio-group",
"@vaadin/scroller": "$@vaadin/scroller",
"@vaadin/select": "$@vaadin/select",
"@vaadin/split-layout": "$@vaadin/split-layout",
"@vaadin/tabs": "$@vaadin/tabs",
"@vaadin/text-area": "$@vaadin/text-area",
"@vaadin/text-field": "$@vaadin/text-field",
"@vaadin/time-picker": "$@vaadin/time-picker",
"@vaadin/upload": "$@vaadin/upload",
"@vaadin/vaadin-accordion": "$@vaadin/vaadin-accordion",
"@vaadin/vaadin-app-layout": "$@vaadin/vaadin-app-layout",
"@vaadin/vaadin-avatar": "$@vaadin/vaadin-avatar",
"@vaadin/vaadin-button": "$@vaadin/vaadin-button",
"@vaadin/vaadin-checkbox": "$@vaadin/vaadin-checkbox",
"@vaadin/vaadin-combo-box": "$@vaadin/vaadin-combo-box",
"@vaadin/vaadin-context-menu": "$@vaadin/vaadin-context-menu",
"@vaadin/vaadin-custom-field": "$@vaadin/vaadin-custom-field",
"@vaadin/vaadin-date-picker": "$@vaadin/vaadin-date-picker",
"@vaadin/vaadin-date-time-picker": "$@vaadin/vaadin-date-time-picker",
"@vaadin/vaadin-details": "$@vaadin/vaadin-details",
"@vaadin/vaadin-development-mode-detector": "$@vaadin/vaadin-development-mode-detector",
"@vaadin/vaadin-dialog": "$@vaadin/vaadin-dialog",
"@vaadin/vaadin-form-layout": "$@vaadin/vaadin-form-layout",
"@vaadin/vaadin-grid": "$@vaadin/vaadin-grid",
"@vaadin/vaadin-icon": "$@vaadin/vaadin-icon",
"@vaadin/vaadin-icons": "$@vaadin/vaadin-icons",
"@vaadin/vaadin-item": "$@vaadin/vaadin-item",
"@vaadin/vaadin-list-box": "$@vaadin/vaadin-list-box",
"@vaadin/vaadin-list-mixin": "$@vaadin/vaadin-list-mixin",
"@vaadin/vaadin-login": "$@vaadin/vaadin-login",
"@vaadin/vaadin-lumo-styles": "$@vaadin/vaadin-lumo-styles",
"@vaadin/vaadin-material-styles": "$@vaadin/vaadin-material-styles",
"@vaadin/vaadin-menu-bar": "$@vaadin/vaadin-menu-bar",
"@vaadin/vaadin-messages": "$@vaadin/vaadin-messages",
"@vaadin/vaadin-notification": "$@vaadin/vaadin-notification",
"@vaadin/vaadin-ordered-layout": "$@vaadin/vaadin-ordered-layout",
"@vaadin/vaadin-overlay": "$@vaadin/vaadin-overlay",
"@vaadin/vaadin-progress-bar": "$@vaadin/vaadin-progress-bar",
"@vaadin/vaadin-radio-button": "$@vaadin/vaadin-radio-button",
"@vaadin/router": "$@vaadin/router",
"@vaadin/vaadin-select": "$@vaadin/vaadin-select",
"@vaadin/vaadin-split-layout": "$@vaadin/vaadin-split-layout",
"@vaadin/vaadin-tabs": "$@vaadin/vaadin-tabs",
"@vaadin/vaadin-template-renderer": "$@vaadin/vaadin-template-renderer",
"@vaadin/vaadin-text-field": "$@vaadin/vaadin-text-field",
"@vaadin/vaadin-themable-mixin": "$@vaadin/vaadin-themable-mixin",
"@vaadin/vaadin-time-picker": "$@vaadin/vaadin-time-picker",
"@vaadin/vaadin-upload": "$@vaadin/vaadin-upload",
"@vaadin/vaadin-usage-statistics": "$@vaadin/vaadin-usage-statistics",
"@vaadin/vaadin-virtual-list": "$@vaadin/vaadin-virtual-list",
"@vaadin/vertical-layout": "$@vaadin/vertical-layout",
"@vaadin/virtual-list": "$@vaadin/virtual-list",
"@vaadin/board": "$@vaadin/board",
"@vaadin/charts": "$@vaadin/charts",
"@vaadin/confirm-dialog": "$@vaadin/confirm-dialog",
"@vaadin/cookie-consent": "$@vaadin/cookie-consent",
"@vaadin/crud": "$@vaadin/crud",
"@vaadin/grid-pro": "$@vaadin/grid-pro",
"@vaadin/rich-text-editor": "$@vaadin/rich-text-editor",
"@vaadin/vaadin-board": "$@vaadin/vaadin-board",
"@vaadin/vaadin-charts": "$@vaadin/vaadin-charts",
"@vaadin/vaadin-confirm-dialog": "$@vaadin/vaadin-confirm-dialog",
"@vaadin/vaadin-cookie-consent": "$@vaadin/vaadin-cookie-consent",
"@vaadin/vaadin-crud": "$@vaadin/vaadin-crud",
"@vaadin/vaadin-grid-pro": "$@vaadin/vaadin-grid-pro",
"@vaadin/vaadin-rich-text-editor": "$@vaadin/vaadin-rich-text-editor",
"@vaadin/bundles": "$@vaadin/bundles",
"@vaadin/map": "$@vaadin/map",
"@polymer/polymer": "$@polymer/polymer",
"@vaadin/common-frontend": "$@vaadin/common-frontend",
"construct-style-sheets-polyfill": "$construct-style-sheets-polyfill",
"date-fns": "$date-fns",
"lit": "$lit",
"chokidar": "$chokidar",
"line-awesome": "$line-awesome"
},
"vaadin": {
"dependencies": {
"@polymer/iron-icon": "3.0.1",
"@polymer/iron-iconset-svg": "3.0.1",
"@polymer/iron-list": "3.1.0",
"@polymer/iron-meta": "3.0.1",
"@polymer/iron-resizable-behavior": "3.0.1",
"@polymer/polymer": "3.4.1",
"@vaadin/accordion": "23.0.7",
"@vaadin/app-layout": "23.0.7",
"@vaadin/avatar": "23.0.7",
"@vaadin/avatar-group": "23.0.7",
"@vaadin/board": "23.0.7",
"@vaadin/bundles": "23.0.7",
"@vaadin/button": "23.0.7",
"@vaadin/charts": "23.0.7",
"@vaadin/checkbox": "23.0.7",
"@vaadin/checkbox-group": "23.0.7",
"@vaadin/combo-box": "23.0.7",
"@vaadin/common-frontend": "0.0.17",
"@vaadin/component-base": "23.0.7",
"@vaadin/confirm-dialog": "23.0.7",
"@vaadin/context-menu": "23.0.7",
"@vaadin/cookie-consent": "23.0.7",
"@vaadin/crud": "23.0.7",
"@vaadin/custom-field": "23.0.7",
"@vaadin/date-picker": "23.0.7",
"@vaadin/date-time-picker": "23.0.7",
"@vaadin/details": "23.0.7",
"@vaadin/dialog": "23.0.7",
"@vaadin/email-field": "23.0.7",
"@vaadin/field-base": "23.0.7",
"@vaadin/field-highlighter": "23.0.7",
"@vaadin/form-layout": "23.0.7",
"@vaadin/grid": "23.0.7",
"@vaadin/grid-pro": "23.0.7",
"@vaadin/horizontal-layout": "23.0.7",
"@vaadin/icon": "23.0.7",
"@vaadin/icons": "23.0.7",
"@vaadin/input-container": "23.0.7",
"@vaadin/integer-field": "23.0.7",
"@vaadin/item": "23.0.7",
"@vaadin/list-box": "23.0.7",
"@vaadin/login": "23.0.7",
"@vaadin/map": "23.0.7",
"@vaadin/menu-bar": "23.0.7",
"@vaadin/message-input": "23.0.7",
"@vaadin/message-list": "23.0.7",
"@vaadin/notification": "23.0.7",
"@vaadin/number-field": "23.0.7",
"@vaadin/password-field": "23.0.7",
"@vaadin/polymer-legacy-adapter": "23.0.7",
"@vaadin/progress-bar": "23.0.7",
"@vaadin/radio-group": "23.0.7",
"@vaadin/rich-text-editor": "23.0.7",
"@vaadin/router": "1.7.4",
"@vaadin/scroller": "23.0.7",
"@vaadin/select": "23.0.7",
"@vaadin/split-layout": "23.0.7",
"@vaadin/tabs": "23.0.7",
"@vaadin/text-area": "23.0.7",
"@vaadin/text-field": "23.0.7",
"@vaadin/time-picker": "23.0.7",
"@vaadin/upload": "23.0.7",
"@vaadin/vaadin-accordion": "23.0.7",
"@vaadin/vaadin-app-layout": "23.0.7",
"@vaadin/vaadin-avatar": "23.0.7",
"@vaadin/vaadin-board": "23.0.7",
"@vaadin/vaadin-button": "23.0.7",
"@vaadin/vaadin-charts": "23.0.7",
"@vaadin/vaadin-checkbox": "23.0.7",
"@vaadin/vaadin-combo-box": "23.0.7",
"@vaadin/vaadin-confirm-dialog": "23.0.7",
"@vaadin/vaadin-context-menu": "23.0.7",
"@vaadin/vaadin-cookie-consent": "23.0.7",
"@vaadin/vaadin-crud": "23.0.7",
"@vaadin/vaadin-custom-field": "23.0.7",
"@vaadin/vaadin-date-picker": "23.0.7",
"@vaadin/vaadin-date-time-picker": "23.0.7",
"@vaadin/vaadin-details": "23.0.7",
"@vaadin/vaadin-development-mode-detector": "2.0.5",
"@vaadin/vaadin-dialog": "23.0.7",
"@vaadin/vaadin-form-layout": "23.0.7",
"@vaadin/vaadin-grid": "23.0.7",
"@vaadin/vaadin-grid-pro": "23.0.7",
"@vaadin/vaadin-icon": "23.0.7",
"@vaadin/vaadin-icons": "23.0.7",
"@vaadin/vaadin-item": "23.0.7",
"@vaadin/vaadin-list-box": "23.0.7",
"@vaadin/vaadin-list-mixin": "23.0.7",
"@vaadin/vaadin-login": "23.0.7",
"@vaadin/vaadin-lumo-styles": "23.0.7",
"@vaadin/vaadin-material-styles": "23.0.7",
"@vaadin/vaadin-menu-bar": "23.0.7",
"@vaadin/vaadin-messages": "23.0.7",
"@vaadin/vaadin-notification": "23.0.7",
"@vaadin/vaadin-ordered-layout": "23.0.7",
"@vaadin/vaadin-overlay": "23.0.7",
"@vaadin/vaadin-progress-bar": "23.0.7",
"@vaadin/vaadin-radio-button": "23.0.7",
"@vaadin/vaadin-rich-text-editor": "23.0.7",
"@vaadin/vaadin-select": "23.0.7",
"@vaadin/vaadin-split-layout": "23.0.7",
"@vaadin/vaadin-tabs": "23.0.7",
"@vaadin/vaadin-template-renderer": "23.0.7",
"@vaadin/vaadin-text-field": "23.0.7",
"@vaadin/vaadin-themable-mixin": "23.0.7",
"@vaadin/vaadin-time-picker": "23.0.7",
"@vaadin/vaadin-upload": "23.0.7",
"@vaadin/vaadin-usage-statistics": "2.1.2",
"@vaadin/vaadin-virtual-list": "23.0.7",
"@vaadin/vertical-layout": "23.0.7",
"@vaadin/virtual-list": "23.0.7",
"construct-style-sheets-polyfill": "3.0.4",
"date-fns": "2.28.0",
"line-awesome": "1.3.0",
"lit": "2.1.4"
},
"devDependencies": {
"chokidar": "^3.5.0",
"compression-webpack-plugin": "4.0.1",
"css-loader": "5.2.7",
"esbuild-loader": "2.15.1",
"extra-watch-webpack-plugin": "1.0.3",
"extract-loader": "5.1.0",
"file-loader": "6.2.0",
"fork-ts-checker-webpack-plugin": "6.2.1",
"glob": "7.1.6",
"html-webpack-plugin": "4.5.1",
"lit-css-loader": "0.1.0",
"loader-utils": "2.0.0",
"typescript": "4.5.3",
"webpack": "4.46.0",
"webpack-cli": "4.9.2",
"webpack-dev-server": "4.8.1",
"webpack-merge": "4.2.2",
"workbox-core": "6.5.0",
"workbox-precaching": "6.5.0",
"workbox-webpack-plugin": "6.5.0"
},
"hash": "17b0ef070c22faffba8f8497c5326037e3d1e25619539613895dd4034ed50603"
}
}

@ -0,0 +1,275 @@
<?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>
<!-- Project from https://start.vaadin.com/project/21111318-49ac-485b-92f4-892e176b446f -->
<groupId>de.kreth.invoice</groupId>
<artifactId>trainerinvoice</artifactId>
<name>Übungsleiterabrechnungen</name>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<java.version>11</java.version>
<vaadin.version>23.0.8</vaadin.version>
<org.keycloak.version>11.0.1</org.keycloak.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.7</version>
</parent>
<repositories>
<!-- The order of definitions matters. Explicitly defining central here to make sure it has the highest priority. -->
<!-- Main Maven repository -->
<repository>
<id>central</id>
<url>https://repo.maven.apache.org/maven2</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>vaadin-prereleases</id>
<url>
https://maven.vaadin.com/vaadin-prereleases/
</url>
</repository>
<!-- Repository used by many Vaadin add-ons -->
<repository>
<id>Vaadin Directory</id>
<url>https://maven.vaadin.com/vaadin-addons</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<!-- The order of definitions matters. Explicitly defining central here to make sure it has the highest priority. -->
<pluginRepository>
<id>central</id>
<url>https://repo.maven.apache.org/maven2</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>vaadin-prereleases</id>
<url>
https://maven.vaadin.com/vaadin-prereleases/
</url>
</pluginRepository>
</pluginRepositories>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-bom</artifactId>
<version>${vaadin.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.keycloak.bom</groupId>
<artifactId>keycloak-adapter-bom</artifactId>
<version>${org.keycloak.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.vaadin</groupId>
<!-- Replace artifactId with vaadin-core to use only free components -->
<artifactId>vaadin</artifactId>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>exampledata</artifactId>
<version>4.1.4</version>
</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-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-security-adapter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-testbench</artifactId>
<scope>test</scope>
</dependency>
<!-- Include JUnit 4 support for TestBench and others -->
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<version>5.0.3</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<defaultGoal>spring-boot:run</defaultGoal>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!-- Clean build and startup time for Vaadin apps sometimes may exceed
the default Spring Boot's 30sec timeout. -->
<configuration>
<wait>500</wait>
<maxAttempts>240</maxAttempts>
</configuration>
</plugin>
<!--
Take care of synchronizing java dependencies and imports in
package.json and main.js files.
It also creates webpack.config.js if not exists yet.
-->
<plugin>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-maven-plugin</artifactId>
<version>${vaadin.version}</version>
<executions>
<execution>
<goals>
<goal>prepare-frontend</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<!-- Production mode is activated using -Pproduction -->
<id>production</id>
<build>
<plugins>
<plugin>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-maven-plugin</artifactId>
<version>${vaadin.version}</version>
<executions>
<execution>
<goals>
<goal>build-frontend</goal>
</goals>
<phase>compile</phase>
</execution>
</executions>
<configuration>
<productionMode>true</productionMode>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>it</id>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<id>start-spring-boot</id>
<phase>pre-integration-test</phase>
<goals>
<goal>start</goal>
</goals>
</execution>
<execution>
<id>stop-spring-boot</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Runs the integration tests (*IT) after the server is started -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
<configuration>
<trimStackTrace>false</trimStackTrace>
<enableAssertions>true</enableAssertions>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

@ -0,0 +1,31 @@
package de.kreth.invoice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import com.vaadin.flow.component.dependency.NpmPackage;
import com.vaadin.flow.component.page.AppShellConfigurator;
import com.vaadin.flow.server.PWA;
import com.vaadin.flow.theme.Theme;
/**
* The entry point of the Spring Boot application.
*
* Use the @PWA annotation make the application installable on phones, tablets
* and some desktop browsers.
*
*/
@SpringBootApplication
@Theme(value = "trainerinvoice")
@PWA(name = "trainerinvoice", shortName = "trainerinvoice", offlineResources = {})
@NpmPackage(value = "line-awesome", version = "1.3.0")
public class Application extends SpringBootServletInitializer implements AppShellConfigurator {
private static final long serialVersionUID = 8632833774084603989L;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

@ -0,0 +1,32 @@
package de.kreth.invoice.components;
import java.text.NumberFormat;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.data.renderer.LocalDateTimeRenderer;
import com.vaadin.flow.data.renderer.NumberRenderer;
import de.kreth.invoice.data.Invoice;
public class InvoiceGrid extends Grid<Invoice> {
private static final long serialVersionUID = 662980245990122807L;
public InvoiceGrid() {
addClassName("bordered");
Column<Invoice> invoiceIdCol = addColumn(Invoice::getInvoiceId);
invoiceIdCol.setHeader("Rechnungsnummer");
Column<Invoice> invoiceDateCol = addColumn(new LocalDateTimeRenderer<>(Invoice::getInvoiceDate,
DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)));
invoiceDateCol.setHeader("Rechnungsdatum");
Column<Invoice> invoiceSum = addColumn(new NumberRenderer<>(Invoice::getSum,
NumberFormat.getCurrencyInstance()));
invoiceSum.setHeader("Summe");
}
}

@ -0,0 +1,119 @@
package de.kreth.invoice.components;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.dialog.Dialog;
import com.vaadin.flow.component.dialog.GeneratedVaadinDialog.OpenedChangeEvent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.component.timepicker.TimePicker;
import com.vaadin.flow.data.provider.Query;
import de.kreth.invoice.data.Article;
import de.kreth.invoice.data.InvoiceItem;
public class InvoiceItemDialog {
private final Dialog dialog = new Dialog();
private ComboBox<Article> article;
private DatePicker startDate;
private TimePicker startTime;
private TimePicker endTime;
private TextField participants;
private boolean closedWithOk;
private DialogCloseListener listener;
public InvoiceItemDialog(InvoiceItem item, List<Article> articles, DialogCloseListener listener) {
this(articles, listener);
readFrom(item);
}
public InvoiceItemDialog(List<Article> articles, DialogCloseListener listener) {
this.listener = listener;
this.startDate = new DatePicker(LocalDate.now());
startDate.setLabel("Datum");
this.startTime = new TimePicker(LocalTime.of(17, 0));
startTime.setLabel("Startzeit");
this.endTime = new TimePicker(LocalTime.of(20, 0));
this.participants = new TextField();
this.participants.setLabel("Teilnehmer");
article = new ComboBox<Article>("Artikel", articles);
article.setItemLabelGenerator(Article::getTitle);
Button ok = new Button("Speichern", this::closeWithOk);
Button discard = new Button("Abbrechen", ev -> dialog.close());
dialog.add(article, startDate, startTime, endTime, participants, new HorizontalLayout(ok, discard));
dialog.addOpenedChangeListener(this::dialogCloseCalled);
}
private void dialogCloseCalled(OpenedChangeEvent<Dialog> ev) {
if (!ev.isOpened()) {
listener.dialogClosed(this);
}
}
public void setVisible() {
closedWithOk = false;
dialog.open();
}
private void closeWithOk(ClickEvent<Button> ev) {
closedWithOk = true;
dialog.close();
}
public boolean isClosedWithOk() {
return closedWithOk;
}
public void readFrom(InvoiceItem item) {
if (item.getArticle() != null) {
article.setValue(item.getArticle());
} else if (article.getDataProvider().size(new Query<>()) == 1) {
article.getDataProvider().fetch(new Query<>())
.findAny().ifPresent(article::setValue);
}
if (item.getStart() != null) {
startDate.setValue(item.getStart().toLocalDate());
startTime.setValue(item.getStart().toLocalTime());
}
if (item.getEnd() != null) {
endTime.setValue(item.getEnd().toLocalTime());
} else if (item.getStart() != null) {
endTime.setValue(item.getStart().toLocalTime().plusHours(1));
}
if (item.getParticipants() != null) {
this.participants.setValue(item.getParticipants());
}
}
public void writeTo(InvoiceItem item) {
if (!closedWithOk) {
throw new IllegalStateException("Dialog was not closed by OK Button, writing is not possible.");
}
item.setArticle(article.getValue());
item.setChangeDate(LocalDateTime.now());
if (item.getCreatedDate() == null) {
item.setCreatedDate(item.getChangeDate());
}
item.setStart(LocalDateTime.of(startDate.getValue(), startTime.getValue()));
item.setEnd(LocalDateTime.of(startDate.getValue(), endTime.getValue()));
item.setParticipants(participants.getValue());
}
public interface DialogCloseListener {
void dialogClosed(InvoiceItemDialog source);
}
}

@ -0,0 +1,177 @@
package de.kreth.invoice.components;
import java.math.BigDecimal;
import java.text.NumberFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import com.vaadin.flow.component.grid.FooterRow;
import com.vaadin.flow.component.grid.FooterRow.FooterCell;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.grid.GridSelectionModel;
import com.vaadin.flow.data.provider.DataChangeEvent;
import com.vaadin.flow.data.provider.DataProvider;
import com.vaadin.flow.data.provider.DataProviderListener;
import com.vaadin.flow.data.provider.ListDataProvider;
import com.vaadin.flow.data.renderer.LocalDateTimeRenderer;
import com.vaadin.flow.data.renderer.NumberRenderer;
import de.kreth.invoice.data.InvoiceItem;
import de.kreth.invoice.persistence.InvoiceItemRepository;
class InvoiceItemGrid extends Grid<InvoiceItem> {
private static final long serialVersionUID = -8653320112619816426L;
private final DateTimeFormatter ofLocalizedDateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM);
private FooterCell priceSumCell;
private FooterCell countCell;
private FooterCell dateSpan;
private final List<InvoiceItem> items = new ArrayList<>();
private InvoiceItemRepository repository;
public InvoiceItemGrid(InvoiceItemRepository invoiceItemRepository) {
this.repository = invoiceItemRepository;
addClassName("bordered");
Column<InvoiceItem> titleCol = addColumn(InvoiceItem::getTitle);
titleCol.setId("Article");
titleCol.setHeader("Artikel");
Column<InvoiceItem> dateColumn = addColumn(new LocalDateTimeRenderer<>(InvoiceItem::getStart,
DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)));
dateColumn.setId("Date");
dateColumn.setHeader("Datum");
Column<InvoiceItem> startColumn = addColumn(new LocalDateTimeRenderer<>(InvoiceItem::getStart,
DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)));
startColumn.setId("Start");
startColumn.setHeader("Beginn");
Column<InvoiceItem> endColumn = addColumn(new LocalDateTimeRenderer<>(InvoiceItem::getEnd,
DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)));
endColumn.setId("Ende");
endColumn.setHeader("Ende");
Column<InvoiceItem> participantColumn = addColumn(InvoiceItem::getParticipants);
participantColumn.setHeader("Teilnehmer");
Column<InvoiceItem> sumPriceColumn = addColumn(
new NumberRenderer<>(InvoiceItem::getSumPrice, NumberFormat.getCurrencyInstance()));
sumPriceColumn.setId("price");
sumPriceColumn.setHeader("Betrag");
// setSortOrder(GridSortOrder.asc(dateColumn).thenAsc(startColumn));
FooterRow footer = appendFooterRow();
priceSumCell = footer.getCell(sumPriceColumn);
// dateSpan = footer.join(dateColumn, startColumn, endColumn);
dateSpan = footer.getCell(dateColumn);
countCell = footer.getCell(titleCol);
// addSelectionListener(this::selectionChanged);
items.addAll(repository.findByInvoiceIsNull());
ListDataProvider<InvoiceItem> dataProvider = new ListDataProvider<InvoiceItem>(items);
setDataProvider(dataProvider);
dataProvider.addDataProviderListener(new InnerDataProviderListener());
}
public void refreshData() {
items.clear();
items.addAll(repository.findByInvoiceIsNull());
}
@Override
public GridSelectionModel<InvoiceItem> setSelectionMode(SelectionMode selectionMode) {
GridSelectionModel<InvoiceItem> setSelectionMode = super.setSelectionMode(selectionMode);
// setSelectionMode.addSelectionListener(this::selectionChanged);
return setSelectionMode;
}
// @SuppressWarnings("unchecked")
// private void selectionChanged(SelectionEvent<T> event) {
// if (event.getAllSelectedItems().isEmpty()) {
// updateFooterWith(((ListDataProvider<T>) getDataProvider()).getItems());
// } else {
// updateFooterWith(event.getAllSelectedItems());
// }
// }
protected void internalSetDataProvider(DataProvider<InvoiceItem, ?> dataProvider) {
if (!(dataProvider instanceof ListDataProvider)) {
throw new IllegalArgumentException("dataProvider must be an instance of ListDataProvider");
}
// super.internalSetDataProvider(dataProvider);
dataProvider.addDataProviderListener(new InnerDataProviderListener());
updateFooterWith(((ListDataProvider<InvoiceItem>) getDataProvider()).getItems());
}
private void updateFooterWith(Collection<InvoiceItem> selected) {
BigDecimal priceSum = BigDecimal.ZERO;
LocalDate min = null;
LocalDate max = null;
for (InvoiceItem t : selected) {
priceSum = priceSum.add(t.getSumPrice());
min = getMin(min, t.getStart().toLocalDate());
max = getMax(max, t.getEnd().toLocalDate());
}
priceSumCell.setText(NumberFormat.getCurrencyInstance().format(priceSum));
if (min != null && max != null) {
dateSpan.setText(min.format(ofLocalizedDateFormatter) + " - " + max.format(ofLocalizedDateFormatter));
} else {
dateSpan.setText("");
}
countCell.setText("Anzahl: " + selected.size());
}
private LocalDate getMax(LocalDate max, LocalDate localDate) {
if (max == null) {
max = localDate;
} else {
if (max.isBefore(localDate)) {
max = localDate;
}
}
return max;
}
private LocalDate getMin(LocalDate min, LocalDate localDate) {
if (min == null) {
min = localDate;
} else {
if (min.isAfter(localDate)) {
min = localDate;
}
}
return min;
}
private class InnerDataProviderListener implements DataProviderListener<InvoiceItem> {
private static final long serialVersionUID = -6094992880488082586L;
@Override
public void onDataChange(DataChangeEvent<InvoiceItem> event) {
if (event.getSource() == getDataProvider()) {
@SuppressWarnings("unchecked")
ListDataProvider<InvoiceItem> provider = (ListDataProvider<InvoiceItem>) getDataProvider();
updateFooterWith(provider.getItems());
}
}
}
}

@ -0,0 +1,53 @@
package de.kreth.invoice.components;
import java.util.List;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import de.kreth.invoice.data.Article;
import de.kreth.invoice.data.InvoiceItem;
import de.kreth.invoice.data.User;
import de.kreth.invoice.persistence.ArticleRepository;
import de.kreth.invoice.persistence.InvoiceItemRepository;
public class InvoiceItemOverviewComponent extends VerticalLayout {
private static final long serialVersionUID = -4486121981960039L;
private final InvoiceItemGrid grid;
private final InvoiceItemRepository invoiceItemRepository;
private final ArticleRepository articleRepository;
private final User user;
public InvoiceItemOverviewComponent(InvoiceItemRepository invoiceItemRepository,
ArticleRepository articleRepository, User user) {
this.invoiceItemRepository = invoiceItemRepository;
this.articleRepository = articleRepository;
this.user = user;
Button addButton = new Button("Hinzufügen", this::createNewitem);
add(new HorizontalLayout(new H3("Rechnungspositionen"), addButton));
grid = new InvoiceItemGrid(invoiceItemRepository);
add(grid);
}
public void refreshData() {
grid.refreshData();
}
private void createNewitem(ClickEvent<Button> ev) {
InvoiceItem item = new InvoiceItem();
List<Article> articles = articleRepository.findByUserId(user.getId());
InvoiceItemDialog dialog = new InvoiceItemDialog(articles, dlg -> {
if (dlg.isClosedWithOk()) {
dlg.writeTo(item);
invoiceItemRepository.save(item);
}
});
dialog.readFrom(item);
dialog.setVisible();
}
}

@ -0,0 +1,18 @@
package de.kreth.invoice.components;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
public class InvoiceOverviewComponent extends VerticalLayout {
private static final long serialVersionUID = 1067257075519373200L;
private final InvoiceGrid grid;
public InvoiceOverviewComponent() {
addClassName("bordered");
this.grid = new InvoiceGrid();
add(new H2("Rechnungen"), grid);
}
}

@ -0,0 +1,43 @@
package de.kreth.invoice.config;
import org.springframework.stereotype.Component;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.server.ServiceInitEvent;
import com.vaadin.flow.server.VaadinServiceInitListener;
import de.kreth.invoice.views.View;
@Component
public class ConfigureUIServiceInitListener implements VaadinServiceInitListener {
private static final long serialVersionUID = 1L;
@Override
public void serviceInit(ServiceInitEvent event) {
event.getSource().addUIInitListener(uiEvent -> {
final UI ui = uiEvent.getUI();
ui.addBeforeEnterListener(this::beforeEnter);
});
}
/**
* Reroutes the user if (s)he is not authorized to access the view.
*
* @param event before navigation event with event details
*/
private void beforeEnter(BeforeEnterEvent event) {
Class<?> navigationTarget = event.getNavigationTarget();
if (isSecureAndNotAuthentificated(navigationTarget)) {
event.rerouteTo("");
}
}
private boolean isSecureAndNotAuthentificated(Class<?> navigationTarget) {
boolean userLoggedIn = SecurityUtils.isUserLoggedIn();
return View.class.equals(navigationTarget) && !userLoggedIn;
}
}

@ -0,0 +1,16 @@
package de.kreth.invoice.config;
import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class KeycloakConfigResolverLocal {
@Bean
public KeycloakConfigResolver keyCloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
}

@ -0,0 +1,36 @@
package de.kreth.invoice.config;
import java.util.stream.Stream;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import com.vaadin.flow.shared.ApplicationConstants;
public class SecurityUtils {
/**
* Tests if the request is an internal framework request. The test consists of
* checking if the request parameter is present and if its value is consistent
* with any of the request types know.
*
* @param request {@link HttpServletRequest}
* @return true if is an internal framework request. False otherwise.
*/
static boolean isFrameworkInternalRequest(HttpServletRequest request) {
final String parameterValue = request.getParameter(ApplicationConstants.REQUEST_TYPE_PARAMETER);
return parameterValue != null
&& Stream.of(com.vaadin.flow.server.HandlerHelper.RequestType.values())
.anyMatch(r -> r.getIdentifier().equals(parameterValue));
}
static boolean isUserLoggedIn() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); //
return authentication != null //
&& !(authentication instanceof AnonymousAuthenticationToken) //
&& authentication.isAuthenticated(); //
}
}

@ -0,0 +1,81 @@
package de.kreth.invoice.config;
import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
import org.keycloak.adapters.springsecurity.KeycloakConfiguration;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
import org.keycloak.adapters.springsecurity.client.KeycloakClientRequestFactory;
import org.keycloak.adapters.springsecurity.client.KeycloakRestTemplate;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
@KeycloakConfiguration
public class UiSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
@Autowired
private KeycloakClientRequestFactory factory;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
KeycloakAuthenticationProvider keyCloakAuthProvider = keycloakAuthenticationProvider();
keyCloakAuthProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keyCloakAuthProvider);
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public KeycloakRestTemplate restTemplate() {
return new KeycloakRestTemplate(factory);
}
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
@Override
public void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.cors().disable()
.csrf().disable()
.anonymous().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED).and()
.authorizeRequests().requestMatchers(SecurityUtils::isFrameworkInternalRequest).permitAll()
.anyRequest().hasAnyRole("ROLE_trainer", "ROLE_admin");
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(
// Vaadin Flow static resources //
"/VAADIN/**",
// the standard favicon URI
"/favicon.ico",
// the robots exclusion standard
"/robots.txt",
// web application manifest //
"/manifest.webmanifest", "/sw.js", "/offline-page.html",
// (development mode) static resources //
"/frontend/**",
// (development mode) webjars //
"/webjars/**",
// (production mode) static resources //
"/frontend-es5/**", "/frontend-es6/**");
}
@Bean
public static KeycloakConfigResolver keycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
}

@ -0,0 +1,112 @@
package de.kreth.invoice.data;
import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.Entity;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;
@Entity
@Table(name = "ADRESS")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "adress_type", discriminatorType = DiscriminatorType.STRING)
public class Adress extends BaseEntity {
private static final long serialVersionUID = 8331249424121577387L;
@Column(nullable = false, length = 255)
private String adress1;
@Column(nullable = true, length = 255)
private String adress2;
@Column(nullable = true, length = 45)
private String zip;
@Column(nullable = true, length = 155)
private String city;
public String getAdress1() {
return adress1;
}
public void setAdress1(String adress1) {
this.adress1 = adress1;
}
public String getAdress2() {
return adress2;
}
public void setAdress2(String adress2) {
this.adress2 = adress2;
}
public String getZip() {
return zip;
}
public void setZip(String zip) {
this.zip = zip;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
@Override
public String toString() {
return "Adress [adress1=" + adress1 + ", adress2=" + adress2 + ", zip="
+ zip + ", city=" + city + "]";
}
public boolean isValid() {
return adress1 != null && !adress1.isBlank();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((adress1 == null) ? 0 : adress1.hashCode());
result = prime * result + ((adress2 == null) ? 0 : adress2.hashCode());
result = prime * result + ((city == null) ? 0 : city.hashCode());
result = prime * result + ((zip == null) ? 0 : zip.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Adress other = (Adress) obj;
if (adress1 == null) {
if (other.adress1 != null)
return false;
} else if (!adress1.equals(other.adress1))
return false;
if (adress2 == null) {
if (other.adress2 != null)
return false;
} else if (!adress2.equals(other.adress2))
return false;
if (city == null) {
if (other.city != null)
return false;
} else if (!city.equals(other.city))
return false;
if (zip == null) {
if (other.zip != null)
return false;
} else if (!zip.equals(other.zip))
return false;
return true;
}
}

@ -0,0 +1,104 @@
package de.kreth.invoice.data;
import java.math.BigDecimal;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
@Entity
@Table(name = "ARTICLE")
public class Article extends BaseEntity {
private static final long serialVersionUID = 6777704420363536698L;
@Column(name = "price")
private BigDecimal pricePerHour;
@Column(nullable = false, length = 50)
private String title;
@Column(nullable = true, length = 255)
private String description;
@Column(name = "user_id")
private int userId;
public BigDecimal getPricePerHour() {
return pricePerHour;
}
public void setPricePerHour(BigDecimal pricePerHour) {
this.pricePerHour = pricePerHour;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
@Override
public String toString() {
return "Article [pricePerHour=" + pricePerHour + ", title=" + title
+ ", description=" + description + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((description == null) ? 0 : description.hashCode());
result = prime * result
+ ((pricePerHour == null) ? 0 : pricePerHour.hashCode());
result = prime * result + ((title == null) ? 0 : title.hashCode());
result = prime * result + userId;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Article other = (Article) obj;
if (description == null) {
if (other.description != null)
return false;
} else if (!description.equals(other.description))
return false;
if (pricePerHour == null) {
if (other.pricePerHour != null)
return false;
} else if (!pricePerHour.equals(other.pricePerHour))
return false;
if (title == null) {
if (other.title != null)
return false;
} else if (!title.equals(other.title))
return false;
if (userId != other.userId)
return false;
return true;
}
}

@ -0,0 +1,83 @@
package de.kreth.invoice.data;
import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.Entity;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;
@Entity
@Table(name = "BANKING_CONNECTION")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "owner_type", discriminatorType = DiscriminatorType.STRING)
public class BankingConnection extends BaseEntity {
private static final long serialVersionUID = -6168631092559375156L;
@Column(nullable = false, length = 150)
private String bankName;
@Column(nullable = false, length = 150)
private String iban;
@Column(nullable = true, length = 150)
private String bic;
public String getBankName() {
return bankName;
}
public void setBankName(String bankName) {
this.bankName = bankName;
}
public String getIban() {
return iban;
}
public void setIban(String iban) {
this.iban = iban;
}
public String getBic() {
return bic;
}
public void setBic(String bic) {
this.bic = bic;
}
@Override
public String toString() {
return iban;
}
public boolean isValid() {
return iban != null;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((iban == null) ? 0 : iban.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
BankingConnection other = (BankingConnection) obj;
if (iban == null) {
if (other.iban != null)
return false;
} else if (!iban.equals(other.iban))
return false;
return true;
}
}

@ -0,0 +1,90 @@
package de.kreth.invoice.data;
import java.io.Serializable;
import java.time.LocalDateTime;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.PrePersist;
@MappedSuperclass
public class BaseEntity implements Serializable {
private static final long serialVersionUID = 6953593432069408729L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@Column(name = "created")
private LocalDateTime createdDate;
@Column(name = "updated")
private LocalDateTime changeDate;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public LocalDateTime getCreatedDate() {
return createdDate;
}
public void setCreatedDate(LocalDateTime createdDate) {
this.createdDate = createdDate;
}
public LocalDateTime getChangeDate() {
return changeDate;
}
public void setChangeDate(LocalDateTime changeDate) {
this.changeDate = changeDate;
}
@PrePersist
void prePersist() {
if (this.createdDate == null) {
LocalDateTime now = LocalDateTime.now();
this.createdDate = now;
this.changeDate = now;
} else {
this.changeDate = LocalDateTime.now();
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((createdDate == null) ? 0 : createdDate.hashCode());
result = prime * result + id;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
BaseEntity other = (BaseEntity) obj;
if (createdDate == null) {
if (other.createdDate != null)
return false;
} else if (!createdDate.equals(other.createdDate))
return false;
if (id != other.id)
return false;
return true;
}
}

@ -0,0 +1,77 @@
package de.kreth.invoice.data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.OneToMany;
import javax.persistence.Table;
@Entity
@Table(name = "INVOICE")
public class Invoice extends BaseEntity {
private static final long serialVersionUID = 736651954892271409L;
@Column(name = "invoiceid", nullable = false, length = 150)
private String invoiceId;
private LocalDateTime invoiceDate;
@OneToMany(mappedBy = "invoice")
private List<InvoiceItem> items;
// @ManyToOne(cascade = CascadeType.REFRESH)
// @JoinColumn(name = "user_id", nullable = false, updatable = false)
private User user;
public String getInvoiceId() {
return invoiceId;
}
public void setInvoiceId(String invoiceId) {
this.invoiceId = invoiceId;
}
public LocalDateTime getInvoiceDate() {
return invoiceDate;
}
public void setInvoiceDate(LocalDateTime invoiceDate) {
this.invoiceDate = invoiceDate;
}
public List<InvoiceItem> getItems() {
return items;
}
public void setItems(Collection<InvoiceItem> items) {
this.items = new ArrayList<>(items);
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public BigDecimal getSum() {
if (items == null || items.isEmpty()) {
return BigDecimal.ZERO;
}
return items.stream().map(i -> i.getSumPrice()).reduce(BigDecimal.ZERO,
BigDecimal::add);
}
@Override
public String toString() {
return "Invoice [invoiceId=" + invoiceId + ", itemscount="
+ items.size() + ", sum=" + getSum() + "]";
}
}

@ -0,0 +1,147 @@
package de.kreth.invoice.data;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
@Entity
@Table(name = "INVOICE_ITEM")
public class InvoiceItem extends BaseEntity {
private static final long serialVersionUID = 3142997452876778041L;
private LocalDateTime start;
private LocalDateTime end;
@Column(nullable = true, length = 15)
private String participants;
@ManyToOne(optional = false)
@JoinColumn(name = "article_id", nullable = false, updatable = false)
private Article article;
@ManyToOne(optional = true)
@JoinColumn(name = "invoice_id", nullable = true, updatable = true)
private Invoice invoice;
@Column(name = "sum_price")
private BigDecimal sumPrice;
public String getTitle() {
return getArticle().getTitle();
}
public LocalDateTime getStart() {
return start;
}
public void setStart(LocalDateTime start) {
this.start = start;
getSumPrice();
}
public LocalDateTime getEnd() {
return end;
}
public void setEnd(LocalDateTime end) {
this.end = end;
getSumPrice();
}
public String getParticipants() {
return participants;
}
public void setParticipants(String participants) {
this.participants = participants;
}
public Article getArticle() {
return article;
}
public void setArticle(Article article) {
this.article = article;
getSumPrice();
}
public BigDecimal getSumPrice() {
if (article == null || start == null || end == null) {
sumPrice = null;
return null;
}
sumPrice = BigDecimal.valueOf(getDurationInMinutes())
.setScale(2, RoundingMode.HALF_UP)
.divide(BigDecimal.valueOf(60), RoundingMode.HALF_UP)
.multiply(article.getPricePerHour())
.setScale(2, RoundingMode.HALF_UP);
return sumPrice;
}
public Invoice getInvoice() {
return invoice;
}
public void setInvoice(Invoice invoice) {
this.invoice = invoice;
}
public long getDurationInMinutes() {
if (start == null || end == null) {
return -1L;
}
return start.until(end, ChronoUnit.MINUTES);
}
@Override
public String toString() {
return "InvoiceItem [id=" + getId() + ", start=" + start + ", end="
+ end + ", article=" + article + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((article == null) ? 0 : article.hashCode());
result = prime * result + ((end == null) ? 0 : end.hashCode());
result = prime * result + ((start == null) ? 0 : start.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
InvoiceItem other = (InvoiceItem) obj;
if (article == null) {
if (other.article != null)
return false;
} else if (!article.equals(other.article))
return false;
if (end == null) {
if (other.end != null)
return false;
} else if (!end.equals(other.end))
return false;
if (start == null) {
if (other.start != null)
return false;
} else if (!start.equals(other.start))
return false;
return true;
}
}

@ -0,0 +1,58 @@
package de.kreth.invoice.data;
import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;
import org.keycloak.representations.AccessToken;
@Entity
@Table(name = "USER")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class User extends BaseEntity {
private static final long serialVersionUID = -2458030336216327580L;
private String principalId;
private String givenName;
private String familyName;
private String email;
@Column(name = "PRINCIPAL_ID", nullable = false, length = 40, updatable = false, insertable = true, unique = true)
public String getPrincipalId() {
return principalId;
}
public void setPrincipal(final AccessToken token) {
final AccessToken accessToken = Objects.requireNonNull(token);
if (this.principalId != null) {
if (!principalId.contentEquals(accessToken.getSubject())) {
throw new IllegalArgumentException("Non updateable value principalId does not match: this.principalId="
+ this.principalId + ", token.principalId=" + accessToken.getSubject());
}
}
principalId = accessToken.getSubject();
givenName = accessToken.getGivenName();
familyName = accessToken.getFamilyName();
email = accessToken.getEmail();
}
@Column(nullable = false, length = 50, updatable = false, insertable = true)
public String getGivenName() {
return givenName;
}
@Column(nullable = false, length = 50, updatable = false, insertable = true)
public String getFamilyName() {
return familyName;
}
@Column(nullable = false, length = 100, updatable = false, insertable = true)
public String getEmail() {
return email;
}
}

@ -0,0 +1,24 @@
package de.kreth.invoice.data;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
@Entity
public class UserAdress extends Adress {
private static final long serialVersionUID = -8104370538500175340L;
@OneToOne(fetch = FetchType.LAZY)
@PrimaryKeyJoinColumn
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}

@ -0,0 +1,32 @@
package de.kreth.invoice.data;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
@Entity
public class UserBank extends BankingConnection {
private static final long serialVersionUID = -7356424394007978241L;
@OneToOne(fetch = FetchType.LAZY)
@PrimaryKeyJoinColumn
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
// if (user.getBank() == null) {
// user.setBank(this);
// } else {
// if (user.getBank().equals(this) == false) {
// throw new IllegalArgumentException(
// "User already set, but other than this.");
// }
// }
}
}

@ -0,0 +1,5 @@
/**
* @author markus
*
*/
package de.kreth.invoice.data;

@ -0,0 +1,12 @@
package de.kreth.invoice.persistence;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
import de.kreth.invoice.data.Article;
public interface ArticleRepository extends CrudRepository<Article, Integer> {
List<Article> findByUserId(int userId);
}

@ -0,0 +1,13 @@
package de.kreth.invoice.persistence;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
import de.kreth.invoice.data.InvoiceItem;
public interface InvoiceItemRepository extends CrudRepository<InvoiceItem, Integer> {
List<InvoiceItem> findByInvoiceIsNull();
}

@ -0,0 +1,11 @@
package de.kreth.invoice.persistence;
import org.springframework.data.repository.CrudRepository;
import de.kreth.invoice.data.User;
import de.kreth.invoice.data.UserAdress;
public interface UserAdressRepository extends CrudRepository<UserAdress, Integer> {
UserAdress findByUser(User user);
}

@ -0,0 +1,11 @@
package de.kreth.invoice.persistence;
import org.springframework.data.repository.CrudRepository;
import de.kreth.invoice.data.User;
import de.kreth.invoice.data.UserBank;
public interface UserBankRepository extends CrudRepository<UserBank, Integer> {
UserBank findByUser(User user);
}

@ -0,0 +1,10 @@
package de.kreth.invoice.persistence;
import org.springframework.data.repository.CrudRepository;
import de.kreth.invoice.data.User;
public interface UserRepository extends CrudRepository<User, Long> {
User findByPrincipalId(String tokenSubject);
}

@ -0,0 +1,55 @@
package de.kreth.invoice.security;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.representations.AccessToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import de.kreth.invoice.data.User;
import de.kreth.invoice.persistence.UserRepository;
@Component
public class UserManager {
private UserRepository userRepository;
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
private AccessToken getAccessToken() {
Authentication authentication = getAuthentication();
KeycloakPrincipal<?> principal = (KeycloakPrincipal<?>) authentication.getPrincipal();
KeycloakSecurityContext context = principal.getKeycloakSecurityContext();
return context.getToken();
}
public User getLoggedInUser() {
AccessToken accessToken = getAccessToken();
if (accessToken != null) {
return userRepository.findByPrincipalId(accessToken.getSubject());
}
return null;
}
public User save(User entity) {
return userRepository.save(entity);
}
private Authentication getAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
public User create() {
User user = new User();
AccessToken accessToken = getAccessToken();
user.setPrincipal(accessToken);
return user;
}
}

@ -0,0 +1,144 @@
package de.kreth.invoice.views;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.Text;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.contextmenu.ContextMenu;
import com.vaadin.flow.component.contextmenu.MenuItem;
import com.vaadin.flow.component.dialog.Dialog;
import com.vaadin.flow.component.html.H1;
import com.vaadin.flow.component.html.Label;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import de.kreth.invoice.components.InvoiceItemOverviewComponent;
import de.kreth.invoice.data.Article;
import de.kreth.invoice.data.User;
import de.kreth.invoice.data.UserAdress;
import de.kreth.invoice.data.UserBank;
import de.kreth.invoice.persistence.ArticleRepository;
import de.kreth.invoice.persistence.InvoiceItemRepository;
import de.kreth.invoice.persistence.UserAdressRepository;
import de.kreth.invoice.persistence.UserBankRepository;
import de.kreth.invoice.security.UserManager;
@PageTitle("")
@Route(value = "")
@PreAuthorize("hasRole('ROLE_TRAINER')")
public class View extends VerticalLayout implements BeforeEnterObserver {
private static final long serialVersionUID = 1L;
private final UserManager userRepository;
private final UserBankRepository bankRepository;
private final UserAdressRepository adressRepository;
private final InvoiceItemRepository invoiceItemRepository;
private final ArticleRepository articleRepository;
private InvoiceItemOverviewComponent invoiceItems;
private User user;
public View(@Autowired UserManager userRepository, @Autowired UserBankRepository userBankRepository,
@Autowired UserAdressRepository adressRepository,
@Autowired InvoiceItemRepository invoiceItemRepository,
@Autowired ArticleRepository articleRepository) {
this.userRepository = userRepository;
this.bankRepository = userBankRepository;
this.adressRepository = adressRepository;
this.invoiceItemRepository = invoiceItemRepository;
this.articleRepository = articleRepository;
}
@Override
public void beforeEnter(BeforeEnterEvent event) {
user = userRepository.getLoggedInUser();
if (user == null) {
user = userRepository.create();
}
// UserBank bank = bankRepository.findByUser(user);
// UserAdress adress = adressRepository.findByUser(user);
// if (isBankOrAdressInvalid(bank, adress)) {
// event.getUI().navigate(LoginDataView.class);
// } else {
// checkArticle();
// createUi();
// }
checkArticle();
createUi();
}
private void checkArticle() {
if (articleRepository.count() <= 0) {
Article article = new Article();
LocalDateTime now = LocalDateTime.now();
article.setChangeDate(now);
article.setCreatedDate(now);
article.setDescription("Dummy Übungsleiter");
article.setTitle("Dummy");
article.setPricePerHour(BigDecimal.valueOf(7.5));
article.setUserId(user.getId());
articleRepository.save(article);
}
}
private boolean isBankOrAdressInvalid(UserBank bank, UserAdress adress) {
return bank == null || adress == null || !bank.isValid() || !adress.isValid();
}
private void createUi() {
Button menuButton = new Button(VaadinIcon.MENU.create());
menuButton.addClickListener(this::onMenuButtonClick);
HorizontalLayout l = new HorizontalLayout(menuButton, new H1("Übungsleiter Abrechnung"));
l.setAlignItems(Alignment.CENTER);
add(l);
Label name = new Label(user.getGivenName() + " " + user.getFamilyName());
Label email = new Label(user.getEmail());
add(name, email);
invoiceItems = new InvoiceItemOverviewComponent(invoiceItemRepository, articleRepository, user);
add(invoiceItems);
}
public void onMenuButtonClick(ClickEvent<Button> event) {
ContextMenu menu = new ContextMenu();
menu.setTarget(event.getSource());
menu.addItem("Einstellungen", this::onSettingsButtonClick);
menu.addItem("Über", this::onAboutButtonClick);
menu.setVisible(true);
}
public void onSettingsButtonClick(ClickEvent<MenuItem> event) {
Dialog dlg = new Dialog();
dlg.add(new H1("Einstellungen"));
dlg.add(new Text("Einstellugen für diese App. Noch nicht implementiert."));
dlg.open();
}
public void onAboutButtonClick(ClickEvent<MenuItem> event) {
Dialog dlg = new Dialog();
dlg.add(new H1("Personeneditor"));
dlg.add(new Text(
"Personeneditor ist eine App zur Erfassung und Änderung von Personen im Trampolin des MTV Groß-Buchholz."));
dlg.open();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

@ -0,0 +1,9 @@
server.port=${PORT:8080}
logging.level.org.atmosphere = warn
spring.mustache.check-template-location = false
# Launch the default browser when starting the application in development mode
vaadin.launch-browser=true
# To improve the performance during development.
# For more information https://vaadin.com/docs/flow/spring/tutorial-spring-configuration.html#special-configuration-parameters
vaadin.whitelisted-packages = com.vaadin,org.vaadin,dev.hilla,de.kreth.invoice

@ -0,0 +1,6 @@
_ _ _ _
| |_ _ __ __ _ (_) _ __ ___ _ __ (_) _ __ __ __ ___ (_) ___ ___
| __|| '__| / _` || || '_ \ / _ \| '__|| || '_ \ \ \ / / / _ \ | | / __| / _ \
| |_ | | | (_| || || | | || __/| | | || | | | \ V / | (_) || || (__ | __/
\__||_| \__,_||_||_| |_| \___||_| |_||_| |_| \_/ \___/ |_| \___| \___|

@ -0,0 +1,34 @@
// This TypeScript configuration file is generated by vaadin-maven-plugin.
// This is needed for TypeScript compiler to compile your TypeScript code in the project.
// It is recommended to commit this file to the VCS.
// You might want to change the configurations to fit your preferences
// For more information about the configurations, please refer to http://www.typescriptlang.org/docs/handbook/tsconfig-json.html
{
"compilerOptions": {
"sourceMap": true,
"inlineSources": true,
"module": "esNext",
"target": "es2019",
"moduleResolution": "node",
"strict": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"noImplicitAny": true,
"noImplicitThis": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"experimentalDecorators": true,
"baseUrl": "frontend",
"paths": {
"Frontend/*": [
"*"
]
}
},
"include": [
"frontend/**/*.ts",
"frontend/index.js",
"types.d.ts"
],
"exclude": []
}

5
types.d.ts vendored

@ -0,0 +1,5 @@
declare module '*.css' {
import { CSSResultGroup } from 'lit';
const content: CSSResultGroup;
export default content;
}

@ -0,0 +1,37 @@
/**
* This file has been autogenerated as it didn't exist or was made for an older incompatible version.
* This file can be used for manual configuration will not be modified if the flowDefaults constant exists.
*/
const merge = require('webpack-merge');
const flowDefaults = require('./webpack.generated.js');
module.exports = merge(flowDefaults, {
});
/**
* This file can be used to configure the flow plugin defaults.
* <code>
* // Add a custom plugin
* flowDefaults.plugins.push(new MyPlugin());
*
* // Update the rules to also transpile `.mjs` files
* if (!flowDefaults.module.rules[0].test) {
* throw "Unexpected structure in generated webpack config";
* }
* flowDefaults.module.rules[0].test = /\.m?js$/
*
* // Include a custom JS in the entry point in addition to generated-flow-imports.js
* if (typeof flowDefaults.entry.index != "string") {
* throw "Unexpected structure in generated webpack config";
* }
* flowDefaults.entry.index = [flowDefaults.entry.index, "myCustomFile.js"];
* </code>
* or add new configuration in the merge block.
* <code>
* module.exports = merge(flowDefaults, {
* mode: 'development',
* devtool: 'inline-source-map'
* });
* </code>
*/

@ -0,0 +1,379 @@
/**
* NOTICE: this is an auto-generated file
*
* This file has been generated by the `flow:prepare-frontend` maven goal.
* This file will be overwritten on every run. Any custom changes should be made to webpack.config.js
*/
const fs = require('fs');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
const { InjectManifest } = require('workbox-webpack-plugin');
const { DefinePlugin } = require('webpack');
const ExtraWatchWebpackPlugin = require('extra-watch-webpack-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const path = require('path');
// this matches /themes/my-theme/ and is used to check css url handling and file path build.
const themePartRegex = /(\\|\/)themes\1[\s\S]*?\1/;
// the folder of app resources:
// - flow templates for classic Flow
// - client code with index.html and index.[ts/js] for CCDM
const frontendFolder = path.resolve(__dirname, 'frontend');
const frontendGeneratedFolder = path.resolve(__dirname, 'frontend/generated');
const fileNameOfTheFlowGeneratedMainEntryPoint = path.resolve(__dirname, 'target/frontend/generated-flow-imports.js');
const mavenOutputFolderForFlowBundledFiles = path.resolve(__dirname, 'target/classes/META-INF/VAADIN/webapp');
const mavenOutputFolderForResourceFiles = path.resolve(__dirname, 'target/classes/META-INF/VAADIN');
const useClientSideIndexFileForBootstrapping = true;
const clientSideIndexHTML = './index.html';
const clientSideIndexEntryPoint = path.resolve(__dirname, 'frontend', 'generated/', 'vaadin.ts');;
const pwaEnabled = true;
const offlinePath = '.';
const clientServiceWorkerEntryPoint = path.resolve(__dirname, 'target/sw');
// public path for resources, must match Flow VAADIN_BUILD
const VAADIN = 'VAADIN';
const build = 'build';
// public path for resources, must match the request used in flow to get the /build/stats.json file
const config = 'config';
const outputFolder = mavenOutputFolderForFlowBundledFiles;
const indexHtmlPath = 'index.html';
// folder for outputting vaadin-bundle and other fragments
const buildFolder = path.resolve(outputFolder, VAADIN, build);
// folder for outputting stats.json
const confFolder = path.resolve(mavenOutputFolderForResourceFiles, config);
const serviceWorkerPath = 'sw.js';
// file which is used by flow to read templates for server `@Id` binding
const statsFile = `${confFolder}/stats.json`;
const buildDirectory = path.resolve(__dirname, 'target');
// Flow plugins
const BuildStatusPlugin = require(buildDirectory + '/plugins/build-status-plugin');
const ThemeLiveReloadPlugin = require(buildDirectory + '/plugins/theme-live-reload-plugin');
const {
ApplicationThemePlugin,
processThemeResources,
extractThemeName,
findParentThemes
} = require(buildDirectory + '/plugins/application-theme-plugin');
const themeLoader = buildDirectory + '/plugins/theme-loader';
// Folders in the project which can contain static assets.
const projectStaticAssetsFolders = [
path.resolve(__dirname, 'src', 'main', 'resources', 'META-INF', 'resources'),
path.resolve(__dirname, 'src', 'main', 'resources', 'static'),
frontendFolder
];
const projectStaticAssetsOutputFolder = path.resolve(__dirname, 'target/classes/META-INF/VAADIN/webapp/VAADIN/static');
// Folders in the project which can contain application themes
const themeProjectFolders = projectStaticAssetsFolders.map((folder) => path.resolve(folder, 'themes'));
const tsconfigJsonFile = path.resolve(__dirname, 'tsconfig.json');
const enableTypeScript = fs.existsSync(tsconfigJsonFile);
// Target flow-fronted auto generated to be the actual target folder
const flowFrontendFolder = path.resolve(__dirname, 'target/flow-frontend');
const statsSetViaCLI = process.argv.find((v) => v.indexOf('--stats') >= 0);
const devMode = process.argv.find((v) => v.indexOf('webpack-dev-server') >= 0);
if (!devMode) {
// make sure that build folder exists before outputting anything
const mkdirp = require('mkdirp');
mkdirp(buildFolder);
mkdirp(confFolder);
}
let stats;
// Open a connection with the Java dev-mode handler in order to finish
// webpack-dev-mode when it exits or crashes.
const watchDogPort = devMode && process.env.watchDogPort;
const watchDogHost = (devMode && process.env.watchDogHost) || 'localhost';
if (watchDogPort) {
const runWatchDog = () => {
const client = new require('net').Socket();
client.setEncoding('utf8');
client.on('error', function (err) {
console.log('Watchdog connection error. Terminating webpack process...', err);
client.destroy();
process.exit(0);
});
client.on('close', function () {
client.destroy();
runWatchDog();
});
client.connect(watchDogPort, watchDogHost);
};
runWatchDog();
}
// Compute the entries that webpack have to visit
const webPackEntries = {};
if (useClientSideIndexFileForBootstrapping) {
webPackEntries.bundle = clientSideIndexEntryPoint;
const dirName = path.dirname(fileNameOfTheFlowGeneratedMainEntryPoint);
const baseName = path.basename(fileNameOfTheFlowGeneratedMainEntryPoint, '.js');
if (
fs
.readdirSync(dirName)
.filter((fileName) => !fileName.startsWith(baseName) && fileName.endsWith('.js') && fileName.includes('-')).length
) {
// if there are vaadin exported views, add a second entry
webPackEntries.export = fileNameOfTheFlowGeneratedMainEntryPoint;
}
} else {
webPackEntries.bundle = fileNameOfTheFlowGeneratedMainEntryPoint;
}
const appShellUrl = '.';
let appShellManifestEntry = undefined;
const swManifestTransform = (manifestEntries) => {
const warnings = [];
const manifest = manifestEntries;
if (useClientSideIndexFileForBootstrapping) {
// `index.html` is a special case: in contrast with the JS bundles produced by webpack
// it's not served as-is directly from the webpack output at `/index.html`.
// It goes through IndexHtmlRequestHandler and is served at `/`.
//
// TODO: calculate the revision based on the IndexHtmlRequestHandler-processed content
// of the index.html file
const indexEntryIdx = manifest.findIndex((entry) => entry.url === 'index.html');
if (indexEntryIdx !== -1) {
manifest[indexEntryIdx].url = appShellUrl;
appShellManifestEntry = manifest[indexEntryIdx];
} else {
// Index entry is only emitted on first compilation. Make sure it is cached also for incremental builds
manifest.push(appShellManifestEntry);
}
}
return { manifest, warnings };
};
const createServiceWorkerPlugin = function () {
return new InjectManifest({
swSrc: clientServiceWorkerEntryPoint,
swDest: serviceWorkerPath,
manifestTransforms: [swManifestTransform],
maximumFileSizeToCacheInBytes: 100 * 1024 * 1024,
dontCacheBustURLsMatching: /.*-[a-z0-9]{20}\.cache\.js/,
include: [
(chunk) => {
return true;
}
],
webpackCompilationPlugins: [
new DefinePlugin({
OFFLINE_PATH: JSON.stringify(offlinePath)
})
]
});
};
const flowFrontendThemesFolder = path.resolve(flowFrontendFolder, 'themes');
const themeOptions = {
devMode: devMode,
// The following matches folder 'target/flow-frontend/themes/'
// (not 'frontend/themes') for theme in JAR that is copied there
themeResourceFolder: flowFrontendThemesFolder,
themeProjectFolders: themeProjectFolders,
projectStaticAssetsOutputFolder: projectStaticAssetsOutputFolder,
frontendGeneratedFolder: frontendGeneratedFolder
};
let themeName = undefined;
let themeWatchFolders = undefined;
if (devMode) {
// Current theme name is being extracted from theme.js located in frontend
// generated folder
themeName = extractThemeName(frontendGeneratedFolder);
const parentThemePaths = findParentThemes(themeName, themeOptions);
const currentThemeFolders = [
...projectStaticAssetsFolders.map((folder) => path.resolve(folder, 'themes', themeName)),
path.resolve(flowFrontendThemesFolder, themeName)
];
// Watch the components folders for component styles update in both
// current theme and parent themes. Other folders or CSS files except
// 'styles.css' should be referenced from `styles.css` anyway, so no need
// to watch them.
themeWatchFolders = [...currentThemeFolders, ...parentThemePaths].map((themeFolder) =>
path.resolve(themeFolder, 'components')
);
}
const processThemeResourcesCallback = (logger) => processThemeResources(themeOptions, logger);
exports = {
frontendFolder: `${frontendFolder}`,
buildFolder: `${buildFolder}`,
confFolder: `${confFolder}`
};
module.exports = {
mode: 'production',
context: frontendFolder,
entry: webPackEntries,
output: {
filename: `${VAADIN}/${build}/vaadin-[name]-[contenthash].cache.js`,
path: outputFolder
},
resolve: {
// Search for import 'x/y' inside these folders, used at least for importing an application theme
modules: ['node_modules', flowFrontendFolder, ...projectStaticAssetsFolders],
extensions: [enableTypeScript && '.ts', '.js'].filter(Boolean),
alias: {
Frontend: frontendFolder
}
},
stats: devMode && !statsSetViaCLI ? 'errors-warnings' : 'normal', // Unclutter output in dev mode
devServer: {
hot: false, // disable HMR
client: false, // disable wds client as we handle reloads and errors better
// webpack-dev-server serves ./, webpack-generated, and java webapp
static: [outputFolder, path.resolve(__dirname, 'src', 'main', 'webapp')],
setupMiddlewares: function (middlewares, devServer) {
devServer.app.get(`/assetsByChunkName`, function (req, res) {
res.json(stats.assetsByChunkName);
});
devServer.app.get(`/stop`, function (req, res) {
// eslint-disable-next-line no-console
console.log("Stopped 'webpack-dev-server'");
process.exit(0);
});
return middlewares;
}
},
module: {
rules: [
enableTypeScript && {
test: /\.ts$/,
loader: 'esbuild-loader',
options: {
loader: 'ts',
target: 'es2019'
}
},
{
test: /\.css$/i,
use: [
{
loader: 'lit-css-loader',
options: {
import: 'lit'
}
},
{
loader: 'extract-loader'
},
{
loader: 'css-loader',
options: {
url: (url, resourcePath) => {
// Only translate files from node_modules
const resolve = resourcePath.match(/(\\|\/)node_modules\1/);
const themeResource = resourcePath.match(themePartRegex) && url.match(/^themes\/[\s\S]*?\//);
return resolve || themeResource;
},
// use theme-loader to also handle any imports in css files
importLoaders: 1
}
},
{
// theme-loader will change any url starting with './' to start with 'VAADIN/static' instead
// NOTE! this loader should be here so it's run before css-loader as loaders are applied Right-To-Left
loader: themeLoader,
options: {
devMode: devMode
}
}
]
},
{
// File-loader only copies files used as imports in .js files or handled by css-loader
test: /\.(png|gif|jpg|jpeg|svg|eot|woff|woff2|otf|ttf)$/,
use: [
{
loader: 'file-loader',
options: {
outputPath: 'VAADIN/static/',
name(resourcePath, resourceQuery) {
if (resourcePath.match(/(\\|\/)node_modules\1/)) {
return /(\\|\/)node_modules\1(?!.*node_modules)([\S]+)/.exec(resourcePath)[2].replace(/\\/g, '/');
}
if (resourcePath.match(/(\\|\/)flow-frontend\1/)) {
return /(\\|\/)flow-frontend\1(?!.*flow-frontend)([\S]+)/.exec(resourcePath)[2].replace(/\\/g, '/');
}
return '[path][name].[ext]';
}
}
}
]
}
].filter(Boolean)
},
performance: {
maxEntrypointSize: 2097152, // 2MB
maxAssetSize: 2097152 // 2MB
},
plugins: [
new ApplicationThemePlugin(themeOptions),
...(devMode && themeName
? [
new ExtraWatchWebpackPlugin({
files: [],
dirs: themeWatchFolders
}),
new ThemeLiveReloadPlugin(processThemeResourcesCallback)
]
: []),
function (compiler) {
// V14 bootstrapping needs the bundle names
compiler.hooks.afterEmit.tapAsync("FlowStatsHelper", (compilation, done) => {
let miniStats = {
assetsByChunkName: compilation.getStats().toJson().assetsByChunkName
};
if (!devMode) {
fs.writeFile(statsFile, JSON.stringify(miniStats, null, 1),
() => done());
} else {
stats = miniStats;
done();
}
});
},
// Includes JS output bundles into "index.html"
useClientSideIndexFileForBootstrapping &&
new HtmlWebpackPlugin({
template: clientSideIndexHTML,
filename: indexHtmlPath,
inject: 'head',
scriptLoading: 'defer',
chunks: ['bundle']
}),
// Service worker for offline
pwaEnabled && createServiceWorkerPlugin(),
// Generate compressed bundles when not devMode
!devMode && new CompressionPlugin(),
enableTypeScript &&
new ForkTsCheckerWebpackPlugin({
typescript: {
configFile: tsconfigJsonFile
}
}),
new BuildStatusPlugin()
].filter(Boolean)
};
Loading…
Cancel
Save