Compare commits

...

29 Commits

Author SHA1 Message Date
b68933b676 added camp registration stop message 2025-03-10 18:16:19 +01:00
c692f7881b allow kerstin to access camp list 2025-03-04 23:02:10 +01:00
72b91dd8bc corrected label 2025-01-31 10:12:05 +01:00
44498c5ea1 costs for nights, not days 2025-01-31 10:05:40 +01:00
faa8140b0c build.gradle aktualisiert 2025-01-31 10:03:59 +01:00
1733821102 omit npe 2025-01-30 22:48:18 +01:00
30fb52a606 camp admin added 2025-01-30 22:37:22 +01:00
bdc0d84e69 added special prices for special wishes 2025-01-15 21:55:24 +01:00
5d61ae6013 added details view 2024-12-13 19:17:27 +01:00
6feecb06c3 added more user friendly welcome page 2024-12-11 22:56:33 +01:00
eef6295bae also respecting day slots 2024-12-03 21:13:52 +01:00
ebb603b569 basic extensions 2024-12-02 22:58:28 +01:00
a0a32563cc removed obsolete code 2024-12-01 19:40:18 +01:00
3c90142162 preview page depending on roles 2024-12-01 19:38:19 +01:00
9c10da579c java 21 2024-12-01 19:20:57 +01:00
634c0d7b1c basic registration feature 2024-12-01 19:16:05 +01:00
bf88306d85 fixed damaged deleting slots, see #5 2024-11-11 23:24:34 +01:00
a1ad23920a repaired datatables extension 2024-10-03 10:03:26 +02:00
8d5a493bf7 ical extension 2024-09-02 21:29:07 +02:00
d28f6b45fc using Kinderstunde klein for date privileges 2024-07-30 11:22:50 +02:00
e3ebc387bb added next 2024-07-30 10:43:25 +02:00
aadfdfa9b5 corrected lib 2024-01-13 16:18:29 +01:00
e568589181 added download option, see #1 2024-01-13 15:59:57 +01:00
cffe483969 german time representation by keeping the ordering right; see #3 2024-01-13 15:42:59 +01:00
f1f9a5be9b fixed null on token 2023-12-26 21:01:06 +01:00
47bcd311ea securing with nextcloud oidc 2023-12-26 18:28:37 +01:00
6fdd4a57e0 added basic spring boot security 2023-12-17 20:56:56 +01:00
0999d41d64 add, edit, delete and assign themes to dates 2023-12-16 22:51:27 +01:00
5356e51f09 corrected subject notes manipulation 2023-12-16 20:53:04 +01:00
66 changed files with 2542 additions and 257 deletions

View File

@ -19,7 +19,7 @@
<attribute name="test" value="true"/> <attribute name="test" value="true"/>
</attributes> </attributes>
</classpathentry> </classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17/"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-21/"/>
<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.web.container"/> <classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.web.container"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"> <classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer">
<attributes> <attributes>

View File

@ -1,2 +1,13 @@
arguments=
auto.sync=false
build.scans.enabled=false
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
connection.project.dir= connection.project.dir=
eclipse.preferences.version=1 eclipse.preferences.version=1
gradle.user.home=
java.home=
jvm.arguments=
offline.mode=false
override.workspace.settings=false
show.console.view=false
show.executions.view=false

View File

@ -0,0 +1,4 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=21
org.eclipse.jdt.core.compiler.compliance=21
org.eclipse.jdt.core.compiler.source=21

View File

@ -3,5 +3,5 @@
<fixed facet="jst.java"/> <fixed facet="jst.java"/>
<fixed facet="jst.web"/> <fixed facet="jst.web"/>
<installed facet="jst.web" version="2.4"/> <installed facet="jst.web" version="2.4"/>
<installed facet="jst.java" version="17"/> <installed facet="jst.java" version="21"/>
</faceted-project> </faceted-project>

View File

@ -1,19 +1,25 @@
plugins { plugins {
id 'java' id 'java'
id 'org.springframework.boot' version '3.1.3' id 'org.springframework.boot' version '3.3.4'
id "io.spring.dependency-management" version "1.1.2" id "io.spring.dependency-management" version "1.1.6"
id 'war' id 'war'
id 'eclipse' id 'eclipse'
id 'application' id 'application'
} }
group = 'de.jottyfan.bico' group = 'de.jottyfan.bico'
version = '0.0.3' version = '0.2.6'
description = """BibleClassOrganizer""" description = """BibleClassOrganizer"""
sourceCompatibility = 17 java {
targetCompatibility = 17 toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
sourceCompatibility = 21
targetCompatibility = 21
mainClassName = "de.jottyfan.bico.Main" mainClassName = "de.jottyfan.bico.Main"
@ -35,24 +41,32 @@ war {
"Implementation-Timestamp": new Date()) "Implementation-Timestamp": new Date())
} }
} }
baseName = project.name archiveBaseName = project.name
version = version archiveVersion = version
archiveName = 'BiCO.war' archiveFileName = 'BiCO.war'
} }
dependencies { dependencies {
implementation 'de.jottyfan:bicolib:3' implementation 'de.jottyfan:bicolib:8'
implementation 'org.mnode.ical4j:ical4j:4.0.4'
implementation 'org.springframework.boot:spring-boot-starter-jooq' implementation 'org.springframework.boot:spring-boot-starter-jooq'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:latest.release' implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:latest.release'
implementation 'org.springframework.security:spring-security-oauth2-client'
implementation 'org.webjars:bootstrap:5.3.1' implementation 'org.webjars:bootstrap:5.3.3'
implementation 'org.webjars.npm:bootstrap-icons:1.10.5' implementation 'org.webjars.npm:bootstrap-icons:1.11.3'
implementation 'org.webjars:jquery:3.7.1' implementation 'org.webjars:jquery:3.7.1'
implementation 'org.webjars:datatables:1.13.5' implementation 'org.webjars.npm:datatables.net:2.1.7'
implementation 'org.webjars.npm:datatables.net-buttons:3.1.1'
implementation 'org.webjars.npm:datatables.net-responsive:3.0.1'
implementation 'org.webjars.npm:datatables.net-bs5:2.1.7'
implementation 'org.springframework.boot:spring-boot-devtools' implementation 'org.springframework.boot:spring-boot-devtools'
@ -69,3 +83,8 @@ test {
springBoot { springBoot {
buildInfo() buildInfo()
} }
tasks.withType(JavaCompile).configureEach {
options.compilerArgs.add("-parameters")
}

Binary file not shown.

View File

@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

309
gradlew vendored
View File

@ -1,78 +1,127 @@
#!/usr/bin/env sh #!/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 UN*X # 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/platforms/jvm/plugins-application/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 # Attempt to set APP_HOME
# Resolve links: $0 may be a link # Resolve links: $0 may be a link
PRG="$0" app_path=$0
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do # Need this for daisy-chained symlinks.
ls=`ls -ld "$PRG"` while
link=`expr "$ls" : '.*-> \(.*\)$'` APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
if expr "$link" : '/.*' > /dev/null; then [ -h "$app_path" ]
PRG="$link" do
else ls=$( ls -ld "$app_path" )
PRG=`dirname "$PRG"`"/$link" link=${ls#*' -> '}
fi case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle" # This is normally unused
APP_BASE_NAME=`basename "$0"` # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
DEFAULT_JVM_OPTS="" APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum" MAX_FD=maximum
warn () { warn () {
echo "$*" echo "$*"
} } >&2
die () { die () {
echo echo
echo "$*" echo "$*"
echo echo
exit 1 exit 1
} } >&2
# OS specific support (must be 'true' or 'false'). # OS specific support (must be 'true' or 'false').
cygwin=false cygwin=false
msys=false msys=false
darwin=false darwin=false
nonstop=false nonstop=false
case "`uname`" in case "$( uname )" in #(
CYGWIN* ) CYGWIN* ) cygwin=true ;; #(
cygwin=true Darwin* ) darwin=true ;; #(
;; MSYS* | MINGW* ) msys=true ;; #(
Darwin* ) NONSTOP* ) nonstop=true ;;
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables # IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java" JAVACMD=$JAVA_HOME/jre/sh/java
else else
JAVACMD="$JAVA_HOME/bin/java" JAVACMD=$JAVA_HOME/bin/java
fi fi
if [ ! -x "$JAVACMD" ] ; then if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@ -81,92 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi fi
else else
JAVACMD="java" JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 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 Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi fi
fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
MAX_FD_LIMIT=`ulimit -H -n` case $MAX_FD in #(
if [ $? -eq 0 ] ; then max*)
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
MAX_FD="$MAX_FD_LIMIT" # shellcheck disable=SC2039,SC3045
fi MAX_FD=$( ulimit -H -n ) ||
ulimit -n $MAX_FD warn "Could not query maximum file descriptor limit"
if [ $? -ne 0 ] ; then esac
warn "Could not set maximum file descriptor limit: $MAX_FD" case $MAX_FD in #(
fi '' | soft) :;; #(
else *)
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
fi # shellcheck disable=SC2039,SC3045
fi ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac esac
fi fi
# Escape application args # Collect all arguments for the java command, stacking in reverse order:
save () { # * args from the command line
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done # * the main class name
echo " " # * -classpath
} # * -D...appname settings
APP_ARGS=$(save "$@") # * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules # For Cygwin or MSYS, switch paths to Windows format before running java
eval set -- $DEFAULT_JVM_OPTS --illegal-access=permit $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong JAVACMD=$( cygpath --unix "$JAVACMD" )
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")" # 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 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" "$@" exec "$JAVACMD" "$@"

View File

@ -1,5 +1,7 @@
package de.jottyfan.bico; package de.jottyfan.bico;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.builder.SpringApplicationBuilder;
@ -12,6 +14,9 @@ import org.springframework.boot.web.servlet.support.SpringBootServletInitializer
*/ */
@SpringBootApplication @SpringBootApplication
public class Main extends SpringBootServletInitializer { public class Main extends SpringBootServletInitializer {
public static final Logger LOGGER = LogManager.getLogger(Main.class);
@Override @Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Main.class); return application.sources(Main.class);

View File

@ -17,7 +17,7 @@ import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
* *
*/ */
@Configuration @Configuration
public class DatabaseConfiguration { public class DatabaseConfig {
@Autowired @Autowired
private DataSource dataSource; private DataSource dataSource;

View File

@ -0,0 +1,26 @@
package de.jottyfan.bico.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
/**
*
* @author jotty
*
*/
@Configuration
public class SecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(
// @formatter:off
r -> r.requestMatchers("/", "/error", "/css/**", "/js/**", "/webjars/**", "/template").permitAll()
.requestMatchers("/**").authenticated())
.oauth2Login(l -> l.authorizationEndpoint(e -> e.baseUri("/oauth2/authorize-client")));
// @formatter:on
return http.build();
}
}

View File

@ -1,8 +1,16 @@
package de.jottyfan.bico.modules; package de.jottyfan.bico.modules;
import org.springframework.beans.factory.annotation.Autowired; import java.security.Principal;
import org.springframework.ui.Model; import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.web.bind.annotation.ModelAttribute;
import de.jottyfan.bico.Main;
import de.jottyfan.bico.modules.profile.ProfileService; import de.jottyfan.bico.modules.profile.ProfileService;
/** /**
@ -11,19 +19,74 @@ import de.jottyfan.bico.modules.profile.ProfileService;
* *
*/ */
public abstract class CommonController { public abstract class CommonController {
private static final List<String> admins = List.of("andre.sieber", "tobias.kuehne", "jotty", "kerstin.meisel");
@Autowired @Autowired
private ProfileService profileService; private ProfileService profileService;
@Value("${spring.security.oauth2.client.provider.nextcloud.issuer-uri}")
private String nextcloudUrl;
@ModelAttribute("isCampAdmin")
public Boolean isCampAdmin(Principal principal) {
return principal == null ? false : admins.contains(principal.getName());
}
@ModelAttribute("hasBUrole")
public Boolean hasBURole(Principal principal) {
OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) principal;
if (token != null) {
OAuth2User user = token.getPrincipal();
@SuppressWarnings("unchecked")
List<String> roles = (List<String>) user.getAttributes().get("roles");
return roles.contains("Bibelunterricht");
} else {
Main.LOGGER.warn("token is null, no roles can be detected");
return false;
}
}
@ModelAttribute("hasDateRole")
public Boolean hasDateRole(Principal principal) {
OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) principal;
if (token != null) {
OAuth2User user = token.getPrincipal();
@SuppressWarnings("unchecked")
List<String> roles = (List<String>) user.getAttributes().get("roles");
return roles.contains("Kinderstunde klein");
} else {
Main.LOGGER.warn("token is null, no roles can be detected");
return false;
}
}
@ModelAttribute("hasAnyRole")
public Boolean hasAnyRole(Principal principal) {
OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) principal;
if (token != null) {
OAuth2User user = token.getPrincipal();
@SuppressWarnings("unchecked")
List<String> roles = (List<String>) user.getAttributes().get("roles");
return roles.size() > 0;
} else {
Main.LOGGER.warn("token is null, no roles can be detected");
return false;
}
}
/** /**
* get the theme for the current session * get the theme for the current session
* *
* @return the theme; light or dark at the moment * @return the theme; light or dark at the moment
*/ */
public Model useThemedModel(Model model) { @ModelAttribute("theme")
// TODO: add profile's user name public String getTheme() {
String username = "jotty"; String username = SecurityContextHolder.getContext().getAuthentication().getName();
model.addAttribute("theme", profileService.getTheme(username)); return profileService.getTheme(username);
return model; }
@ModelAttribute("nextcloudUrl")
public String getNextcloudUrl() {
return nextcloudUrl;
} }
} }

