Import kontor-spring into directory springboot

This commit is contained in:
2025-01-06 17:09:38 +00:00
committed by Thomas Peetz
parent 3aed8af868
commit 78632e0e12
269 changed files with 19844 additions and 4 deletions
+4 -4
View File
@@ -1,5 +1,5 @@
__pycache__
.idea/
bonus
icons
icons-shadowless
__pycache__/
bonus/
icons/
icons-shadowless/
+9
View File
@@ -0,0 +1,9 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# Linux start script should use lf
/gradlew text eol=lf
# These are Windows script files and should use crlf
*.bat text eol=crlf
+32
View File
@@ -0,0 +1,32 @@
.gradle/
.settings/
build/
bin/
# Ignore Gradle GUI config
gradle-app.setting
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
.project
.classpath
.vscode/
.idea/
*.lock
logs/
frontend/generated
frontend/index.html
package*.json
tsconfig.json
types.d.ts
node_modules/
vite.*
kontor*Db
tags*
kontorHSQLDB*
.vs/
.winget
src/main/resources/application-local.properties
src/main/resources/application-prod.properties
src/main/resources/application-*.yml
+3
View File
@@ -0,0 +1,3 @@
# kontor-spring
Kontor Anwendung mit Spring Boot und Vaadin
+240
View File
@@ -0,0 +1,240 @@
buildscript {
configurations.classpath {
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
if (details.requested.group == 'com.burgstaller' && details.requested.name == 'okhttp-digest' && details.requested.version == '1.10') {
details.useTarget "io.github.rburgst:${details.requested.name}:1.21"
details.because 'Dependency has moved'
}
}
}
repositories {
mavenCentral()
maven { setUrl("https://maven.vaadin.com/vaadin-prereleases") }
maven { setUrl("https://repo.spring.io/milestone") }
}
}
plugins {
id 'java'
id 'application'
id 'maven-publish'
id "com.google.cloud.artifactregistry.gradle-plugin" version "2.2.0"
id 'jvm-test-suite'
id 'jacoco'
id 'test-report-aggregation'
id 'jacoco-report-aggregation'
alias(libs.plugins.spring.boot)
alias(libs.plugins.spring.dependencies)
alias(libs.plugins.vaadin)
alias(libs.plugins.lombok)
alias(libs.plugins.asciidoctorPdf)
alias(libs.plugins.asciidoctorConvert)
alias(libs.plugins.asciidoctorGems)
}
repositories {
mavenCentral()
ruby.gems()
maven { setUrl("https://maven.vaadin.com/vaadin-prereleases") }
maven { setUrl("https://repo.spring.io/milestone") }
maven { setUrl("https://maven.vaadin.com/vaadin-addons") }
}
sourceCompatibility = '17'
configurations {
developmentOnly
runtimeClasspath {
extendsFrom developmentOnly
}
}
dependencies {
implementation 'com.vaadin:vaadin-core'
implementation 'com.vaadin:vaadin-spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
implementation 'org.springframework.security:spring-security-oauth2-jose'
implementation 'org.springframework.security:spring-security-oauth2-resource-server'
implementation 'com.h2database:h2'
implementation libs.hsqldb
runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
implementation 'com.sun.mail:javax.mail:1.6.2'
implementation 'org.hibernate.orm:hibernate-community-dialects'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'com.vaadin:vaadin-testbench-junit5'
testImplementation 'io.projectreactor:reactor-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
asciidoctorGems libs.rouge
//asciidoctorGems libs.diagram
}
def pdfFile = layout.buildDirectory.file("docs/asciidocPdf/kontor-spring.pdf")
def pdfArtifact = artifacts.add('archives', pdfFile.get().asFile) {
type 'pdf'
builtBy asciidoctorPdf
}
publishing {
publications {
maven(MavenPublication) {
groupId = group + '.docs'
artifactId = project.name
artifact pdfArtifact
}
bootJava(MavenPublication) {
artifact tasks.named("bootDistTar")
}
}
repositories {
maven {
name = "gitlabPackageRegistry"
url = uri("https://gitlab.com/api/v4/projects/62010300/packages/maven")
credentials(PasswordCredentials)
}
}
}
final BUILD_DATE = new Date().format('dd.MM.yyyy').toString()
asciidoctorPdf {
dependsOn asciidoctorGemsPrepare
baseDirFollowsSourceFile()
asciidoctorj {
modules {
diagram.use()
}
requires 'rouge'
attributes 'build-gradle': file('build.gradle'),
'endpoint-url': 'https://www.thpeetz.de',
'source-highlighter': 'rouge',
'imagesdir': './images',
'toc': 'left',
'toc-title': 'Inhaltsverzeichnis',
'revdate': BUILD_DATE,
'revnumber': { project.version.toString() },
'revremark': 'Entwurf',
'chapter-label': '',
'icons': 'font',
'idprefix': 'id_',
'idseparator': '-',
'docinfo1': ''
}
}
build.dependsOn asciidoctorPdf
dependencyManagement {
imports {
mavenBom libs.vaadin.bom.get().toString()
}
}
application {
mainClass = 'de.thpeetz.kontor.Application'
}
bootRun {
args = ["--spring.profiles.active=${project.properties['profile'] ?: 'prod'}"]
}
vaadin {
productionMode = true
}
testing {
suites {
configureEach {
useJUnitJupiter()
dependencies {
implementation project()
implementation 'com.vaadin:vaadin-core'
implementation 'com.vaadin:vaadin-spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'com.h2database:h2'
implementation libs.hsqldb
implementation libs.sqlite.jdbc
//runtimeOnly 'com.mysql:mysql-connector-j'
runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
implementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
implementation 'org.springframework.security:spring-security-test'
implementation 'com.vaadin:vaadin-testbench-junit5'
implementation 'io.projectreactor:reactor-test'
runtimeOnly 'org.junit.platform:junit-platform-launcher'
}
}
test(JvmTestSuite) {
testType = TestSuiteType.UNIT_TEST
targets {
all {
testTask.configure {
reports {
junitXml {
outputPerTestCase = true // defaults to false
mergeReruns = true // defaults to false
}
}
finalizedBy(jacocoTestReport)
}
}
}
}
integrationTest(JvmTestSuite) {
testType = "view-test"
targets {
all {
testTask.configure {
shouldRunAfter(test)
finalizedBy(jacocoTestReport)
}
}
}
}
}
}
tasks.named('check') {
dependsOn(testing.suites.integrationTest)
dependsOn(testing.suites.test)
dependsOn tasks.named('testAggregateTestReport', TestReport)
dependsOn tasks.named('integrationTestAggregateTestReport', TestReport)
}
jacocoTestReport {
dependsOn test, integrationTest
reports {
xml.required = true
csv.required = false
}
}
reporting {
reports {
testAggregateTestReport(AggregateTestReport) {
testType = TestSuiteType.UNIT_TEST
}
integrationTestAggregateTestReport(AggregateTestReport) {
testType = "view-test"
}
integrationTestCodeCoverageReport(JacocoCoverageReport) {
testType = "view-test"
}
}
}
wrapper {
gradleVersion = "8.6"
}
@@ -0,0 +1,10 @@
@media all and (max-width: 1100px) {
.list-view.editing .toolbar,
.list-view.editing .contact-grid {
display: none;
}
}
a[highlight] {
font-weight: bold;
text-decoration: underline;
}
@@ -0,0 +1,3 @@
{
"lumoImports" : [ "typography", "color", "spacing", "badge", "utility" ]
}
+3
View File
@@ -0,0 +1,3 @@
description='Kontor with Spring Boot'
version=0.1.0-SNAPSHOT
group=de.thpeetz
+58
View File
@@ -0,0 +1,58 @@
[versions]
gradle = "8.6"
args4j = "2.33"
commonscli = "1.5.0"
junit = "5.8.2"
logback = "1.1.2"
mockito = "1.9.5"
picoli = "4.7.0"
slf4j = "1.7.22"
hsqldb = "2.7.1"
sqlite = "3.25.2"
spotbugs = "6.0.7"
asciidoctor = "4.0.2"
rouge = "3.15.0"
#diagram = "2.2.2"
diagram = "2.3.1"
sonarqube = "3.3"
cimtConventions = "1.0.0-SNAPSHOT"
springboot = "3.2.5"
springdependencies = "1.1.4"
vaadin = "24.3.8"
lombok = "8.6"
[libraries]
args4j = { module = "args4j:args4j", version.ref = "args4j" }
commonscli = { module = "commons-cli:commons-cli", version.ref = "commonscli" }
junit = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }
logbackCore = { module = "ch.qos.logback:logback-core", version.ref = "logback" }
logbackClassic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
mockito = { module = "org.mockito:mockito-all", version.ref = "mockito" }
picocli = { module = "info.picocli:picocli", version.ref = "picoli" }
slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
hsqldb = { module = "org.hsqldb:hsqldb", version.ref = "hsqldb" }
sqlite-jdbc = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite" }
vaadin-bom = { module = "com.vaadin:vaadin-bom", version.ref = "vaadin" }
asciidoctorGradleJvmGems = { module = "org.asciidoctor:asciidoctor-gradle-jvm-gems", version.ref= "asciidoctor" }
asciidoctorGradleJvm = { module = "org.asciidoctor:asciidoctor-gradle-jvm", version.ref= "asciidoctor" }
asciidoctorGradleJvmPdf = { module = "org.asciidoctor:asciidoctor-gradle-jvm-pdf", version.ref= "asciidoctor" }
rouge = { module = "rubygems:rouge", version.ref = "rouge" }
diagram = { module = "rubygems:asciidoctor-diagram", version.ref = "diagram" }
[bundles]
logback = ["logbackCore", "logbackClassic"]
[plugins]
spotbugs = { id = "com.github.spotbugs", version.ref = "spotbugs" }
sonarqube = { id = "org.sonarqube", version.ref = "sonarqube" }
asciidoctorPdf = { id = "org.asciidoctor.jvm.pdf", version.ref = "asciidoctor" }
asciidoctorConvert = { id = "org.asciidoctor.jvm.convert", version.ref = "asciidoctor" }
asciidoctorGems = { id = "org.asciidoctor.jvm.gems", version.ref = "asciidoctor" }
javaConvention = { id = "de.cimt.java-conventions", version.ref = "cimtConventions" }
applicationConvention = { id = "de.cimt.application-conventions", version.ref = "cimtConventions" }
libraryConvention = { id = "de.cimt.library-conventions", version.ref = "cimtConventions" }
asciidoctorConvention = { id = "de.cimt.asciidoctor-conventions", version.ref = "cimtConventions" }
spring-boot = { id = "org.springframework.boot", version.ref = "springboot"}
spring-dependencies = { id = "io.spring.dependency-management", version.ref = "springdependencies" }
vaadin = { id = "com.vaadin", version.ref = "vaadin" }
lombok = { id = "io.freefair.lombok", version.ref = "lombok" }
Binary file not shown.
+7
View File
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored Executable
+249
View File
@@ -0,0 +1,249 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original 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
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
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
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
+92
View File
@@ -0,0 +1,92 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+24
View File
@@ -0,0 +1,24 @@
pluginManagement {
resolutionStrategy {
eachPlugin {
if (requested.id.id == 'org.springframework.boot') {
useModule("org.springframework.boot:spring-boot-gradle-plugin:${requested.version}")
}
if (requested.id.id == 'org.gradle.toolchains.foojay-resolver') {
useModule("org.gradle.toolchains.foojay-resolver-convention:0.4.0")
}
}
}
repositories {
gradlePluginPortal()
mavenCentral()
maven { setUrl("https://maven.vaadin.com/vaadin-prereleases") }
maven { setUrl("https://repo.spring.io/milestone") }
maven { url 'https://plugins.gradle.org/m2/' }
}
// plugins {
// id 'com.vaadin' version "${vaadinVersion}"
// }
}
rootProject.name = 'kontor-spring'
@@ -0,0 +1,509 @@
= Projektbeschreibung kontor-spring: Entwicklungs- und Projekthandbuch
:author: Thomas Peetz
:email: <thomas.peetz@thpeetz.de>
:doctype: book
:sectnums:
:sectnumlevels: 4
:toc:
:toclevels: 4
:table-caption!:
:counter: table-number: 0
[title="Dokumenthistorie", id="Table-{counter:table-number}", options="header"]
|===
| Version | Datum | Autor | Änderungsgrund / Bemerkungen
| 1.0.0 | 16.05.2022 | Thomas Peetz | Ersterstellung
|===
== Allgemeines
=== Zweck des Dokumentes
Das Entwicklungshandbuch beschreibt die Werkzeuge und die Vorgehensweise bei der Entwicklung
im Projekt kontor-spring und der Erstellung der Dokumentation.
=== Verwendete Tools
==== Gitea
Für die Verwaltung des Sourcecode kommt ((Gitea))<<gitea>> zum Einsatz.
Mit Gitea werden auch die Projektaufgaben verwaltet.
Das Projekt und das dazugehörige Git Repository sind unter der Adresse
https://gitea.thpeetz.de/kontor/kontor-spring
zu finden.
== Erstellung der Dokumentation
Die Dokumentation des Projektes wird mit ((Asciidoctor))<<asciidoctor>> geschrieben.
Die Dokumente erhalten ihre Namen nach dem jeweiligen Hauptdokument.
=== Quellcode Verwaltung
Die Asciidoctor-Dateien haben die Endung `.adoc`.
=== Buildsystem
Zur Erstellung der PDF-Dateien aus den Asciidoctor-Dateien wird das Buildsystem ((Gradle))<<gradle>> verwendet.
Die Dateien für die Dokumente liegen im Verzeichnis `src/docs/asciidoc`.
Der Gradle Build wird über die Datei `build.gradle` definiert.
== Einführung
=== Zweck
=== Stakeholder des Systems
=== Systemumfang
==== Zielsetzung des Systems
=== Systemübersicht
==== Systemkontext
==== Systemarchitektur
==== Systemschnittstellen
===== Realisierte Schnittstellen
===== Verwendete Schnittstellen
==== Logisches Datenmodell
===== Benutzer ER-Diagramm
[mermaid, kontor-user-er, png]
.Benutzer ER-Diagramm
....
erDiagram
user {
string id PK
datetime created_date
datetime last_modified_date
int version
string email
boolean enabled
string firstName
string lastName
string password
string token
boolean tokenExpired
string userName UNIQUE
}
role {
string id PK
datetime created_date
datetime last_modified_date
int version
string name
}
authorization_matrix {
string id PK
datetime created_date
datetime last_modified_date
int version
string user_id FK
string role_id FK
}
module_data {
string id PK
datetime created_date
datetime last_modified_date
int version
boolean import_data
string module_name UNIQUE
}
user ||--o{ authorization_matrix : "matrix"
role ||--o{ authorization_matrix : "matrix"
....
===== Comics ER-Diagramm
[mermaid, kontor-comics-er, png]
.Comics ER-Diagramm
....
erDiagram
comic {
string id PK
datetime created_date
datetime last_modified_date
int version
boolean completed
boolean currentOrder
string title
string publisher_id FK
}
volume {
string id PK
datetime created_date
datetime last_modified_date
int version
string name
string comic_id FK
}
issue {
string id PK
datetime created_date
datetime last_modified_date
int version
boolean in_stock
boolean is_read
string issue_number
string comic_id FK
string volume_id FK
}
publisher {
string id PK
datetime created_date
datetime last_modified_date
int version
string name
}
artist {
string id PK
datetime created_date
datetime last_modified_date
int version
string name
}
story_arc {
string id PK
datetime created_date
datetime last_modified_date
int version
string name
string comic_id FK
}
trade_paperback {
string id PK
datetime created_date
datetime last_modified_date
int version
int issueStart
int issueEnd
string name
string comic_id FK
}
worktype {
string id PK
datetime created_date
datetime last_modified_date
int version
string name
}
comic_work {
string id PK
datetime created_date
datetime last_modified_date
int version
string artist_id FK
string comic_id FK
string worktype_id FK
}
comic ||--o{ comic_work : "1"
artist ||--o{ comic_work : "1"
worktype ||--o{ comic-work : "1"
publisher ||--o{ comic : "1"
comic ||--o{ issue : "1"
comic ||--o{ volume : "1"
comic ||--o{ story_arc : "1"
comic ||--o{ trade_paperback : "1"
volume ||--o{ issue : "1"
....
===== TYSC ER-Diagramm
[mermaid, kontor-tysc-er, png]
.TYSC ER-Diagramm
....
erDiagram
sport {
string id PK
datetime created_date
datetime last_modified_date
int version
string name
}
team {
string id PK
datetime created_date
datetime last_modified_date
int version
string name
string short_name
string sport_id FK
}
field_position {
string id PK
datetime created_date
datetime last_modified_date
int version
string name
string short_name
string sport_id FK
}
rooster {
string id PK
datetime created_date
datetime last_modified_date
int version
int year
string player_id FK
string position_id FK
string team_id FK
}
player {
string id PK
datetime created_date
datetime last_modified_date
int version
string first_name
string last_name
}
vendor {
string id PK
datetime created_date
datetime last_modified_date
int version
string name
}
card_set {
string id PK
datetime created_date
datetime last_modified_date
int version
boolean insert_set
string name
boolean parallel_set
string vendor_id FK
}
card {
string id PK
datetime created_date
datetime last_modified_date
int version
int cardNumber
int year
string card_set FK
string rooster_id FK
string vendor_id FK
}
sport ||--o{ team : "1"
sport ||--o{ field_position : "1"
field_position ||--o{ rooster : "1"
player ||--o{ rooster : "1"
team ||--o{ rooster : "1"
vendor ||--o{ card : "1"
card_set ||--o{ card : "1"
rooster ||--o{ card : "1"
....
===== Bookshelf ER-Diagramm
[mermaid, kontor-bookshelf-er, png]
.Bookshelf ER-Diagramm
....
erDiagram
article {
string id PK
datetime created_date
datetime last_modified_date
int version
string title
}
book {
string id PK
datetime created_date
datetime last_modified_date
int version
string isbn UNIQUE
string title
int year
string publisher_id FK
}
bookshelf_publisher {
string id PK
datetime created_date
datetime last_modified_date
int version
string name UNIQUE
}
author {
string id PK
datetime created_date
datetime last_modified_date
int version
string first_name
string last_name
}
article_author {
string id PK
datetime created_date
datetime last_modified_date
int version
string article_id FK
string author_id FK
}
book_author {
string id PK
datetime created_date
datetime last_modified_date
int version
string book_id FK
string author_id FK
}
publisher ||--o{ book : "1"
article ||--o{ article_author : "1"
author ||--o{ article_author : "1"
book ||--o{ book_author : "1"
author ||--o{ book_author : "1"
....
===== Mail ER-Diagramm
[mermaid, kontor-mail-er, png]
.Mail ER-Diagramm
....
erDiagram
mail {
string id PK
datetime created_date
datetime last_modified_date
int version
string subject
string content
datetime received_date
datetime sent_date
}
mail_account {
string id PK
datetime created_date
datetime last_modified_date
int version
string host
string password
int port
string protocol
boolean start_tls
string user_name
}
mail_address {
string id PK
datetime created_date
datetime last_modified_date
int version
string internet_address UNIQUE
string personal
string user_id FK
}
user ||--o{ mail_address : "1"
....
==== Einschränkungen
== Anforderungen der Domäne
=== Systemfunktionen
==== Anwendungsfälle
==== Akteure
==== Zielgruppen
=== Anforderungen
==== Anforderungen an externe Schnittstellen
==== Funktionale Anforderungen
==== Qualitätsanforderungen
==== Randbedingungen
==== Weitere Anforderungen
==== Wartungs- und Supportinformationen
=== Verifikation
== Projektbeschreibung
=== Ausgangslage
//==== Rechtliche Vorgaben und Rahmenbedingungen
//=== Rahmenbedingungen
//==== Vorhandene Regelungen
=== Projektziele
=== Projektabgrenzung
//=== Voraussichtliche Kosten
//=== Projektrisiken
//==== Produktivität
//==== Finanzielle Risiken
//==== Akzeptanz
== Projektorganisation
=== Projekt-Aufbauorganisation
=== Rollendefinition
//==== Projektauftraggeber
//==== Projektausschuss
//==== Beratung / Qualitätssicherung
==== Projekteiter
==== Projektteam
==== Liste der Stakeholder
=== Projektablauforganisation
==== Projekt-Phasen
===== Erstellung der Projektdokumentation
== Verschiedenes
=== Erreichbarkeiten
[bibliography]
== Referenzen
- [[[asciidoctor]]] http://asciidoctor.org
- [[[gitea]]] http://www.gitea.org
- [[[gradle]]] http://www.gradle.org
- [[[jenkins]]] http://jenkins-ci.org
[glossary]
== Glossar
[index]
== Index
== Verzeichnisse
=== Abbildungsverzeichnis
=== Tabellenverzeichnis
<<Table-1, Tabelle 1>> <<Table-1>>
@@ -0,0 +1,41 @@
package de.thpeetz.kontor.comics.views;
import static org.junit.jupiter.api.Assertions.*;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.vaadin.flow.component.grid.Grid;
import de.thpeetz.kontor.comics.data.Artist;
@SpringBootTest
class ArtistViewTest {
@Autowired
private ArtistView artistView;
@Test
void formShownWhenArtistSelected() {
Grid<Artist> grid = artistView.getGrid();
Artist firstArtist = getFirstItem(grid);
ArtistForm form = artistView.getForm();
assertFalse(form.isVisible());
grid.asSingleSelect().setValue(firstArtist);
assertTrue(form.isVisible());
assertEquals(firstArtist.getName(), form.name.getValue());
}
private Artist getFirstItem(Grid<Artist> grid) {
int count = grid.getListDataView().getItemCount();
List<Artist> artists = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(5, count);
return artists.get(0);
}
}
@@ -0,0 +1,63 @@
package de.thpeetz.kontor.comics.views;
import static org.junit.jupiter.api.Assertions.*;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import de.thpeetz.kontor.comics.data.Artist;
@SpringBootTest
class ArtistformTest {
private Artist artist1;
private static final String ARTISTNAME= "Lee, Stan";
@BeforeEach
void setupData() {
artist1 = new Artist();
artist1.setName(ARTISTNAME);
}
@Test
void formFieldsPopulated() {
ArtistForm form = new ArtistForm();
form.setArtist(artist1);
assertEquals(ARTISTNAME, form.name.getValue());
}
@Test
void saveEventHasCorrectValues() {
ArtistForm form = new ArtistForm();
Artist artist = new Artist();
form.setArtist(artist);
form.name.setValue(ARTISTNAME);
AtomicReference<Artist> savedArtistReference = new AtomicReference<>(null);
form.addSaveListener(e -> {
savedArtistReference.set(e.getArtist());
});
form.save.click();
Artist savedArtist = savedArtistReference.get();
assertEquals(ARTISTNAME, savedArtist.getName());
}
@Test
void deleteEventHasCorrectValues() {
ArtistForm form = new ArtistForm();
Artist artist = new Artist();
form.setArtist(artist);
form.name.setValue(ARTISTNAME);
AtomicReference<Artist> deletedArtistReference = new AtomicReference<>(null);
form.addDeleteListener(e -> {
deletedArtistReference.set(e.getArtist());
});
form.delete.click();
Artist deletedArtist = deletedArtistReference.get();
assertEquals(ARTISTNAME, deletedArtist.getName());
}
}
@@ -0,0 +1,43 @@
package de.thpeetz.kontor.comics.views;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.vaadin.flow.component.grid.Grid;
import de.thpeetz.kontor.comics.data.Comic;
@SpringBootTest
public class ComicViewTest {
@Autowired
private ComicView comicView;
@Test
void formShownWhenComicSelected() {
Grid<Comic> grid = comicView.getGrid();
Comic firstComic = getFirstItem(grid);
ComicForm form = comicView.getForm();
assertFalse(form.isVisible());
grid.asSingleSelect().setValue(firstComic);
assertTrue(form.isVisible());
assertEquals(firstComic.getTitle(), form.title.getValue());
}
private Comic getFirstItem(Grid<Comic> grid) {
int count = grid.getListDataView().getItemCount();
List<Comic> comics = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(169, count);
return comics.get(0);
}
}
@@ -0,0 +1,43 @@
package de.thpeetz.kontor.comics.views;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.vaadin.flow.component.grid.Grid;
import de.thpeetz.kontor.comics.data.ComicWork;
@SpringBootTest
class ComicWorkViewTest {
@Autowired
private ComicWorkView comicWorkView;
@Test
void formShownWhenComicSelected() {
Grid<ComicWork> grid = comicWorkView.getGrid();
ComicWork firstComicWork = getFirstItem(grid);
ComicWorkForm form = comicWorkView.getForm();
assertFalse(form.isVisible());
grid.asSingleSelect().setValue(firstComicWork);
assertTrue(form.isVisible());
assertEquals(firstComicWork.getComic(), form.comic.getValue());
}
private ComicWork getFirstItem(Grid<ComicWork> grid) {
int count = grid.getListDataView().getItemCount();
List<ComicWork> comicWorks = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(18, count);
return comicWorks.get(0);
}
}
@@ -0,0 +1,43 @@
package de.thpeetz.kontor.comics.views;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.vaadin.flow.component.grid.Grid;
import de.thpeetz.kontor.comics.data.Issue;
@SpringBootTest
public class IssueViewTest {
@Autowired
private IssueView issueView;
@Test
void formShownWhenIssueSelected() {
Grid<Issue> grid = issueView.getGrid();
Issue firstIssue = getFirstItem(grid);
IssueForm form = issueView.getForm();
assertFalse(form.isVisible());
grid.asSingleSelect().setValue(firstIssue);
assertTrue(form.isVisible());
assertEquals(firstIssue.getIssueNumber(), form.issueNumber.getValue());
}
private Issue getFirstItem(Grid<Issue> grid) {
int count = grid.getListDataView().getItemCount();
List<Issue> issues = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(750, count);
return issues.get(0);
}
}
@@ -0,0 +1,41 @@
package de.thpeetz.kontor.comics.views;
import static org.junit.jupiter.api.Assertions.*;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.vaadin.flow.component.grid.Grid;
import de.thpeetz.kontor.comics.data.Publisher;
@SpringBootTest
class PublisherViewTest {
@Autowired
private PublisherView publisherView;
@Test
void formShownWhenPublisherSelected() {
Grid<Publisher> grid = publisherView.getGrid();
Publisher firstPublisher = getFirstItem(grid);
PublisherForm form = publisherView.getForm();
assertFalse(form.isVisible());
grid.asSingleSelect().setValue(firstPublisher);
assertTrue(form.isVisible());
assertEquals(firstPublisher.getName(), form.name.getValue());
}
private Publisher getFirstItem(Grid<Publisher> grid) {
int count = grid.getListDataView().getItemCount();
List<Publisher> publishers = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(18, count);
return publishers.get(0);
}
}
@@ -0,0 +1,43 @@
package de.thpeetz.kontor.comics.views;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.vaadin.flow.component.grid.Grid;
import de.thpeetz.kontor.comics.data.StoryArc;
@SpringBootTest
class StoryArcViewTest {
@Autowired
private StoryArcView storyArcView;
@Test
void formShownWhenStoryArcSelected() {
Grid<StoryArc> grid = storyArcView.getGrid();
StoryArc firstStoryArc = getFirstItem(grid);
StoryArcForm form = storyArcView.getForm();
assertFalse(form.isVisible());
grid.asSingleSelect().setValue(firstStoryArc);
assertTrue(form.isVisible());
assertEquals(firstStoryArc.getName(), form.name.getValue());
}
private StoryArc getFirstItem(Grid<StoryArc> grid) {
int count = grid.getListDataView().getItemCount();
List<StoryArc> storyArcs = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(3, count);
return storyArcs.get(0);
}
}
@@ -0,0 +1,47 @@
package de.thpeetz.kontor.comics.views;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.vaadin.flow.component.grid.Grid;
import de.thpeetz.kontor.comics.data.TradePaperback;
import de.thpeetz.kontor.comics.data.Volume;
@SpringBootTest
class TradePaperbackViewTest {
@Autowired
private TradePaperbackView tradePaperbackView;
@Test
void formShownWhenVolumeSelected() {
Grid<TradePaperback> grid = tradePaperbackView.getGrid();
TradePaperback firstTradePaperback = getFirstItem(grid);
TradePaperBackForm form = tradePaperbackView.getForm();
assertFalse(form.isVisible());
if (firstTradePaperback != null) {
grid.asSingleSelect().setValue(firstTradePaperback);
assertTrue(form.isVisible());
assertEquals(firstTradePaperback.getName(), form.name.getValue());
}
}
private TradePaperback getFirstItem(Grid<TradePaperback> grid) {
int count = grid.getListDataView().getItemCount();
List<TradePaperback> tradePaperbacks = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(40, count);
return tradePaperbacks.get(0);
}
}
@@ -0,0 +1,50 @@
package de.thpeetz.kontor.comics.views;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.vaadin.flow.component.grid.Grid;
import de.thpeetz.kontor.comics.data.Volume;
@SpringBootTest
class VolumeViewTest {
@Autowired
private VolumeView volumeView;
@Test
void formShownWhenVolumeSelected() {
Grid<Volume> grid = volumeView.getGrid();
Volume firstVolume = getFirstItem(grid);
VolumeForm form = volumeView.getForm();
assertFalse(form.isVisible());
if (firstVolume != null) {
grid.asSingleSelect().setValue(firstVolume);
assertTrue(form.isVisible());
assertEquals(firstVolume.getName(), form.name.getValue());
}
}
private Volume getFirstItem(Grid<Volume> grid) {
int count = grid.getListDataView().getItemCount();
List<Volume> volumes = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(0, count);
if (count > 0) {
return volumes.get(0);
} else {
return null;
}
}
}
@@ -0,0 +1,49 @@
package de.thpeetz.kontor.comics.views;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.vaadin.flow.component.grid.Grid;
import de.thpeetz.kontor.comics.data.Worktype;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@SpringBootTest
class WorktypeViewTest {
@Autowired
private WorktypeView worktypeView;
@Test
void formShownWhenWorktypeSelected() {
Grid<Worktype> grid = worktypeView.getGrid();
Worktype firstWorktype = getFirstItem(grid);
WorktypeForm form = worktypeView.getForm();
assertFalse(form.isVisible());
if (firstWorktype != null) {
grid.asSingleSelect().setValue(firstWorktype);
assertTrue(form.isVisible());
assertEquals(firstWorktype.getName(), form.name.getValue());
}
}
private Worktype getFirstItem(Grid<Worktype> grid) {
int count = grid.getListDataView().getItemCount();
List<Worktype> worktypes = grid.getListDataView().getItems().collect(Collectors.toList());
log.info("found worktypes: {}", worktypes);
assertEquals(3, count);
return worktypes.get(0);
}
}
@@ -0,0 +1,43 @@
package de.thpeetz.kontor.tysc.views;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.vaadin.flow.component.grid.Grid;
import de.thpeetz.kontor.tysc.data.CardSet;
@SpringBootTest
class CardSetViewTest {
@Autowired
private CardSetView cardSetView;
@Test
void formShownWhenCardSetSelected() {
Grid<CardSet> grid = cardSetView.getGrid();
CardSet firstCardSet = getFirstItem(grid);
CardSetForm form = cardSetView.getForm();
assertFalse(form.isVisible());
grid.asSingleSelect().setValue(firstCardSet);
assertTrue(form.isVisible());
assertEquals(firstCardSet.getName(), form.name.getValue());
}
private CardSet getFirstItem(Grid<CardSet> grid) {
int count = grid.getListDataView().getItemCount();
List<CardSet> cardSets = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(15, count);
return cardSets.get(0);
}
}
@@ -0,0 +1,43 @@
package de.thpeetz.kontor.tysc.views;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.vaadin.flow.component.grid.Grid;
import de.thpeetz.kontor.tysc.data.Card;
@SpringBootTest
class CardViewTest {
@Autowired
private CardView cardView;
@Test
void formShownWhenCardSelected() {
Grid<Card> grid = cardView.getGrid();
Card firstCard = getFirstItem(grid);
CardForm form = cardView.getForm();
assertFalse(form.isVisible());
grid.asSingleSelect().setValue(firstCard);
assertTrue(form.isVisible());
assertEquals(String.valueOf(firstCard.getCardNumber()), form.cardNumber.getValue());
}
private Card getFirstItem(Grid<Card> grid) {
int count = grid.getListDataView().getItemCount();
List<Card> cards = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(10, count);
return cards.get(0);
}
}
@@ -0,0 +1,43 @@
package de.thpeetz.kontor.tysc.views;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.vaadin.flow.component.grid.Grid;
import de.thpeetz.kontor.tysc.data.FieldPosition;
@SpringBootTest
class FieldPositionViewTest {
@Autowired
private PositionView positionView;
@Test
void formShownWhenPositionSelected() {
Grid<FieldPosition> grid = positionView.getGrid();
FieldPosition firstFieldPosition = getFirstItem(grid);
PositionForm form = positionView.getForm();
assertFalse(form.isVisible());
grid.asSingleSelect().setValue(firstFieldPosition);
assertTrue(form.isVisible());
assertEquals(firstFieldPosition.getName(), form.name.getValue());
}
private FieldPosition getFirstItem(Grid<FieldPosition> grid) {
int count = grid.getListDataView().getItemCount();
List<FieldPosition> positions = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(44, count);
return positions.get(0);
}
}
@@ -0,0 +1,44 @@
package de.thpeetz.kontor.tysc.views;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.vaadin.flow.component.grid.Grid;
import de.thpeetz.kontor.tysc.data.Player;
@SpringBootTest
class PlayerViewTest {
@Autowired
private PlayerView playerView;
@Test
void formShownWhenPlayerSelected() {
Grid<Player> grid = playerView.getGrid();
Player firstPlayer = getFirstItem(grid);
PlayerForm form = playerView.getForm();
assertFalse(form.isVisible());
grid.asSingleSelect().setValue(firstPlayer);
assertTrue(form.isVisible());
assertEquals(firstPlayer.getLastName(), form.lastName.getValue());
assertEquals(firstPlayer.getFirstName(), form.firstName.getValue());
}
private Player getFirstItem(Grid<Player> grid) {
int count = grid.getListDataView().getItemCount();
List<Player> players = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(38, count);
return players.get(0);
}
}
@@ -0,0 +1,43 @@
package de.thpeetz.kontor.tysc.views;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.vaadin.flow.component.grid.Grid;
import de.thpeetz.kontor.tysc.data.Rooster;
@SpringBootTest
class RoosterViewTest {
@Autowired
private RoosterView roosterView;
@Test
void formShownWhenRoosterSelected() {
Grid<Rooster> grid = roosterView.getGrid();
Rooster firstRooster = getFirstItem(grid);
RoosterForm form = roosterView.getForm();
assertFalse(form.isVisible());
grid.asSingleSelect().setValue(firstRooster);
assertTrue(form.isVisible());
assertEquals(firstRooster.getYear(), form.year.getValue());
}
private Rooster getFirstItem(Grid<Rooster> grid) {
int count = grid.getListDataView().getItemCount();
List<Rooster> roosters = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(11, count);
return roosters.get(0);
}
}
@@ -0,0 +1,43 @@
package de.thpeetz.kontor.tysc.views;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.vaadin.flow.component.grid.Grid;
import de.thpeetz.kontor.tysc.data.Sport;
@SpringBootTest
class SportViewTest {
@Autowired
private SportView sportView;
@Test
void formShownWhenSportSelected() {
Grid<Sport> grid = sportView.getGrid();
Sport firstSport = getFirstItem(grid);
SportForm form = sportView.getForm();
assertFalse(form.isVisible());
grid.asSingleSelect().setValue(firstSport);
assertTrue(form.isVisible());
assertEquals(firstSport.getName(), form.name.getValue());
}
private Sport getFirstItem(Grid<Sport> grid) {
int count = grid.getListDataView().getItemCount();
List<Sport> sports = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(4, count);
return sports.get(0);
}
}
@@ -0,0 +1,43 @@
package de.thpeetz.kontor.tysc.views;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.vaadin.flow.component.grid.Grid;
import de.thpeetz.kontor.tysc.data.Team;
@SpringBootTest
class TeamViewTest {
@Autowired
private TeamView teamView;
@Test
void formShownWhenTeamSelected() {
Grid<Team> grid = teamView.getGrid();
Team firstTeam = getFirstItem(grid);
TeamForm form = teamView.getForm();
assertFalse(form.isVisible());
grid.asSingleSelect().setValue(firstTeam);
assertTrue(form.isVisible());
assertEquals(firstTeam.getName(), form.name.getValue());
}
private Team getFirstItem(Grid<Team> grid) {
int count = grid.getListDataView().getItemCount();
List<Team> teams = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(122, count);
return teams.get(0);
}
}
@@ -0,0 +1,43 @@
package de.thpeetz.kontor.tysc.views;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.vaadin.flow.component.grid.Grid;
import de.thpeetz.kontor.tysc.data.Vendor;
@SpringBootTest
class VendorViewTest {
@Autowired
private VendorView vendorView;
@Test
void formShownWhenVendorSelected() {
Grid<Vendor> grid = vendorView.getGrid();
Vendor firstVendor = getFirstItem(grid);
VendorForm form = vendorView.getForm();
assertFalse(form.isVisible());
grid.asSingleSelect().setValue(firstVendor);
assertTrue(form.isVisible());
assertEquals(firstVendor.getName(), form.name.getValue());
}
private Vendor getFirstItem(Grid<Vendor> grid) {
int count = grid.getListDataView().getItemCount();
List<Vendor> vendors = grid.getListDataView().getItems().collect(Collectors.toList());
assertEquals(9, count);
return vendors.get(0);
}
}
@@ -0,0 +1,30 @@
server.port=8085
spring.hibernate.dialect=org.hibernate.dialect.HSQLDialect
spring.jpa.database-platform=org.hibernate.dialect.HSQLDialect
spring.datasource.driverClassName=org.hsqldb.jdbc.JDBCDriver
spring.datasource.url=jdbc:hsqldb:mem:itDb
spring.datasource.username=sa
spring.datasource.password=sa
#spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect
#spring.datasource.driverClassName=org.sqlite.JDBC
#spring.datasource.url=jdbc:sqlite:file:./kontorITDb?cache=shared
#spring.datasource.username=sa
#spring.datasource.password=sa
spring.jpa.defer-datasource-initialization = true
#spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=false
spring.sql.init.mode=always
spring.mustache.check-template-location = false
logging.level.org.atmosphere=INFO
logging.level.org.springframework.web=INFO
logging.level.guru.springframework.controllers=DEBUG
logging.level.org.hibernate=INFO
logging.level.de.thpeetz=DEBUG
jwt.auth.secret=J6GOtcwC2NJI1l0VkHu20PacPFGTxpirBxWwynoHjsc=
@@ -0,0 +1,24 @@
package de.thpeetz.kontor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import com.vaadin.flow.component.page.AppShellConfigurator;
import com.vaadin.flow.server.PWA;
import com.vaadin.flow.theme.Theme;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@Slf4j
@EnableJpaAuditing
@SpringBootApplication
@Theme(value = "kontor")
@PWA(name = "Vaadin Kontor", shortName = "Kontor", offlinePath = "offline.html", offlineResources = { "images/offline.png" })
public class Application implements AppShellConfigurator {
public static void main(String[] args) {
log.info("Starting Kontor application");
SpringApplication.run(Application.class);
}
}
@@ -0,0 +1,53 @@
package de.thpeetz.kontor.admin;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.sidenav.SideNavItem;
import com.vaadin.flow.router.RouterLink;
import de.thpeetz.kontor.admin.views.*;
import de.thpeetz.kontor.comics.ComicConstants;
import de.thpeetz.kontor.comics.views.ComicWorkView;
public class AdminConstants {
private AdminConstants() {
// private constructor to hide the implicit public one
}
public static final String ADMIN_TITLE = "Verwaltung";
public static final String AUTHORIZATION = "Berechtigungen";
public static final String AUTHORIZATION_ROUTE = "/admin/authorization";
public static final String DATA = "Daten";
public static final String METADATA_ROUTE = "admin/metadata";
public static final String ROLE = "Rollen";
public static final String ROLE_ROUTE = "/admin/role";
public static final String USER = "Benutzer";
public static final String USER_ROUTE = "/admin/user";
public static final String ADMIN = "admin";
public static final String ADMIN_ROUTE = "/admin";
public static RouterLink getUserNavigation() {
return new RouterLink(USER, UserView.class);
}
public static RouterLink getRoleNavigation() {
return new RouterLink(ROLE, RoleView.class);
}
public static RouterLink getAuthorizationNavigation() {
return new RouterLink(AUTHORIZATION, AuthorizationView.class);
}
public static SideNavItem getAdminNavigation() {
SideNavItem administration = new SideNavItem(ADMIN_TITLE, USER_ROUTE, VaadinIcon.GROUP.create());
administration.addItem(new SideNavItem(USER, USER_ROUTE, VaadinIcon.USERS.create()));
administration.addItem(new SideNavItem(ROLE, RoleView.class));
SideNavItem data = new SideNavItem(DATA, AUTHORIZATION_ROUTE, VaadinIcon.DATABASE.create());
data.addItem(new SideNavItem(ComicConstants.COMICWORK, ComicWorkView.class));
data.addItem(new SideNavItem(AUTHORIZATION, AuthorizationView.class));
data.addItem(new SideNavItem("Data Import", ModuleDataView.class));
data.addItem(new SideNavItem("Meta Data", MetaDataView.class));
administration.addItem(data);
return administration;
}
}
@@ -0,0 +1,78 @@
package de.thpeetz.kontor.admin;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "mail")
public class MailProperties {
private String protocol;
private String host;
private Integer port;
private String userName;
private String password;
private Boolean startTls;
public String getProtocol() {
return protocol;
}
public void setProtocol(String protocol) {
this.protocol = protocol;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public Boolean getStartTls() {
return startTls;
}
public void setStartTls(Boolean startTls) {
this.startTls = startTls;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("MailProperties{");
sb.append("protocol='").append(protocol).append('\'');
sb.append(", host='").append(host).append('\'');
sb.append(", port=").append(port);
sb.append(", starttls=").append(startTls);
sb.append(", userName='").append(userName).append('\'');
sb.append(", password='").append(password).append('\'');
sb.append('}');
return sb.toString();
}
}
@@ -0,0 +1,336 @@
package de.thpeetz.kontor.admin;
import de.thpeetz.kontor.admin.data.*;
import de.thpeetz.kontor.admin.services.AdminService;
import de.thpeetz.kontor.admin.services.MetaDataService;
import de.thpeetz.kontor.mailclient.data.MailAccount;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.List;
@Slf4j
@Component
public class SetupModuleAdmin implements ApplicationListener<ContextRefreshedEvent> {
boolean alreadySetup = false;
@Autowired
private UserRepository userRepository;
@Autowired
private AuthorizationMatrixRepository authorizationMatrixRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private MailAccountRepository mailAccountRepository;
@Autowired
private MailProperties mailProperties;
@Autowired
private AdminService adminService;
@Autowired
private MetaDataService metaDataService;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (alreadySetup) {
return;
}
// Create initial roles and users
Role adminRole = adminService.addRole("ROLE_ADMIN");
Role userRole = adminService.addRole("ROLE_USER");
List<User> users = userRepository.findAll();
if (users.isEmpty()) {
User adminUser = initAdminUser();
initMatrix(adminRole, adminUser);
initMatrix(userRole, adminUser);
}
log.info("MailProperties: {}", mailProperties);
initMail(mailProperties);
initMetaData();
}
private void initMail(MailProperties mailProperties) {
log.info("initMail: Host {} with User {}", mailProperties.getHost(), mailProperties.getUserName());
if (mailProperties.getHost() == null || mailProperties.getHost().isEmpty()) {
return;
}
boolean addMailAccount = false;
List<MailAccount> mailAccounts = mailAccountRepository.findAll();
if (mailAccounts.isEmpty()) {
addMailAccount = true;
}
for (MailAccount mailAccount : mailAccounts) {
String accountUser = mailAccount.getUserName();
String propertyUser = mailProperties.getUserName();
String accountHost = mailAccount.getHost();
String propertyHost = mailProperties.getHost();
if (propertyHost.equals(accountHost) && propertyUser.equals(accountUser)) {
log.debug("already configured: {}", mailAccount);
} else {
addMailAccount = true;
}
}
if (addMailAccount) {
log.info("add Mail Account: {}", mailProperties);
MailAccount mailAccount = new MailAccount();
mailAccount.setProtocol(mailProperties.getProtocol());
mailAccount.setHost(mailProperties.getHost());
mailAccount.setPort(mailProperties.getPort());
mailAccount.setUserName(mailProperties.getUserName());
mailAccount.setPassword(mailProperties.getPassword());
mailAccount.setStartTls(mailProperties.getStartTls());
mailAccountRepository.save(mailAccount);
}
}
private void initMatrix(Role role, User user) {
log.info("initMatrix: Role {} for User {}", role.getName(), user.getUserName());
Collection<AuthorizationMatrix> configuredRoles = authorizationMatrixRepository.findByUser(user);
if (configuredRoles.stream().anyMatch(matrix -> matrix.getRole().getId().equals(role.getId()))) {
log.info("Role {} already defined", role.getName());
} else {
log.info("add Role {} to User {}", role.getName(), user.getUserName());
final AuthorizationMatrix adminMatrix = new AuthorizationMatrix();
adminMatrix.setUser(user);
adminMatrix.setRole(role);
authorizationMatrixRepository.save(adminMatrix);
}
}
private User initAdminUser() {
log.info("initAdminUser");
User admin = userRepository.findByUserName("admin");
if (admin == null) {
log.info("User admin not found, will be created.");
admin = new User();
admin.setFirstName("Admin");
admin.setLastName("Administrator");
admin.setUserName("admin");
admin.setEmail("admin@example.org");
admin.setPassword(passwordEncoder.encode("admin"));
userRepository.save(admin);
}
return admin;
}
private void initMetaData() {
log.info("initMetaData");
MetaDataTable mediaArticleTable = metaDataService.getTable("media_article");
metaDataService.getColumn(mediaArticleTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1);
metaDataService.getColumn(mediaArticleTable, "created_date", "created", "TIMESTAMP", null, 2);
metaDataService.getColumn(mediaArticleTable, "last_modified_date", "modified", "TIMESTAMP", null, 3);
metaDataService.getColumn(mediaArticleTable, "version", "version", "LONG", null, 4);
metaDataService.getColumn(mediaArticleTable, "url", "link_url", "TEXT", "UNIQUE", 5);
metaDataService.getColumn(mediaArticleTable, "review", "review", "BOOLEAN", null, 6);
metaDataService.getColumn(mediaArticleTable, "title", "title", "TEXT", null, 7);
MetaDataTable mediaVideoTable = metaDataService.getTable("media_video");
metaDataService.getColumn(mediaVideoTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1);
metaDataService.getColumn(mediaVideoTable, "created_date", "created", "TIMESTAMP", null, 2);
metaDataService.getColumn(mediaVideoTable, "last_modified_date", "modified", "TIMESTAMP", null, 3);
metaDataService.getColumn(mediaVideoTable, "version", "version", "LONG", null, 4);
metaDataService.getColumn(mediaVideoTable, "url", "link_url", "TEXT", "UNIQUE", 5);
metaDataService.getColumn(mediaVideoTable, "review", "review", "BOOLEAN", null, 6);
metaDataService.getColumn(mediaVideoTable, "should_download", "should_download", "BOOLEAN", null, 7);
metaDataService.getColumn(mediaVideoTable, "title", "title", "TEXT", null, 8);
metaDataService.getColumn(mediaVideoTable, "file_name", "file_name", "TEXT", null, 9);
metaDataService.getColumn(mediaVideoTable, "path", "path", "TEXT", null, 10);
metaDataService.getColumn(mediaVideoTable, "cloud_link", "cloud_link", "TEXT", null, 11);
MetaDataTable mediaFileTable = metaDataService.getTable("media_file");
metaDataService.getColumn(mediaFileTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1);
metaDataService.getColumn(mediaFileTable, "created_date", "created", "TIMESTAMP", null, 2);
metaDataService.getColumn(mediaFileTable, "last_modified_date", "modified", "TIMESTAMP", null, 3);
metaDataService.getColumn(mediaFileTable, "version", "version", "LONG", null, 4);
metaDataService.getColumn(mediaFileTable, "url", "link_url", "TEXT", "UNIQUE", 5);
metaDataService.getColumn(mediaFileTable, "review", "review", "BOOLEAN", null, 6);
metaDataService.getColumn(mediaFileTable, "should_download", "should_download", "BOOLEAN", null, 7);
metaDataService.getColumn(mediaFileTable, "title", "title", "TEXT", null, 8);
metaDataService.getColumn(mediaFileTable, "file_name", "file_name", "TEXT", null, 9);
metaDataService.getColumn(mediaFileTable, "path", "path", "TEXT", null, 10);
metaDataService.getColumn(mediaFileTable, "cloud_link", "cloud_link", "TEXT", null, 11);
MetaDataTable artistTable = metaDataService.getTable("artist");
metaDataService.getColumn(artistTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1);
metaDataService.getColumn(artistTable, "created_date", "created", "TIMESTAMP", null, 2);
metaDataService.getColumn(artistTable, "last_modified_date", "modified", "TIMESTAMP", null, 3);
metaDataService.getColumn(artistTable, "version", "version", "LONG", null, 4);
metaDataService.getColumn(artistTable, "name", "name", "TEXT", "UNIQUE", 5);
MetaDataTable publisherTable = metaDataService.getTable("publisher");
metaDataService.getColumn(publisherTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1);
metaDataService.getColumn(publisherTable, "created_date", "created", "TIMESTAMP", null, 2);
metaDataService.getColumn(publisherTable, "last_modified_date", "modified", "TIMESTAMP", null, 3);
metaDataService.getColumn(publisherTable, "version", "version", "LONG", null, 4);
metaDataService.getColumn(publisherTable, "name", "name", "TEXT", "UNIQUE", 5);
MetaDataTable comicTable = metaDataService.getTable("comic");
metaDataService.getColumn(comicTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1);
metaDataService.getColumn(comicTable, "created_date", "created", "TIMESTAMP", null, 2);
metaDataService.getColumn(comicTable, "last_modified_date", "modified", "TIMESTAMP", null, 3);
metaDataService.getColumn(comicTable, "version", "version", "LONG", null, 4);
metaDataService.getColumn(comicTable, "completed", "completed", "BOOLEAN", null, 5);
metaDataService.getColumn(comicTable, "current_order", "current_order", "BOOLEAN", null, 6);
metaDataService.getColumn(comicTable, "title", "title", "TEXT", "UNIQUE", 7);
metaDataService.getColumn(comicTable, "publisher_id", "publisher_id", "TEXT", null, 8);
MetaDataTable issueTable = metaDataService.getTable("issue");
metaDataService.getColumn(issueTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1);
metaDataService.getColumn(issueTable, "created_date", "created", "TIMESTAMP", null, 2);
metaDataService.getColumn(issueTable, "last_modified_date", "modified", "TIMESTAMP", null, 3);
metaDataService.getColumn(issueTable, "version", "version", "LONG", null, 4);
metaDataService.getColumn(issueTable, "in_stock", "in_stock", "BOOLEAN", null, 5);
metaDataService.getColumn(issueTable, "is_read", "is_read", "BOOLEAN", null, 6);
metaDataService.getColumn(issueTable, "issue_number", "issue_number", "TEXT", null, 7);
metaDataService.getColumn(issueTable, "comic_id", "comic_id", "TEXT", null, 8);
metaDataService.getColumn(issueTable, "volume_id", "volume_id", "TEXT", null, 9);
MetaDataTable volumeTable = metaDataService.getTable("volume");
metaDataService.getColumn(volumeTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1);
metaDataService.getColumn(volumeTable, "created_date", "created", "TIMESTAMP", null, 2);
metaDataService.getColumn(volumeTable, "last_modified_date", "modified", "TIMESTAMP", null, 3);
metaDataService.getColumn(volumeTable, "version", "version", "LONG", null, 4);
metaDataService.getColumn(volumeTable, "name", "name", "TEXT", null, 5);
metaDataService.getColumn(volumeTable, "comic_id", "comic_id", "TEXT", null, 6);
MetaDataTable tpbTable = metaDataService.getTable("trade_paperback");
metaDataService.getColumn(tpbTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1);
metaDataService.getColumn(tpbTable, "created_date", "created", "TIMESTAMP", null, 2);
metaDataService.getColumn(tpbTable, "last_modified_date", "modified", "TIMESTAMP", null, 3);
metaDataService.getColumn(tpbTable, "version", "version", "LONG", null, 4);
metaDataService.getColumn(tpbTable, "issue_start", "issue_start", "LONG", null, 5);
metaDataService.getColumn(tpbTable, "issue_end", "issue_end", "LONG", null, 6);
metaDataService.getColumn(tpbTable, "name", "name", "TEXT", null, 7);
metaDataService.getColumn(tpbTable, "comic_id", "comic_id", "TEXT", null, 8);
MetaDataTable storyArcTable = metaDataService.getTable("story_arc");
metaDataService.getColumn(storyArcTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1);
metaDataService.getColumn(storyArcTable, "created_date", "created", "TIMESTAMP", null, 2);
metaDataService.getColumn(storyArcTable, "last_modified_date", "modified", "TIMESTAMP", null, 3);
metaDataService.getColumn(storyArcTable, "version", "version", "LONG", null, 4);
metaDataService.getColumn(storyArcTable, "name", "name", "TEXT", null, 5);
metaDataService.getColumn(storyArcTable, "comic_id", "comic_id", "TEXT", null, 6);
MetaDataTable worktypeTable = metaDataService.getTable("worktype");
metaDataService.getColumn(worktypeTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1);
metaDataService.getColumn(worktypeTable, "created_date", "created", "TIMESTAMP", null, 2);
metaDataService.getColumn(worktypeTable, "last_modified_date", "modified", "TIMESTAMP", null, 3);
metaDataService.getColumn(worktypeTable, "version", "version", "LONG", null, 4);
metaDataService.getColumn(worktypeTable, "name", "name", "TEXT", "UNIQUE", 5);
MetaDataTable comicworkTable = metaDataService.getTable("comic_work");
metaDataService.getColumn(comicworkTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1);
metaDataService.getColumn(comicworkTable, "created_date", "created", "TIMESTAMP", null, 2);
metaDataService.getColumn(comicworkTable, "last_modified_date", "modified", "TIMESTAMP", null, 3);
metaDataService.getColumn(comicworkTable, "version", "version", "LONG", null, 4);
metaDataService.getColumn(comicworkTable, "artist_id", "artist_id", "TEXT", null, 5);
metaDataService.getColumn(comicworkTable, "comic_id", "comic_id", "TEXT", null, 6);
metaDataService.getColumn(comicworkTable, "work_type_id", "work_type_id", "TEXT", null, 7);
MetaDataTable authorTable = metaDataService.getTable("author");
metaDataService.getColumn(authorTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1);
metaDataService.getColumn(authorTable, "created_date", "created", "TIMESTAMP", null, 2);
metaDataService.getColumn(authorTable, "last_modified_date", "modified", "TIMESTAMP", null, 3);
metaDataService.getColumn(authorTable, "version", "version", "LONG", null, 4);
metaDataService.getColumn(authorTable, "first_name", "first_name", "TEXT", null, 5);
metaDataService.getColumn(authorTable, "last_name", "last_name", "TEXT", null, 6);
MetaDataTable articleTable = metaDataService.getTable("article");
metaDataService.getColumn(articleTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1);
metaDataService.getColumn(articleTable, "created_date", "created", "TIMESTAMP", null, 2);
metaDataService.getColumn(articleTable, "last_modified_date", "modified", "TIMESTAMP", null, 3);
metaDataService.getColumn(articleTable, "version", "version", "LONG", null, 4);
metaDataService.getColumn(articleTable, "title", "title", "TEXT", "UNIQUE", 5);
MetaDataTable articleAuthorTable = metaDataService.getTable("article_author");
metaDataService.getColumn(articleAuthorTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1);
metaDataService.getColumn(articleAuthorTable, "created_date", "created", "TIMESTAMP", null, 2);
metaDataService.getColumn(articleAuthorTable, "last_modified_date", "modified", "TIMESTAMP", null, 3);
metaDataService.getColumn(articleAuthorTable, "version", "version", "LONG", null, 4);
metaDataService.getColumn(articleAuthorTable, "article_id", "article_id", "TEXT", null, 5);
metaDataService.getColumn(articleAuthorTable, "author_id", "author_id", "TEXT", null, 6);
MetaDataTable bookTable = metaDataService.getTable("book");
metaDataService.getColumn(bookTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1);
metaDataService.getColumn(bookTable, "created_date", "created", "TIMESTAMP", null, 2);
metaDataService.getColumn(bookTable, "last_modified_date", "modified", "TIMESTAMP", null, 3);
metaDataService.getColumn(bookTable, "version", "version", "LONG", null, 4);
metaDataService.getColumn(bookTable, "isbn", "isbn", "TEXT", "UNIQUE", 5);
metaDataService.getColumn(bookTable, "title", "title", "TEXT", null, 6);
metaDataService.getColumn(bookTable, "year", "year", "LONG", null, 7);
metaDataService.getColumn(bookTable, "publisher_id", "publisher_id", "TEXT", null, 8);
MetaDataTable bookAuthorTable = metaDataService.getTable("book_author");
metaDataService.getColumn(bookAuthorTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1);
metaDataService.getColumn(bookAuthorTable, "created_date", "created", "TIMESTAMP", null, 2);
metaDataService.getColumn(bookAuthorTable, "last_modified_date", "modified", "TIMESTAMP", null, 3);
metaDataService.getColumn(bookAuthorTable, "version", "version", "LONG", null, 4);
metaDataService.getColumn(bookAuthorTable, "author_id", "author_id", "TEXT", null, 5);
metaDataService.getColumn(bookAuthorTable, "book_id", "book_id", "TEXT", null, 6);
MetaDataTable bookshelfPublisherTable = metaDataService.getTable("bookshelf_publisher");
metaDataService.getColumn(bookshelfPublisherTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1);
metaDataService.getColumn(bookshelfPublisherTable, "created_date", "created", "TIMESTAMP", null, 2);
metaDataService.getColumn(bookshelfPublisherTable, "last_modified_date", "modified", "TIMESTAMP", null, 3);
metaDataService.getColumn(bookshelfPublisherTable, "version", "version", "LONG", null, 4);
metaDataService.getColumn(bookshelfPublisherTable, "name", "name", "TEXT", "UNIQUE", 5);
MetaDataTable sportTable = metaDataService.getTable("sport");
metaDataService.getColumn(sportTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1);
metaDataService.getColumn(sportTable, "created_date", "created", "TIMESTAMP", null, 2);
metaDataService.getColumn(sportTable, "last_modified_date", "modified", "TIMESTAMP", null, 3);
metaDataService.getColumn(sportTable, "version", "version", "LONG", null, 4);
metaDataService.getColumn(sportTable, "name", "name", "TEXT", "UNIQUE", 5);
MetaDataTable playerTable = metaDataService.getTable("player");
metaDataService.getColumn(playerTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1);
metaDataService.getColumn(playerTable, "created_date", "created", "TIMESTAMP", null, 2);
metaDataService.getColumn(playerTable, "last_modified_date", "modified", "TIMESTAMP", null, 3);
metaDataService.getColumn(playerTable, "version", "version", "LONG", null, 4);
metaDataService.getColumn(playerTable, "first_name", "first_name", "TEXT", null, 5);
metaDataService.getColumn(playerTable, "last_name", "last_name", "TEXT", null, 6);
MetaDataTable teamTable = metaDataService.getTable("team");
metaDataService.getColumn(teamTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1);
metaDataService.getColumn(teamTable, "created_date", "created", "TIMESTAMP", null, 2);
metaDataService.getColumn(teamTable, "last_modified_date", "modified", "TIMESTAMP", null, 3);
metaDataService.getColumn(teamTable, "version", "version", "LONG", null, 4);
metaDataService.getColumn(teamTable, "name", "name", "TEXT", "UNIQUE", 5);
metaDataService.getColumn(teamTable, "short_name", "short_name", "TEXT", null, 6);
metaDataService.getColumn(teamTable, "sport_id", "sport_id", "TEXT", null, 7);
MetaDataTable vendorTable = metaDataService.getTable("vendor");
metaDataService.getColumn(vendorTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1);
metaDataService.getColumn(vendorTable, "created_date", "created", "TIMESTAMP", null, 2);
metaDataService.getColumn(vendorTable, "last_modified_date", "modified", "TIMESTAMP", null, 3);
metaDataService.getColumn(vendorTable, "version", "version", "LONG", null, 4);
metaDataService.getColumn(vendorTable, "name", "name", "TEXT", "UNIQUE", 5);
MetaDataTable fieldPositionTable = metaDataService.getTable("field_position");
metaDataService.getColumn(fieldPositionTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1);
metaDataService.getColumn(fieldPositionTable, "created_date", "created", "TIMESTAMP", null, 2);
metaDataService.getColumn(fieldPositionTable, "last_modified_date", "modified", "TIMESTAMP", null, 3);
metaDataService.getColumn(fieldPositionTable, "version", "version", "LONG", null, 4);
metaDataService.getColumn(fieldPositionTable, "name", "name", "TEXT", null, 5);
metaDataService.getColumn(fieldPositionTable, "short_name", "short_name", "TEXT", null, 6);
metaDataService.getColumn(fieldPositionTable, "sport_id", "sport_id", "TEXT", null, 7);
MetaDataTable roosterTable = metaDataService.getTable("rooster");
metaDataService.getColumn(roosterTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1);
metaDataService.getColumn(roosterTable, "created_date", "created", "TIMESTAMP", null, 2);
metaDataService.getColumn(roosterTable, "last_modified_date", "modified", "TIMESTAMP", null, 3);
metaDataService.getColumn(roosterTable, "version", "version", "LONG", null, 4);
metaDataService.getColumn(roosterTable, "year", "year", "LONG", null, 5);
metaDataService.getColumn(roosterTable, "player_id", "player_id", "TEXT", null, 6);
metaDataService.getColumn(roosterTable, "position_id", "position_id", "TEXT", null, 7);
metaDataService.getColumn(roosterTable, "team_id", "team_id", "TEXT", null, 8);
MetaDataTable cardSetTable = metaDataService.getTable("card_set");
metaDataService.getColumn(cardSetTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1);
metaDataService.getColumn(cardSetTable, "created_date", "created", "TIMESTAMP", null, 2);
metaDataService.getColumn(cardSetTable, "last_modified_date", "modified", "TIMESTAMP", null, 3);
metaDataService.getColumn(cardSetTable, "version", "version", "LONG", null, 4);
metaDataService.getColumn(cardSetTable, "insert_set", "insert_set", "BOOLEAN", null, 5);
metaDataService.getColumn(cardSetTable, "parallel_set", "parallel_set", "BOOLEAN", null, 6);
metaDataService.getColumn(cardSetTable, "name", "name", "TEXT", null, 7);
metaDataService.getColumn(cardSetTable, "vendor_id", "vendor_id", "TEXT", null, 8);
MetaDataTable cardTable = metaDataService.getTable("card");
metaDataService.getColumn(cardTable, "id", "identifier", "TEXT", "PRIMARY KEY", 1);
metaDataService.getColumn(cardTable, "created_date", "created", "TIMESTAMP", null, 2);
metaDataService.getColumn(cardTable, "last_modified_date", "modified", "TIMESTAMP", null, 3);
metaDataService.getColumn(cardTable, "version", "version", "LONG", null, 4);
metaDataService.getColumn(cardTable, "card_number", "card_number", "LONG", null, 5);
metaDataService.getColumn(cardTable, "year", "year", "LONG", null, 6);
metaDataService.getColumn(cardTable, "card_set_id", "card_set_id", "TEXT", null, 7);
metaDataService.getColumn(cardTable, "rooster_id", "rooster_id", "TEXT", null, 8);
metaDataService.getColumn(cardTable, "vendor_id", "vendor_id", "TEXT", null, 9);
}
}
@@ -0,0 +1,35 @@
package de.thpeetz.kontor.admin.data;
import de.thpeetz.kontor.common.data.AbstractEntity;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@Entity
public class AuthorizationMatrix extends AbstractEntity {
@ManyToOne
@JoinColumn(name = "user_id")
@NotNull
private User user;
@ManyToOne
@JoinColumn(name = "role_id")
@NotNull
private Role role;
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("AuthorizationMatrix{");
sb.append("user=").append(user.getUserName());
sb.append(", role=").append(role.getName());
sb.append('}');
return sb.toString();
}
}
@@ -0,0 +1,13 @@
package de.thpeetz.kontor.admin.data;
import java.util.List;
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
public interface AuthorizationMatrixRepository extends JpaRepository<AuthorizationMatrix, String> {
List<AuthorizationMatrix> findByUser(User user);
List<AuthorizationMatrix> findByRole(Role role);
}
@@ -0,0 +1,8 @@
package de.thpeetz.kontor.admin.data;
import de.thpeetz.kontor.mailclient.data.MailAccount;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MailAccountRepository extends JpaRepository<MailAccount, String> {
}
@@ -0,0 +1,37 @@
package de.thpeetz.kontor.admin.data;
import de.thpeetz.kontor.common.data.AbstractEntity;
import jakarta.annotation.Nullable;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
@Entity
@Getter
@Setter
@Table(
indexes = @Index(columnList = "columnName, table_id"),
uniqueConstraints = @UniqueConstraint(columnNames = {"table_id", "columnOrder"})
)
public class MetaDataColumn extends AbstractEntity {
@NotNull
private String columnName;
private String columnSyncName;
private String columnType;
@Nullable
private String columnModifier;
private Integer columnOrder;
private Boolean isShown;
@ManyToOne
@JoinColumn(name = "table_id")
@NotNull
private MetaDataTable table;
}
@@ -0,0 +1,10 @@
package de.thpeetz.kontor.admin.data;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface MetaDataColumnRepository extends JpaRepository<MetaDataColumn, String> {
List<MetaDataColumn> findByTable(MetaDataTable table);
}
@@ -0,0 +1,26 @@
package de.thpeetz.kontor.admin.data;
import de.thpeetz.kontor.common.data.AbstractEntity;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
import java.util.LinkedList;
import java.util.List;
@Entity
@Getter
@Setter
@Table(
indexes = @Index(columnList = "tableName"),
uniqueConstraints = @UniqueConstraint(columnNames = {"tableName"})
)
public class MetaDataTable extends AbstractEntity {
@NotNull
private String tableName;
@OneToMany(fetch = FetchType.EAGER, mappedBy = "table")
private List<MetaDataColumn> tableColumns = new LinkedList<>();
}
@@ -0,0 +1,8 @@
package de.thpeetz.kontor.admin.data;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MetaDataTableRepository extends JpaRepository<MetaDataTable, String> {
MetaDataTable findByTableName(String tableName);
}
@@ -0,0 +1,25 @@
package de.thpeetz.kontor.admin.data;
import de.thpeetz.kontor.common.data.AbstractEntity;
import jakarta.persistence.Entity;
import jakarta.persistence.Index;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import jakarta.validation.constraints.NotEmpty;
import lombok.Getter;
import lombok.Setter;
@Entity
@Getter
@Setter
@Table(
indexes = @Index(columnList = "moduleName"),
uniqueConstraints = @UniqueConstraint(columnNames = {"moduleName"})
)
public class ModuleData extends AbstractEntity {
@NotEmpty
private String moduleName;
private Boolean importData;
}
@@ -0,0 +1,15 @@
package de.thpeetz.kontor.admin.data;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface ModuleDataRepository extends JpaRepository<ModuleData, String> {
@Query("select m from ModuleData m where lower(m.moduleName) like lower(concat('%', :searchTerm, '%')) ")
List<ModuleData> search(@Param("searchTerm") String searchTerm);
ModuleData findByModuleName(String moduleName);
}
@@ -0,0 +1,30 @@
package de.thpeetz.kontor.admin.data;
import java.util.List;
import de.thpeetz.kontor.common.data.AbstractEntity;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.OneToMany;
import jakarta.validation.constraints.NotEmpty;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
@Slf4j
@Getter
@Setter
@ToString
@Entity
public class Role extends AbstractEntity {
@NotEmpty
private String name;
@OneToMany(fetch = FetchType.EAGER, mappedBy = "role")
@Nullable
private List<AuthorizationMatrix> matrix;
}
@@ -0,0 +1,18 @@
package de.thpeetz.kontor.admin.data;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface RoleRepository extends JpaRepository<Role, String> {
@Query("select r from Role r " +
"where lower(r.name) like lower(concat('%', :searchTerm, '%')) ")
List<Role> search(@Param("searchTerm") String searchTerm);
@Query("select r from Role r " +
"where lower(r.name) like lower(:name) ")
Role findByName(@Param("name") String name);
}
@@ -0,0 +1,62 @@
package de.thpeetz.kontor.admin.data;
import de.thpeetz.kontor.common.data.AbstractEntity;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Index;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import jakarta.validation.constraints.NotEmpty;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import java.util.LinkedList;
import java.util.List;
@Slf4j
@Getter
@Setter
@ToString
@Entity
@Table(indexes = @Index(columnList = "userName"), uniqueConstraints = @UniqueConstraint(columnNames = {"userName"}))
public class User extends AbstractEntity {
private String firstName;
private String lastName;
@NotEmpty
private String userName;
private String email;
private String password;
private boolean enabled;
private boolean tokenExpired;
private String token;
@OneToMany(fetch = FetchType.EAGER, mappedBy = "user")
@Nullable
private List<AuthorizationMatrix> matrix = new LinkedList<>();
public String getFullName() {
StringBuilder fullNamBuilder = new StringBuilder();
if (firstName != null) {
fullNamBuilder.append(firstName);
}
if (lastName != null) {
if (fullNamBuilder.length() > 0) {
fullNamBuilder.append(" ");
}
fullNamBuilder.append(lastName);
}
return fullNamBuilder.toString();
}
}
@@ -0,0 +1,16 @@
package de.thpeetz.kontor.admin.data;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface UserRepository extends JpaRepository<User, String> {
@Query("select u from User u " +
"where lower(u.lastName) like lower(concat('%', :searchTerm, '%')) ")
List<User> search(@Param("searchTerm") String searchTerm);
User findByUserName(String userName);
}
@@ -0,0 +1,110 @@
package de.thpeetz.kontor.admin.services;
import java.util.Collection;
import java.util.List;
import org.springframework.stereotype.Service;
import de.thpeetz.kontor.admin.data.AuthorizationMatrix;
import de.thpeetz.kontor.admin.data.AuthorizationMatrixRepository;
import de.thpeetz.kontor.admin.data.Role;
import de.thpeetz.kontor.admin.data.RoleRepository;
import de.thpeetz.kontor.admin.data.User;
import de.thpeetz.kontor.admin.data.UserRepository;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class AdminService {
private final UserRepository userRepository;
private final RoleRepository roleRepository;
private final AuthorizationMatrixRepository authorizationMatrixRepository;
public AdminService(UserRepository userRepository, RoleRepository roleRepository,
AuthorizationMatrixRepository authorizationMatrixRepository) {
this.userRepository = userRepository;
this.roleRepository = roleRepository;
this.authorizationMatrixRepository = authorizationMatrixRepository;
}
public List<User> findAllUsers() {
return userRepository.findAll();
}
public List<Role> findAllRoles() {
return roleRepository.findAll();
}
public Collection<Role> findAllRoles(String stringFilter) {
if (stringFilter == null || stringFilter.isEmpty()) {
return roleRepository.findAll();
} else {
return roleRepository.search(stringFilter);
}
}
public Role addRole(String roleName) {
Role role = roleRepository.findByName(roleName);
if (role == null) {
log.info("Role {} was not found, will create it.", roleName);
role = new Role();
role.setName(roleName);
roleRepository.save(role);
}
return role;
}
public void saveRole(Role role) {
if (role == null) {
log.warn("Role is null. Can't save it.");
}
log.info("saveRole: role={}", role);
roleRepository.save(role);
}
public void deleteRole(Role role) {
if (role == null) {
log.warn("Role is null. Can't delete it.");
}
log.info("deleteRole: role={}", role);
roleRepository.delete(role);
}
public List<AuthorizationMatrix> findAllAuthorizationMatrices() {
return authorizationMatrixRepository.findAll();
}
public void saveAuthorizationMatrix(AuthorizationMatrix authorizationMatrix) {
if (authorizationMatrix == null) {
log.warn("AuthorizationMatrix is null. Can't save it.");
}
log.info("saveAuthorizationMatrix: authorizationMatrix={}", authorizationMatrix);
authorizationMatrixRepository.save(authorizationMatrix);
}
public void deleteAuthorizationMatrix(AuthorizationMatrix authorizationMatrix) {
if (authorizationMatrix == null) {
log.warn("AuthorizationMatrix is null. Can't delete it.");
}
log.info("deleteAuthorizationMatrix: authorizationMatrix={}", authorizationMatrix);
authorizationMatrixRepository.delete(authorizationMatrix);
}
public String getUserFullName(String userName) {
log.debug("get Fullname für user {}", userName);
User user = userRepository.findByUserName(userName);
if (user == null) {
log.info("keinen Eintrag für {} gefunden", userName);
return userName;
} else {
log.info("Voller Name des User {}: {}", userName, user.getFullName());
return user.getFullName();
}
}
public User getUser(String userName) {
log.debug("get User {}", userName);
return userRepository.findByUserName(userName);
}
}
@@ -0,0 +1,125 @@
package de.thpeetz.kontor.admin.services;
import de.thpeetz.kontor.admin.data.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Service("userDetailsService")
public class KontorUserDetailsService implements UserDetailsService {
private static SecureRandom random = new SecureRandom();
@Autowired
private UserRepository userRepository;
@Autowired
private RoleRepository roleRepository;
@Autowired
private AuthorizationMatrixRepository authorizationMatrixRepository;
public Collection<User> findAllUsers(String stringFilter) {
if (stringFilter == null || stringFilter.isEmpty()) {
return userRepository.findAll();
} else {
return userRepository.search(stringFilter);
}
}
public void saveUser(User user) {
if (user == null) {
log.warn("User is null. Can't save it.");
}
log.info("saveUser: user={}", user);
userRepository.save(user);
}
public void saveUser(User user, List<Role> roles) {
if (user == null) {
log.warn("User is null. Can't save it.");
}
log.info("First save user: {}", user);
user = userRepository.save(user);
List<Role> copy = roles.stream().collect(Collectors.toList());
List<AuthorizationMatrix> permissions = user.getMatrix();
permissions.forEach(matrix -> {
if (roles.contains(matrix.getRole())) {
log.info("Role {} already assigned", matrix.getRole());
copy.remove(matrix.getRole());
} else {
log.info("Role {} has to be removed", matrix.getRole());
authorizationMatrixRepository.delete(matrix);
}
});
log.info("remaining roles: {}", copy);
for (Role role : copy) {
AuthorizationMatrix matrix = new AuthorizationMatrix();
matrix.setUser(user);
matrix.setRole(role);
authorizationMatrixRepository.save(matrix);
}
}
public void deleteUser(User user) {
if (user == null) {
log.warn("User is null. Can't delete it.");
}
log.info("deleteUser: user={}", user);
userRepository.delete(user);
}
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
log.info("loadUserByUsername: userName={}", userName);
User user = userRepository.findByUserName(userName);
if (user == null) {
log.info("User not found");
return null;
}
Collection<? extends GrantedAuthority> authorities = getAuthorities(user);
log.info("User {} hat Rolen: {}", userName, authorities);
return new org.springframework.security.core.userdetails.User(user.getUserName(), user.getPassword(),
authorities);
}
private Collection<? extends GrantedAuthority> getAuthorities(User user) {
return authorizationMatrixRepository.findByUser(user).stream()
.map(matrix -> matrix.getRole().getName())
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
public String getRememberedUser(String id) {
log.info("getRememberedUser: id={}", id);
return "admin";
}
public String rememberUser(String username) {
String randomId = new BigInteger(130, random).toString(32);
log.info("rememberUser: username={}", username);
return randomId;
}
public void removeRememberedUser(String id) {
log.info("removeRememberedUser: id={}", id);
}
public List<Role> findAllRoles() {
return roleRepository.findAll();
}
}
@@ -0,0 +1,24 @@
package de.thpeetz.kontor.admin.services;
import de.thpeetz.kontor.mailclient.data.MailAccount;
import de.thpeetz.kontor.admin.data.MailAccountRepository;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class MailService {
private final MailAccountRepository mailAccountRepository;
public MailService(MailAccountRepository mailAccountRepository) {
this.mailAccountRepository = mailAccountRepository;
}
public List<MailAccount> findAllMailAccounts() {
return mailAccountRepository.findAll();
}
}
@@ -0,0 +1,101 @@
package de.thpeetz.kontor.admin.services;
import org.springframework.stereotype.Service;
import de.thpeetz.kontor.admin.data.MetaDataColumn;
import de.thpeetz.kontor.admin.data.MetaDataColumnRepository;
import de.thpeetz.kontor.admin.data.MetaDataTable;
import de.thpeetz.kontor.admin.data.MetaDataTableRepository;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
@Slf4j
@Service
public class MetaDataService {
private final MetaDataTableRepository metaDataTableRepository;
private final MetaDataColumnRepository metaDataColumnRepository;
public MetaDataService(MetaDataTableRepository metaDataTableRepository, MetaDataColumnRepository metaDataColumnRepository) {
this.metaDataTableRepository = metaDataTableRepository;
this.metaDataColumnRepository = metaDataColumnRepository;
}
public MetaDataTable getTable(String tableName) {
MetaDataTable table = metaDataTableRepository.findByTableName(tableName);
if (table == null) {
log.info("Metadata for table {} not found, will create it", tableName);
table = new MetaDataTable();
table.setTableName(tableName);
metaDataTableRepository.save(table);
}
return table;
}
public void getColumn(MetaDataTable table, String columnName, String columnSyncName, String columnType, String columnModifier, Integer columnOrder) {
if (table.getTableColumns().stream().anyMatch(column -> column.getColumnName().equals(columnName))) {
log.info("Column {} with name {} of table {} found, check Values", columnOrder, columnName, table.getTableName());
MetaDataColumn column = table.getTableColumns().get(columnOrder.intValue()-1);
if (!column.getColumnName().equals(columnName)) {
log.debug("columnName has to be changed to {}", columnName);
column.setColumnName(columnName);
}
if (!column.getColumnSyncName().equals(columnSyncName)) {
log.debug("columnSyncName has to be changed to {}", columnSyncName);
column.setColumnSyncName(columnSyncName);
}
if (!column.getColumnType().equals(columnType)) {
log.debug("columnType has to be changed to {}", columnType);
column.setColumnType(columnType);
}
if (columnModifier != null && !column.getColumnModifier().equals(columnModifier)) {
log.debug("columnModifier has to be changed to {}", columnModifier);
column.setColumnModifier(columnModifier);
}
if (column.getIsShown() == null) {
log.debug("isShown set to false");
column.setIsShown(Boolean.FALSE);
}
metaDataColumnRepository.save(column);
} else {
log.info("Column {} of table {} not found, will create it", columnName, table.getTableName());
MetaDataColumn column = new MetaDataColumn();
column.setTable(table);
column.setColumnName(columnName);
column.setColumnSyncName(columnSyncName);
column.setColumnType(columnType);
column.setColumnModifier(columnModifier);
column.setColumnOrder(columnOrder);
column.setIsShown(Boolean.FALSE);
metaDataColumnRepository.save(column);
}
}
public List<MetaDataColumn> findAllMetaDataColumns() {
return metaDataColumnRepository.findAll();
}
public void deleteMetaDataColumn(MetaDataColumn metaDataColumn) {
if (metaDataColumn == null) {
log.warn("MetaDataColumn is null, can't delete it");
return;
}
log.debug("deleteMetaDataColumn: MetaDataColumn={}", metaDataColumn);
metaDataColumnRepository.delete(metaDataColumn);
}
public void saveMetaDataColumn(MetaDataColumn metaDataColumn) {
if (metaDataColumn == null) {
log.warn("MetaDataColumn is null, can't save it");
return;
}
log.debug("saveMetaDataColumn: MetaDataColumn={}", metaDataColumn);
metaDataColumnRepository.save(metaDataColumn);
}
public List<MetaDataTable> findAllTables() {
return metaDataTableRepository.findAll();
}
}
@@ -0,0 +1,76 @@
package de.thpeetz.kontor.admin.services;
import de.thpeetz.kontor.admin.data.ModuleData;
import de.thpeetz.kontor.admin.data.ModuleDataRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
@Slf4j
@Service
public class ModuleService {
private final ModuleDataRepository moduleDataRepository;
public ModuleService(ModuleDataRepository moduleDataRepository) {
this.moduleDataRepository = moduleDataRepository;
}
public List<ModuleData> findAll(String stringFilter) {
if (stringFilter == null || stringFilter.isEmpty()) {
return moduleDataRepository.findAll();
} else {
return moduleDataRepository.search(stringFilter);
}
}
public ModuleData findByName(String moduleName) {
if (moduleName == null || moduleName.isEmpty()) {
return null;
} else {
return moduleDataRepository.findByModuleName(moduleName);
}
}
public boolean importData(String moduleName) {
ModuleData module = moduleDataRepository.findByModuleName(moduleName);
if (module != null) {
return module.getImportData();
} else {
log.info("Module {} not found, should import data", moduleName);
return true;
}
}
public void setDataImported(String moduleName) {
ModuleData module = moduleDataRepository.findByModuleName(moduleName);
if (module == null) {
log.info("Module {} not found, will create it", moduleName);
module = new ModuleData();
module.setModuleName(moduleName);
module.setImportData(false);
moduleDataRepository.save(module);
} else {
log.info("Module {} found, change import data", module);
module.setImportData(false);
moduleDataRepository.save(module);
}
}
public void saveModuleData(ModuleData moduleData) {
if (moduleData == null) {
log.warn("ModuleData is null, can't save it.");
} else {
moduleDataRepository.save(moduleData);
}
}
public void deleteModuleData(ModuleData moduleData) {
if (moduleData == null) {
log.warn("ModuleData is null, can't delete it.");
} else {
moduleDataRepository.delete(moduleData);
}
}
}
@@ -0,0 +1,36 @@
package de.thpeetz.kontor.admin.views;
import com.vaadin.flow.component.applayout.AppLayout;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.theme.lumo.LumoUtility;
import de.thpeetz.kontor.admin.AdminConstants;
import de.thpeetz.kontor.admin.services.AdminService;
import de.thpeetz.kontor.common.views.KontorLayoutUtil;
import de.thpeetz.kontor.security.SecurityService;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class AdminLayout extends AppLayout {
private final AdminService adminService;
private final SecurityService securityService;
public AdminLayout(AdminService adminService, SecurityService securityService) {
this.adminService = adminService;
this.securityService = securityService;
KontorLayoutUtil layout = new KontorLayoutUtil(this, adminService, securityService);
layout.setSecondaryNavigation(getSecondaryNavigation());
layout.createHeader(AdminConstants.ADMIN_TITLE);
}
private HorizontalLayout getSecondaryNavigation() {
HorizontalLayout navigation = new HorizontalLayout();
navigation.addClassNames(LumoUtility.JustifyContent.CENTER, LumoUtility.Gap.SMALL, LumoUtility.Height.MEDIUM);
navigation.add(AdminConstants.getUserNavigation(), AdminConstants.getRoleNavigation(),
AdminConstants.getAuthorizationNavigation());
return navigation;
}
}
@@ -0,0 +1,112 @@
package de.thpeetz.kontor.admin.views;
import java.util.List;
import com.vaadin.flow.component.ComponentEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.Binder;
import de.thpeetz.kontor.admin.data.AuthorizationMatrix;
import de.thpeetz.kontor.admin.data.Role;
import de.thpeetz.kontor.admin.data.User;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class AuthorizationForm extends FormLayout {
ComboBox<User> user = new ComboBox<>("User");
ComboBox<Role> role = new ComboBox<>("Role");
Button save = new Button("Save");
Button delete = new Button("Delete");
Button close = new Button("Cancel");
Binder<AuthorizationMatrix> binder = new BeanValidationBinder<>(AuthorizationMatrix.class);
public AuthorizationForm(List<User> users, List<Role> roles) {
addClassName("authorizationmatrix-form");
binder.bindInstanceFields(this);
user.setItems(users);
user.setItemLabelGenerator(User::getUserName);
role.setItems(roles);
role.setItemLabelGenerator(Role::getName);
add(user, role, createButtonsLayout());
}
private HorizontalLayout createButtonsLayout() {
save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
delete.addThemeVariants(ButtonVariant.LUMO_ERROR);
close.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
save.addClickShortcut(Key.ENTER);
close.addClickShortcut(Key.ESCAPE);
save.addClickListener(event -> validateAndSave());
delete.addClickListener(event -> fireEvent(new DeleteEvent(this, binder.getBean())));
close.addClickListener(event -> fireEvent(new CloseEvent(this)));
binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid()));
return new HorizontalLayout(save, delete, close);
}
private void validateAndSave() {
if (binder.isValid()) {
fireEvent(new SaveEvent(this, binder.getBean()));
}
}
public void setAuthorizationMatrix(AuthorizationMatrix authorizationMatrix) {
binder.setBean(authorizationMatrix);
}
public abstract static class AuthorizationFormEvent extends ComponentEvent<AuthorizationForm> {
private AuthorizationMatrix authorizationMatrix;
protected AuthorizationFormEvent(AuthorizationForm source, AuthorizationMatrix authorizationMatrix) {
super(source, false);
this.authorizationMatrix = authorizationMatrix;
}
public AuthorizationMatrix getAuthorizationMatrix() {
return authorizationMatrix;
}
}
public static class SaveEvent extends AuthorizationFormEvent {
SaveEvent(AuthorizationForm source, AuthorizationMatrix authorizationMatrix) {
super(source, authorizationMatrix);
}
}
public static class DeleteEvent extends AuthorizationFormEvent {
DeleteEvent(AuthorizationForm source, AuthorizationMatrix authorizationMatrix) {
super(source, authorizationMatrix);
}
}
public static class CloseEvent extends AuthorizationFormEvent {
CloseEvent(AuthorizationForm source) {
super(source, null);
}
}
public void addDeleteListener(ComponentEventListener<DeleteEvent> listener) {
addListener(DeleteEvent.class, listener);
}
public void addSaveListener(ComponentEventListener<SaveEvent> listener) {
addListener(SaveEvent.class, listener);
}
public void addCloseListener(ComponentEventListener<CloseEvent> listener) {
addListener(CloseEvent.class, listener);
}
}
@@ -0,0 +1,114 @@
package de.thpeetz.kontor.admin.views;
import org.springframework.context.annotation.Scope;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import de.thpeetz.kontor.admin.AdminConstants;
import de.thpeetz.kontor.admin.data.AuthorizationMatrix;
import de.thpeetz.kontor.admin.services.AdminService;
import de.thpeetz.kontor.common.views.MainLayout;
import jakarta.annotation.security.RolesAllowed;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@SpringComponent
@Scope("prototype")
@RolesAllowed("ROLE_ADMIN")
@Route(value = AdminConstants.AUTHORIZATION_ROUTE, layout = MainLayout.class)
@PageTitle("Authorization | Admin | Kontor")
public class AuthorizationView extends VerticalLayout {
Grid<AuthorizationMatrix> grid = new Grid<>(AuthorizationMatrix.class);
AuthorizationForm form;
AdminService service;
public AuthorizationView(AdminService service) {
this.service = service;
addClassName("authoriaztionmatrix-view");
setSizeFull();
configureGrid();
configureForm();
add(getToolbar(), getContent());
updateList();
}
private void configureGrid() {
grid.addClassName("authorizationmatrix-grid");
grid.setSizeFull();
grid.setColumns("user.userName", "role.name");
grid.getColumns().forEach(col -> col.setAutoWidth(true));
grid.asSingleSelect().addValueChangeListener(event -> editAuthorizationMatrix(event.getValue()));
}
private void configureForm() {
form = new AuthorizationForm(service.findAllUsers(), service.findAllRoles());
form.setWidth("25em");
form.addSaveListener(this::saveAuthorizationMatrix);
form.addDeleteListener(this::deleteAuthorizationMatrix);
form.addCloseListener(e -> closeEditor());
}
private void saveAuthorizationMatrix(AuthorizationForm.SaveEvent event) {
AuthorizationMatrix authorizationMatrix = event.getAuthorizationMatrix();
service.saveAuthorizationMatrix(authorizationMatrix);
updateList();
closeEditor();
}
private void deleteAuthorizationMatrix(AuthorizationForm.DeleteEvent event) {
service.deleteAuthorizationMatrix(event.getAuthorizationMatrix());
updateList();
closeEditor();
}
private Component getContent() {
HorizontalLayout content = new HorizontalLayout(grid, form);
content.setFlexGrow(2, grid);
content.setFlexGrow(1, form);
content.addClassName("content");
content.setSizeFull();
return content;
}
private HorizontalLayout getToolbar() {
Button addAuthorizationMaxtrixButton = new Button("Add permssion", click -> addAuthorizationMatrix());
HorizontalLayout toolbar = new HorizontalLayout(addAuthorizationMaxtrixButton);
toolbar.addClassName("toolbar");
return toolbar;
}
public void editAuthorizationMatrix(AuthorizationMatrix authorizationMatrix) {
if (authorizationMatrix == null) {
closeEditor();
} else {
form.setAuthorizationMatrix(authorizationMatrix);
form.setVisible(true);
addClassName("editing");
}
}
public void closeEditor() {
form.setAuthorizationMatrix(null);
form.setVisible(false);
removeClassName("editing");
}
private void addAuthorizationMatrix() {
grid.asSingleSelect().clear();
editAuthorizationMatrix(new AuthorizationMatrix());
}
private void updateList() {
grid.setItems(service.findAllAuthorizationMatrices());
}
}
@@ -0,0 +1,51 @@
package de.thpeetz.kontor.admin.views;
import org.springframework.security.core.context.SecurityContextHolder;
import com.vaadin.flow.component.html.H1;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.login.LoginForm;
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 com.vaadin.flow.server.auth.AnonymousAllowed;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Route("login")
@PageTitle("Login | Vaadin Kontor")
@AnonymousAllowed
public class LoginView extends VerticalLayout implements BeforeEnterObserver {
private final LoginForm login = new LoginForm();
public LoginView() {
addClassName("login-view");
setSizeFull();
setAlignItems(Alignment.CENTER);
setJustifyContentMode(JustifyContentMode.CENTER);
login.setAction("login");
add(new H1("Vaadin Kontor"));
add(new Span("Username: user, Password: password"));
add(new Span("Username: admin, Password: password"));
add(login);
}
@Override
public void beforeEnter(BeforeEnterEvent beforeEnterEvent) {
log.info("beforeEnter: {}", beforeEnterEvent.getLocation());
log.info("beforeEnter: {}", SecurityContextHolder.getContext().getAuthentication());
// inform the user about an authentication error
if (beforeEnterEvent.getLocation()
.getQueryParameters()
.getParameters()
.containsKey("error")) {
login.setError(true);
}
}
}
@@ -0,0 +1,113 @@
package de.thpeetz.kontor.admin.views;
import com.vaadin.flow.component.ComponentEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.checkbox.Checkbox;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.textfield.IntegerField;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.Binder;
import de.thpeetz.kontor.admin.data.MetaDataColumn;
import de.thpeetz.kontor.admin.data.MetaDataTable;
import java.util.List;
public class MetaDataForm extends FormLayout {
ComboBox<MetaDataTable> table = new ComboBox<>("Table");
TextField columnName = new TextField("Column Name");
TextField columnSyncName = new TextField("Column Sync Name");
TextField columnModifier = new TextField("Column Modifier");
IntegerField columnOrder = new IntegerField("Column Order");
Checkbox isShown = new Checkbox("Is Shown");
Button save = new com.vaadin.flow.component.button.Button("Save");
Button delete = new com.vaadin.flow.component.button.Button("Delete");
Button close = new Button("Cancel");
Binder<MetaDataColumn> binder = new BeanValidationBinder<>(MetaDataColumn.class);
public MetaDataForm(List<MetaDataTable> tables) {
addClassName("metaData-form");
binder.bindInstanceFields(this);
table.setItems(tables);
table.setItemLabelGenerator(MetaDataTable::getTableName);
add(table, columnName, columnSyncName, columnModifier, columnOrder, isShown, createButtonsLayout());
}
private HorizontalLayout createButtonsLayout() {
save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
delete.addThemeVariants(ButtonVariant.LUMO_ERROR);
close.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
save.addClickShortcut(Key.ENTER);
close.addClickShortcut(Key.ESCAPE);
save.addClickListener(event -> validateAndSave());
delete.addClickListener(event -> fireEvent(new MetaDataForm.DeleteEvent(this, binder.getBean())));
close.addClickListener(event -> fireEvent(new MetaDataForm.CloseEvent(this)));
binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid()));
return new HorizontalLayout(save, delete, close);
}
private void validateAndSave() {
if (binder.isValid()) {
fireEvent(new MetaDataForm.SaveEvent(this, binder.getBean()));
}
}
public void setMetaDataColumn(MetaDataColumn metaDataColumn) {
binder.setBean(metaDataColumn);
}
public abstract static class MetaDataFormEvent extends ComponentEvent<MetaDataForm> {
private MetaDataColumn metaDataColumn;
protected MetaDataFormEvent(MetaDataForm source, MetaDataColumn metaDataColumn) {
super(source, false);
this.metaDataColumn = metaDataColumn;
}
public MetaDataColumn getMetaDataColumn() {
return metaDataColumn;
}
}
public static class SaveEvent extends MetaDataForm.MetaDataFormEvent {
SaveEvent(MetaDataForm source, MetaDataColumn metaDataColumn) {
super(source, metaDataColumn);
}
}
public static class DeleteEvent extends MetaDataForm.MetaDataFormEvent {
DeleteEvent(MetaDataForm source, MetaDataColumn metaDataColumn) {
super(source, metaDataColumn);
}
}
public static class CloseEvent extends MetaDataForm.MetaDataFormEvent {
CloseEvent(MetaDataForm source) {
super(source, null);
}
}
public void addDeleteListener(ComponentEventListener<MetaDataForm.DeleteEvent> listener) {
addListener(MetaDataForm.DeleteEvent.class, listener);
}
public void addSaveListener(ComponentEventListener<MetaDataForm.SaveEvent> listener) {
addListener(MetaDataForm.SaveEvent.class, listener);
}
public void addCloseListener(ComponentEventListener<MetaDataForm.CloseEvent> listener) {
addListener(MetaDataForm.CloseEvent.class, listener);
}
}
@@ -0,0 +1,112 @@
package de.thpeetz.kontor.admin.views;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import de.thpeetz.kontor.admin.AdminConstants;
import de.thpeetz.kontor.admin.data.MetaDataColumn;
import de.thpeetz.kontor.admin.services.MetaDataService;
import de.thpeetz.kontor.common.views.MainLayout;
import jakarta.annotation.security.RolesAllowed;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Scope;
@Slf4j
@SpringComponent
@Scope("prototype")
@RolesAllowed("ROLE_ADMIN")
@Route(value = AdminConstants.METADATA_ROUTE, layout = MainLayout.class)
@PageTitle("Meta Data | Admin | Kontor")
public class MetaDataView extends VerticalLayout {
Grid<MetaDataColumn> grid = new Grid<>(MetaDataColumn.class);
MetaDataForm form;
MetaDataService service;
public MetaDataView(MetaDataService service) {
this.service = service;
addClassName("metadata-view");
setSizeFull();
configureGrid();
configureForm();
add(getToolbar(), getContent());
updateList();
}
private void configureGrid() {
grid.addClassName("metadata-grid");
grid.setSizeFull();
grid.setColumns("table.tableName", "columnName", "columnSyncName", "columnModifier", "columnOrder", "isShown");
grid.getColumns().forEach(col -> col.setAutoWidth(true));
grid.asSingleSelect().addValueChangeListener(event -> editMetaData(event.getValue()));
}
private void configureForm() {
form = new MetaDataForm(service.findAllTables());
form.setWidth("25em");
form.setVisible(false);
form.addSaveListener(this::saveMetaData);
form.addDeleteListener(this::deleteMetaData);
form.addCloseListener(e -> closeEditor());
}
private void saveMetaData(MetaDataForm.SaveEvent event) {
MetaDataColumn metaDataColumn = event.getMetaDataColumn();
service.saveMetaDataColumn(metaDataColumn);
updateList();
closeEditor();
}
private void deleteMetaData(MetaDataForm.DeleteEvent event) {
service.deleteMetaDataColumn(event.getMetaDataColumn());
updateList();
closeEditor();
}
private Component getContent() {
HorizontalLayout content = new HorizontalLayout(grid, form);
content.setFlexGrow(2, grid);
content.setFlexGrow(1, form);
content.addClassName("content");
content.setSizeFull();
return content;
}
private HorizontalLayout getToolbar() {
Button addAuthorizationMaxtrixButton = new Button("Add Meta Data", click -> addMetaDataColumn());
HorizontalLayout toolbar = new HorizontalLayout(addAuthorizationMaxtrixButton);
toolbar.addClassName("toolbar");
return toolbar;
}
public void editMetaData(MetaDataColumn metaDataColumn) {
if (metaDataColumn == null) {
closeEditor();
} else {
form.setMetaDataColumn(metaDataColumn);
form.setVisible(true);
addClassName("editing");
}
}
public void closeEditor() {
form.setMetaDataColumn(null);
form.setVisible(false);
removeClassName("editing");
}
private void addMetaDataColumn() {
grid.asSingleSelect().clear();
editMetaData(new MetaDataColumn());
}
private void updateList() {
grid.setItems(service.findAllMetaDataColumns());
}
}
@@ -0,0 +1,100 @@
package de.thpeetz.kontor.admin.views;
import com.vaadin.flow.component.ComponentEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.checkbox.Checkbox;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.Binder;
import de.thpeetz.kontor.admin.data.ModuleData;
public class ModuleDataForm extends FormLayout {
TextField moduleName = new TextField("Module Name");
Checkbox importData = new Checkbox("Import Data");
Button save = new com.vaadin.flow.component.button.Button("Save");
Button delete = new com.vaadin.flow.component.button.Button("Delete");
Button close = new Button("Cancel");
Binder<ModuleData> binder = new BeanValidationBinder<>(ModuleData.class);
public ModuleDataForm() {
addClassName("moduleData-form");
binder.bindInstanceFields(this);
add(moduleName, importData, createButtonsLayout());
}
private HorizontalLayout createButtonsLayout() {
save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
delete.addThemeVariants(ButtonVariant.LUMO_ERROR);
close.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
save.addClickShortcut(Key.ENTER);
close.addClickShortcut(Key.ESCAPE);
save.addClickListener(event -> validateAndSave());
delete.addClickListener(event -> fireEvent(new ModuleDataForm.DeleteEvent(this, binder.getBean())));
close.addClickListener(event -> fireEvent(new ModuleDataForm.CloseEvent(this)));
binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid()));
return new HorizontalLayout(save, delete, close);
}
private void validateAndSave() {
if (binder.isValid()) {
fireEvent(new ModuleDataForm.SaveEvent(this, binder.getBean()));
}
}
public void setModuleData(ModuleData moduleData) {
binder.setBean(moduleData);
}
public abstract static class ModuleDataFormEvent extends ComponentEvent<ModuleDataForm> {
private ModuleData moduleData;
protected ModuleDataFormEvent(ModuleDataForm source, ModuleData moduleData) {
super(source, false);
this.moduleData = moduleData;
}
public ModuleData getModuleData() {
return moduleData;
}
}
public static class SaveEvent extends ModuleDataForm.ModuleDataFormEvent {
SaveEvent(ModuleDataForm source, ModuleData moduleData) {
super(source, moduleData);
}
}
public static class DeleteEvent extends ModuleDataForm.ModuleDataFormEvent {
DeleteEvent(ModuleDataForm source, ModuleData moduleData) {
super(source, moduleData);
}
}
public static class CloseEvent extends ModuleDataForm.ModuleDataFormEvent {
CloseEvent(ModuleDataForm source) {
super(source, null);
}
}
public void addDeleteListener(ComponentEventListener<ModuleDataForm.DeleteEvent> listener) {
addListener(ModuleDataForm.DeleteEvent.class, listener);
}
public void addSaveListener(ComponentEventListener<ModuleDataForm.SaveEvent> listener) {
addListener(ModuleDataForm.SaveEvent.class, listener);
}
public void addCloseListener(ComponentEventListener<ModuleDataForm.CloseEvent> listener) {
addListener(ModuleDataForm.CloseEvent.class, listener);
}
}
@@ -0,0 +1,118 @@
package de.thpeetz.kontor.admin.views;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.value.ValueChangeMode;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import de.thpeetz.kontor.admin.data.ModuleData;
import de.thpeetz.kontor.admin.services.ModuleService;
import de.thpeetz.kontor.common.views.MainLayout;
import jakarta.annotation.security.RolesAllowed;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Scope;
@Slf4j
@SpringComponent
@Scope("prototype")
@RolesAllowed("ROLE_ADMIN")
@Route(value = "admin/module", layout = MainLayout.class)
@PageTitle("Module Data | Admin | Kontor")
public class ModuleDataView extends VerticalLayout {
Grid<ModuleData> grid = new Grid<>(ModuleData.class);
TextField filterText = new TextField();
ModuleDataForm form;
ModuleService service;
public ModuleDataView(ModuleService service) {
this.service = service;
addClassName("moduleData-view");
setSizeFull();
configureGrid();
configureForm();
add(getToolbar(), getContent());
updateList();
}
private void configureGrid() {
grid.addClassName("moduleData-grid");
grid.setSizeFull();
grid.setColumns("moduleName", "importData");
grid.getColumns().forEach(col -> col.setAutoWidth(true));
grid.asSingleSelect().addValueChangeListener(event -> editModuleData(event.getValue()));
}
private void configureForm() {
form = new ModuleDataForm();
form.setWidth("25em");
form.setVisible(false);
form.addSaveListener(this::saveModuleData);
form.addDeleteListener(this::deleteModuleData);
form.addCloseListener(e -> closeEditor());
}
private void saveModuleData(ModuleDataForm.SaveEvent event) {
ModuleData moduleData = event.getModuleData();
service.saveModuleData(moduleData);
updateList();
closeEditor();
}
private void deleteModuleData(ModuleDataForm.DeleteEvent event) {
service.deleteModuleData(event.getModuleData());
updateList();
closeEditor();
}
private Component getContent() {
HorizontalLayout content = new HorizontalLayout(grid, form);
content.setFlexGrow(2, grid);
content.setFlexGrow(1, form);
content.addClassName("content");
content.setSizeFull();
return content;
}
private HorizontalLayout getToolbar() {
filterText.setPlaceholder("Filter by module name...");
filterText.setClearButtonVisible(true);
filterText.setValueChangeMode(ValueChangeMode.LAZY);
filterText.addValueChangeListener(e -> updateList());
Button addModuleDataButton = new Button("Add module", click -> addModuleData());
HorizontalLayout toolbar = new HorizontalLayout(filterText, addModuleDataButton);
toolbar.addClassName("toolbar");
return toolbar;
}
public void editModuleData(ModuleData moduleData) {
if (moduleData == null) {
closeEditor();
} else {
form.setModuleData(moduleData);
form.setVisible(true);
addClassName("editing");
}
}
public void closeEditor() {
form.setModuleData(null);
form.setVisible(false);
removeClassName("editing");
}
private void addModuleData() {
grid.asSingleSelect().clear();
editModuleData(new ModuleData());
}
private void updateList() {
grid.setItems(service.findAll(filterText.getValue()));
}
}
@@ -0,0 +1,102 @@
package de.thpeetz.kontor.admin.views;
import com.vaadin.flow.component.ComponentEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.Binder;
import de.thpeetz.kontor.admin.data.Role;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class RoleForm extends FormLayout {
TextField name = new TextField("Role name");
Button save = new Button("Save");
Button delete = new Button("Delete");
Button close = new Button("Cancel");
Binder<Role> binder = new BeanValidationBinder<>(Role.class);
public RoleForm() {
addClassName("role-form");
binder.bindInstanceFields(this);
add(name, createButtonsLayout());
}
private HorizontalLayout createButtonsLayout() {
save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
delete.addThemeVariants(ButtonVariant.LUMO_ERROR);
close.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
save.addClickShortcut(Key.ENTER);
close.addClickShortcut(Key.ESCAPE);
save.addClickListener(event -> validateAndSave());
delete.addClickListener(event -> fireEvent(new DeleteEvent(this, binder.getBean())));
close.addClickListener(event -> fireEvent(new CloseEvent(this)));
binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid()));
return new HorizontalLayout(save, delete, close);
}
private void validateAndSave() {
if (binder.isValid()) {
fireEvent(new SaveEvent(this, binder.getBean()));
}
}
public void setRole(Role role) {
binder.setBean(role);
}
public abstract static class RoleFormEvent extends ComponentEvent<RoleForm> {
private Role role;
protected RoleFormEvent(RoleForm source, Role role) {
super(source, false);
this.role = role;
}
public Role getRole() {
return role;
}
}
public static class SaveEvent extends RoleFormEvent {
SaveEvent(RoleForm source, Role role) {
super(source, role);
}
}
public static class DeleteEvent extends RoleFormEvent {
DeleteEvent(RoleForm source, Role role) {
super(source, role);
}
}
public static class CloseEvent extends RoleFormEvent {
CloseEvent(RoleForm source) {
super(source, null);
}
}
public void addDeleteListener(ComponentEventListener<DeleteEvent> listener) {
addListener(DeleteEvent.class, listener);
}
public void addSaveListener(ComponentEventListener<SaveEvent> listener) {
addListener(SaveEvent.class, listener);
}
public void addCloseListener(ComponentEventListener<CloseEvent> listener) {
addListener(CloseEvent.class, listener);
}
}
@@ -0,0 +1,118 @@
package de.thpeetz.kontor.admin.views;
import org.springframework.context.annotation.Scope;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.value.ValueChangeMode;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import de.thpeetz.kontor.admin.AdminConstants;
import de.thpeetz.kontor.admin.data.Role;
import de.thpeetz.kontor.admin.services.AdminService;
import de.thpeetz.kontor.common.views.MainLayout;
import jakarta.annotation.security.RolesAllowed;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@SpringComponent
@Scope("prototype")
@RolesAllowed("ROLE_ADMIN")
@Route(value = AdminConstants.ROLE_ROUTE, layout = MainLayout.class)
@PageTitle("Rollen | Admin | Kontor")
public class RoleView extends VerticalLayout {
Grid<Role> grid = new Grid<>(Role.class);
TextField filterText = new TextField();
RoleForm form;
AdminService service;
public RoleView(AdminService service) {
this.service = service;
addClassName("user-view");
setSizeFull();
configureGrid();
configureForm();
add(getToolbar(), getContent());
updateList();
}
private void configureGrid() {
grid.addClassName("user-grid");
grid.setSizeFull();
grid.setColumns("name");
grid.getColumns().forEach(col -> col.setAutoWidth(true));
grid.asSingleSelect().addValueChangeListener(event -> editRole(event.getValue()));
}
private void configureForm() {
form = new RoleForm();
form.setWidth("25em");
form.addSaveListener(this::saveRole);
form.addDeleteListener(this::deleteRole);
form.addCloseListener(e -> closeEditor());
}
private void saveRole(RoleForm.SaveEvent event) {
service.saveRole(event.getRole());
updateList();
closeEditor();
}
private void deleteRole(RoleForm.DeleteEvent event) {
service.deleteRole(event.getRole());
updateList();
closeEditor();
}
private Component getContent() {
HorizontalLayout content = new HorizontalLayout(grid, form);
content.setFlexGrow(2, grid);
content.setFlexGrow(1, form);
content.addClassName("content");
content.setSizeFull();
return content;
}
private HorizontalLayout getToolbar() {
filterText.setPlaceholder("Filter by user name...");
filterText.setClearButtonVisible(true);
filterText.setValueChangeMode(ValueChangeMode.LAZY);
filterText.addValueChangeListener(e -> updateList());
Button addUserButton = new Button("Add user", click -> addUser());
HorizontalLayout toolbar = new HorizontalLayout(filterText, addUserButton);
toolbar.addClassName("toolbar");
return toolbar;
}
public void editRole(Role role) {
if (role == null) {
closeEditor();
} else {
form.setRole(role);
form.setVisible(true);
addClassName("editing");
}
}
public void closeEditor() {
form.setRole(null);
form.setVisible(false);
removeClassName("editing");
}
private void addUser() {
grid.asSingleSelect().clear();
editRole(new Role());
}
private void updateList() {
grid.setItems(service.findAllRoles(filterText.getValue()));
}
}
@@ -0,0 +1,143 @@
package de.thpeetz.kontor.admin.views;
import com.vaadin.flow.component.ComponentEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.checkbox.Checkbox;
import com.vaadin.flow.component.checkbox.CheckboxGroup;
import com.vaadin.flow.component.checkbox.CheckboxGroupVariant;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.textfield.EmailField;
import com.vaadin.flow.component.textfield.PasswordField;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.Binder;
import de.thpeetz.kontor.admin.data.Role;
import de.thpeetz.kontor.admin.data.User;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
@Slf4j
public class UserForm extends FormLayout {
TextField userName = new TextField("User name");
PasswordField password = new PasswordField("Password");
EmailField email = new EmailField("Email");
TextField firstName = new TextField("First name");
TextField lastName = new TextField("Last name");
Checkbox enabled = new Checkbox("Enabled");
String originalPassword;
CheckboxGroup<Role> permissions = new CheckboxGroup<>("Permissions");
Button save = new Button("Save");
Button delete = new Button("Delete");
Button close = new Button("Cancel");
Binder<User> binder = new BeanValidationBinder<>(User.class);
public UserForm() {
addClassName("user-form");
binder.bindInstanceFields(this);
add(userName, password, email, firstName, lastName, enabled, configurePermissionsGroup(), createButtonsLayout());
}
private CheckboxGroup<Role> configurePermissionsGroup() {
permissions.addThemeVariants(CheckboxGroupVariant.LUMO_VERTICAL);
permissions.setItemLabelGenerator(Role::getName);
permissions.addValueChangeListener(event -> {
log.debug("permissions changed: {}", event);
});
return permissions;
}
private HorizontalLayout createButtonsLayout() {
save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
delete.addThemeVariants(ButtonVariant.LUMO_ERROR);
close.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
save.addClickShortcut(Key.ENTER);
close.addClickShortcut(Key.ESCAPE);
save.addClickListener(event -> validateAndSave());
delete.addClickListener(event -> fireEvent(new DeleteEvent(this, binder.getBean())));
close.addClickListener(event -> fireEvent(new CloseEvent(this)));
binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid()));
return new HorizontalLayout(save, delete, close);
}
private void validateAndSave() {
if (binder.isValid()) {
fireEvent(new SaveEvent(this, binder.getBean()));
}
}
public void setUser(User user) {
binder.setBean(user);
//log.debug("UserForm.setUser: {}", user);
if (user != null) {
this.originalPassword = user.getPassword();
} else {
this.originalPassword = null;
}
}
public void setRoles(List<Role> roles, User user) {
permissions.setItems(roles);
user.getMatrix().stream().forEach(authorizationMatrix -> {
permissions.select(authorizationMatrix.getRole());
});
}
public boolean hasPasswordChanged(User user) {
return !originalPassword.equals(user.getPassword());
}
public abstract static class UserFormEvent extends ComponentEvent<UserForm> {
private User user;
protected UserFormEvent(UserForm source, User user) {
super(source, false);
this.user = user;
}
public User getUser() {
return user;
}
}
public static class SaveEvent extends UserFormEvent {
SaveEvent(UserForm source, User user) {
super(source, user);
}
}
public static class DeleteEvent extends UserFormEvent {
DeleteEvent(UserForm source, User user) {
super(source, user);
}
}
public static class CloseEvent extends UserFormEvent {
CloseEvent(UserForm source) {
super(source, null);
}
}
public void addDeleteListener(ComponentEventListener<DeleteEvent> listener) {
addListener(DeleteEvent.class, listener);
}
public void addSaveListener(ComponentEventListener<SaveEvent> listener) {
addListener(SaveEvent.class, listener);
}
public void addCloseListener(ComponentEventListener<CloseEvent> listener) {
addListener(CloseEvent.class, listener);
}
}
@@ -0,0 +1,73 @@
package de.thpeetz.kontor.admin.views;
import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.Binder;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import de.thpeetz.kontor.admin.data.User;
import de.thpeetz.kontor.admin.services.AdminService;
import de.thpeetz.kontor.common.views.MainLayout;
import de.thpeetz.kontor.security.SecurityService;
import jakarta.annotation.security.PermitAll;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Route(value="user/profile", layout = MainLayout.class)
@PermitAll
@PageTitle("Profile | User | Kontor")
public class UserProfileView extends VerticalLayout {
private SecurityService securityService;
private AdminService adminService;
TextField firstName = new TextField("First name");
TextField lastName = new TextField("Last name");
Button save = new Button("Save");
Button close = new Button("Cancel");
Binder<User> binder = new BeanValidationBinder<>(User.class);
public UserProfileView(AdminService adminService, SecurityService securityService) {
this.adminService = adminService;
this.securityService = securityService;
binder.bindInstanceFields(this);
add(firstName, lastName, createButtonsLayout());
securityService.getAuthenticatedUser().ifPresent(user -> {
log.info("UserProfileView: {}", user.getUsername());
binder.setBean(adminService.getUser(user.getUsername()));
});
}
private HorizontalLayout createButtonsLayout() {
save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
close.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
save.addClickShortcut(Key.ENTER);
close.addClickShortcut(Key.ESCAPE);
save.addClickListener(event -> validateAndSave());
close.addClickListener(event -> closeView());
binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid()));
return new HorizontalLayout(save, close);
}
private void validateAndSave() {
if (binder.isValid()) {
log.info("update user profile");
}
}
private void closeView() {
log.info("close user profile view");
}
}
@@ -0,0 +1,135 @@
package de.thpeetz.kontor.admin.views;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.value.ValueChangeMode;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import de.thpeetz.kontor.admin.data.Role;
import de.thpeetz.kontor.admin.data.User;
import de.thpeetz.kontor.admin.services.KontorUserDetailsService;
import de.thpeetz.kontor.common.views.MainLayout;
import jakarta.annotation.security.RolesAllowed;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@SpringComponent
@Scope("prototype")
@RolesAllowed("ROLE_ADMIN")
@Route(value = "admin/user", layout = MainLayout.class)
@PageTitle("User | Admin | Kontor")
public class UserView extends VerticalLayout {
Grid<User> grid = new Grid<>(User.class);
TextField filterText = new TextField();
UserForm form;
KontorUserDetailsService service;
@Autowired
PasswordEncoder passwordEncoder;
public UserView(KontorUserDetailsService service) {
this.service = service;
addClassName("user-view");
setSizeFull();
configureGrid();
configureForm();
add(getToolbar(), getContent());
updateList();
}
private void configureGrid() {
grid.addClassName("user-grid");
grid.setSizeFull();
grid.setColumns("userName", "email", "firstName", "lastName", "enabled");
grid.getColumns().forEach(col -> col.setAutoWidth(true));
grid.asSingleSelect().addValueChangeListener(event -> editUser(event.getValue()));
}
private void configureForm() {
form = new UserForm();
form.setWidth("25em");
form.setVisible(false);
form.addSaveListener(this::saveUser);
form.addDeleteListener(this::deleteUser);
form.addCloseListener(e -> closeEditor());
}
private void saveUser(UserForm.SaveEvent event) {
User user = event.getUser();
log.debug("UserView.saveUser: {}", user);
List<Role> permissions = form.permissions.getSelectedItems().stream().collect(Collectors.toList());
log.info("selected permissions: {}", permissions);
if (form.hasPasswordChanged(user)) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
log.debug("password changed for user {}", user);
}
service.saveUser(user, permissions);
updateList();
closeEditor();
}
private void deleteUser(UserForm.DeleteEvent event) {
service.deleteUser(event.getUser());
updateList();
closeEditor();
}
private Component getContent() {
HorizontalLayout content = new HorizontalLayout(grid, form);
content.setFlexGrow(2, grid);
content.setFlexGrow(1, form);
content.addClassName("content");
content.setSizeFull();
return content;
}
private HorizontalLayout getToolbar() {
filterText.setPlaceholder("Filter by user name...");
filterText.setClearButtonVisible(true);
filterText.setValueChangeMode(ValueChangeMode.LAZY);
filterText.addValueChangeListener(e -> updateList());
Button addUserButton = new Button("Add user", click -> addUser());
HorizontalLayout toolbar = new HorizontalLayout(filterText, addUserButton);
toolbar.addClassName("toolbar");
return toolbar;
}
public void editUser(User user) {
if (user == null) {
closeEditor();
} else {
form.setUser(user);
form.setRoles(service.findAllRoles(), user);
form.setVisible(true);
addClassName("editing");
}
}
public void closeEditor() {
form.setUser(null);
form.setVisible(false);
removeClassName("editing");
}
private void addUser() {
grid.asSingleSelect().clear();
editUser(new User());
}
private void updateList() {
grid.setItems(service.findAllUsers(filterText.getValue()));
}
}
@@ -0,0 +1,53 @@
package de.thpeetz.kontor.bookshelf;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.sidenav.SideNav;
import com.vaadin.flow.component.sidenav.SideNavItem;
import com.vaadin.flow.router.RouterLink;
import de.thpeetz.kontor.bookshelf.views.ArticleView;
import de.thpeetz.kontor.bookshelf.views.AuthorView;
import de.thpeetz.kontor.bookshelf.views.BookView;
import de.thpeetz.kontor.bookshelf.views.BookshelfPublisherView;
public class BookshelfConstants {
public static final String AUTHOR = "Author";
public static final String ARTICLE = "Article";
public static final String BOOK = "Book";
public static final String BOOKSHELF = "Bookshelf";
public static final String PUBLISHER = "Publisher";
public static final String PUBLISHER_ROUTE = "bookshelf/publisher";
public static final String AUTHOR_ROUTE = "bookshelf/author";
public static final String BOOK_ROUTE = "bookshelf/book";
public static final String ARTICLE_ROUTE = "bookshelf/article";
public static RouterLink getPublisherLink() {
return new RouterLink(PUBLISHER, BookshelfPublisherView.class);
}
public static RouterLink getAuthorLink() {
return new RouterLink(AUTHOR, AuthorView.class);
}
public static RouterLink getBookLink() {
return new RouterLink(BOOK, BookView.class);
}
public static RouterLink getArticleLink() {
return new RouterLink(ARTICLE, ArticleView.class);
}
public static SideNavItem getBookshelfNavigation() {
SideNavItem bookshelf = new SideNavItem(BOOKSHELF, BookView.class, VaadinIcon.OPEN_BOOK.create());
bookshelf.addItem(new SideNavItem(BOOK, BookView.class));
bookshelf.addItem(new SideNavItem(PUBLISHER, BookshelfPublisherView.class));
bookshelf.addItem(new SideNavItem(AUTHOR, AuthorView.class));
bookshelf.addItem(new SideNavItem(ARTICLE, ArticleView.class));
return bookshelf;
}
private BookshelfConstants() {
// private constructor to hide the implicit public one
}
}
@@ -0,0 +1,82 @@
package de.thpeetz.kontor.bookshelf;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import de.thpeetz.kontor.admin.services.ModuleService;
import de.thpeetz.kontor.bookshelf.data.ArticleAuthorRepository;
import de.thpeetz.kontor.bookshelf.data.Author;
import de.thpeetz.kontor.bookshelf.data.AuthorRepository;
import de.thpeetz.kontor.bookshelf.data.BookAuthorRepository;
import de.thpeetz.kontor.bookshelf.data.BookRepository;
import de.thpeetz.kontor.bookshelf.data.BookshelfPublisher;
import de.thpeetz.kontor.bookshelf.data.BookshelfPublisherRepository;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
public class SetupModuleBookshelf implements ApplicationListener<ContextRefreshedEvent> {
boolean alreadySetup = false;
@Autowired
private BookshelfPublisherRepository publisherRepository;
@Autowired
private AuthorRepository authorRepository;
@Autowired
private ArticleAuthorRepository articleAuthorRepository;
@Autowired
private BookRepository bookRepository;
@Autowired
private BookAuthorRepository bookAuthorRepository;
@Autowired
private ModuleService moduleService;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (alreadySetup) {
log.info("SetupModuleBookshelf already executed, skipping");
return;
}
if (!moduleService.importData(BookshelfConstants.BOOKSHELF)) {
log.info("Module Bookshelf should not setup data");
return;
}
log.info("Set up Bookshelf data");
Author douglasadams = createAuthorIfNotFound("Douglas", "Adams");
moduleService.setDataImported(BookshelfConstants.BOOKSHELF);
}
private Author createAuthorIfNotFound(String firstName, String lastName) {
log.info("createAuthorIfNotFound {} {}", firstName, lastName);
Author author = authorRepository.findByFirstNameAndLastName(firstName, lastName);
if (author == null) {
log.info("Author {} {} not found, will create it", firstName, lastName);
author = new Author();
author.setFirstName(firstName);
author.setLastName(lastName);
authorRepository.save(author);
}
return author;
}
private BookshelfPublisher createPublisherIfNotFound(String publisherName) {
log.info("createPublisherIfNotFound {}", publisherName);
BookshelfPublisher publisher = publisherRepository.findByName(publisherName);
if (publisher == null) {
log.info("Publisher {} not found, will create it", publisherName);
publisher = new BookshelfPublisher();
publisher.setName(publisherName);
publisherRepository.save(publisher);
}
return publisher;
}
}
@@ -0,0 +1,27 @@
package de.thpeetz.kontor.bookshelf.data;
import java.util.LinkedList;
import java.util.List;
import de.thpeetz.kontor.common.data.AbstractEntity;
import jakarta.annotation.Nullable;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotEmpty;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
@Entity
@Table(indexes = @Index(columnList = "title"), uniqueConstraints = @UniqueConstraint(columnNames = {"title"}))
public class Article extends AbstractEntity {
@NotEmpty
private String title;
@OneToMany(fetch = FetchType.EAGER, mappedBy = "article")
@Nullable
private List<ArticleAuthor> authors = new LinkedList<>();
}
@@ -0,0 +1,29 @@
package de.thpeetz.kontor.bookshelf.data;
import de.thpeetz.kontor.common.data.AbstractEntity;
import jakarta.persistence.Entity;
import jakarta.persistence.Index;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Entity
@Table(indexes = @Index(columnList = "author_id, article_id"), uniqueConstraints = @UniqueConstraint(columnNames = {"author_id", "article_id"}))
public class ArticleAuthor extends AbstractEntity {
@ManyToOne
@JoinColumn(name = "author_id")
@NotNull
private Author author;
@ManyToOne
@JoinColumn(name = "article_id")
@NotNull
private Article article;
}
@@ -0,0 +1,12 @@
package de.thpeetz.kontor.bookshelf.data;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ArticleAuthorRepository extends JpaRepository<ArticleAuthor, String> {
List<ArticleAuthor> findByAuthor(Author author);
List<ArticleAuthor> findByArticle(Article article);
}
@@ -0,0 +1,16 @@
package de.thpeetz.kontor.bookshelf.data;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface ArticleRepository extends JpaRepository<Article, String> {
@Query("select a from Article a " +
"where lower(a.title) like lower(concat('%', :searchTerm, '%'))")
List<Article> search(@Param("searchTerm") String searchTerm);
List<Article> findByTitle(String title);
}
@@ -0,0 +1,44 @@
package de.thpeetz.kontor.bookshelf.data;
import java.util.LinkedList;
import java.util.List;
import de.thpeetz.kontor.common.data.AbstractEntity;
import jakarta.annotation.Nullable;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Index;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import jakarta.validation.constraints.NotEmpty;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
@Entity
@Table(indexes = {
@Index(columnList = "firstName, lastName"),
@Index(columnList = "lastName, firstName")
}, uniqueConstraints = {
@UniqueConstraint(columnNames = { "firstName", "lastName" })
})
public class Author extends AbstractEntity {
@NotEmpty
private String firstName;
@NotEmpty
private String lastName;
@OneToMany(fetch = FetchType.EAGER, mappedBy = "author")
@Nullable
private List<BookAuthor> bookAuthors = new LinkedList<>();
@OneToMany(fetch = FetchType.EAGER, mappedBy = "author")
@Nullable
private List<ArticleAuthor> articleAuthors = new LinkedList<>();
}
@@ -0,0 +1,18 @@
package de.thpeetz.kontor.bookshelf.data;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface AuthorRepository extends JpaRepository<Author, String> {
@Query("select a from Author a " +
"where lower(a.firstName) like lower(concat('%', :searchTerm, '%')) " +
"or lower(a.lastName) like lower(concat('%', :searchTerm, '%'))")
List<Author> search(@Param("searchTerm") String searchTerm);
Author findByFirstNameAndLastName(String firstName, String lastName);
}
@@ -0,0 +1,42 @@
package de.thpeetz.kontor.bookshelf.data;
import java.util.LinkedList;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import de.thpeetz.kontor.common.data.AbstractEntity;
import jakarta.annotation.Nullable;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
@Entity
@Table(indexes = @Index(columnList = "title, isbn"), uniqueConstraints = @UniqueConstraint(columnNames = {"isbn"}))
public class Book extends AbstractEntity {
@NotEmpty
private String title;
@NotEmpty
private String isbn;
@Nullable
private int year;
@OneToMany(fetch = FetchType.EAGER, mappedBy = "book")
@Nullable
private List<BookAuthor> authors = new LinkedList<>();
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "publisher_id")
@NotNull
@JsonIgnoreProperties({ "books" })
private BookshelfPublisher publisher;
}
@@ -0,0 +1,29 @@
package de.thpeetz.kontor.bookshelf.data;
import de.thpeetz.kontor.common.data.AbstractEntity;
import jakarta.persistence.Entity;
import jakarta.persistence.Index;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Entity
@Table(indexes = @Index(columnList = "author_id, book_id"), uniqueConstraints = @UniqueConstraint(columnNames = {"author_id", "book_id"}))
public class BookAuthor extends AbstractEntity {
@ManyToOne
@JoinColumn(name = "author_id")
@NotNull
private Author author;
@ManyToOne
@JoinColumn(name = "book_id")
@NotNull
private Book book;
}
@@ -0,0 +1,12 @@
package de.thpeetz.kontor.bookshelf.data;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
public interface BookAuthorRepository extends JpaRepository<BookAuthor, String> {
List<BookAuthor> findByAuthor(Author author);
List<BookAuthor> findByBook(Book book);
}
@@ -0,0 +1,21 @@
package de.thpeetz.kontor.bookshelf.data;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface BookRepository extends JpaRepository<Book, String> {
@Query("select b from Book b " +
"where lower(b.title) like lower(concat('%', :searchTerm, '%')) " +
"or lower(b.isbn) like lower(concat('%', :searchTerm, '%'))")
List<Book> search(@Param("searchTerm") String searchTerm);
List<Book> findByTitle(String name);
List<Book> findByTitleIgnoreCase(String name);
List<Book> findByIsbn(String isbn);
}
@@ -0,0 +1,32 @@
package de.thpeetz.kontor.bookshelf.data;
import java.util.LinkedList;
import java.util.List;
import de.thpeetz.kontor.common.data.AbstractEntity;
import jakarta.annotation.Nullable;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Index;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import jakarta.validation.constraints.NotEmpty;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
@Entity
@Table(indexes = @Index(columnList = "name"), uniqueConstraints = @UniqueConstraint(columnNames = { "name" }))
public class BookshelfPublisher extends AbstractEntity {
@NotEmpty
private String name;
@OneToMany(fetch = FetchType.EAGER, mappedBy = "publisher", orphanRemoval = true)
@Nullable
List<Book> books = new LinkedList<>();
}
@@ -0,0 +1,18 @@
package de.thpeetz.kontor.bookshelf.data;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface BookshelfPublisherRepository extends JpaRepository<BookshelfPublisher, String> {
@Query("select p from BookshelfPublisher p " +
"where lower(p.name) like lower(concat('%', :searchTerm, '%')) ")
List<BookshelfPublisher> search(@Param("searchTerm") String searchTerm);
BookshelfPublisher findByName(String name);
List<BookshelfPublisher> findByNameIgnoreCase(String name);
}
@@ -0,0 +1,118 @@
package de.thpeetz.kontor.bookshelf.services;
import java.util.List;
import de.thpeetz.kontor.bookshelf.data.*;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class BookshelfService {
private final AuthorRepository authorRepository;
private final ArticleAuthorRepository articleAuthorRepository;
private final ArticleRepository articleRepository;
private final BookRepository bookRepository;
private final BookAuthorRepository bookAuthorRepository;
private final BookshelfPublisherRepository publisherRepository;
public BookshelfService(AuthorRepository authorRepository, ArticleAuthorRepository articleAuthorRepository,
ArticleRepository articleRepository, BookRepository bookRepository,
BookAuthorRepository bookAuthorRepository, BookshelfPublisherRepository publisherRepository) {
this.authorRepository = authorRepository;
this.articleAuthorRepository = articleAuthorRepository;
this.articleRepository = articleRepository;
this.bookRepository = bookRepository;
this.bookAuthorRepository = bookAuthorRepository;
this.publisherRepository = publisherRepository;
}
public List<BookshelfPublisher> findAllPublishers(String stringFilter) {
if (stringFilter == null || stringFilter.isEmpty()) {
return publisherRepository.findAll();
} else {
return publisherRepository.search(stringFilter);
}
}
public BookshelfPublisher findPublisherByName(String publisherName) {
return publisherRepository.findByName(publisherName);
}
public void deletePublisher(BookshelfPublisher publisher) {
publisherRepository.delete(publisher);
}
public void savePublisher(BookshelfPublisher publisher) {
if (publisher == null) {
log.warn("Publisher is null. Are you sure you have connected your form to the application?");
return;
}
publisherRepository.save(publisher);
}
public List<Author> findAllAuthors(String filter) {
if (filter == null || filter.isEmpty()) {
return authorRepository.findAll();
} else {
return authorRepository.search(filter);
}
}
public void saveAuthor(Author author) {
if (author == null) {
log.warn("Author is null. Are you sure you have connected your form to the application?");
return;
}
authorRepository.save(author);
}
public void deleteAuthor(Author author) {
authorRepository.delete(author);
}
public List<Book> findAllBooks(String filter) {
if (filter == null || filter.isEmpty()) {
return bookRepository.findAll();
} else {
return bookRepository.search(filter);
}
}
public void saveBook(Book book) {
if (book == null) {
log.warn("Book is null. Are you sure you have connected your form to the application?");
return;
}
bookRepository.save(book);
}
public void deleteBook(Book book) {
BookshelfPublisher publisher = book.getPublisher();
publisher.getBooks().remove(book);
publisherRepository.save(publisher);
bookRepository.delete(book);
}
public List<Article> findAllArticles(String filter) {
if (filter == null || filter.isEmpty()) {
return articleRepository.findAll();
} else {
return articleRepository.search(filter);
}
}
public void saveArticle(Article article) {
if (article == null) {
log.warn("Article is null, Are you sure you have connected your form to the application?");
return;
}
articleRepository.save(article);
}
public void deleteArticle(Article article) {
articleRepository.delete(article);
}
}
@@ -0,0 +1,106 @@
package de.thpeetz.kontor.bookshelf.views;
import com.vaadin.flow.component.ComponentEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.Binder;
import de.thpeetz.kontor.bookshelf.data.Article;
import de.thpeetz.kontor.bookshelf.data.Author;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
@Slf4j
public class ArticleForm extends FormLayout {
TextField title = new TextField("Title");
Grid<Author> author = new Grid<>(Author.class);
Button save = new Button("Save");
Button delete = new Button("Delete");
Button close = new Button("Cancel");
Binder<Article> binder = new BeanValidationBinder<>(Article.class);
public ArticleForm() {
addClassName("article-form");
binder.bindInstanceFields(this);
add(title, author, createButtonsLayout());
}
private HorizontalLayout createButtonsLayout() {
save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
delete.addThemeVariants(ButtonVariant.LUMO_ERROR);
close.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
save.addClickShortcut(Key.ENTER);
close.addClickShortcut(Key.ESCAPE);
save.addClickListener(event -> validateAndSave());
delete.addClickListener(event -> fireEvent(new ArticleForm.DeleteEvent(this, binder.getBean())));
close.addClickListener(event -> fireEvent(new ArticleForm.CloseEvent(this)));
binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid()));
return new HorizontalLayout(save, delete, close);
}
private void validateAndSave() {
if (binder.isValid()) {
fireEvent(new ArticleForm.SaveEvent(this, binder.getBean()));
}
}
public void setArticle(Article article) {
binder.setBean(article);
}
public abstract static class ArticleFormEvent extends ComponentEvent<ArticleForm> {
private Article article;
protected ArticleFormEvent(ArticleForm source, Article article) {
super(source, false);
this.article = article;
}
public Article getArticle() {
return article;
}
}
public static class SaveEvent extends ArticleForm.ArticleFormEvent {
SaveEvent(ArticleForm source, Article article) {
super(source, article);
}
}
public static class DeleteEvent extends ArticleForm.ArticleFormEvent {
DeleteEvent(ArticleForm source, Article article) {
super(source, article);
}
}
public static class CloseEvent extends ArticleForm.ArticleFormEvent {
CloseEvent(ArticleForm source) {
super(source, null);
}
}
public void addDeleteListener(ComponentEventListener<ArticleForm.DeleteEvent> listener) {
addListener(ArticleForm.DeleteEvent.class, listener);
}
public void addSaveListener(ComponentEventListener<ArticleForm.SaveEvent> listener) {
addListener(ArticleForm.SaveEvent.class, listener);
}
public void addCloseListener(ComponentEventListener<ArticleForm.CloseEvent> listener) {
addListener(ArticleForm.CloseEvent.class, listener);
}
}
@@ -0,0 +1,126 @@
package de.thpeetz.kontor.bookshelf.views;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.value.ValueChangeMode;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import de.thpeetz.kontor.bookshelf.BookshelfConstants;
import de.thpeetz.kontor.bookshelf.data.Article;
import de.thpeetz.kontor.bookshelf.data.Book;
import de.thpeetz.kontor.bookshelf.data.BookAuthor;
import de.thpeetz.kontor.bookshelf.services.BookshelfService;
import de.thpeetz.kontor.common.views.MainLayout;
import jakarta.annotation.security.PermitAll;
import lombok.Getter;
import org.springframework.context.annotation.Scope;
import java.util.stream.Collectors;
@SpringComponent
@Scope("prototype")
@PermitAll
@Route(value = BookshelfConstants.ARTICLE_ROUTE, layout = MainLayout.class)
@PageTitle("Article | Bookshelf | Kontor")
public class ArticleView extends VerticalLayout {
@Getter
Grid<Article> grid = new Grid<>(Article.class);
TextField filterText = new TextField();
@Getter
ArticleForm form;
BookshelfService service;
public ArticleView(BookshelfService service) {
this.service = service;
addClassName("article-view");
setSizeFull();
configureGrid();
configureForm();
add(getToolbar(), getContent());
updateList();
}
private void configureGrid() {
grid.addClassName("article-grid");
grid.setSizeFull();
grid.setColumns("title");
grid.getColumns().forEach(col -> col.setAutoWidth(true));
grid.asSingleSelect().addValueChangeListener(event -> editArticle(event.getValue()));
}
private void configureForm() {
form = new ArticleForm();
form.setWidth("25em");
form.setVisible(false);
form.addSaveListener(this::saveArticle);
form.addDeleteListener(this::deleteArticle);
form.addCloseListener(e -> closeEditor());
}
private void saveArticle(ArticleForm.SaveEvent event) {
service.saveArticle(event.getArticle());
updateList();
closeEditor();
}
private void deleteArticle(ArticleForm.DeleteEvent event) {
service.deleteArticle(event.getArticle());
updateList();
closeEditor();
}
private Component getContent() {
HorizontalLayout content = new HorizontalLayout(grid, form);
content.setFlexGrow(2, grid);
content.setFlexGrow(1, form);
content.addClassName("content");
content.setSizeFull();
return content;
}
private HorizontalLayout getToolbar() {
filterText.setPlaceholder("Filter by title or isbn...");
filterText.setClearButtonVisible(true);
filterText.setValueChangeMode(ValueChangeMode.LAZY);
filterText.addValueChangeListener(e -> updateList());
Button addArticleButton = new Button("Add article");
addArticleButton.addClickListener(click -> addArticle());
HorizontalLayout toolbar = new HorizontalLayout(filterText, addArticleButton);
toolbar.addClassName("toolbar");
return toolbar;
}
public void editArticle(Article article) {
if (article == null) {
closeEditor();
} else {
form.setArticle(article);
form.setVisible(true);
addClassName("editing");
}
}
private void closeEditor() {
form.setArticle(null);
form.setVisible(false);
removeClassName("editing");
}
private void addArticle() {
grid.asSingleSelect().clear();
editArticle(new Article());
}
public void updateList() {
grid.setItems(service.findAllArticles(filterText.getValue()));
}
}
@@ -0,0 +1,117 @@
package de.thpeetz.kontor.bookshelf.views;
import com.vaadin.flow.component.ComponentEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.listbox.ListBox;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.Binder;
import de.thpeetz.kontor.bookshelf.data.Author;
import de.thpeetz.kontor.bookshelf.data.Book;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
@Slf4j
public class AuthorForm extends FormLayout {
TextField firstName = new TextField("First Name");
TextField lastName = new TextField("Last Name");
ListBox<Book> books = new ListBox<>();
Button save = new Button("Save");
Button delete = new Button("Delete");
Button close = new Button("Cancel");
Binder<Author> binder = new BeanValidationBinder<>(Author.class);
public AuthorForm() {
addClassName("author-form");
binder.bindInstanceFields(this);
books.setHeight("100px");
//books.setColumns("title", "publisher.name");
//books.getColumns().forEach(col -> col.setAutoWidth(true));
add(firstName, lastName, books, createButtonsLayout());
}
private HorizontalLayout createButtonsLayout() {
save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
delete.addThemeVariants(ButtonVariant.LUMO_ERROR);
close.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
save.addClickShortcut(Key.ENTER);
close.addClickShortcut(Key.ESCAPE);
save.addClickListener(event -> validateAndSave());
delete.addClickListener(event -> fireEvent(new AuthorForm.DeleteEvent(this, binder.getBean())));
close.addClickListener(event -> fireEvent(new AuthorForm.CloseEvent(this)));
binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid()));
return new HorizontalLayout(save, delete, close);
}
private void validateAndSave() {
if (binder.isValid()) {
fireEvent(new AuthorForm.SaveEvent(this, binder.getBean()));
}
}
public void setAuthor(Author author) {
binder.setBean(author);
}
public void setBooks(List<Book> books) {
log.info("setting Books: ", books);
this.books.setItems(books);
}
public abstract static class AuthorFormEvent extends ComponentEvent<AuthorForm> {
private Author author;
protected AuthorFormEvent(AuthorForm source, Author author) {
super(source, false);
this.author = author;
}
public Author getAuthor() {
return author;
}
}
public static class SaveEvent extends AuthorForm.AuthorFormEvent {
SaveEvent(AuthorForm source, Author author) {
super(source, author);
}
}
public static class DeleteEvent extends AuthorForm.AuthorFormEvent {
DeleteEvent(AuthorForm source, Author author) {
super(source, author);
}
}
public static class CloseEvent extends AuthorForm.AuthorFormEvent {
CloseEvent(AuthorForm source) {
super(source, null);
}
}
public void addDeleteListener(ComponentEventListener<AuthorForm.DeleteEvent> listener) {
addListener(AuthorForm.DeleteEvent.class, listener);
}
public void addSaveListener(ComponentEventListener<AuthorForm.SaveEvent> listener) {
addListener(AuthorForm.SaveEvent.class, listener);
}
public void addCloseListener(ComponentEventListener<AuthorForm.CloseEvent> listener) {
addListener(AuthorForm.CloseEvent.class, listener);
}
}
@@ -0,0 +1,132 @@
package de.thpeetz.kontor.bookshelf.views;
import java.util.stream.Collectors;
import org.springframework.context.annotation.Scope;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.value.ValueChangeMode;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import de.thpeetz.kontor.bookshelf.BookshelfConstants;
import de.thpeetz.kontor.bookshelf.data.Author;
import de.thpeetz.kontor.bookshelf.data.BookAuthor;
import de.thpeetz.kontor.bookshelf.services.BookshelfService;
import de.thpeetz.kontor.common.views.MainLayout;
import jakarta.annotation.security.PermitAll;
@SpringComponent
@Scope("prototype")
@PermitAll
@Route(value = BookshelfConstants.AUTHOR_ROUTE, layout = MainLayout.class)
@PageTitle("Author | Bookshelf | Kontor")
public class AuthorView extends VerticalLayout {
Grid<Author> grid = new Grid<>(Author.class);
TextField filterText = new TextField();
AuthorForm form;
BookshelfService service;
public AuthorView(BookshelfService service) {
this.service = service;
addClassName("author-view");
setSizeFull();
configureGrid();
configureForm();
add(getToolbar(), getContent());
updateList();
}
public Grid<Author> getGrid() {
return grid;
}
public AuthorForm getForm() {
return form;
}
private void configureGrid() {
grid.addClassName("author-grid");
grid.setSizeFull();
grid.setColumns("firstName", "lastName");
grid.getColumns().forEach(col -> col.setAutoWidth(true));
grid.asSingleSelect().addValueChangeListener(event -> editAuthor(event.getValue()));
}
private void configureForm() {
form = new AuthorForm();
form.setWidth("25em");
form.setVisible(false);
form.addSaveListener(this::saveAuthor);
form.addDeleteListener(this::deleteAuthor);
form.addCloseListener(e -> closeEditor());
}
private void saveAuthor(AuthorForm.SaveEvent event) {
service.saveAuthor(event.getAuthor());
updateList();
closeEditor();
}
private void deleteAuthor(AuthorForm.DeleteEvent event) {
service.deleteAuthor(event.getAuthor());
updateList();
closeEditor();
}
private Component getContent() {
HorizontalLayout content = new HorizontalLayout(grid, form);
content.setFlexGrow(2, grid);
content.setFlexGrow(1, form);
content.addClassName("content");
content.setSizeFull();
return content;
}
private HorizontalLayout getToolbar() {
filterText.setPlaceholder("Filter by name...");
filterText.setClearButtonVisible(true);
filterText.setValueChangeMode(ValueChangeMode.LAZY);
filterText.addValueChangeListener(e -> updateList());
Button addAuthorButton = new Button("Add author");
addAuthorButton.addClickListener(click -> addAuthor());
HorizontalLayout toolbar = new HorizontalLayout(filterText, addAuthorButton);
toolbar.addClassName("toolbar");
return toolbar;
}
public void editAuthor(Author author) {
if (author == null) {
closeEditor();
} else {
form.setAuthor(author);
form.setBooks(author.getBookAuthors().stream().map(BookAuthor::getBook).collect(Collectors.toList()));
form.setVisible(true);
addClassName("editing");
}
}
private void closeEditor() {
form.setAuthor(null);
form.setVisible(false);
removeClassName("editing");
}
private void addAuthor() {
grid.asSingleSelect().clear();
editAuthor(new Author());
}
public void updateList() {
grid.setItems(service.findAllAuthors(filterText.getValue()));
}
}
@@ -0,0 +1,111 @@
package de.thpeetz.kontor.bookshelf.views;
import com.vaadin.flow.component.ComponentEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.textfield.IntegerField;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.Binder;
import de.thpeetz.kontor.bookshelf.data.Book;
import de.thpeetz.kontor.bookshelf.data.BookshelfPublisher;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
@Slf4j
public class BookForm extends FormLayout {
TextField title = new TextField("Title");
TextField isbn = new TextField("ISBN");
IntegerField year = new IntegerField("Year");
ComboBox<BookshelfPublisher> publisher = new ComboBox<>("Publisher");
Button save = new Button("Save");
Button delete = new Button("Delete");
Button close = new Button("Cancel");
Binder<Book> binder = new BeanValidationBinder<>(Book.class);
public BookForm(List<BookshelfPublisher> publishers) {
addClassName("book-form");
binder.bindInstanceFields(this);
publisher.setItems(publishers);
publisher.setItemLabelGenerator(BookshelfPublisher::getName);
add(title, isbn, year, publisher, createButtonsLayout());
}
private HorizontalLayout createButtonsLayout() {
save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
delete.addThemeVariants(ButtonVariant.LUMO_ERROR);
close.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
save.addClickShortcut(Key.ENTER);
close.addClickShortcut(Key.ESCAPE);
save.addClickListener(event -> validateAndSave());
delete.addClickListener(event -> fireEvent(new BookForm.DeleteEvent(this, binder.getBean())));
close.addClickListener(event -> fireEvent(new BookForm.CloseEvent(this)));
binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid()));
return new HorizontalLayout(save, delete, close);
}
private void validateAndSave() {
if (binder.isValid()) {
fireEvent(new BookForm.SaveEvent(this, binder.getBean()));
}
}
public void setBook(Book book) {
binder.setBean(book);
}
public abstract static class BookFormEvent extends ComponentEvent<BookForm> {
private Book book;
protected BookFormEvent(BookForm source, Book book) {
super(source, false);
this.book = book;
}
public Book getBook() {
return book;
}
}
public static class SaveEvent extends BookForm.BookFormEvent {
SaveEvent(BookForm source, Book book) {
super(source, book);
}
}
public static class DeleteEvent extends BookForm.BookFormEvent {
DeleteEvent(BookForm source, Book book) {
super(source, book);
}
}
public static class CloseEvent extends BookForm.BookFormEvent {
CloseEvent(BookForm source) {
super(source, null);
}
}
public void addDeleteListener(ComponentEventListener<BookForm.DeleteEvent> listener) {
addListener(BookForm.DeleteEvent.class, listener);
}
public void addSaveListener(ComponentEventListener<BookForm.SaveEvent> listener) {
addListener(BookForm.SaveEvent.class, listener);
}
public void addCloseListener(ComponentEventListener<BookForm.CloseEvent> listener) {
addListener(BookForm.CloseEvent.class, listener);
}
}
@@ -0,0 +1,124 @@
package de.thpeetz.kontor.bookshelf.views;
import org.springframework.context.annotation.Scope;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.value.ValueChangeMode;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import de.thpeetz.kontor.bookshelf.BookshelfConstants;
import de.thpeetz.kontor.bookshelf.data.Book;
import de.thpeetz.kontor.bookshelf.services.BookshelfService;
import de.thpeetz.kontor.common.views.MainLayout;
import jakarta.annotation.security.PermitAll;
import lombok.Getter;
@SpringComponent
@Scope("prototype")
@PermitAll
@Route(value = BookshelfConstants.BOOK_ROUTE, layout = MainLayout.class)
@PageTitle("Book | Bookshelf | Kontor")
public class BookView extends VerticalLayout {
@Getter
Grid<Book> grid = new Grid<>(Book.class);
TextField filterText = new TextField();
@Getter
BookForm form;
BookshelfService service;
public BookView(BookshelfService service) {
this.service = service;
addClassName("book-view");
setSizeFull();
configureGrid();
configureForm();
add(getToolbar(), getContent());
updateList();
}
private void configureGrid() {
grid.addClassName("book-grid");
grid.setSizeFull();
grid.setColumns("title", "isbn", "publisher.name", "year");
grid.getColumns().forEach(col -> col.setAutoWidth(true));
grid.asSingleSelect().addValueChangeListener(event -> editBook(event.getValue()));
}
private void configureForm() {
form = new BookForm(service.findAllPublishers(null));
form.setWidth("25em");
form.setVisible(false);
form.addSaveListener(this::saveBook);
form.addDeleteListener(this::deleteBook);
form.addCloseListener(e -> closeEditor());
}
private void saveBook(BookForm.SaveEvent event) {
service.saveBook(event.getBook());
updateList();
closeEditor();
}
private void deleteBook(BookForm.DeleteEvent event) {
service.deleteBook(event.getBook());
updateList();
closeEditor();
}
private Component getContent() {
HorizontalLayout content = new HorizontalLayout(grid, form);
content.setFlexGrow(2, grid);
content.setFlexGrow(1, form);
content.addClassName("content");
content.setSizeFull();
return content;
}
private HorizontalLayout getToolbar() {
filterText.setPlaceholder("Filter by title or isbn...");
filterText.setClearButtonVisible(true);
filterText.setValueChangeMode(ValueChangeMode.LAZY);
filterText.addValueChangeListener(e -> updateList());
Button addBookButton = new Button("Add book");
addBookButton.addClickListener(click -> addBook());
HorizontalLayout toolbar = new HorizontalLayout(filterText, addBookButton);
toolbar.addClassName("toolbar");
return toolbar;
}
public void editBook(Book book) {
if (book == null) {
closeEditor();
} else {
form.setBook(book);
form.setVisible(true);
addClassName("editing");
}
}
private void closeEditor() {
form.setBook(null);
form.setVisible(false);
removeClassName("editing");
}
private void addBook() {
grid.asSingleSelect().clear();
editBook(new Book());
}
public void updateList() {
grid.setItems(service.findAllBooks(filterText.getValue()));
}
}
@@ -0,0 +1,34 @@
package de.thpeetz.kontor.bookshelf.views;
import com.vaadin.flow.component.applayout.AppLayout;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.theme.lumo.LumoUtility;
import de.thpeetz.kontor.admin.services.AdminService;
import de.thpeetz.kontor.bookshelf.BookshelfConstants;
import de.thpeetz.kontor.common.views.KontorLayoutUtil;
import de.thpeetz.kontor.security.SecurityService;
public class BookshelfLayout extends AppLayout {
private final AdminService adminService;
private final SecurityService securityService;
public BookshelfLayout(AdminService adminService, SecurityService securityService) {
this.adminService = adminService;
this.securityService = securityService;
KontorLayoutUtil layout = new KontorLayoutUtil(this, adminService, securityService);
layout.setSecondaryNavigation(getSecondaryNavigation());
layout.createHeader(BookshelfConstants.BOOKSHELF);
}
private HorizontalLayout getSecondaryNavigation() {
HorizontalLayout navigation = new HorizontalLayout();
navigation.addClassNames(LumoUtility.JustifyContent.CENTER, LumoUtility.Gap.SMALL, LumoUtility.Height.MEDIUM);
navigation.add(BookshelfConstants.getBookLink(), BookshelfConstants.getArticleLink(),
BookshelfConstants.getPublisherLink(), BookshelfConstants.getAuthorLink());
return navigation;
}
}
@@ -0,0 +1,131 @@
package de.thpeetz.kontor.bookshelf.views;
import de.thpeetz.kontor.common.views.MainLayout;
import de.thpeetz.kontor.common.views.SeparateMainLayout;
import org.springframework.context.annotation.Scope;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.value.ValueChangeMode;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import de.thpeetz.kontor.bookshelf.BookshelfConstants;
import de.thpeetz.kontor.bookshelf.data.BookshelfPublisher;
import de.thpeetz.kontor.bookshelf.services.BookshelfService;
import jakarta.annotation.security.PermitAll;
@SpringComponent
@Scope("prototype")
@PermitAll
@Route(value = BookshelfConstants.PUBLISHER_ROUTE, layout = MainLayout.class)
@PageTitle("Publisher | Bookshelf | Kontor")
public class BookshelfPublisherView extends VerticalLayout {
Grid<BookshelfPublisher> grid = new Grid<>(BookshelfPublisher.class);
TextField filterText = new TextField();
PublisherForm form;
BookshelfService service;
public BookshelfPublisherView(BookshelfService service) {
this.service = service;
addClassName("publisher-view");
setSizeFull();
configureGrid();
configureForm();
add(getToolbar(), getContent());
updateList();
}
private void configureGrid() {
grid.addClassName("publisher-grid");
grid.setSizeFull();
grid.setColumns("name");
grid.getColumns().forEach(col -> col.setAutoWidth(true));
grid.asSingleSelect().addValueChangeListener(event -> editPublisher(event.getValue()));
}
private void configureForm() {
form = new PublisherForm();
form.setWidth("25em");
form.setVisible(false);
form.addSaveListener(this::savePublisher);
form.addDeleteListener(this::deletePublisher);
form.addCloseListener(e -> closeEditor());
}
private void savePublisher(PublisherForm.SaveEvent event) {
service.savePublisher(event.getPublisher());
updateList();
closeEditor();
}
private void deletePublisher(PublisherForm.DeleteEvent event) {
service.deletePublisher(event.getPublisher());
updateList();
closeEditor();
}
public Grid<BookshelfPublisher> getGrid() {
return grid;
}
public PublisherForm getForm() {
return form;
}
private Component getContent() {
HorizontalLayout content = new HorizontalLayout(grid, form);
content.setFlexGrow(2, grid);
content.setFlexGrow(1, form);
content.addClassName("content");
content.setSizeFull();
return content;
}
private HorizontalLayout getToolbar() {
filterText.setPlaceholder("Filter by name...");
filterText.setClearButtonVisible(true);
filterText.setValueChangeMode(ValueChangeMode.LAZY);
filterText.addValueChangeListener(e -> updateList());
Button addPublisherButton = new Button("Add publisher");
addPublisherButton.addClickListener(click -> addPublisher());
HorizontalLayout toolbar = new HorizontalLayout(filterText, addPublisherButton);
toolbar.addClassName("toolbar");
return toolbar;
}
public void editPublisher(BookshelfPublisher publisher) {
if (publisher == null) {
closeEditor();
} else {
form.setPublisher(publisher);
form.setVisible(true);
addClassName("editing");
}
}
private void closeEditor() {
form.setPublisher(null);
form.setVisible(false);
removeClassName("editing");
}
private void addPublisher() {
grid.asSingleSelect().clear();
editPublisher(new BookshelfPublisher());
}
public void updateList() {
grid.setItems(service.findAllPublishers(filterText.getValue()));
}
}
@@ -0,0 +1,108 @@
package de.thpeetz.kontor.bookshelf.views;
import com.vaadin.flow.component.ComponentEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.Binder;
import de.thpeetz.kontor.bookshelf.data.BookshelfPublisher;
public class PublisherForm extends FormLayout {
private TextField name = new TextField("Name");
Button save = new Button("Save");
Button delete = new Button("Delete");
Button close = new Button("Cancel");
Binder<BookshelfPublisher> binder = new BeanValidationBinder(BookshelfPublisher.class);
public PublisherForm() {
addClassName("publisher-form");
binder.bindInstanceFields(this);
add(name, createButtonsLayout());
}
private HorizontalLayout createButtonsLayout() {
save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
delete.addThemeVariants(ButtonVariant.LUMO_ERROR);
close.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
save.addClickShortcut(Key.ENTER);
close.addClickShortcut(Key.ESCAPE);
save.addClickListener(event -> validateAndSave());
delete.addClickListener(event -> fireEvent(new DeleteEvent(this, binder.getBean())));
close.addClickListener(event -> fireEvent(new CloseEvent(this)));
binder.addStatusChangeListener(e -> save.setEnabled(binder.isValid()));
return new HorizontalLayout(save, delete, close);
}
private void validateAndSave() {
if (binder.isValid()) {
fireEvent(new SaveEvent(this, binder.getBean()));
}
}
public TextField getName() {
return name;
}
public void setName(TextField name) {
this.name = name;
}
public void setPublisher(BookshelfPublisher publisher) {
binder.setBean(publisher);
}
public abstract static class PublisherFormEvent extends ComponentEvent<PublisherForm> {
private BookshelfPublisher publisher;
protected PublisherFormEvent(PublisherForm source, BookshelfPublisher publisher) {
super(source, false);
this.publisher = publisher;
}
public BookshelfPublisher getPublisher() {
return publisher;
}
}
public static class SaveEvent extends PublisherFormEvent {
SaveEvent(PublisherForm source, BookshelfPublisher publisher) {
super(source, publisher);
}
}
public static class DeleteEvent extends PublisherFormEvent {
DeleteEvent(PublisherForm source, BookshelfPublisher publisher) {
super(source, publisher);
}
}
public static class CloseEvent extends PublisherFormEvent {
CloseEvent(PublisherForm source) {
super(source, null);
}
}
public void addDeleteListener(ComponentEventListener<DeleteEvent> listener) {
addListener(DeleteEvent.class, listener);
}
public void addSaveListener(ComponentEventListener<SaveEvent> listener) {
addListener(SaveEvent.class, listener);
}
public void addCloseListener(ComponentEventListener<CloseEvent> listener) {
addListener(CloseEvent.class, listener);
}
}
@@ -0,0 +1,93 @@
package de.thpeetz.kontor.comics;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.sidenav.SideNavItem;
import com.vaadin.flow.router.RouterLink;
import de.thpeetz.kontor.comics.views.ArtistView;
import de.thpeetz.kontor.comics.views.ComicView;
import de.thpeetz.kontor.comics.views.ComicWorkView;
import de.thpeetz.kontor.comics.views.IssueView;
import de.thpeetz.kontor.comics.views.PublisherView;
import de.thpeetz.kontor.comics.views.StoryArcView;
import de.thpeetz.kontor.comics.views.TradePaperbackView;
import de.thpeetz.kontor.comics.views.VolumeView;
import de.thpeetz.kontor.comics.views.WorktypeView;
/**
* The {@code ComicConstants} class contains constant values related to comics.
*/
public class ComicConstants {
public static final String COMICS = "Comics";
public static final String COMICS_ROUTE = "comics/comic";
public static final String PUBLISHER = "Publisher";
public static final String PUBLISHER_ROUTE = "comics/publisher";
public static final String COMICWORK = "ComicWork";
public static final String COMICWORK_ROUTE = "comics/comicwork";
public static final String WORKTYPE = "Worktype";
public static final String WORKTYPE_ROUTE = "comics/worktype";
public static final String ARTIST = "Artist";
public static final String ARTIST_ROUTE = "comics/artist";
public static final String TPB = "TradePaperback";
public static final String TPB_ROUTE = "comics/tradepaperback";
public static final String STORYARC = "Story Arc";
public static final String STORYARC_ROTE = "comics/storyarc";
public static final String ISSUE = "Issue";
public static final String ISSUE_ROUTE = "comics/issue";
public static final String VOLUME = "Volume";
public static final String VOLUME_ROUTE = "comics/volume";
public static RouterLink getArtistLink() {
return new RouterLink(ARTIST, ArtistView.class);
}
public static RouterLink getComicLink() {
return new RouterLink(COMICS, ComicView.class);
}
public static RouterLink getPublisherLink() {
return new RouterLink(PUBLISHER, PublisherView.class);
}
public static RouterLink getComicWorkLink() {
return new RouterLink(COMICWORK, ComicWorkView.class);
}
public static RouterLink getIssueLink() {
return new RouterLink(ISSUE, IssueView.class);
}
public static RouterLink getTradePaperbackLink() {
return new RouterLink(TPB, TradePaperbackView.class);
}
public static Component getStoryArcLink() {
return new RouterLink(STORYARC, StoryArcView.class);
}
public static RouterLink getWorktypeLink() {
return new RouterLink(WORKTYPE, WorktypeView.class);
}
public static RouterLink getVolumeLink() {
return new RouterLink(VOLUME, VolumeView.class);
}
public static SideNavItem getComicsNavigation() {
SideNavItem comics = new SideNavItem(COMICS, COMICS_ROUTE, VaadinIcon.RECORDS.create());
comics.addItem(new SideNavItem(ARTIST, ArtistView.class));
comics.addItem(new SideNavItem(COMICS, ComicView.class));
comics.addItem(new SideNavItem(PUBLISHER, PublisherView.class));
comics.addItem(new SideNavItem(ISSUE, IssueView.class));
comics.addItem(new SideNavItem(TPB, TradePaperbackView.class));
comics.addItem(new SideNavItem(STORYARC, StoryArcView.class));
comics.addItem(new SideNavItem(VOLUME, VolumeView.class));
return comics;
}
private ComicConstants() {
// private constructor to hide the implicit public one
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,44 @@
package de.thpeetz.kontor.comics.data;
import java.util.List;
import de.thpeetz.kontor.common.data.AbstractEntity;
import io.micrometer.common.lang.Nullable;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.OneToMany;
import jakarta.validation.constraints.NotEmpty;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
/**
* Represents an artist in the system.
*/
@Getter
@Setter
@EqualsAndHashCode(callSuper = false)
@Slf4j
@Entity
public class Artist extends AbstractEntity {
@NotEmpty
@Column(unique = true)
private String name;
@OneToMany(fetch = FetchType.EAGER, mappedBy = "artist", cascade = CascadeType.REFRESH, orphanRemoval = true)
@Nullable
List<ComicWork> comicWorks;
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("Artist{");
sb.append("name='").append(name).append('\'');
sb.append('}');
return sb.toString();
}
}
@@ -0,0 +1,17 @@
package de.thpeetz.kontor.comics.data;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface ArtistRepository extends JpaRepository<Artist, String> {
@Query("select a from Artist a " +
"where lower(a.name) like lower(concat('%', :searchTerm, '%')) ")
List<Artist> search(@Param("searchTerm") String searchTerm);
List<Artist> findByNameIgnoreCase(String name);
Artist findByName(String name);
}
@@ -0,0 +1,77 @@
package de.thpeetz.kontor.comics.data;
import java.util.LinkedList;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import de.thpeetz.kontor.common.data.AbstractEntity;
import io.micrometer.common.lang.Nullable;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* Represents a comic entity.
*/
@Getter
@Setter
@EqualsAndHashCode(callSuper = false)
@Entity
public class Comic extends AbstractEntity {
@NotEmpty
@Column(unique = true)
private String title;
@ManyToOne
@JoinColumn(name = "publisher_id")
@NotNull
@JsonIgnoreProperties({ "comics" })
private Publisher publisher;
private Boolean currentOrder = false;
private Boolean completed = false;
@OneToMany(fetch = FetchType.EAGER, mappedBy = "comic", cascade = CascadeType.REFRESH, orphanRemoval = true)
@Nullable
List<ComicWork> comicWorks;
@OneToMany(fetch = FetchType.EAGER, mappedBy = "comic", cascade = CascadeType.REFRESH, orphanRemoval = true)
@Nullable
private List<Issue> issues = new LinkedList<>();
@OneToMany(fetch = FetchType.EAGER, mappedBy = "comic", cascade = CascadeType.REFRESH, orphanRemoval = true)
@Nullable
private List<StoryArc> storyArcs = new LinkedList<>();
@OneToMany(fetch = FetchType.EAGER, mappedBy = "comic", cascade = CascadeType.REFRESH, orphanRemoval = true)
@Nullable
private List<TradePaperback> tradePaperbacks = new LinkedList<>();
@OneToMany(fetch = FetchType.EAGER, mappedBy = "comic", cascade = CascadeType.REFRESH, orphanRemoval = true)
@Nullable
private List<Volume> volumes = new LinkedList<>();
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("Comic{");
sb.append("title='").append(title).append('\'');
sb.append(", publisher=").append(publisher);
sb.append(", currentOrder=").append(currentOrder);
sb.append(", completed=").append(completed);
sb.append('}');
return sb.toString();
}
}
@@ -0,0 +1,19 @@
package de.thpeetz.kontor.comics.data;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface ComicRepository extends JpaRepository<Comic, String> {
@Query("select c from Comic c " +
"where lower(c.title) like lower(concat('%', :searchTerm, '%')) ")
List<Comic> search(@Param("searchTerm") String searchTerm);
Comic findByTitleAndPublisher(String title, Publisher publisher);
List<Comic> findByTitle(String title);
List<Comic> findByTitleIgnoreCase(String title);
}
@@ -0,0 +1,48 @@
package de.thpeetz.kontor.comics.data;
import de.thpeetz.kontor.common.data.AbstractEntity;
import jakarta.persistence.Entity;
import jakarta.persistence.Index;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import jakarta.validation.constraints.NotNull;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@EqualsAndHashCode(callSuper = false)
@Entity
@Table(indexes = {@Index(columnList = "comic_id, artist_id, workType_id") },
uniqueConstraints = @UniqueConstraint(columnNames = {"comic_id", "artist_id", "workType_id" })
)
public class ComicWork extends AbstractEntity {
@ManyToOne
@JoinColumn(name = "comic_id")
@NotNull
private Comic comic;
@ManyToOne
@JoinColumn(name = "artist_id")
@NotNull
private Artist artist;
@ManyToOne
@JoinColumn(name = "workType_id")
@NotNull
private Worktype workType;
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("ComicWork{");
sb.append("comic=").append(comic);
sb.append(", artist=").append(artist);
sb.append(", workType=").append(workType);
sb.append('}');
return sb.toString();
}
}

Some files were not shown because too many files have changed in this diff Show More