fixed registration failure checks

This commit is contained in:
Jottyfan 2024-04-01 18:04:42 +02:00
parent 7ab500e510
commit f0d30ec6ed
10 changed files with 260 additions and 115 deletions

View File

@ -1,5 +1,5 @@
plugins {
id 'org.springframework.boot' version '3.2.1'
id 'org.springframework.boot' version '3.2.3'
id "io.spring.dependency-management" version "1.1.4"
id 'java'
id 'war'

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

2
gradlew vendored
View File

@ -162,7 +162,7 @@ save () {
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
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"
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then

View File

@ -1,11 +1,12 @@
package de.jottyfan.camporganizer.module.registration;
import jakarta.validation.Valid;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
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.validation.ObjectError;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
@ -15,6 +16,7 @@ import de.jottyfan.camporganizer.db.EnumConverter;
import de.jottyfan.camporganizer.module.camplist.CommonController;
import de.jottyfan.camporganizer.module.registration.model.CampBean;
import de.jottyfan.camporganizer.module.registration.model.RegistrationBean;
import jakarta.validation.Valid;
/**
*
@ -23,6 +25,7 @@ import de.jottyfan.camporganizer.module.registration.model.RegistrationBean;
*/
@Controller
public class RegistrationController extends CommonController {
private static final Logger LOGGER = LogManager.getLogger(RegistrationController.class);
@Autowired
private RegistrationService service;
@ -61,14 +64,33 @@ public class RegistrationController extends CommonController {
public String register(@Valid @ModelAttribute("bean") RegistrationBean bean, final BindingResult bindingResult,
Model model) {
if (bindingResult.hasErrors()) {
for (ObjectError error : bindingResult.getAllErrors()) {
LOGGER.info("found {}", error);
}
CampBean campBean = service.getCamp(bean.getFkCamp());
model.addAttribute("camp", campBean);
model.addAttribute("sexes", EnumConverter.getSexes());
model.addAttribute("roles", EnumConverter.getRoles());
String currentUser = getCurrentUser();
if (currentUser != null) {
model.addAttribute("wellknown", service.getWellKnownRegistrations(currentUser));
}
return "/registration/registration";
}
Boolean result = service.register(bean, getCurrentUser());
return result ? "/registration/success" : "/error";
Integer result = service.register(bean, getCurrentUser());
if (result < 1) {
LOGGER.trace("added less than 1 row");
CampBean campBean = service.getCamp(bean.getFkCamp());
model.addAttribute("camp", campBean);
model.addAttribute("sexes", EnumConverter.getSexes());
model.addAttribute("roles", EnumConverter.getRoles());
String currentUser = getCurrentUser();
if (currentUser != null) {
model.addAttribute("wellknown", service.getWellKnownRegistrations(currentUser));
}
return "/registration/registration";
}
return "/registration/success";
}
@GetMapping("/registration/cancel/{id}")

View File

@ -23,8 +23,7 @@ import org.jasypt.util.password.StrongPasswordEncryptor;
import org.jooq.DSLContext;
import org.jooq.DeleteConditionStep;
import org.jooq.InsertResultStep;
import org.jooq.InsertValuesStep12;
import org.jooq.InsertValuesStep13;
import org.jooq.InsertReturningStep;
import org.jooq.InsertValuesStep2;
import org.jooq.Record;
import org.jooq.Record1;
@ -75,7 +74,7 @@ public class RegistrationRepository {
*/
public CampBean getCamp(Integer pk) {
SelectConditionStep<VCampRecord> sql = jooq.selectFrom(V_CAMP).where(V_CAMP.PK.eq(pk));
LOGGER.debug(sql.toString());
LOGGER.trace(sql.toString());
VCampRecord r = sql.fetchOne();
if (r != null) {
CampBean bean = new CampBean();
@ -103,7 +102,7 @@ public class RegistrationRepository {
.selectFrom(T_PROFILE)
.where(DSL.lower(T_PROFILE.USERNAME).eq(login));
// @formatter:on
LOGGER.debug(sql);
LOGGER.trace(sql);
return sql.fetch().size() < 1;
}
@ -112,9 +111,9 @@ public class RegistrationRepository {
* registerInKeycloak is true
*
* @param bean the bean
* @return true or false
* @return number of data base rows; should be > 0
*/
public Boolean register(RegistrationBean bean) {
public Integer register(RegistrationBean bean) {
LambdaResultWrapper lrw = new LambdaResultWrapper();
jooq.transaction(t -> {
if (bean.getLogin() != null && !bean.getLogin().isEmpty()) {
@ -137,7 +136,7 @@ public class RegistrationRepository {
.values(bean.getForename(), bean.getSurname(), bean.getLogin().toLowerCase(), oldPassword, LocalDateTime.now().plus(356, ChronoUnit.DAYS), UUID.nameUUIDFromBytes(bean.getLogin().getBytes()).toString())
.returning(T_PROFILE.PK);
// @formatter:on
LOGGER.debug(sql1.toString());
LOGGER.trace(sql1.toString());
fkProfile = sql1.fetchOne().getPk();
InsertValuesStep2<TRssRecord, String, String> sql2 = jooq
@ -156,12 +155,11 @@ public class RegistrationRepository {
.from(T_PROFILE)
.where(DSL.lower(T_PROFILE.USERNAME).eq(bean.getLogin().toLowerCase()));
// @formatter:on
LOGGER.debug(sql1.toString());
LOGGER.trace(sql1.toString());
fkProfile = sql1.fetchOne().get(T_PROFILE.PK);
}
// register the person for camp participation
InsertValuesStep13<TPersonRecord, String, String, EnumSex, LocalDate, String, String, String, String, String, EnumCamprole, Integer, String, Integer> sql2 = DSL
.using(t)
InsertReturningStep<TPersonRecord> sql2 = DSL.using(t)
// @formatter:off
.insertInto(T_PERSON,
T_PERSON.FORENAME,
@ -179,14 +177,15 @@ public class RegistrationRepository {
T_PERSON.FK_PROFILE)
.values(bean.getForename(), bean.getSurname(), bean.getSex(),
bean.getBirthDate(), bean.getStreet(), bean.getZip(), bean.getCity(), bean.getEmail(),
bean.getPhone(), bean.getCampRole(), bean.getFkCamp(), bean.getComment(), fkProfile);
bean.getPhone(), bean.getCampRole(), bean.getFkCamp(), bean.getComment(), fkProfile)
.onConflict(T_PERSON.FORENAME, T_PERSON.SURNAME, T_PERSON.BIRTHDATE, T_PERSON.FK_CAMP)
.doNothing();
// @formatter:on
LOGGER.debug(sql2.toString());
LOGGER.trace(sql2.toString());
lrw.add(sql2.execute());
// register the login for the portal
} else {
InsertValuesStep12<TPersonRecord, String, String, EnumSex, LocalDate, String, String, String, String, String, EnumCamprole, Integer, String> sql = DSL
.using(t)
InsertReturningStep<TPersonRecord> sql = DSL.using(t)
// @formatter:off
.insertInto(T_PERSON,
T_PERSON.FORENAME,
@ -203,13 +202,15 @@ public class RegistrationRepository {
T_PERSON.COMMENT)
.values(bean.getForename(), bean.getSurname(), bean.getSex(),
bean.getBirthDate(), bean.getStreet(), bean.getZip(), bean.getCity(), bean.getEmail(),
bean.getPhone(), bean.getCampRole(), bean.getFkCamp(), bean.getComment());
bean.getPhone(), bean.getCampRole(), bean.getFkCamp(), bean.getComment())
.onConflict(T_PERSON.FORENAME, T_PERSON.SURNAME, T_PERSON.BIRTHDATE, T_PERSON.FK_CAMP)
.doNothing();
// @formatter:on
LOGGER.debug(sql.toString());
LOGGER.trace(sql.toString());
lrw.add(sql.execute());
}
});
return lrw.getCounter() > 0;
return lrw.getCounter();
}
/**
@ -232,7 +233,7 @@ public class RegistrationRepository {
.leftJoin(T_CAMP).on(T_CAMP.PK.eq(T_PERSON.FK_CAMP))
.where(T_PERSON.PK.eq(id));
// @formatter:on
LOGGER.debug(sql.toString());
LOGGER.trace(sql.toString());
Record7<Integer, String, String, EnumCamprole, String, LocalDateTime, LocalDateTime> r = sql.fetchOne();
if (r != null) {
BookingBean bean = new BookingBean();
@ -265,7 +266,7 @@ public class RegistrationRepository {
.leftJoin(T_PROFILE).on(T_PROFILE.PK.eq(T_PERSON.FK_PROFILE))
.where(T_PERSON.PK.eq(id));
// @formatter:on
LOGGER.debug(sql0.toString());
LOGGER.trace(sql0.toString());
Record5<String, String, String, String, LocalDateTime> r = sql0.fetchOne();
if (r == null) {
throw new DataAccessException("no such entry in t_person with id = " + id);
@ -286,11 +287,11 @@ public class RegistrationRepository {
DeleteConditionStep<TPersondocumentRecord> sql1 = DSL.using(t).deleteFrom(T_PERSONDOCUMENT)
.where(T_PERSONDOCUMENT.FK_PERSON.eq(id));
LOGGER.debug(sql1.toString());
LOGGER.trace(sql1.toString());
sql1.execute();
DeleteConditionStep<TPersonRecord> sql2 = DSL.using(t).deleteFrom(T_PERSON).where(T_PERSON.PK.eq(id));
LOGGER.debug(sql2.toString());
LOGGER.trace(sql2.toString());
lrw.add(sql2.execute());
InsertValuesStep2<TRssRecord, String, String> sql3 = DSL.using(t)
@ -300,7 +301,7 @@ public class RegistrationRepository {
T_RSS.RECIPIENT)
.values(rssMessage.toString(), "registrator");
// @formatter:on
LOGGER.debug("{}", sql3.toString());
LOGGER.trace("{}", sql3.toString());
sql3.execute();
});
return lrw.getCounter();
@ -319,7 +320,7 @@ public class RegistrationRepository {
.from(T_CAMP)
.where(T_CAMP.PK.eq(fkCamp));
// @formatter:on
LOGGER.debug(sql.toString());
LOGGER.trace(sql.toString());
Iterator<Record1<LocalDateTime>> i = sql.fetch().iterator();
while (i.hasNext()) {
LocalDateTime depart = i.next().get(T_CAMP.DEPART);
@ -343,7 +344,7 @@ public class RegistrationRepository {
.set(T_PERSON.FK_PROFILE, (Integer) null)
.where(T_PERSON.FK_PROFILE.eq(bean.getPk()));
// @formatter:off
LOGGER.debug("{}", sql.toString());
LOGGER.trace("{}", sql.toString());
sql.execute();
DeleteConditionStep<?> sql1 = DSL.using(t)
@ -356,7 +357,7 @@ public class RegistrationRepository {
.where(T_PROFILE.USERNAME.eq(bean.getUsername())
)));
// @formatter:on
LOGGER.debug("{}", sql1.toString());
LOGGER.trace("{}", sql1.toString());
sql1.execute();
DeleteConditionStep<?> sql2 = DSL.using(t)
@ -364,7 +365,7 @@ public class RegistrationRepository {
.deleteFrom(T_PROFILE)
.where(T_PROFILE.USERNAME.eq(bean.getUsername()));
// @formatter:on
LOGGER.debug("{}", sql2.toString());
LOGGER.trace("{}", sql2.toString());
sql2.execute();
InsertValuesStep2<TRssRecord, String, String> sql3 = DSL.using(t)
@ -374,7 +375,7 @@ public class RegistrationRepository {
T_RSS.RECIPIENT)
.values(new StringBuilder(bean.getFullname()).append(" hat sich vom Portal CampOrganizer2 abgemeldet.").toString(), "admin");
// @formatter:on
LOGGER.debug("{}", sql3.toString());
LOGGER.trace("{}", sql3.toString());
sql3.execute();
});
}
@ -393,7 +394,7 @@ public class RegistrationRepository {
.set(T_PERSON.CONSENT_CATALOG_PHOTO, consent)
.where(T_PERSON.PK.eq(id));
// @formatter:on
LOGGER.debug("{}", sql.toString());
LOGGER.trace("{}", sql.toString());
sql.execute();
}
@ -415,7 +416,7 @@ public class RegistrationRepository {
.from(T_CAMP)
.where(T_CAMP.PK.eq(fkCamp));
// @formatter:on
LOGGER.debug(sql.toString());
LOGGER.trace(sql.toString());
Record r = sql.fetchOne();
Integer minTeacherAge = r.get(T_CAMP.MAX_AGE) + 2; // by default, we need 2 years older teachers at least
DayToSecond currentTeacherAge = r.get("teacherAge", DayToSecond.class);
@ -468,4 +469,28 @@ public class RegistrationRepository {
}
return list;
}
/**
* returns true if and only if this combination of registration has already been done
*
* @param forename the forename
* @param surname the surname
* @param birthDate the birth date
* @param campId the ID of the camp
* @return true or false
*/
public Boolean checkAlreadyRegistered(String forename, String surname, LocalDate birthDate, Integer campId) {
SelectConditionStep<Record1<Integer>> sql = jooq
// @formatter:off
.select(T_PERSON.PK)
.from(T_PERSON)
.where(T_PERSON.FORENAME.eq(forename))
.and(T_PERSON.SURNAME.eq(surname))
.and(T_PERSON.BIRTHDATE.eq(birthDate))
.and(T_PERSON.FK_CAMP.eq(campId));
// @formatter:on
LOGGER.trace(sql);
Iterator<Integer> i = sql.fetch(T_PERSON.PK).iterator();
return i.hasNext();
}
}

View File

@ -20,7 +20,7 @@ import de.jottyfan.camporganizer.module.registration.model.RegistrationBean;
public class RegistrationService {
@Autowired
private RegistrationRepository gateway;
private RegistrationRepository repository;
@Autowired
private KeycloakRepository keycloak;
@ -32,7 +32,7 @@ public class RegistrationService {
* @return true or false
*/
public Boolean campIsNotYetOver(Integer fkCamp) {
return gateway.campIsNotYetOver(fkCamp);
return repository.campIsNotYetOver(fkCamp);
}
/**
@ -42,22 +42,22 @@ public class RegistrationService {
* @return the camp bean or null
*/
public CampBean getCamp(Integer fkCamp) {
return gateway.getCamp(fkCamp);
return repository.getCamp(fkCamp);
}
/**
* register the person for a camp; if registerInKeycloak, do so also
*
* @param bean the bean
* @return true if successful, false otherwise
* @return number of database rows; should be > 0
*/
public Boolean register(RegistrationBean bean, String currentUser) {
public Integer register(RegistrationBean bean, String currentUser) {
if (currentUser != null) {
bean.setRegisterInKeycloak(false); // already registered
bean.setLogin(currentUser);
}
Boolean result = gateway.register(bean);
if (result && bean.getRegisterInKeycloak()) {
Integer result = repository.register(bean);
if (result > 0 && bean.getRegisterInKeycloak()) {
keycloak.register(bean.getKcForename(), bean.getKcSurname(), bean.getLogin(), bean.getPassword(),
bean.getKcEmail());
}
@ -71,7 +71,7 @@ public class RegistrationService {
* @return the booking bean or null
*/
public BookingBean getBooking(Integer id) {
return gateway.getBooking(id);
return repository.getBooking(id);
}
/**
@ -80,11 +80,11 @@ public class RegistrationService {
* @param id the id of the booking (t_person.pk)
*/
public Boolean removeBooking(Integer id) {
return gateway.removeBooking(id) > 0;
return repository.removeBooking(id) > 0;
}
public void toggleConsent(Integer id) {
gateway.toggleConsent(id);
repository.toggleConsent(id);
}
/**
@ -97,7 +97,7 @@ public class RegistrationService {
if (currentUser == null) {
return null;
} else {
List<RegistrationBean> r = gateway.getRegistrations(currentUser);
List<RegistrationBean> r = repository.getRegistrations(currentUser);
if (r == null) {
return null;
} else {

View File

@ -11,6 +11,7 @@ import org.springframework.format.annotation.DateTimeFormat;
import de.jottyfan.camporganizer.db.jooq.enums.EnumCamprole;
import de.jottyfan.camporganizer.db.jooq.enums.EnumSex;
import de.jottyfan.camporganizer.module.registration.validate.AlreadyRegisteredCheck;
import de.jottyfan.camporganizer.module.registration.validate.TeacherAgeCheck;
import de.jottyfan.camporganizer.module.registration.validate.UnusedUsername;
@ -21,9 +22,10 @@ import de.jottyfan.camporganizer.module.registration.validate.UnusedUsername;
*/
@UnusedUsername(field = "login", message = "Dieses Login ist leider bereits vergeben. Bitte wähle ein anderes.")
@TeacherAgeCheck(field = "birthDate", fkCamp = "fkCamp", campRole = "campRole", message = "Als Mitarbeiter bist Du leider zu jung für diese Freizeit.")
// TODO: registration completeness annotation; in case of registerInKeycloak == true, force login, password, kcForename, kcSurname and kcEmail not to be blank
@AlreadyRegisteredCheck(field = "forename", surname = "surname", birthDate = "birthDate", fkCamp = "fkCamp", message = "Diese Anmeldung wurde bereits vorgenommen. Wenn Du sie in Deinem Profil nicht sehen kannst, wurde sie vielleicht von jemand Anderem durchgeführt. In Absprache mit der Freizeitleitung kannst Du die Anmeldung von einem Administrator Deinem Konto zuordnen lassen.")
public class RegistrationBean implements Serializable {
private static final long serialVersionUID = 1L;
//TODO: registration completeness annotation; in case of registerInKeycloak == true, force login, password, kcForename, kcSurname and kcEmail not to be blank
private static final long serialVersionUID = 2L;
@NotBlank(message = "Bitte gib deinen Vornamen an.")
private String forename;
@ -55,6 +57,31 @@ public class RegistrationBean implements Serializable {
private String kcSurname;
private String kcEmail;
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append("RegistrationBean{forename=").append(forename);
buf.append(", surname=").append(surname);
buf.append(", sex=").append(sex);
buf.append(", birthDate=").append(birthDate);
buf.append(", street=").append(street);
buf.append(", zip=").append(zip);
buf.append(", city=").append(city);
buf.append(", email=").append(email);
buf.append(", phone=").append(phone);
buf.append(", comment=").append(comment);
buf.append(", fkCamp=").append(fkCamp);
buf.append(", campRole=").append(campRole);
buf.append(", registerInKeycloak=").append(registerInKeycloak);
buf.append(", login=").append(login);
buf.append(", password=********");
buf.append(", kcForename=").append(kcForename);
buf.append(", kcSurname=").append(kcSurname);
buf.append(", kcEmail=").append(kcEmail);
buf.append("}");
return buf.toString();
}
/**
* @return forename + surname, separated by a space
*/

View File

@ -0,0 +1,33 @@
package de.jottyfan.camporganizer.module.registration.validate;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
/**
*
* @author jotty
*
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = AlreadyRegisteredCheckValidator.class)
@Documented
public @interface AlreadyRegisteredCheck {
String message() default "already registered";
String field();
String surname();
String birthDate();
String fkCamp();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,60 @@
package de.jottyfan.camporganizer.module.registration.validate;
import java.time.LocalDate;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.factory.annotation.Autowired;
import de.jottyfan.camporganizer.module.registration.RegistrationRepository;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
/**
*
* @author jotty
*
*/
public class AlreadyRegisteredCheckValidator implements ConstraintValidator<AlreadyRegisteredCheck, Object> {
private String field;
private String fkCamp;
private String surname;
private String birthDate;
private String message;
@Autowired
private RegistrationRepository gateway;
public void initialize(AlreadyRegisteredCheck arc) {
this.field = arc.field();
this.fkCamp = arc.fkCamp();
this.surname = arc.surname();
this.birthDate = arc.birthDate();
this.message = arc.message();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
Object forename = new BeanWrapperImpl(value).getPropertyValue(field);
Object campId = new BeanWrapperImpl(value).getPropertyValue(fkCamp);
Object theSurname = new BeanWrapperImpl(value).getPropertyValue(surname);
Object theBirthDate = new BeanWrapperImpl(value).getPropertyValue(birthDate);
Boolean found = false;
if (theBirthDate != null) {
String fn = (String) forename;
Integer camp = (Integer) campId;
String sn = (String) theSurname;
LocalDate bd = (LocalDate) theBirthDate;
found = gateway.checkAlreadyRegistered(fn, sn, bd, camp);
if (found) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(message).addPropertyNode(fkCamp).addConstraintViolation();
context.buildConstraintViolationWithTemplate("s.o.").addPropertyNode(field).addConstraintViolation();
context.buildConstraintViolationWithTemplate("s.o.").addPropertyNode(surname).addConstraintViolation();
context.buildConstraintViolationWithTemplate("s.o.").addPropertyNode(birthDate).addConstraintViolation();
}
}
return !found;
}
}

View File

@ -11,70 +11,48 @@
<div class="col-12">
<h2 class="headlinefont">Was ist der Onkel Werner Freizeiten Verein?</h2>
<p>Der Verein „Onkel Werner Freizeiten e.V.“ ist ein christliches Kinder- und Jugendwerk.</p>
<p>Seit unserer Gründung führen wir Ferienfreizeiten für Kinder und Jugendliche an verschiedenen Orten in Deutschland und
der Schweiz durch. Diese ziehen jährlich bis zu 400 junge Menschen im Alter von 6 bis 17 Jahren aus ganz Deutschland an.</p>
<p>Wir sind vom Finanzamt Heilbronn als gemeinnützig und mildtätig anerkannt. Unser Name leitet sich vom Gründer der
Freizeitarbeit, Werner Blödtner, ab, der von vielen schlicht „Onkel Werner“ genannt wird.</p>
<p>Seit unserer Gründung führen wir Ferienfreizeiten für Kinder und Jugendliche an verschiedenen Orten in Deutschland und der Schweiz durch. Diese ziehen jährlich bis zu 400 junge
Menschen im Alter von 6 bis 17 Jahren aus ganz Deutschland an.</p>
<p>Wir sind vom Finanzamt Heilbronn als gemeinnützig und mildtätig anerkannt. Unser Name leitet sich vom Gründer der Freizeitarbeit, Werner Blödtner, ab, der von vielen schlicht
„Onkel Werner“ genannt wird.</p>
<h2 class="headlinefont">Wie ist der Verein entstanden?</h2>
<p>Unser Verein wurde offiziell im November 2013 gegründet, um dem sozialen Engagement für Kinder und Jugendliche in der
christlichen Freizeitarbeit einen Rahmen zu geben.</p>
<p>Unser Verein wurde offiziell im November 2013 gegründet, um dem sozialen Engagement für Kinder und Jugendliche in der christlichen Freizeitarbeit einen Rahmen zu geben.</p>
<h3 class="headlinefont">Wie alles anfing</h3>
<p>Doch unsere Geschichte beginnt eigentlich schon vor mehr als fünf Jahrzehnten mit einem jungen Mann, der seine Liebe zu
Jesus Christus fand und diese teilen wollte. Werner Blödtner (später schlicht Onkel Werner genannt) hatte bereits zahlreiche
Stunden in Sonntagsschule und Jungschar einer christlichen Gemeinde gestaltet, als er 1963 angefragt wird, eine mehrtägige
Freizeit in den Schulferien durchzuführen. Er lässt sich auf das Abenteuer ein und übernimmt 1978 die überregionale Kinder-
und Teeniearbeit der Barmer Zeltmission.</p>
<p>Doch unsere Geschichte beginnt eigentlich schon vor mehr als fünf Jahrzehnten mit einem jungen Mann, der seine Liebe zu Jesus Christus fand und diese teilen wollte. Werner Blödtner
(später schlicht Onkel Werner genannt) hatte bereits zahlreiche Stunden in Sonntagsschule und Jungschar einer christlichen Gemeinde gestaltet, als er 1963 angefragt wird, eine mehrtägige
Freizeit in den Schulferien durchzuführen. Er lässt sich auf das Abenteuer ein und übernimmt 1978 die überregionale Kinder- und Teeniearbeit der Barmer Zeltmission.</p>
<h4 class="headlinefont">Die Bedingung</h4>
<p>Allerdings nur unter folgender Vereinbarung mit Gott: Sollte sich kein Kind für den Herrn Jesus entscheiden, würde er
mit der Arbeit wieder aufhören. Zu einer Kinderwoche im Odenwald an Ostern kommen ca. 100 Kinder aus dem Ort und umliegenden
Dörfern. Onkel Werner selbst sagt: „Das war für mich eine große Herausforderung. Wie würde Gott antworten? Ich war sehr
erstaunt, dass nach und nach fünf Kinder den Wunsch äußerten, den Herrn Jesus in ihr Herz und Leben einzuladen.“</p>
<p>Allerdings nur unter folgender Vereinbarung mit Gott: Sollte sich kein Kind für den Herrn Jesus entscheiden, würde er mit der Arbeit wieder aufhören. Zu einer Kinderwoche im
Odenwald an Ostern kommen ca. 100 Kinder aus dem Ort und umliegenden Dörfern. Onkel Werner selbst sagt: „Das war für mich eine große Herausforderung. Wie würde Gott antworten? Ich war
sehr erstaunt, dass nach und nach fünf Kinder den Wunsch äußerten, den Herrn Jesus in ihr Herz und Leben einzuladen.“</p>
<h4 class="headlinefont">Unterwegs für Jesus</h4>
<p>Seither ist Onkel Werner also für Jesus Christus unterwegs. Neben den Kinderstundenwochen entwickelte sich nach und
nach die Freizeitarbeit als Angebot in den Ferien der Kinder und Teenies.</p>
<p>Seither ist Onkel Werner also für Jesus Christus unterwegs. Neben den Kinderstundenwochen entwickelte sich nach und nach die Freizeitarbeit als Angebot in den Ferien der Kinder und
Teenies.</p>
<h3 class="headlinefont">Der Verein</h3>
<p>Und unser Verein? Der ist eben aus genau diesen Kindern und Jugendlichen entstanden, die selbst Onkel Werners
Freizeiten besucht haben und später dort Mitarbeiter geworden sind. Wir möchten, dass seine Arbeit weitergeführt wird. Aber
vor allem wollen wir, dass noch viele Kinder und Jugendliche den Herrn Jesus kennen lernen können und mit ihm leben wollen.</p>
<p>Und unser Verein? Der ist eben aus genau diesen Kindern und Jugendlichen entstanden, die selbst Onkel Werners Freizeiten besucht haben und später dort Mitarbeiter geworden sind.
Wir möchten, dass seine Arbeit weitergeführt wird. Aber vor allem wollen wir, dass noch viele Kinder und Jugendliche den Herrn Jesus kennen lernen können und mit ihm leben wollen.</p>
<p>
<h3 class="headlinefont">Die Vereinsmitglieder</h3>
<p>
In unserem Verein arbeiten verschiedene Freizeitbegeisterte mit, von denen sich hier ein paar vorstellen:
</p>
<p>In unserem Verein arbeiten verschiedene Freizeitbegeisterte mit, von denen sich hier ein paar vorstellen:</p>
</div>
<div class="col-sm-12 col-md-6">
<img th:src="@{/images/simeon.velleuer.jpg}" align="left" class="rounded" style="margin-right: 8px" />
Simeon Velleuer, 41 Jahre, verheiratet mit Anja und Vater von zwei wunderbaren Kindern, Sophie und Kaleb. Für alle „Süddeutschen“
meine Heimat liegt „im Norden“ in Mettmann.<br />
<br />
Ich darf mittlerweile auf eine über zwanzigjährige Zeit in der Mitarbeit mit Kids und Teens zurückblicken und bin unserem gnädigen
Gott und Vater dafür sehr dankbar. Er hat mich errettet und aus der Finsternis in sein wunderbares Licht berufen (1.Petr. 2,9).
Diese Botschaft möchte ich gerne weitergeben - auf Freizeiten durch die froh machende Botschaft der Bibel und durch das ganz
praktische und tägliche Leben.<br />
<br />
Ich erhoffe mir von den Freizeiten, dass wir auf ein Leben mit Gott aufmerksam machen können. Das ist spannend und herausfordernd
zugleich. Durch zahlreiche Erfahrungen in meinem Leben habe ich feststellen können, dass Gott immer den richtigen Plan für jeden
ganz persönlich längst fertig hat. Wir dürfen uns ihm ganz anvertrauen.<br />
<br />
(Es ist schon ein paar Jahre her, als ich selber noch Teilnehmer auf der wunderschönen Griesalp war. Heutzutage fahren wir ins
Kiental. Großartig gelegen inmitten der faszinierenden Bergwelt des Schweizer Berner Oberlandes. Die Kombination aus Wanderungen
und Andachten ist genau das Richtige für mich...und mit Sicherheit auch für dich.)<br />
<img th:src="@{/images/simeon.velleuer.jpg}" align="left" class="rounded" style="margin-right: 8px" /> Simeon Velleuer, 41 Jahre, verheiratet mit Anja und Vater von zwei wunderbaren
Kindern, Sophie und Kaleb. Für alle „Süddeutschen“ meine Heimat liegt „im Norden“ in Mettmann.<br /> <br /> Ich darf mittlerweile auf eine über zwanzigjährige Zeit in der Mitarbeit
mit Kids und Teens zurückblicken und bin unserem gnädigen Gott und Vater dafür sehr dankbar. Er hat mich errettet und aus der Finsternis in sein wunderbares Licht berufen (1.Petr. 2,9).
Diese Botschaft möchte ich gerne weitergeben - auf Freizeiten durch die froh machende Botschaft der Bibel und durch das ganz praktische und tägliche Leben.<br /> <br /> Ich erhoffe mir
von den Freizeiten, dass wir auf ein Leben mit Gott aufmerksam machen können. Das ist spannend und herausfordernd zugleich. Durch zahlreiche Erfahrungen in meinem Leben habe ich
feststellen können, dass Gott immer den richtigen Plan für jeden ganz persönlich längst fertig hat. Wir dürfen uns ihm ganz anvertrauen.<br /> <br /> (Es ist schon ein paar Jahre her,
als ich selber noch Teilnehmer auf der wunderschönen Griesalp war. Heutzutage fahren wir ins Kiental. Großartig gelegen inmitten der faszinierenden Bergwelt des Schweizer Berner
Oberlandes. Die Kombination aus Wanderungen und Andachten ist genau das Richtige für mich...und mit Sicherheit auch für dich.)<br />
</div>
<div class="col-sm-12 col-md-6">
<img th:src="@{/images/joerg.henke.jpg}" align="left" class="rounded" style="margin-right: 8px" />
Jörg Henke, Vater von zwei atemberaubenden Töchtern. Aus Dresden unterstütze ich die Freizeitarbeit v.a. im Bereich der Homepage und
den Abrechnungen. Wenn ich es mal wieder schaffe, auch an einer Freizeit teilzunehmen, spiele ich gerne Gitarre und singe viele
Lieder mit den Teilnehmern.<br />
<br />
Als einer von Onkel Werners "Erben" darf ich mich an meine Bekehrung erinnern, die ich am 31.10.1993 erlebt habe. Details dazu gibt
es auch hier: <a href="https://www.jottyfan.de/bekehrung/" target="_blank">https://www.jottyfan.de/bekehrung/</a>.<br />
<br />
Gerade deswegen ist es mir wichtig, dass unsere Freizeiten stattfinden, offen für finanziell schwächere Familien sind und wir durch
die uns anvertrauten Spenden unterstützen können. Jedes Kind / jeder Teenie soll die rettende Botschaft vom Kreuz hören: dass Jesus
Christus gerade für dich gestorben und auferstanden ist, um dich von deinen Sünden zu befreien und dir damit ewiges Leben in der
Gemeinschaft mit Gott schenkt. Sehr wertvoll finde ich daher auch die Abendandachten, die es ermöglichen, genau diese Wahrheit zu
verdeutlichen.<br />
</div>
<img th:src="@{/images/joerg.henke.jpg}" align="left" class="rounded" style="margin-right: 8px" /> Jörg Henke, Vater von zwei atemberaubenden Töchtern. Aus Dresden unterstütze ich die
Freizeitarbeit v.a. im Bereich der Homepage und den Abrechnungen. Wenn ich es mal wieder schaffe, auch an einer Freizeit teilzunehmen, spiele ich gerne Gitarre und singe viele Lieder mit
den Teilnehmern.<br /> <br /> Als einer von Onkel Werners "Erben" darf ich mich an meine Bekehrung erinnern, die ich am 31.10.1993 erlebt habe. Details dazu gibt es auch hier: <a
href="https://www.jottyfan.de/bekehrung/" target="_blank">https://www.jottyfan.de/bekehrung/</a>.<br /> <br /> Gerade deswegen ist es mir wichtig, dass unsere Freizeiten stattfinden,
offen für finanziell schwächere Familien sind und wir durch die uns anvertrauten Spenden unterstützen können. Jedes Kind / jeder Teenie soll die rettende Botschaft vom Kreuz hören: dass
Jesus Christus gerade für dich gestorben und auferstanden ist, um dich von deinen Sünden zu befreien und dir damit ewiges Leben in der Gemeinschaft mit Gott schenkt. Sehr wertvoll finde
ich daher auch die Abendandachten, die es ermöglichen, genau diese Wahrheit zu verdeutlichen.<br />
</div>
</div>
</div>