View File

@ -0,0 +1,51 @@
package de.jottyfan.bico.modules.camp;
import java.io.IOException;
import java.security.Principal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import de.jottyfan.bico.modules.CommonController;
import jakarta.servlet.http.HttpServletResponse;
/**
*
* @author jotty
*
*/
@Configuration
@Controller
public class AdminRegistrationController extends CommonController {
@Autowired
private AdminRegistrationService service;
@GetMapping("/camp/registration/admin")
public String getList(Model model, Principal principal) {
if (isCampAdmin(principal)) {
model.addAttribute("list", service.getAllRegistrations());
model.addAttribute("ages", service.getAges());
}
return "/camp/list";
}
@GetMapping("/camp/registration/admin/download")
@ResponseBody
public String download(HttpServletResponse response, Principal principal) throws IOException {
if (isCampAdmin(principal)) {
response.setHeader("Content-Disposition", String.format("attachment; filename=Gemeindefreizeit-Anmeldungen-%s.csv",
LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME)));
response.setContentType("text/csv; charset=utf-8");
return service.getDownload();
} else {
return "forbidden";
}
}
}

View File

@ -0,0 +1,33 @@
package de.jottyfan.bico.modules.camp;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import de.jottyfan.bico.db.camp.tables.records.TAgeRecord;
import de.jottyfan.bico.modules.camp.model.RegistrationBean;
/**
*
* @author jotty
*
*/
@Service
public class AdminRegistrationService {
@Autowired
private RegistrationRepository repository;
public List<RegistrationBean> getAllRegistrations() {
return repository.getAllRegistrations();
}
public List<TAgeRecord> getAges() {
return repository.getAges();
}
public String getDownload() {
return repository.getDownload();
}
}

View File

@ -0,0 +1,77 @@
package de.jottyfan.bico.modules.camp;
import java.security.Principal;
import java.util.ArrayList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import de.jottyfan.bico.db.camp.enums.EnumSex;
import de.jottyfan.bico.modules.CommonController;
import de.jottyfan.bico.modules.camp.model.RegistrationBean;
import jakarta.validation.Valid;
/**
*
* @author jotty
*
*/
@Controller
public class RegistrationController extends CommonController {
@Autowired
private RegistrationService service;
@GetMapping("/camp/registration")
public String loadForm(Model model, Principal principal) {
model.addAttribute("registrations", service.getRegistrations(principal));
model.addAttribute("bean", RegistrationBean.withAllDays());
model.addAttribute("sexes", EnumSex.values());
model.addAttribute("ages", service.getAges());
return "/camp/registration";
}
@PostMapping("/camp/registration/submit")
public String submitAddForm(@Valid @ModelAttribute("bean") RegistrationBean bean, BindingResult bindingResult,
Model model, Principal principal) {
if (bindingResult.hasErrors()) {
model.addAttribute("registrations", new ArrayList<RegistrationBean>()); // hack to make "Neue Anmeldung" appear
model.addAttribute("sexes", EnumSex.values());
model.addAttribute("ages", service.getAges());
return "/camp/registration";
}
service.save(bean, principal);
return "redirect:/camp/registration";
}
@GetMapping("/camp/registration/edit/{id}")
public String loadEditForm(@PathVariable("id") Integer id, Model model, Principal principal) {
model.addAttribute("bean", service.getBeanOfPrincipal(id, principal));
model.addAttribute("sexes", EnumSex.values());
model.addAttribute("ages", service.getAges());
return "/camp/edit";
}
@GetMapping("/camp/registration/delete/{id}")
public String delete(@PathVariable("id") Integer id, Model model, Principal principal) {
service.delete(id, principal);
return "redirect:/camp/registration";
}
@PostMapping("/camp/registration/correct")
public String submitEditForm(@Valid @ModelAttribute("bean") RegistrationBean bean, BindingResult bindingResult,
Model model, Principal principal) {
if (bindingResult.hasErrors()) {
model.addAttribute("sexes", EnumSex.values());
return "/camp/registration";
}
service.save(bean, principal);
return "redirect:/camp/registration";
}
}

View File

@ -0,0 +1,222 @@
package de.jottyfan.bico.modules.camp;
import static de.jottyfan.bico.db.camp.Tables.T_AGE;
import static de.jottyfan.bico.db.camp.Tables.T_REGISTRATION;
import java.time.LocalDateTime;
import java.util.List;
import org.jooq.DSLContext;
import org.jooq.DeleteConditionStep;
import org.jooq.InsertValuesStep20;
import org.jooq.Record19;
import org.jooq.SelectConditionStep;
import org.jooq.SelectOnConditionStep;
import org.jooq.SelectSeekStep1;
import org.jooq.UpdateConditionStep;
import org.jooq.impl.DSL;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import de.jottyfan.bico.Main;
import de.jottyfan.bico.db.camp.enums.EnumCamp;
import de.jottyfan.bico.db.camp.enums.EnumSex;
import de.jottyfan.bico.db.camp.tables.records.TAgeRecord;
import de.jottyfan.bico.db.camp.tables.records.TRegistrationRecord;
import de.jottyfan.bico.modules.camp.model.RegistrationBean;
import jakarta.validation.Valid;
/**
*
* @author jotty
*
*/
@Repository
public class RegistrationRepository {
@Autowired
private DSLContext jooq;
/**
* save the validated bean
*
* @param bean the bean
* @param registrator the principal name
*/
public void save(@Valid RegistrationBean bean, String registrator) {
jooq.transaction(t -> {
if (bean.getPkRegistration() == null) {
InsertValuesStep20<TRegistrationRecord, EnumCamp, String, String, String, EnumSex, Boolean, String, Integer, Boolean, String, Boolean, Integer, Boolean, Boolean, Boolean, Boolean, Boolean, Boolean, Boolean, Boolean> sql = DSL.using(t)
// @formatter:off
.insertInto(T_REGISTRATION,
T_REGISTRATION.CAMP,
T_REGISTRATION.REGISTRATOR,
T_REGISTRATION.FORENAME,
T_REGISTRATION.SURNAME,
T_REGISTRATION.SEX,
T_REGISTRATION.BARRIER_FREE,
T_REGISTRATION.NUTRITION,
T_REGISTRATION.DRIVER_PROVIDE_PLACES,
T_REGISTRATION.WANT_PLACE_IN_CAR,
T_REGISTRATION.DISEASES,
T_REGISTRATION.REQUIRE_PAYMENT,
T_REGISTRATION.FK_AGE,
T_REGISTRATION.DAY0,
T_REGISTRATION.DAY1,
T_REGISTRATION.DAY2,
T_REGISTRATION.DAY3,
T_REGISTRATION.DAY4,
T_REGISTRATION.TOWELS,
T_REGISTRATION.BED_LINEN,
T_REGISTRATION.COT)
.values(EnumCamp.Gemeindefreizeit_2025, registrator, bean.getForename(), bean.getSurname(), EnumSex.lookupLiteral(bean.getSex()),
bean.getBarrierFree(), bean.getNutrition(), bean.getDriverProvidePlaces(), bean.getWantPlaceInCar(), bean.getDiseases(), bean.getRequirePayment(),
bean.getFkAge(), bean.getDay0(), bean.getDay1(), bean.getDay2(), bean.getDay3(), bean.getDay4(), bean.getTowels(), bean.getBedLinen(), bean.getCot());
// @formatter:on
Main.LOGGER.trace(sql);
sql.execute();
} else {
UpdateConditionStep<TRegistrationRecord> sql = DSL.using(t)
// @formatter:off
.update(T_REGISTRATION)
.set(T_REGISTRATION.FORENAME, bean.getForename())
.set(T_REGISTRATION.SURNAME, bean.getSurname())
.set(T_REGISTRATION.SEX, EnumSex.lookupLiteral(bean.getSex()))
.set(T_REGISTRATION.REGISTRATOR, registrator)
.set(T_REGISTRATION.BARRIER_FREE, bean.getBarrierFree())
.set(T_REGISTRATION.NUTRITION, bean.getNutrition())
.set(T_REGISTRATION.DRIVER_PROVIDE_PLACES, bean.getDriverProvidePlaces())
.set(T_REGISTRATION.WANT_PLACE_IN_CAR, bean.getWantPlaceInCar())
.set(T_REGISTRATION.DISEASES, bean.getDiseases())
.set(T_REGISTRATION.REQUIRE_PAYMENT, bean.getRequirePayment())
.set(T_REGISTRATION.FK_AGE, bean.getFkAge())
.set(T_REGISTRATION.DAY0, bean.getDay0())
.set(T_REGISTRATION.DAY1, bean.getDay1())
.set(T_REGISTRATION.DAY2, bean.getDay2())
.set(T_REGISTRATION.DAY3, bean.getDay3())
.set(T_REGISTRATION.DAY4, bean.getDay4())
.set(T_REGISTRATION.TOWELS, bean.getTowels())
.set(T_REGISTRATION.BED_LINEN, bean.getBedLinen())
.set(T_REGISTRATION.COT, bean.getCot())
.where(T_REGISTRATION.PK_REGISTRATION.eq(bean.getPkRegistration()));
// @formatter:on
Main.LOGGER.trace(sql);
sql.execute();
}
});
}
/**
* get all registrations (for admins only)
*
* @return the registrations
*/
public List<RegistrationBean> getAllRegistrations() {
SelectSeekStep1<TRegistrationRecord, LocalDateTime> sql = jooq
// @formatter:off
.selectFrom(T_REGISTRATION)
.orderBy(T_REGISTRATION.CREATED);
// @formatter:on
Main.LOGGER.trace(sql);
return sql.fetchInto(RegistrationBean.class);
}
/**
* get all registrations of name
*
* @param name the name
* @return the registrations
*/
public List<RegistrationBean> getRegistrations(String name) {
SelectConditionStep<TRegistrationRecord> sql = jooq
// @formatter:off
.selectFrom(T_REGISTRATION)
.where(T_REGISTRATION.REGISTRATOR.eq(name));
// @formatter:on
Main.LOGGER.trace(sql);
return sql.fetchInto(RegistrationBean.class);
}
/**
* get the registration of id if the creator is the name; null else
*
* @param id the id
* @param name the name
* @return the bean or null
*/
public RegistrationBean getBean(Integer id, String name) {
SelectConditionStep<TRegistrationRecord> sql = jooq
// @formatter:off
.selectFrom(T_REGISTRATION)
.where(T_REGISTRATION.PK_REGISTRATION.eq(id))
.and(T_REGISTRATION.REGISTRATOR.eq(name));
// @formatter:on
Main.LOGGER.trace(sql);
return sql.fetchOneInto(RegistrationBean.class);
}
/**
* delete the registration if the name is the creator
*
* @param id the id
* @param name the name
*/
public void delete(Integer id, String name) {
DeleteConditionStep<TRegistrationRecord> sql = jooq
// @formatter:off
.deleteFrom(T_REGISTRATION)
.where(T_REGISTRATION.PK_REGISTRATION.eq(id))
.and(T_REGISTRATION.REGISTRATOR.eq(name));
// @formatter:on
Main.LOGGER.trace(sql);
sql.execute();
}
/**
* get all ages
*
* @return the ages
*/
public List<TAgeRecord> getAges() {
SelectSeekStep1<TAgeRecord, Integer> sql = jooq
// @formatter:off
.selectFrom(T_AGE)
.orderBy(T_AGE.PK_AGE);
// @formatter:on
Main.LOGGER.trace(sql);
return sql.fetchInto(TAgeRecord.class);
}
/**
* get CSV version of the list, only for admins
*
* @return the csv
*/
public String getDownload() {
SelectOnConditionStep<Record19<LocalDateTime, String, String, EnumSex, String, Boolean, Boolean, Boolean, Boolean, Boolean, Boolean, Boolean, Boolean, Boolean, Boolean, String, String, Integer, Boolean>> sql = jooq
// @formatter:off
.select(T_REGISTRATION.CREATED,
T_REGISTRATION.FORENAME,
T_REGISTRATION.SURNAME,
T_REGISTRATION.SEX,
T_AGE.NAME,
T_REGISTRATION.DAY0,
T_REGISTRATION.DAY1,
T_REGISTRATION.DAY2,
T_REGISTRATION.DAY3,
T_REGISTRATION.DAY4,
T_REGISTRATION.BARRIER_FREE,
T_REGISTRATION.TOWELS,
T_REGISTRATION.BED_LINEN,
T_REGISTRATION.COT,
T_REGISTRATION.REQUIRE_PAYMENT,
T_REGISTRATION.DISEASES,
T_REGISTRATION.NUTRITION,
T_REGISTRATION.DRIVER_PROVIDE_PLACES,
T_REGISTRATION.WANT_PLACE_IN_CAR)
.from(T_REGISTRATION)
.leftJoin(T_AGE).on(T_AGE.PK_AGE.eq(T_REGISTRATION.FK_AGE));
// @formatter:on
Main.LOGGER.trace(sql);
return sql.fetch().formatCSV(true);
}
}

View File

@ -0,0 +1,72 @@
package de.jottyfan.bico.modules.camp;
import java.security.Principal;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import de.jottyfan.bico.db.camp.tables.records.TAgeRecord;
import de.jottyfan.bico.modules.camp.model.RegistrationBean;
import jakarta.validation.Valid;
/**
*
* @author jotty
*
*/
@Service
public class RegistrationService {
@Autowired
private RegistrationRepository repository;
/**
* save the bean content
*
* @param bean the bean
*/
public void save(@Valid RegistrationBean bean, Principal principal) {
repository.save(bean, principal.getName());
}
/**
* get the registrations of principal
*
* @param principal the principal
* @return the registrations
*/
public List<RegistrationBean> getRegistrations(Principal principal) {
return repository.getRegistrations(principal.getName());
}
/**
* get the registration if the principal was the creator
*
* @param id the ID of the registration
* @param principal the principal
* @return the bean or null if not found or allowed
*/
public RegistrationBean getBeanOfPrincipal(Integer id, Principal principal) {
return repository.getBean(id, principal.getName());
}
/**
* delete the registration if the principal was the creator
*
* @param id the ID of the registration
* @param principal the principal
*/
public void delete(Integer id, Principal principal) {
repository.delete(id, principal.getName());
}
/**
* get the ages
*
* @return the ages
*/
public List<TAgeRecord> getAges() {
return repository.getAges();
}
}

View File

@ -0,0 +1,320 @@
package de.jottyfan.bico.modules.camp.model;
import java.io.Serializable;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
/**
*
* @author jotty
*
*/
public class RegistrationBean implements Serializable {
private static final long serialVersionUID = 1L;
private Integer pkRegistration;
@NotBlank
private String forename;
@NotBlank
private String surname;
@NotBlank
private String sex;
@NotNull
private Boolean barrierFree;
private Boolean day0;
private Boolean day1;
private Boolean day2;
private Boolean day3;
private Boolean day4;
private Boolean towels;
private Boolean bedLinen;
private Boolean cot;
private String nutrition;
private Integer driverProvidePlaces;
private Boolean wantPlaceInCar;
private String diseases;
private Boolean requirePayment;
@NotNull
private Integer fkAge;
public static final RegistrationBean withAllDays() {
RegistrationBean bean = new RegistrationBean();
bean.setDay0(true);
bean.setDay1(true);
bean.setDay2(true);
bean.setDay3(true);
bean.setDay4(true);
return bean;
}
/**
* @return the forename
*/
public String getForename() {
return forename;
}
/**
* @param forename the forename to set
*/
public void setForename(String forename) {
this.forename = forename;
}
/**
* @return the surname
*/
public String getSurname() {
return surname;
}
/**
* @param surname the surname to set
*/
public void setSurname(String surname) {
this.surname = surname;
}
/**
* @return the sex
*/
public String getSex() {
return sex;
}
/**
* @param sex the sex to set
*/
public void setSex(String sex) {
this.sex = sex;
}
/**
* @return the pkRegistration
*/
public Integer getPkRegistration() {
return pkRegistration;
}
/**
* @param pkRegistration the pkRegistration to set
*/
public void setPkRegistration(Integer pkRegistration) {
this.pkRegistration = pkRegistration;
}
/**
* @return the barrierFree
*/
public Boolean getBarrierFree() {
return barrierFree;
}
/**
* @param barrierFree the barrierFree to set
*/
public void setBarrierFree(Boolean barrierFree) {
this.barrierFree = barrierFree;
}
/**
* @return the nutrition
*/
public String getNutrition() {
return nutrition;
}
/**
* @param nutrition the nutrition to set
*/
public void setNutrition(String nutrition) {
this.nutrition = nutrition;
}
/**
* @return the diseases
*/
public String getDiseases() {
return diseases;
}
/**
* @param diseases the diseases to set
*/
public void setDiseases(String diseases) {
this.diseases = diseases;
}
/**
* @return the driverProvidePlaces
*/
public Integer getDriverProvidePlaces() {
return driverProvidePlaces;
}
/**
* @param driverProvidePlaces the driverProvidePlaces to set
*/
public void setDriverProvidePlaces(Integer driverProvidePlaces) {
this.driverProvidePlaces = driverProvidePlaces;
}
/**
* @return the wantPlaceInCar
*/
public Boolean getWantPlaceInCar() {
return wantPlaceInCar;
}
/**
* @param wantPlaceInCar the wantPlaceInCar to set
*/
public void setWantPlaceInCar(Boolean wantPlaceInCar) {
this.wantPlaceInCar = wantPlaceInCar;
}
/**
* @return the requirePayment
*/
public Boolean getRequirePayment() {
return requirePayment;
}
/**
* @param requirePayment the requirePayment to set
*/
public void setRequirePayment(Boolean requirePayment) {
this.requirePayment = requirePayment;
}
/**
* @return the fkAge
*/
public Integer getFkAge() {
return fkAge;
}
/**
* @param fkAge the fkAge to set
*/
public void setFkAge(Integer fkAge) {
this.fkAge = fkAge;
}
/**
* @return the day0
*/
public Boolean getDay0() {
return day0;
}
/**
* @param day0 the day0 to set
*/
public void setDay0(Boolean day0) {
this.day0 = day0;
}
/**
* @return the day1
*/
public Boolean getDay1() {
return day1;
}
/**
* @param day1 the day1 to set
*/
public void setDay1(Boolean day1) {
this.day1 = day1;
}
/**
* @return the day2
*/
public Boolean getDay2() {
return day2;
}
/**
* @param day2 the day2 to set
*/
public void setDay2(Boolean day2) {
this.day2 = day2;
}
/**
* @return the day3
*/
public Boolean getDay3() {
return day3;
}
/**
* @param day3 the day3 to set
*/
public void setDay3(Boolean day3) {
this.day3 = day3;
}
/**
* @return the day4
*/
public Boolean getDay4() {
return day4;
}
/**
* @param day4 the day4 to set
*/
public void setDay4(Boolean day4) {
this.day4 = day4;
}
/**
* @return the towels
*/
public Boolean getTowels() {
return towels;
}
/**
* @param towels the towels to set
*/
public void setTowels(Boolean towels) {
this.towels = towels;
}
/**
* @return the bedLinen
*/
public Boolean getBedLinen() {
return bedLinen;
}
/**
* @param bedLinen the bedLinen to set
*/
public void setBedLinen(Boolean bedLinen) {
this.bedLinen = bedLinen;
}
/**
* @return the cot
*/
public Boolean getCot() {
return cot;
}
/**
* @param cot the cot to set
*/
public void setCot(Boolean cot) {
this.cot = cot;
}
}

View File

@ -0,0 +1,29 @@
package de.jottyfan.bico.modules.download;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import de.jottyfan.bico.modules.CommonController;
/**
*
* @author jotty
*
*/
@Controller
public class DownloadController extends CommonController {
@Autowired
private DownloadService service;
@GetMapping(value = "/download", produces = "text/csv")
@ResponseBody
@ResponseStatus(HttpStatus.OK)
public String download() {
return service.getCsv();
}
}

View File

@ -0,0 +1,31 @@
package de.jottyfan.bico.modules.download;
import static de.jottyfan.bico.db.public_.Tables.V_CALENDAR;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jooq.DSLContext;
import org.jooq.SelectWhereStep;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import de.jottyfan.bico.db.public_.tables.records.VCalendarRecord;
/**
*
* @author jotty
*
*/
@Repository
public class DownloadRepository {
private static final Logger LOGGER = LogManager.getLogger(DownloadRepository.class);
@Autowired
private DSLContext jooq;
public String getCsv() {
SelectWhereStep<VCalendarRecord> sql = jooq.selectFrom(V_CALENDAR);
LOGGER.trace(sql);
return sql.fetch().formatCSV();
}
}

View File

@ -0,0 +1,20 @@
package de.jottyfan.bico.modules.download;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
*
* @author jotty
*
*/
@Service
public class DownloadService {
@Autowired
private DownloadRepository repository;
public String getCsv() {
return repository.getCsv();
}
}

View File

@ -0,0 +1,30 @@
package de.jottyfan.bico.modules.ical;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import de.jottyfan.bico.modules.CommonController;
import jakarta.servlet.http.HttpServletResponse;
/**
*
* @author jotty
*
*/
@Controller
public class IcalController extends CommonController{
@Autowired
private IcalService service;
@GetMapping("/ical")
public void getIcalExport(HttpServletResponse response) throws Exception {
response.setHeader("Content-Disposition", "attachment; filename=bico.ics");
response.setContentType("application/octet-stream");
response.setHeader("Content-Transfer-Encoding", "binary");
response.setHeader("Accept-Ranges", "bytes");
service.createCalendar(response.getOutputStream());
response.flushBuffer();
}
}

View File

@ -0,0 +1,31 @@
package de.jottyfan.bico.modules.ical;
import static de.jottyfan.bico.db.public_.Tables.V_CALENDAR;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jooq.DSLContext;
import org.jooq.SelectWhereStep;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import de.jottyfan.bico.db.public_.tables.records.VCalendarRecord;
/**
*
* @author jotty
*
*/
@Repository
public class IcalRepository {
private static final Logger LOGGER = LogManager.getLogger(IcalRepository.class);
@Autowired
private DSLContext jooq;
public VCalendarRecord[] getAllDates() {
SelectWhereStep<VCalendarRecord> sql = jooq.selectFrom(V_CALENDAR);
LOGGER.trace(sql);
return sql.fetchArray();
}
}

View File

@ -0,0 +1,40 @@
package de.jottyfan.bico.modules.ical;
import java.io.IOException;
import java.io.OutputStream;
import java.time.LocalDateTime;
import java.time.LocalTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import de.jottyfan.bico.db.public_.tables.records.VCalendarRecord;
import net.fortuna.ical4j.data.CalendarOutputter;
import net.fortuna.ical4j.model.Calendar;
import net.fortuna.ical4j.model.component.VEvent;
import net.fortuna.ical4j.validate.ValidationException;
/**
*
* @author jotty
*
*/
@Service
public class IcalService {
@Autowired
private IcalRepository repository;
public void createCalendar(OutputStream stream) throws ValidationException, IOException {
Calendar calendar = new Calendar();
CalendarOutputter out = new CalendarOutputter();
for (VCalendarRecord record : repository.getAllDates()) {
String summary = record.getFullname();
LocalDateTime startEvent = LocalDateTime.of(record.getSlotDay(), LocalTime.of(10, 30));
LocalDateTime endEvent = LocalDateTime.of(record.getSlotDay(), LocalTime.of(12, 0));
VEvent event = new VEvent(startEvent, endEvent, summary);
calendar.add(event);
}
out.output(calendar, stream);;
}
}

View File

@ -1,7 +1,6 @@
package de.jottyfan.bico.modules.index; package de.jottyfan.bico.modules.index;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import de.jottyfan.bico.modules.CommonController; import de.jottyfan.bico.modules.CommonController;
@ -14,8 +13,7 @@ import de.jottyfan.bico.modules.CommonController;
@Controller @Controller
public class IndexController extends CommonController { public class IndexController extends CommonController {
@GetMapping("/") @GetMapping("/")
public String getIndex(Model model) { public String getIndex() {
useThemedModel(model); return "redirect:/next";
return "redirect:/sheet";
} }
} }

View File

@ -9,7 +9,7 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import de.jottyfan.bico.db.tables.records.TLessonRecord; import de.jottyfan.bico.db.public_.tables.records.TLessonRecord;
import de.jottyfan.bico.modules.CommonController; import de.jottyfan.bico.modules.CommonController;
/** /**
* *

View File

@ -1,8 +1,8 @@
package de.jottyfan.bico.modules.lesson; package de.jottyfan.bico.modules.lesson;
import static de.jottyfan.bico.db.Tables.T_LESSON; import static de.jottyfan.bico.db.public_.Tables.T_LESSON;
import static de.jottyfan.bico.db.Tables.T_PERSON; import static de.jottyfan.bico.db.public_.Tables.T_PERSON;
import static de.jottyfan.bico.db.Tables.T_SLOT; import static de.jottyfan.bico.db.public_.Tables.T_SLOT;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List; import java.util.List;
@ -19,8 +19,8 @@ import org.jooq.UpdateConditionStep;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import de.jottyfan.bico.db.tables.records.TLessonRecord; import de.jottyfan.bico.db.public_.tables.records.TLessonRecord;
import de.jottyfan.bico.db.tables.records.TPersonRecord; import de.jottyfan.bico.db.public_.tables.records.TPersonRecord;
/** /**
* *

View File

@ -6,8 +6,8 @@ import java.util.List;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import de.jottyfan.bico.db.tables.records.TLessonRecord; import de.jottyfan.bico.db.public_.tables.records.TLessonRecord;
import de.jottyfan.bico.db.tables.records.TPersonRecord; import de.jottyfan.bico.db.public_.tables.records.TPersonRecord;
/** /**
* *

View File

@ -0,0 +1,38 @@
package de.jottyfan.bico.modules.next;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import de.jottyfan.bico.modules.CommonController;
/**
*
* @author jotty
*
*/
@Controller
public class NextController extends CommonController {
@Autowired
private NextService service;
@GetMapping("/next")
public String getNext(Model model) {
model.addAttribute("list", service.getNext(LocalDate.now()));
return "/next";
}
@GetMapping("/next/{date}")
public String getNextForDate(@PathVariable("date") String dateStr, Model model) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate date = LocalDate.parse(dateStr, formatter);
model.addAttribute("list", service.getNext(date));
return "/next";
}
}

View File

@ -0,0 +1,56 @@
package de.jottyfan.bico.modules.next;
import static de.jottyfan.bico.db.public_.Tables.V_CALENDAR;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jooq.DSLContext;
import org.jooq.Record2;
import org.jooq.SelectSeekStep1;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import de.jottyfan.bico.modules.next.model.NextBean;
/**
*
* @author jotty
*
*/
@Repository
public class NextRepository {
private static final Logger LOGGER = LogManager.getLogger(NextRepository.class);
@Autowired
private DSLContext jooq;
/**
* get the next dates from date on
*
* @param date the date
* @return the next dates as beans in a list; an empty list at least
*/
public List<NextBean> getNext(LocalDate date) {
SelectSeekStep1<Record2<String, LocalDate>, LocalDate> sql = jooq
// @formatter:off
.selectDistinct(V_CALENDAR.FULLNAME, V_CALENDAR.SLOT_DAY)
.from(V_CALENDAR)
.where(V_CALENDAR.SLOT_DAY.ge(date))
.orderBy(V_CALENDAR.SLOT_DAY.asc());
// @formatter:on
LOGGER.trace(sql);
Iterator<Record2<String, LocalDate>> i = sql.fetch().iterator();
List<NextBean> list = new ArrayList<>();
while (i.hasNext()) {
Record2<String, LocalDate> r = i.next();
list.add(NextBean.of(r.get(V_CALENDAR.FULLNAME), r.get(V_CALENDAR.SLOT_DAY)));
}
return list;
}
}

View File

@ -0,0 +1,32 @@
package de.jottyfan.bico.modules.next;
import java.time.LocalDate;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import de.jottyfan.bico.modules.next.model.NextBean;
/**
*
* @author jotty
*
*/
@Service
public class NextService {
@Autowired
private NextRepository repository;
/**
* get the next dates
*
* @param date the date
* @return the list of next dates
*/
public List<NextBean> getNext(LocalDate date) {
return repository.getNext(date);
}
}

View File

@ -0,0 +1,54 @@
package de.jottyfan.bico.modules.next.model;
import java.io.Serializable;
import java.time.LocalDate;
/**
*
* @author jotty
*
*/
public class NextBean implements Serializable {
private static final long serialVersionUID = 1L;
private String fullname;
private LocalDate day;
private NextBean() {
}
public final static NextBean of(String fullname, LocalDate day) {
NextBean bean = new NextBean();
bean.setDay(day);
bean.setFullname(fullname);
return bean;
}
/**
* @return the fullname
*/
public String getFullname() {
return fullname;
}
/**
* @return the day
*/
public LocalDate getDay() {
return day;
}
/**
* @param fullname the fullname to set
*/
private void setFullname(String fullname) {
this.fullname = fullname;
}
/**
* @param day the day to set
*/
private void setDay(LocalDate day) {
this.day = day;
}
}

View File

@ -25,7 +25,7 @@ public class ProfileController extends CommonController {
* @param theme the theme * @param theme the theme
*/ */
@PostMapping("/updateTheme/{theme}") @PostMapping("/updateTheme/{theme}")
public ResponseEntity<?> updateTheme(@PathVariable String theme) { public ResponseEntity<?> updateTheme(@PathVariable("theme") String theme) {
// TODO: add profile's user name // TODO: add profile's user name
String username = "jotty"; String username = "jotty";
service.updateTheme(username, theme); service.updateTheme(username, theme);

View File

@ -1,6 +1,6 @@
package de.jottyfan.bico.modules.profile; package de.jottyfan.bico.modules.profile;
import static de.jottyfan.bico.db.Tables.T_PROFILE; import static de.jottyfan.bico.db.public_.Tables.T_PROFILE;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@ -10,7 +10,7 @@ import org.jooq.SelectConditionStep;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import de.jottyfan.bico.db.tables.records.TProfileRecord; import de.jottyfan.bico.db.public_.tables.records.TProfileRecord;
import de.jottyfan.bico.modules.profile.model.ProfileBean; import de.jottyfan.bico.modules.profile.model.ProfileBean;
/** /**

View File

@ -20,7 +20,7 @@ public class SheetController extends CommonController {
@GetMapping("/sheet") @GetMapping("/sheet")
public String getSheet(Model model) { public String getSheet(Model model) {
useThemedModel(model).addAttribute("list", service.getList()); model.addAttribute("list", service.getList());
return "/sheet"; return "/sheet";
} }
} }

View File

@ -1,6 +1,6 @@
package de.jottyfan.bico.modules.sheet; package de.jottyfan.bico.modules.sheet;
import static de.jottyfan.bico.db.Tables.V_CALENDAR; import static de.jottyfan.bico.db.public_.Tables.V_CALENDAR;
import java.util.List; import java.util.List;
@ -11,7 +11,7 @@ import org.jooq.SelectWhereStep;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import de.jottyfan.bico.db.tables.records.VCalendarRecord; import de.jottyfan.bico.db.public_.tables.records.VCalendarRecord;
/** /**
* *

View File

@ -5,7 +5,7 @@ import java.util.List;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import de.jottyfan.bico.db.tables.records.VCalendarRecord; import de.jottyfan.bico.db.public_.tables.records.VCalendarRecord;
/** /**
* *

View File

@ -30,20 +30,20 @@ public class SlotController extends CommonController {
} }
@GetMapping("/slot/{id}") @GetMapping("/slot/{id}")
public String load(@PathVariable Integer id, Model model) { public String load(@PathVariable("id") Integer id, Model model) {
model.addAttribute("bean", id == null ? new SlotBean() : service.loadSlot(id)); model.addAttribute("bean", id == null ? new SlotBean() : service.loadSlot(id));
model.addAttribute("hasLesson", service.slotHasLesson(id)); model.addAttribute("hasLesson", service.slotHasLesson(id));
return "/slot/item"; return "/slot/item";
} }
@GetMapping("/slot/{id}/delete") @GetMapping("/slot/{id}/delete")
public String loadEnsurance(@PathVariable Integer id, Model model) { public String loadEnsurance(@PathVariable("id") Integer id, Model model) {
model.addAttribute("bean", service.loadDeletableSlot(id)); model.addAttribute("bean", service.loadDeletableSlot(id));
return "/slot/delete"; return "/slot/delete";
} }
@GetMapping("/slot/{id}/destroy") @GetMapping("/slot/{id}/destroy")
public String destroy(@PathVariable Integer id, Model model) { public String destroy(@PathVariable("id") Integer id, Model model) {
service.removeSlot(id); service.removeSlot(id);
return "redirect:/"; return "redirect:/";
} }

View File

@ -1,7 +1,7 @@
package de.jottyfan.bico.modules.slot; package de.jottyfan.bico.modules.slot;
import static de.jottyfan.bico.db.Tables.T_LESSON; import static de.jottyfan.bico.db.public_.Tables.T_LESSON;
import static de.jottyfan.bico.db.Tables.T_SLOT; import static de.jottyfan.bico.db.public_.Tables.T_SLOT;
import java.util.Iterator; import java.util.Iterator;
@ -15,8 +15,8 @@ import org.jooq.UpdateConditionStep;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import de.jottyfan.bico.db.tables.records.TLessonRecord; import de.jottyfan.bico.db.public_.tables.records.TLessonRecord;
import de.jottyfan.bico.db.tables.records.TSlotRecord; import de.jottyfan.bico.db.public_.tables.records.TSlotRecord;
import de.jottyfan.bico.modules.slot.model.SlotBean; import de.jottyfan.bico.modules.slot.model.SlotBean;
/** /**
@ -139,7 +139,7 @@ public class SlotRepository {
.selectFrom(T_LESSON) .selectFrom(T_LESSON)
.where(T_LESSON.FK_SLOT.eq(slotId)); .where(T_LESSON.FK_SLOT.eq(slotId));
// @formatter:on // @formatter:on
LOGGER.trace(sql); LOGGER.info(sql);
return sql.fetch().size() > 0; return sql.fetch().size() > 0;
} }
} }

View File

@ -1,8 +1,14 @@
package de.jottyfan.bico.modules.subject; package de.jottyfan.bico.modules.subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import de.jottyfan.bico.db.public_.tables.records.TSubjectRecord;
import de.jottyfan.bico.modules.CommonController; import de.jottyfan.bico.modules.CommonController;
/** /**
@ -13,8 +19,44 @@ import de.jottyfan.bico.modules.CommonController;
@Controller @Controller
public class SubjectController extends CommonController { public class SubjectController extends CommonController {
@Autowired
private SubjectService service;
@GetMapping("/subject/list")
public String getSubjectList(Model model) {
model.addAttribute("list", service.getSubjects());
return "/subject/list";
}
@GetMapping("/subject/{id}")
public String getSubject(@PathVariable("id") Integer pkSubject, Model model) {
model.addAttribute("bean", service.getSubject(pkSubject));
model.addAttribute("sources", service.getSources());
return "/subject/item";
}
@PostMapping("/subject/save")
public String saveSubject(@ModelAttribute("bean") TSubjectRecord bean) {
service.save(bean);
return "redirect:/subject/list";
}
@GetMapping("/subject/{id}/delete")
public String checkDeletionOfSubject(@PathVariable("id") Integer pkSubject, Model model) {
model.addAttribute("bean", service.getBeanIfDeletable(pkSubject));
return "/subject/delete";
}
@GetMapping("/subject/{id}/remove")
public String removeSubject(@PathVariable("id") Integer pkSubject, Model model) {
service.removeSubject(pkSubject);
return "redirect:/subject/list";
}
@GetMapping("/subject/new") @GetMapping("/subject/new")
public String loadNewSubjectForm() { public String loadNewSubjectForm(Model model) {
return "/subject/new"; model.addAttribute("bean", new TSubjectRecord());
model.addAttribute("sources", service.getSources());
return "/subject/item";
} }
} }

View File

@ -0,0 +1,115 @@
package de.jottyfan.bico.modules.subject;
import static de.jottyfan.bico.db.public_.Tables.T_LESSON_SUBJECT;
import static de.jottyfan.bico.db.public_.Tables.T_SOURCE;
import static de.jottyfan.bico.db.public_.Tables.T_SUBJECT;
import static de.jottyfan.bico.db.public_.Tables.V_LESSON;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jooq.DSLContext;
import org.jooq.DeleteConditionStep;
import org.jooq.InsertValuesStep8;
import org.jooq.SelectConditionStep;
import org.jooq.SelectOrderByStep;
import org.jooq.SelectWhereStep;
import org.jooq.UpdateConditionStep;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import de.jottyfan.bico.db.public_.tables.records.TSourceRecord;
import de.jottyfan.bico.db.public_.tables.records.TSubjectRecord;
import de.jottyfan.bico.db.public_.tables.records.VLessonRecord;
/**
*
* @author jotty
*
*/
@Repository
public class SubjectRepository {
private static final Logger LOGGER = LogManager.getLogger(SubjectRepository.class);
@Autowired
private DSLContext jooq;
public List<VLessonRecord> getSubjects() {
SelectWhereStep<VLessonRecord> sql = jooq.selectFrom(V_LESSON);
LOGGER.trace(sql);
return sql.fetch().stream().toList();
}
public List<TSourceRecord> getSources() {
SelectWhereStep<TSourceRecord> sql = jooq.selectFrom(T_SOURCE);
LOGGER.trace(sql);
return sql.fetch().stream().toList();
}
public TSubjectRecord getSubject(Integer pkSubject) {
SelectConditionStep<TSubjectRecord> sql = jooq
// @formatter:off
.selectFrom(T_SUBJECT)
.where(T_SUBJECT.PK_SUBJECT.eq(pkSubject));
// @formatter:on
LOGGER.trace(sql);
return sql.fetchOne();
}
public void addSubject(TSubjectRecord bean) {
InsertValuesStep8<TSubjectRecord, Integer, String, String, String, String, String, String, Integer> sql = jooq
// @formatter:off
.insertInto(T_SUBJECT,
T_SUBJECT.FK_SOURCE,
T_SUBJECT.THEME,
T_SUBJECT.SUBTHEME,
T_SUBJECT.BOOK_PAGES,
T_SUBJECT.WORKSHEETS,
T_SUBJECT.BIBLEVERSE,
T_SUBJECT.NOTES,
T_SUBJECT.ORDER_NR)
.values(bean.getFkSource(), bean.getTheme(), bean.getSubtheme(), bean.getBookPages(), bean.getWorksheets(),
bean.getBibleverse(), bean.getNotes(), bean.getOrderNr());
// @formatter:on
LOGGER.trace(sql);
sql.execute();
}
public void updateSubject(TSubjectRecord bean) {
UpdateConditionStep<TSubjectRecord> sql = jooq
// @formatter:off
.update(T_SUBJECT)
.set(T_SUBJECT.FK_SOURCE, bean.getFkSource())
.set(T_SUBJECT.THEME, bean.getTheme())
.set(T_SUBJECT.SUBTHEME, bean.getSubtheme())
.set(T_SUBJECT.BOOK_PAGES, bean.getBookPages())
.set(T_SUBJECT.WORKSHEETS, bean.getWorksheets())
.set(T_SUBJECT.BIBLEVERSE, bean.getBibleverse())
.set(T_SUBJECT.NOTES, bean.getNotes())
.set(T_SUBJECT.ORDER_NR, bean.getOrderNr())
.where(T_SUBJECT.PK_SUBJECT.eq(bean.getPkSubject()));
// @formatter:on
LOGGER.trace(sql);
sql.execute();
}
public TSubjectRecord getBeanIfDeletable(Integer pkSubject) {
SelectOrderByStep<TSubjectRecord> sql = jooq
// @formatter:off
.selectFrom(T_SUBJECT)
.where(T_SUBJECT.PK_SUBJECT.notIn(jooq
.select(T_LESSON_SUBJECT.FK_SUBJECT)
.from(T_LESSON_SUBJECT)))
.and(T_SUBJECT.PK_SUBJECT.eq(pkSubject));
// @formatter:off
LOGGER.trace(sql);
return sql.fetchOne();
}
public void removeSubject(Integer pkSubject) {
DeleteConditionStep<TSubjectRecord> sql = jooq.deleteFrom(T_SUBJECT).where(T_SUBJECT.PK_SUBJECT.eq(pkSubject));
LOGGER.trace(sql);
sql.execute();
}
}

View File

@ -0,0 +1,50 @@
package de.jottyfan.bico.modules.subject;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import de.jottyfan.bico.db.public_.tables.records.TSourceRecord;
import de.jottyfan.bico.db.public_.tables.records.TSubjectRecord;
import de.jottyfan.bico.db.public_.tables.records.VLessonRecord;
/**
*
* @author jotty
*
*/
@Service
public class SubjectService {
@Autowired
private SubjectRepository repository;
public List<VLessonRecord> getSubjects() {
return repository.getSubjects();
}
public TSubjectRecord getSubject(Integer pkSubject) {
return repository.getSubject(pkSubject);
}
public List<TSourceRecord> getSources() {
return repository.getSources();
}
public void save(TSubjectRecord bean) {
if (bean.getPkSubject() == null) {
repository.addSubject(bean);
} else {
repository.updateSubject(bean);
}
}
public TSubjectRecord getBeanIfDeletable(Integer pkSubject) {
return repository.getBeanIfDeletable(pkSubject);
}
public void removeSubject(Integer pkSubject) {
repository.removeSubject(pkSubject);
}
}

View File

@ -0,0 +1,164 @@
package de.jottyfan.bico.modules.subject.model;
import java.io.Serializable;
import de.jottyfan.bico.db.public_.tables.records.TSubjectRecord;
/**
*
* @author jotty
*
*/
public class SubjectBean implements Serializable {
private static final long serialVersionUID = 1L;
private Integer pkSubject;
private Integer fkSource;
private String theme;
private String subtheme;
private String bookPages;
private String worksheets;
private String bibleverse;
private String notes;
private Integer orderNr;
public static final SubjectBean of(TSubjectRecord r) {
SubjectBean bean = new SubjectBean();
bean.setPkSubject(r.getPkSubject());
bean.setFkSource(r.getFkSource());
bean.setTheme(r.getTheme());
bean.setSubtheme(r.getSubtheme());
bean.setBookPages(r.getBookPages());
bean.setWorksheets(r.getWorksheets());
bean.setBibleverse(r.getBibleverse());
bean.setNotes(r.getNotes());
bean.setOrderNr(r.getOrderNr());
return bean;
}
/**
* @return the pkSubject
*/
public Integer getPkSubject() {
return pkSubject;
}
/**
* @param pkSubject the pkSubject to set
*/
public void setPkSubject(Integer pkSubject) {
this.pkSubject = pkSubject;
}
/**
* @return the fkSource
*/
public Integer getFkSource() {
return fkSource;
}
/**
* @param fkSource the fkSource to set
*/
public void setFkSource(Integer fkSource) {
this.fkSource = fkSource;
}
/**
* @return the theme
*/
public String getTheme() {
return theme;
}
/**
* @param theme the theme to set
*/
public void setTheme(String theme) {
this.theme = theme;
}
/**
* @return the subtheme
*/
public String getSubtheme() {
return subtheme;
}
/**
* @param subtheme the subtheme to set
*/
public void setSubtheme(String subtheme) {
this.subtheme = subtheme;
}
/**
* @return the bookPages
*/
public String getBookPages() {
return bookPages;
}
/**
* @param bookPages the bookPages to set
*/
public void setBookPages(String bookPages) {
this.bookPages = bookPages;
}
/**
* @return the worksheets
*/
public String getWorksheets() {
return worksheets;
}
/**
* @param worksheets the worksheets to set
*/
public void setWorksheets(String worksheets) {
this.worksheets = worksheets;
}
/**
* @return the bibleverse
*/
public String getBibleverse() {
return bibleverse;
}
/**
* @param bibleverse the bibleverse to set
*/
public void setBibleverse(String bibleverse) {
this.bibleverse = bibleverse;
}
/**
* @return the notes
*/
public String getNotes() {
return notes;
}
/**
* @param notes the notes to set
*/
public void setNotes(String notes) {
this.notes = notes;
}
/**
* @return the orderNr
*/
public Integer getOrderNr() {
return orderNr;
}
/**
* @param orderNr the orderNr to set
*/
public void setOrderNr(Integer orderNr) {
this.orderNr = orderNr;
}
}

View File

@ -8,7 +8,7 @@ import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import de.jottyfan.bico.db.tables.records.TLessonRecord; import de.jottyfan.bico.db.public_.tables.records.TLessonRecord;
import de.jottyfan.bico.modules.CommonController; import de.jottyfan.bico.modules.CommonController;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
@ -26,6 +26,7 @@ public class ThemeController extends CommonController {
@GetMapping("/theme") @GetMapping("/theme")
public String getThemeManipulationBySlotId(@RequestParam("slotId") Integer slotId, Model model) { public String getThemeManipulationBySlotId(@RequestParam("slotId") Integer slotId, Model model) {
model.addAttribute("slotId", slotId); model.addAttribute("slotId", slotId);
model.addAttribute("day", service.getSlotDay(slotId));
model.addAttribute("list", service.getThemeManipulation(slotId)); model.addAttribute("list", service.getThemeManipulation(slotId));
model.addAttribute("themes", service.getAllThemes()); model.addAttribute("themes", service.getAllThemes());
model.addAttribute("lesson", service.getLesson(slotId)); model.addAttribute("lesson", service.getLesson(slotId));
@ -49,6 +50,6 @@ public class ThemeController extends CommonController {
@PostMapping("/theme/update") @PostMapping("/theme/update")
public String updateLesson(@RequestParam("slotId") Integer slotId, @ModelAttribute("lesson") TLessonRecord bean) { public String updateLesson(@RequestParam("slotId") Integer slotId, @ModelAttribute("lesson") TLessonRecord bean) {
service.updateLesson(bean); service.updateLesson(bean);
return "redirect:/theme?slotId=" + bean.getFkSlot(); return "redirect:/theme?slotId=" + slotId;
} }
} }

View File

@ -1,10 +1,12 @@
package de.jottyfan.bico.modules.theme; package de.jottyfan.bico.modules.theme;
import static de.jottyfan.bico.db.Tables.T_LESSON; import static de.jottyfan.bico.db.public_.Tables.T_LESSON;
import static de.jottyfan.bico.db.Tables.T_LESSON_SUBJECT; import static de.jottyfan.bico.db.public_.Tables.T_LESSON_SUBJECT;
import static de.jottyfan.bico.db.Tables.T_SOURCE; import static de.jottyfan.bico.db.public_.Tables.T_SLOT;
import static de.jottyfan.bico.db.Tables.T_SUBJECT; import static de.jottyfan.bico.db.public_.Tables.T_SOURCE;
import static de.jottyfan.bico.db.public_.Tables.T_SUBJECT;
import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -13,6 +15,7 @@ import org.jboss.logging.Logger;
import org.jooq.DSLContext; import org.jooq.DSLContext;
import org.jooq.DeleteConditionStep; import org.jooq.DeleteConditionStep;
import org.jooq.InsertOnDuplicateStep; import org.jooq.InsertOnDuplicateStep;
import org.jooq.Record1;
import org.jooq.Record10; import org.jooq.Record10;
import org.jooq.Record4; import org.jooq.Record4;
import org.jooq.SelectConditionStep; import org.jooq.SelectConditionStep;
@ -22,8 +25,8 @@ import org.jooq.impl.DSL;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import de.jottyfan.bico.db.tables.records.TLessonRecord; import de.jottyfan.bico.db.public_.tables.records.TLessonRecord;
import de.jottyfan.bico.db.tables.records.TLessonSubjectRecord; import de.jottyfan.bico.db.public_.tables.records.TLessonSubjectRecord;
import de.jottyfan.bico.modules.theme.model.KeyValueBean; import de.jottyfan.bico.modules.theme.model.KeyValueBean;
import de.jottyfan.bico.modules.theme.model.ThemeBean; import de.jottyfan.bico.modules.theme.model.ThemeBean;
@ -138,4 +141,15 @@ public class ThemeRepository {
sql.execute(); sql.execute();
} }
public LocalDate getSlotDay(Integer slotId) {
SelectConditionStep<Record1<LocalDate>> sql = jooq
// @formatter:off
.select(T_SLOT.SLOT_DAY)
.from(T_SLOT)
.where(T_SLOT.PK_SLOT.eq(slotId));
// @formatter:on
LOGGER.trace(sql);
return sql.fetchOne().get(T_SLOT.SLOT_DAY);
}
} }

View File

@ -1,11 +1,12 @@
package de.jottyfan.bico.modules.theme; package de.jottyfan.bico.modules.theme;
import java.time.LocalDate;
import java.util.List; import java.util.List;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import de.jottyfan.bico.db.tables.records.TLessonRecord; import de.jottyfan.bico.db.public_.tables.records.TLessonRecord;
import de.jottyfan.bico.modules.theme.model.KeyValueBean; import de.jottyfan.bico.modules.theme.model.KeyValueBean;
import de.jottyfan.bico.modules.theme.model.ThemeBean; import de.jottyfan.bico.modules.theme.model.ThemeBean;
@ -44,4 +45,7 @@ public class ThemeService {
repository.updateLesson(bean); repository.updateLesson(bean);
} }
public LocalDate getSlotDay(Integer slotId) {
return repository.getSlotDay(slotId);
}
} }

View File

@ -0,0 +1,9 @@
{
"properties": [
{
"name": "nextcloud.url",
"type": "java.lang.String",
"description": "the URL to nextcloud for logout operations"
}
]
}

View File

@ -9,17 +9,10 @@ spring.datasource.password = ${db.password}
server.servlet.context-path = ${my.context-path:/BiCO} server.servlet.context-path = ${my.context-path:/BiCO}
# security # nextcloud open ID connection
spring.security.oauth2.client.registration.keycloak.client-id = ${keycloak.client-id} spring.security.oauth2.client.provider.nextcloud.issuer-uri = ${nextcloud.issuer-uri}
spring.security.oauth2.client.registration.keycloak.scope = openid spring.security.oauth2.client.registration.nextcloud.client-id = ${nextcloud.client-id}
spring.security.oauth2.client.registration.keycloak.authorization-grant-type = authorization_code spring.security.oauth2.client.registration.nextcloud.client-secret = ${nextcloud.client-secret}
spring.security.oauth2.client.registration.keycloak.redirect-uri = ${keycloak.redirect-uri} spring.security.oauth2.client.registration.nextcloud.authorization-grant-type = authorization_code
spring.security.oauth2.client.provider.keycloak.issuer-uri = ${keycloak.issuer-uri} spring.security.oauth2.client.registration.nextcloud.redirect-uri = ${nextcloud.redirect-uri}
spring.security.oauth2.client.provider.keycloak.authorization-uri = ${keycloak.openid-url}/auth spring.security.oauth2.client.registration.nextcloud.client-authentication-method = client_secret_post
spring.security.oauth2.client.provider.keycloak.token-uri = ${keycloak.openid-url}/token
spring.security.oauth2.client.provider.keycloak.user-info-uri = ${keycloak.openid-url}/userinfo
spring.security.oauth2.client.provider.keycloak.jwk-set-uri = ${keycloak.openid-url}/certs
spring.security.oauth2.client.provider.keycloak.user-name-attribute = preferred_username
# for development only
server.port = 8081

View File

@ -28,7 +28,7 @@ body {
} }
.rightaligned { .rightaligned {
right: 5px; right: 20px;
position: absolute; position: absolute;
} }
@ -46,3 +46,23 @@ body {
margin: 4px; margin: 4px;
border-radius: 4px; border-radius: 4px;
} }
.centeredalert {
width: 400px;
margin: auto;
}
.campbadge {
border: 1px solid #222;
color: #222;
}
[data-bs-theme=dark] .campbadge {
border-color: silver;
color: silver;
}
.full-size {
width: 100% !important;
max-width: inherit !important;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View File

@ -1,7 +1,7 @@
toggleDarkMode = function() { toggleDarkMode = function() {
var oldValue = $("html").attr("data-bs-theme"); var oldValue = $("html").attr("data-bs-theme");
var newValue = oldValue == "dark" ? "light" : "dark"; var newValue = oldValue == "dark" ? "light" : "dark";
var updateUrl = /*[[@{/updateTheme}]]*/ 'updateTheme'; var updateUrl = /*[[@{/updateTheme}]]*/ '/BiCO/updateTheme';
updateUrl = updateUrl + "/" + newValue; updateUrl = updateUrl + "/" + newValue;
$("html").attr("data-bs-theme", newValue); $("html").attr("data-bs-theme", newValue);
$.ajax({ $.ajax({

View File

@ -0,0 +1,122 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" layout:decorate="~{template}" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<body>
<th:block layout:fragment="content">
<div class="borderdist">
<div class="container">
<div class="row g-2">
<div class="col-sm-12">
<h2>Anmeldung zur Gemeindefreizeit 2025 bearbeiten</h2>
</div>
</div>
<div class="alert alert-danger" th:unless="${bean}">Sie haben keine Berechtigung, die Anmeldung mit dieser URL zu ändern.</div>
<form th:action="@{/camp/registration/correct}" method="post" th:object="${bean}" th:if="${bean}">
<input type="hidden" th:field="*{pkRegistration}" />
<div class="row g-3">
<div class="col-sm-3">Vorname</div>
<div class="col-sm-9">
<span th:if="${#fields.hasErrors('forename')}" th:errors="*{forename}" class="text-danger"></span> <input type="text" th:field="*{forename}" class="form-control" />
</div>
<div class="col-sm-3">Nachname</div>
<div class="col-sm-9">
<span th:if="${#fields.hasErrors('surname')}" th:errors="*{surname}" class="text-danger"></span> <input type="text" th:field="*{surname}" class="form-control" />
</div>
<div class="col-sm-3">Geschlecht</div>
<div class="col-sm-9">
<span th:if="${#fields.hasErrors('sex')}" th:errors="*{sex}" class="text-danger"></span> <select th:field="*{sex}" class="form-select">
<option value="">--- bitte wählen ---</option>
<option th:each="s : ${sexes}" th:value="${s}" th:text="${s}"></option>
</select>
</div>
<div class="col-sm-3">Alter</div>
<div class="col-sm-9">
<span th:if="${#fields.hasErrors('fkAge')}" th:errors="*{fkAge}" class="text-danger"></span>
<select th:field="*{fkAge}" class="form-select">
<option th:each="a : ${ages}" th:value="${a.pkAge}" th:label="${a.name} + ', ' + ${a.price} + ' € / Übernachtung'"></option>
</select>
</div>
<div class="col-sm-3">Welche Tage</div>
<div class="col-sm-9">
<input id="day0" type="checkbox" name="active" th:field="*{day0}" class="form-check-input" />
<label class="form-check-label" for="day0">Donnerstag</label>
<input id="day1" type="checkbox" name="active" th:field="*{day1}" class="form-check-input" />
<label class="form-check-label" for="day1">Freitag</label>
<input id="day2" type="checkbox" name="active" th:field="*{day2}" class="form-check-input" />
<label class="form-check-label" for="day2">Sonnabend</label>
<input id="day3" type="checkbox" name="active" th:field="*{day3}" class="form-check-input" />
<label class="form-check-label" for="day3">Sonntag</label>
<input id="day4" type="checkbox" name="active" th:field="*{day4}" class="form-check-input" />
<label class="form-check-label" for="day4">Montag</label>
</div>
<div class="col-sm-3">Sonderleistungen</div>
<div class="col-sm-9">
<input id="towels" type="checkbox" name="active" th:field="*{towels}" class="form-check-input" />
<label class="form-check-label" for="towels">Handtücher: 2 € / Person</label><br />
<input id="bed_linen" type="checkbox" name="active" th:field="*{bedLinen}" class="form-check-input" />
<label class="form-check-label" for="bed_linen">Bettwäsche: 7 € / Person</label><br />
<input id="cot" type="checkbox" name="active" th:field="*{cot}" class="form-check-input" />
<label class="form-check-label" for="cot">Kinderbett: 10 € einmalig</label>
</div>
<div class="col-sm-3">Barrierefrei</div>
<div class="col-sm-9">
<span th:if="${#fields.hasErrors('barrierFree')}" th:errors="*{barrierFree}" class="text-danger"></span>
<select th:field="*{barrierFree}" class="form-select">
<option value="false">Nein</option>
<option value="true">Ja</option>
</select>
</div>
<div class="col-sm-3">Spezielle Ernährung (Allergien, Unverträglichkeiten)</div>
<div class="col-sm-9">
<textarea th:field="*{nutrition}" class="form-control"></textarea>
</div>
<div class="col-sm-3">Biete Plätze zum Mitfahren</div>
<div class="col-sm-9">
<input th:field="*{driverProvidePlaces}" type="number" min="0" max="8" class="form-control" />
</div>
<div class="col-sm-3">Benötige eine Mitfahrgelegenheit</div>
<div class="col-sm-9">
<input id="wantPlaceInCar" type="checkbox" name="active" th:field="*{wantPlaceInCar}" class="form-check-input" />
<label class="form-check-label" for="wantPlaceInCar">Ja, brauche ich</label>
</div>
<div class="col-sm-3">Besonderheiten / Krankheiten</div>
<div class="col-sm-9">
<textarea th:field="*{diseases}" class="form-control"></textarea>
</div>
<div class="col-sm-3">Finanzierungsunterstützung</div>
<div class="col-sm-9">
<input id="requirePayment" type="checkbox" name="active" th:field="*{requirePayment}" class="form-check-input" />
<label class="form-check-label" for="requirePayment">Ja, brauche ich</label>
</div>
<div class="col-sm-3"></div>
<div class="col-sm-9">
<button type="submit" class="btn btn-outline-success">Korrigeren</button>
&nbsp; <a th:href="@{/camp/registration}" class="btn btn-outline-secondary">Abbrechen</a> &nbsp;
<button type="button" class="btn btn-outline-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">Stornieren</button>
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="deleteModalLabel">Löschen einer Anmeldung</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Soll die Anmeldung von <span th:text="${bean.forename}"></span>&nbsp;<span th:text="${bean.surname}"></span>&nbsp;wirklich gelöscht werden?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Abbrechen</button>
<a th:href="@{/camp/registration/delete/{id}(id=${bean.pkRegistration})}" class="btn btn-outline-danger">Endgültig löschen</a>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</th:block>
</body>
</html>

View File

@ -0,0 +1,81 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" layout:decorate="~{template}" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<body>
<th:block layout:fragment="content">
<div class="borderdist">
<div class="container full-size">
<div class="row g-2">
<div class="col-sm-12">
<h2>Anmeldungen zur Gemeindefreizeit 2025</h2>
</div>
</div>
<div class="alert alert-danger" th:unless="${list}">Sie haben keine Berechtigung, die Anmeldungen einzusehen.</div>
<div th:if="${list}">
<table id="table" class="table table-striped">
<thead>
<tr>
<th>Vorname</th>
<th>Nachname</th>
<th>Geschlecht</th>
<th>Alter</th>
<th>Tage dabei</th>
<th>Extras</th>
<th>Unverträglichkeit</th>
<th>Mitfahrplätze</th>
<th>Besonderheiten</th>
</tr>
</thead>
<tbody>
<tr th:each="l : ${list}">
<td th:text="${l.forename}"></td>
<td th:text="${l.surname}"></td>
<td th:text="${l.sex}"></td>
<td>
<th:block th:each="a : ${ages}">
<span th:text="${a.name}" th:if="${a.pkAge == l.fkAge}"></span>
</th:block>
</td>
<td>
<span th:if="${l.day0}" class="badge campbadge">Donnerstag</span>
<span th:if="${l.day1}" class="badge campbadge">Freitag</span>
<span th:if="${l.day2}" class="badge campbadge">Sonnabend</span>
<span th:if="${l.day3}" class="badge campbadge">Sonntag</span>
<span th:if="${l.day4}" class="badge campbadge">Montag</span>
</td>
<td>
<span th:if="${l.barrierFree}" class="badge campbadge">barrierefrei</span>
<span th:if="${l.towels}" class="badge campbadge">Handtücher</span>
<span th:if="${l.bedLinen}" class="badge campbadge">Bettwäsche</span>
<span th:if="${l.cot}" class="badge campbadge">Kinderbett</span>
<span th:if="${l.requirePayment}" class="badge campbadge">finanzielle Unterstützung erbeten</span>
</td>
<td th:text="${l.nutrition}"></td>
<td>
<span th:if="${l.driverProvidePlaces}">biete <span th:text="${l.driverProvidePlaces}"></span></span>
<span th:if="${l.wantPlaceInCar}">benötige 1</span>
</td>
<td th:text="${l.diseases}"></td>
</tr>
</tbody>
</table>
<script th:inline="javascript">
/*<![CDATA[*/
$(document).ready(function() {
$("#table").DataTable({
"language" : locale_de,
"responsive" : true
});
});
/*]]>*/
</script>
</div>
<div class="row g-2" th:if="${list}">
<div class="col-sm-12">
<a th:href="@{/camp/registration/admin/download}" class="btn btn-outline-primary">Download</a>
</div>
</div>
</div>
</div>
</th:block>
</body>
</html>

View File

@ -0,0 +1,170 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" layout:decorate="~{template}" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<body>
<th:block layout:fragment="content">
<div class="borderdist">
<div class="container">
<div class="row g-2">
<div class="col-sm-12">
<h2>Anmeldung zur Gemeindefreizeit 2025</h2>
</div>
</div>
<script th:inline="javascript">
function toggleTo(opener, name) {
$('.nav-opener').removeClass('active');
$('.tabpanel').hide();
$(opener).addClass('active');
$(name).show();
}
$(document).ready(function() {
var regsize = parseInt(/*[[${registrations.size()}]]*/"0");
$("#details").hide();
if (regsize < 1) {
$("#open_new").addClass("active");
$("#new").show();
$("#open_found").removeClass("active");
$("#found").hide();
} else {
$("#open_new").removeClass("active");
$("#new").hide();
$("#open_found").addClass("active");
$("#found").show();
}
});
</script>
<ul class="nav nav-tabs">
<li class="nav-item"><a id="open_new" class="nav-link active nav-opener" aria-current="page" href="#" onclick="toggleTo(this, '#new')">Neue Anmeldung</a></li>
<li class="nav-item"><a id="open_found" class="nav-link nav-opener" href="#" onclick="toggleTo(this, '#found')">Bisherige Anmeldungen</a></li>
<li class="nav-item"><a id="open_details" class="nav-link nav-opener" href="#" onclick="toggleTo(this, '#details')">Überweisung</a></li>
</ul>
<div class="tab-content">
<div id="new" class="tabpanel">
<div class="alert alert-info">Die Zeit für die Anmeldung zur Gemeindefreizeit 2025 ist vorbei.</div>
<form th:action="@{/camp/registration/submit}" method="post" th:object="${bean}" th:if="${oldie}">
<div class="row g-3">
<div class="col-sm-3">Vorname</div>
<div class="col-sm-9">
<span th:if="${#fields.hasErrors('forename')}" th:errors="*{forename}" class="text-danger"></span> <input type="text" th:field="*{forename}" class="form-control" />
</div>
<div class="col-sm-3">Nachname</div>
<div class="col-sm-9">
<span th:if="${#fields.hasErrors('surname')}" th:errors="*{surname}" class="text-danger"></span> <input type="text" th:field="*{surname}" class="form-control" />
</div>
<div class="col-sm-3">Geschlecht</div>
<div class="col-sm-9">
<span th:if="${#fields.hasErrors('sex')}" th:errors="*{sex}" class="text-danger"></span> <select th:field="*{sex}" class="form-select">
<option value="">--- bitte wählen ---</option>
<option th:each="s : ${sexes}" th:value="${s}" th:text="${s}"></option>
</select>
</div>
<div class="col-sm-3">Alter</div>
<div class="col-sm-9">
<span th:if="${#fields.hasErrors('fkAge')}" th:errors="*{fkAge}" class="text-danger"></span>
<select th:field="*{fkAge}" class="form-select">
<option th:each="a : ${ages}" th:value="${a.pkAge}" th:label="${a.name} + ', ' + ${a.price} + ' € / Übernachtung'"></option>
</select>
</div>
<div class="col-sm-3">Welche Tage</div>
<div class="col-sm-9">
<input id="day0" type="checkbox" name="active" th:field="*{day0}" class="form-check-input" />
<label class="form-check-label" for="day0">Donnerstag</label>
<input id="day1" type="checkbox" name="active" th:field="*{day1}" class="form-check-input" />
<label class="form-check-label" for="day1">Freitag</label>
<input id="day2" type="checkbox" name="active" th:field="*{day2}" class="form-check-input" />
<label class="form-check-label" for="day2">Sonnabend</label>
<input id="day3" type="checkbox" name="active" th:field="*{day3}" class="form-check-input" />
<label class="form-check-label" for="day3">Sonntag</label>
<input id="day4" type="checkbox" name="active" th:field="*{day4}" class="form-check-input" />
<label class="form-check-label" for="day4">Montag</label>
</div>
<div class="col-sm-3">Sonderleistungen</div>
<div class="col-sm-9">
<input id="towels" type="checkbox" name="active" th:field="*{towels}" class="form-check-input" />
<label class="form-check-label" for="towels">Handtücher: 2 € / Person</label><br />
<input id="bed_linen" type="checkbox" name="active" th:field="*{bedLinen}" class="form-check-input" />
<label class="form-check-label" for="bed_linen">Bettwäsche: 7 € / Person</label><br />
<input id="cot" type="checkbox" name="active" th:field="*{cot}" class="form-check-input" />
<label class="form-check-label" for="cot">Kinderbett: 10 € einmalig</label>
</div>
<div class="col-sm-3">Barrierefrei</div>
<div class="col-sm-9">
<span th:if="${#fields.hasErrors('barrierFree')}" th:errors="*{barrierFree}" class="text-danger"></span>
<select th:field="*{barrierFree}" class="form-select">
<option value="false">Nein</option>
<option value="true">Ja</option>
</select>
</div>
<div class="col-sm-3">Spezielle Ernährung (Allergien, Unverträglichkeiten)</div>
<div class="col-sm-9">
<textarea th:field="*{nutrition}" class="form-control"></textarea>
</div>
<div class="col-sm-3">Biete Plätze zum Mitfahren</div>
<div class="col-sm-9">
<input th:field="*{driverProvidePlaces}" type="number" min="0" max="8" class="form-control" />
</div>
<div class="col-sm-3">Benötige eine Mitfahrgelegenheit</div>
<div class="col-sm-9">
<input id="wantPlaceInCar" type="checkbox" name="active" th:field="*{wantPlaceInCar}" class="form-check-input" />
<label class="form-check-label" for="wantPlaceInCar">Ja, brauche ich</label>
</div>
<div class="col-sm-3">Besonderheiten / Krankheiten</div>
<div class="col-sm-9">
<textarea th:field="*{diseases}" class="form-control"></textarea>
</div>
<div class="col-sm-3">Finanzierungsunterstützung</div>
<div class="col-sm-9">
<input id="requirePayment" type="checkbox" name="active" th:field="*{requirePayment}" class="form-check-input" />
<label class="form-check-label" for="requirePayment">Ja, brauche ich</label>
</div>
<div class="col-sm-3"></div>
<div class="col-sm-9">
<button type="submit" class="btn btn-outline-success">Speichern</button>
&nbsp; <a th:href="@{/camp/registration}" class="btn btn-outline-secondary">Abbrechen</a>&nbsp;
</div>
</div>
</form>
</div>
<div id="found" class="tabpanel">
<table class="table table-striped">
<thead>
<tr>
<th>Vorname</th>
<th>Nachname</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<tr th:each="r : ${registrations}">
<td th:text="${r.forename}"></td>
<td th:text="${r.surname}"></td>
<td><a th:href="@{/camp/registration/edit/{id}(id=${r.pkRegistration})}" class="btn btn-outline-secondary"><i class="bi bi-pencil"></i></a></td>
</tr>
</tbody>
</table>
</div>
<div id="details" class="tabpanel">
<div class="card">
<div class="card-header">Informationen zu den Kosten</div>
<div class="card-body">
<i>Bitte überweise den Betrag, der sich aus Deiner Anmeldung ergibt, auf das Gemeindekonto:</i><br />
Kontoverbindung<br />
EFG Dresden Süd-Ost<br />
IBAN: DE40 8601 0090 0102 0449 00<br />
BIC: PBNKDEFF Konto 102 044 900<br />
Postbank Leipzig BLZ 860 100 90<br />
</div>
<div class="card-footer">
<img th:src="@{/images/Bildschirmfoto 2024-12-02 um 13.36.25.png}" style="max-width: 600px; width: 100%" /><br />
<img th:src="@{/images/PastedGraphic-2.png}" style="max-width: 600px; width: 100%" />
</div>
</div>
</div>
</div>
</div>
</div>
</th:block>
</body>
</html>

View File

@ -3,18 +3,7 @@
<body> <body>
<th:block layout:fragment="content"> <th:block layout:fragment="content">
<div class="borderdist"> <div class="borderdist">
TODO: implement <div class="alert alert-danger">Es ist ein Fehler aufgetreten. Wenden Sie sich bitte an Ihren Entwickler.</div>
<pre>
fields:
fk_source
theme
subtheme
book_pages
worksheets
bibleverse
notes
order_nr
</pre>
</div> </div>
</th:block> </th:block>
</body> </body>

View File

@ -3,7 +3,7 @@
<body> <body>
<th:block layout:fragment="content"> <th:block layout:fragment="content">
<div class="borderdist"> <div class="borderdist">
<div class="container"> <div class="container" sec:authorize="hasRole('Bibelunterricht')">
<div class="row g-2"> <div class="row g-2">
<div class="col-sm-12"> <div class="col-sm-12">
<h2>Dozent-Reservierung</h2> <h2>Dozent-Reservierung</h2>
@ -30,7 +30,8 @@
<div class="col-sm-9"> <div class="col-sm-9">
<button type="submit" class="btn btn-outline-success">Speichern</button>&nbsp; <button type="submit" class="btn btn-outline-success">Speichern</button>&nbsp;
<a th:href="@{/}" class="btn btn-outline-secondary">Abbrechen</a>&nbsp; <a th:href="@{/}" class="btn btn-outline-secondary">Abbrechen</a>&nbsp;
<a th:href="@{/lesson/{id}/remove(id=${bean.pkLesson})}" class="btn btn-outline-danger">Löschen</a> <a th:href="@{/lesson/{id}/remove(id=${bean.pkLesson})}" class="btn btn-outline-danger">Zuordnung löschen</a>
<a th:href="@{/slot/{id}(id=${bean.fkSlot})}" class="btn btn-outline-warning">Termin bearbeiten</a>
</div> </div>
</div> </div>
</form> </form>

View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" layout:decorate="~{template}" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<body>
<th:block layout:fragment="content">
<div class="borderdist">
<div class="container" th:if="${hasDateRole || hasBUrole}">
<pre>Kommende Einteilung für den Bibelunterricht</pre>
<div class="row">
<div class="col-sm-6 col-md-4 col-lg-2 card p-2 m-1" th:each="s : ${list}">
<div th:text="${#temporals.format(s.day, 'dd.MM.yyyy')}"></div>
<div th:text="${s.fullname}"></div>
</div>
<div class="alert alert-info" th:if="${list.size() < 1}">Es gibt noch keine neuen Termine oder Zusagen für Termine.</div>
</div>
</div>
<div class="container" th:unless="${hasDateRole || hasBUrole}">
<div class="alert alert-info">
Willkommen im Anmeldeportal für die Gemeindefreizeit. Durch das Anklicken des Buttons <a class="btn btn-outline-secondary" th:href="@{/camp/registration}">Anmeldung Gemeindefreizeit</a> kannst du das Anmeldeformular öffnen.
</div>
</div>
</div>
</th:block>
</body>
</html>

View File

@ -3,7 +3,7 @@
<body> <body>
<th:block layout:fragment="content"> <th:block layout:fragment="content">
<div class="borderdist"> <div class="borderdist">
<table id="table" class="table table-striped"> <table id="table" class="table table-striped" sec:authorize="hasRole('Bibelunterricht')">
<thead> <thead>
<tr> <tr>
<th>Tag</th> <th>Tag</th>
@ -14,7 +14,7 @@
</thead> </thead>
<tbody> <tbody>
<tr th:each="s : ${list}"> <tr th:each="s : ${list}">
<td><span th:text="${#temporals.format(s.slotDay, 'yyyy-MM-dd')}"></span></td> <td th:data-sort="${#temporals.format(s.slotDay, 'yyyy-MM-dd')}"><a th:href="@{/slot/{id}(id=${s.pkSlot})}" th:text="${#temporals.format(s.slotDay, 'dd.MM.yyyy')}"></a></td>
<td><a th:href="@{/lesson?slotId={id}(id=${s.pkSlot})}" class="btn btn-outline-secondary"> <span th:text="${s.abbreviation}" th:if="${s.abbreviation}"></span> <i <td><a th:href="@{/lesson?slotId={id}(id=${s.pkSlot})}" class="btn btn-outline-secondary"> <span th:text="${s.abbreviation}" th:if="${s.abbreviation}"></span> <i
class="bi bi-pencil" th:if="${s.abbreviation == null || s.abbreviation.isBlank()}"></i> class="bi bi-pencil" th:if="${s.abbreviation == null || s.abbreviation.isBlank()}"></i>
</a></td> </a></td>
@ -32,15 +32,13 @@
</div> </div>
</div> </div>
</td> </td>
<td><span th:text="${s.slotNotes}" class="rightpadding64"></span><a th:href="@{/slot/{id}(id=${s.pkSlot})}" class="btn btn-outline-secondary rightaligned"><i <td><span th:text="${s.slotNotes}" class="rightpadding64"></span></td>
class="bi bi-pencil"></i></a></td>
</tr> </tr>
</tbody> </tbody>
<tfoot> <tfoot>
<tr> <tr>
<td colspan="4"> <td colspan="4">
<a th:href="@{/slot}" class="btn btn-outline-success">ein neues Datum anlegen</a> <a th:href="@{/slot}" class="btn btn-outline-success">ein neues Datum anlegen</a>
<a th:href="@{/subject/new}" class="btn btn-outline-primary">ein neues Thema anlegen</a>
</td> </td>
</tr> </tr>
</tfoot> </tfoot>
@ -49,10 +47,6 @@
/*<![CDATA[*/ /*<![CDATA[*/
$(document).ready(function() { $(document).ready(function() {
$("#table").DataTable({ $("#table").DataTable({
"columnDefs" : [ {
"targets" : 0,
"type" : "date-eu"
} ],
"order" : [ [ 0, 'desc' ] ], "order" : [ [ 0, 'desc' ] ],
"language" : locale_de "language" : locale_de
}); });

View File

@ -2,7 +2,7 @@
<html xmlns:th="http://www.thymeleaf.org" layout:decorate="~{template}" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:sec="http://www.thymeleaf.org/extras/spring-security"> <html xmlns:th="http://www.thymeleaf.org" layout:decorate="~{template}" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<body> <body>
<th:block layout:fragment="content"> <th:block layout:fragment="content">
<div class="borderdist"> <div class="borderdist" sec:authorize="hasRole('Bibelunterricht')">
<div class="alert alert-danger" th:if="${bean}"> <div class="alert alert-danger" th:if="${bean}">
Wollen Sie den Slot <span th:text="${#temporals.format(bean.slotDay, 'dd.MM.yyyy')}"></span> wirklich löschen?<br /> Wollen Sie den Slot <span th:text="${#temporals.format(bean.slotDay, 'dd.MM.yyyy')}"></span> wirklich löschen?<br />
<a th:href="@{/slot/{id}/destroy(id=${bean.pkSlot})}" class="btn btn-outline-danger" th:if="${bean.pkSlot}">Ja, definitiv</a> <a th:href="@{/slot/{id}/destroy(id=${bean.pkSlot})}" class="btn btn-outline-danger" th:if="${bean.pkSlot}">Ja, definitiv</a>

View File

@ -3,7 +3,7 @@
<body> <body>
<th:block layout:fragment="content"> <th:block layout:fragment="content">
<div class="borderdist"> <div class="borderdist">
<div class="container"> <div class="container" sec:authorize="hasRole('Bibelunterricht')">
<div class="row g-2"> <div class="row g-2">
<div class="col-sm-12"> <div class="col-sm-12">
<h2>Terminfestlegung</h2> <h2>Terminfestlegung</h2>

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" layout:decorate="~{template}" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<body>
<th:block layout:fragment="content">
<div class="borderdist">
<div class="alert alert-danger centeredalert" th:if="${bean}">
Soll das Thema <span th:text="${bean.theme}" style="font-weight: bolder"></span> wirklich gelöscht werden?
<br />
<br />
<a th:href="@{/subject/{id}/remove(id=${bean.pkSubject})}" class="btn btn-outline-danger">Ja, wirklich löschen</a>
</div>
<div class="alert alert-warning centeredalert" th:unless="${bean}">
Dieses Thema kann nicht gelöscht werden. Möglicherweise wurde es bereits in der Vergangenheit gehalten.
</div>
</div>
</th:block>
</body>
</html>

View File

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" layout:decorate="~{template}" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<body>
<th:block layout:fragment="content">
<div class="borderdist">
<form th:action="@{/subject/save}" method="post" th:object="${bean}">
<input type="hidden" th:field="*{pkSubject}" />
<div class="container">
<div class="row g-2">
<div class="col-sm-3">Quelle</div>
<div class="col-sm-9"><select th:field="*{fkSource}" class="form-select">
<option th:each="o : ${sources}" th:value="${o.pkSource}" th:text="${o.name}"></option>
</select></div>
<div class="col-sm-3">Thema</div>
<div class="col-sm-9"><input type="text" th:field="*{theme}" class="form-control" /></div>
<div class="col-sm-3">Unterthema</div>
<div class="col-sm-9"><input type="text" th:field="*{subtheme}" class="form-control" /></div>
<div class="col-sm-3">Buchseiten</div>
<div class="col-sm-9"><input type="text" th:field="*{bookPages}" class="form-control" /></div>
<div class="col-sm-3">Arbeitsblätter</div>
<div class="col-sm-9"><input type="text" th:field="*{worksheets}" class="form-control" /></div>
<div class="col-sm-3">Bibelvers / Lernvers</div>
<div class="col-sm-9"><input type="text" th:field="*{bibleverse}" class="form-control" /></div>
<div class="col-sm-3">Anmerkungen</div>
<div class="col-sm-9"><input type="text" th:field="*{notes}" class="form-control" /></div>
<div class="col-sm-3">Reihenfolge</div>
<div class="col-sm-9"><input type="number" th:field="*{orderNr}" class="form-control" /></div>
<div class="col-sm-3">&nbsp;</div>
<div class="col-sm-9">
<button type="submit" class="btn btn-outline-primary">Übernehmen</button>
<a th:href="@{/subject/list}" class="btn btn-outline-secondary">Abbrechen</a>
<a th:href="@{/subject/{id}/delete(id=${bean.pkSubject})}" class="btn btn-outline-danger" th:if="${bean.pkSubject}">Löschen</a>
</div>
</div>
</div>
</form>
</div>
</th:block>
</body>
</html>

View File

@ -0,0 +1,52 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" layout:decorate="~{template}" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<body>
<th:block layout:fragment="content">
<div class="borderdist">
<table id="table" class="table table-striped">
<thead>
<tr>
<th>Quelle</th>
<th>Thema</th>
<th>gehalten</th>
<th>Reihenfolge</th>
<th></th>
</tr>
</thead>
<tbody>
<tr th:each="s : ${list}">
<td th:text="${s.sourceName}"></td>
<td>
<div><span th:text="${s.theme}" style="font-weight: bolder"></span></div>
<div th:text="${s.subtheme}"></div>
</td>
<td><div th:text="${#temporals.format(d, 'dd.MM.yyyy')}" th:each="d : ${s.slots}"></div></td>
<td th:text="${s.orderNr}"></td>
<td>
<a th:href="@{/subject/{id}(id=${s.pkSubject})}" class="btn btn-outline-secondary"><i class="bi bi-pencil"></i></a>
<a th:href="@{/subject/{id}/delete(id=${s.pkSubject})}" class="btn btn-outline-danger" th:if="${s.slots.length < 1}"><i class="bi bi-trash"></i></a>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="5">
<a th:href="@{/subject/new}" class="btn btn-outline-primary">ein neues Thema anlegen</a>
</td>
</tr>
</tfoot>
</table>
<script th:inline="javascript">
/*<![CDATA[*/
$(document).ready(function() {
$("#table").DataTable({
"order" : [ [ 3, 'asc' ] ],
"language" : locale_de
});
});
/*]]>*/
</script>
</div>
</th:block>
</body>
</html>

View File

@ -4,14 +4,17 @@
<title>Bible Class Organizer</title> <title>Bible Class Organizer</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link th:rel="stylesheet" type="text/css" media="all" th:href="@{/webjars/bootstrap/5.3.1/css/bootstrap.min.css}" /> <link rel="stylesheet" type="text/css" media="all" th:href="@{/webjars/bootstrap/5.3.3/css/bootstrap.min.css}" />
<link th:rel="stylesheet" type="text/css" media="all" th:href="@{/webjars/bootstrap-icons/1.10.5/font/bootstrap-icons.css}" /> <link rel="stylesheet" type="text/css" media="all" th:href="@{/webjars/bootstrap-icons/1.11.3/font/bootstrap-icons.css}" />
<link th:rel="stylesheet" type="text/css" media="all" th:href="@{/webjars/datatables/1.13.5/css/jquery.dataTables.min.css}" /> <link rel="stylesheet" type="text/css" media="all" th:href="@{/webjars/datatables.net-bs5/2.1.7/css/dataTables.bootstrap5.min.css}"/>
<link th:rel="stylesheet" type="text/css" media="all" th:href="@{/css/style.css}" /> <link rel="stylesheet" type="text/css" media="all" th:href="@{/css/style.css}" />
<script th:src="@{/webjars/bootstrap/5.3.1/js/bootstrap.bundle.min.js}"></script> <script type="application/javascript" th:src="@{/webjars/bootstrap/5.3.3/js/bootstrap.bundle.min.js}"></script>
<script th:src="@{/webjars/jquery/3.7.1/jquery.min.js}"></script> <script type="application/javascript" th:src="@{/webjars/jquery/3.7.1/jquery.min.js}"></script>
<script th:src="@{/webjars/datatables/1.13.5/js/jquery.dataTables.min.js}"></script> <script type="application/javascript" th:src="@{/webjars/datatables.net/2.1.7/js/dataTables.min.js}"></script>
<script th:src="@{/js/dataTables.de.js}"></script> <script type="application/javascript" th:src="@{/js/dataTables.de.js}"></script>
<script type="application/javascript" th:src="@{/webjars/datatables.net-bs5/2.1.7/js/dataTables.bootstrap5.min.js}"></script>
<script type="application/javascript" th:src="@{/webjars/datatables.net-responsive/3.0.1/js/dataTables.responsive.min.js}"></script>
<script type="application/javascript" th:src="@{/webjars/datatables.net-buttons/3.1.1/js/dataTables.buttons.min.js}"></script>
<script th:src="@{/js/stylehelp.js}"></script> <script th:src="@{/js/stylehelp.js}"></script>
</head> </head>
<body> <body>
@ -21,20 +24,37 @@
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarSupportedContent" style="margin-right: 20px"> <div class="collapse navbar-collapse" id="navbarSupportedContent" style="margin-right: 20px">
<ul class="navbar-nav mb-2 mb-lg-0"> <ul class="navbar-nav mb-2 mb-lg-0" th:if="${hasAnyRole}">
<li class="nav-item"><a class="btn btn-outline-secondary" th:href="@{/}" style="margin-left: 12px">Startseite</a></li> <li class="nav-item"><a class="btn btn-outline-secondary" th:href="@{/camp/registration}" style="margin-left: 12px">Anmeldung Gemeindefreizeit</a></li>
<li class="nav-item"><a class="btn btn-outline-secondary" th:href="@{/camp/registration/admin}" style="margin-left: 12px" th:if="${isCampAdmin}">Gemeindefreizeitliste</a></li>
<li class="nav-item"><a class="btn btn-outline-secondary" th:href="@{/next}" style="margin-left: 12px" th:if="${hasDateRole || hasBUrole}">Dienstplan</a></li>
<li class="nav-item"><a class="btn btn-outline-secondary" th:href="@{/sheet}" style="margin-left: 12px" th:if="${hasBUrole}">Einteilung</a></li>
<li class="nav-item"><a class="btn btn-outline-secondary" th:href="@{/subject/list}" style="margin-left: 12px" th:if="${hasBUrole}">Themen</a></li>
<li class="nav-item"><a class="btn btn-outline-secondary" th:href="@{/download}" style="margin-left: 12px" th:if="${hasBUrole}">Download</a></li>
</ul> </ul>
<ul layout:fragment="header"></ul> <ul layout:fragment="header"></ul>
<ul class="nav navbar-nav ms-auto"> <ul class="nav navbar-nav ms-auto">
<li class="nav-item" th:if="${hasDateRole}">
<a th:href="@{/ical}" class="btn btn-outline-secondary"><i class="bi bi-calendar-week"></i></a>
</li>
<li class="nav-item"> <li class="nav-item">
<a href="https://gitlab.com/jottyfan/bico/-/issues" class="btn btn-outline-secondary" target="_blank" th:text="${'v' + @manifestBean.getVersion()}"></a> <a href="https://git.jottyfan.de/church/BiCO" class="btn btn-outline-secondary" target="_blank" th:text="${'v' + @manifestBean.getVersion()}"></a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="#" class="btn btn-outline-secondary" onclick="toggleDarkMode()"><i class="bi bi-moon"></i></a> <a href="#" class="btn btn-outline-secondary" onclick="toggleDarkMode()"><i class="bi bi-moon"></i></a>
</li> </li>
<li class="nav-item">
<a th:href="@{/logout}" class="btn btn-outline-secondary">⏼ Abmelden</a>
<a th:href="@{${nextcloudUrl}}" class="btn btn-outline-secondary">&#8594; nextcloud</a>
</li>
</ul> </ul>
</div> </div>
</nav> </nav>
<div layout:fragment="content">content</div> <div layout:fragment="content" th:if="${hasAnyRole}">content</div>
<div th:unless="${hasAnyRole}">
<div class="borderdist">
<div class="alert alert-danger">Leider fehlen Ihnen die Berechtigungen, um diese Anwendung nutzen zu können.</div>
</div>
</div>
</body> </body>
</html> </html>

View File

@ -3,11 +3,18 @@
<body> <body>
<th:block layout:fragment="content"> <th:block layout:fragment="content">
<div class="borderdist"> <div class="borderdist">
<div class="container"> <div class="container" sec:authorize="hasRole('Bibelunterricht')">
<div class="row g-2">
<h1>
Themen für den <span th:text="${#temporals.format(day, 'dd.MM.yyyy')}"></span>
</h1>
</div>
<th:block th:each="b : ${list}" th:if="${list.size() > 0}"> <th:block th:each="b : ${list}" th:if="${list.size() > 0}">
<div class="row g-2 blockframe"> <div class="row g-2 blockframe">
<div class="col-sm-3">Thema:</div> <div class="col-sm-3">Thema:</div>
<div class="col-sm-8"><span th:text="${b.theme}"></span></div> <div class="col-sm-8">
<span th:text="${b.theme}"></span>
</div>
<div class="col-sm-1"> <div class="col-sm-1">
<div class="dropdown"> <div class="dropdown">
<button class="btn btn-outline-danger dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false"> <button class="btn btn-outline-danger dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
@ -18,18 +25,30 @@
</ul> </ul>
</div> </div>
</div> </div>
<div class="col-sm-3">Unterthema:</div> <div class="col-sm-3" th:if="${b.subtheme}">Unterthema:</div>
<div class="col-sm-9"><span th:text="${b.subtheme}"></span></div> <div class="col-sm-9" th:if="${b.subtheme}">
<div class="col-sm-3">Buchseiten:</div> <span th:text="${b.subtheme}"></span>
<div class="col-sm-9"><span th:text="${b.bookPages}"></span></div> </div>
<div class="col-sm-3">Arbeitsblätter:</div> <div class="col-sm-3" th:if="${b.bookPages}">Buchseiten:</div>
<div class="col-sm-9"><span th:text="${b.worksheets}"></span></div> <div class="col-sm-9" th:if="${b.bookPages}">
<div class="col-sm-3">Bibelvers (Lernvers):</div> <span th:text="${b.bookPages}"></span>
<div class="col-sm-9"><span th:text="${b.bibleverse}"></span></div> </div>
<div class="col-sm-3">Anmerkungen zum Thema:</div> <div class="col-sm-3" th:if="${b.worksheets}">Arbeitsblätter:</div>
<div class="col-sm-9"><span th:text="${b.notes}"></span></div> <div class="col-sm-9" th:if="${b.worksheets}">
<span th:text="${b.worksheets}"></span>
</div>
<div class="col-sm-3" th:if="${b.bibleverse}">Bibelvers (Lernvers):</div>
<div class="col-sm-9" th:if="${b.bibleverse}">
<span th:text="${b.bibleverse}"></span>
</div>
<div class="col-sm-3" th:if="${b.notes}">Anmerkungen zum Thema:</div>
<div class="col-sm-9" th:if="${b.notes}">
<span th:text="${b.notes}"></span>
</div>
<div class="col-sm-3">Quelle:</div> <div class="col-sm-3">Quelle:</div>
<div class="col-sm-9"><span th:text="${b.sourceName}"></span></div> <div class="col-sm-9">
<span th:text="${b.sourceName}"></span>
</div>
</div> </div>
</th:block> </th:block>
<form th:action="@{/theme/update?slotId={id}(id=${slotId})}" method="post" th:object="${lesson}"> <form th:action="@{/theme/update?slotId={id}(id=${slotId})}" method="post" th:object="${lesson}">
@ -54,7 +73,9 @@
<option th:each="t : ${themes}" th:value="${t.key}" th:text="${t.value}"></option> <option th:each="t : ${themes}" th:value="${t.key}" th:text="${t.value}"></option>
</select> </select>
</div> </div>
<div class="col-sm-2"><button type="submit" class="btn btn-outline-primary">hinzufügen</button></div> <div class="col-sm-2">
<button type="submit" class="btn btn-outline-primary">hinzufügen</button>
</div>
</div> </div>
</form> </form>
</div> </div>