This commit is contained in:
Jottyfan 2023-04-03 20:24:13 +02:00
parent c4f81c425b
commit 45e6c4cbd4
12 changed files with 160 additions and 116 deletions

View File

@ -18,7 +18,7 @@ apply plugin: 'war'
apply plugin: 'application'
group = 'de.jottyfan.camporganizer'
version = '0.4.1'
version = '0.4.2'
sourceCompatibility = 17
mainClassName = "de.jottyfan.camporganizer.Main"

View File

@ -1,48 +0,0 @@
package de.jottyfan.camporganizer.module.confirmation.person;
import java.util.List;
import de.jottyfan.camporganizer.module.confirmation.person.impl.CampBean;
import de.jottyfan.camporganizer.module.confirmation.person.impl.PersonBean;
/**
*
* @author jotty
*
*/
public interface IPersonService {
/**
* get person bean from db if username can read it
*
* @param username the user to read from the database
* @param pk the ID of the person to be read
* @return the person or null
*/
public PersonBean getPerson(String username, Integer pk);
/**
* update bean in the database
*
* @param bean the bean
* @param worker the user that is doing the changes
* @return number of affected database rows
*/
public Integer updatePerson(PersonBean bean, String worker);
/**
* get all camps from the database that the user has access to
*
* @param username the user
* @return a list of camp beans; an empty one at least
*/
public List<CampBean> getCamps(String username);
/**
* get several annotations as a String
*
* @param pk the ID of the person
* @return the annotations; may be an empty string
*/
public String getAnnotations(Integer pk);
}

View File

@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.PostMapping;
import de.jottyfan.camporganizer.module.camplist.CommonController;
import de.jottyfan.camporganizer.module.confirmation.confirmation.IConfirmationService;
import de.jottyfan.camporganizer.module.confirmation.person.impl.PersonBean;
import de.jottyfan.camporganizer.module.confirmation.person.impl.PersonService;
/**
*
@ -29,7 +30,7 @@ public class PersonController extends CommonController {
private IConfirmationService confirmationService;
@Autowired
private IPersonService personService;
private PersonService personService;
@GetMapping("/confirmation/person/{pk}")
public String getIndex(Model model, @PathVariable Integer pk) {

View File

@ -34,6 +34,7 @@ import de.jottyfan.camporganizer.db.jooq.enums.EnumModule;
import de.jottyfan.camporganizer.db.jooq.tables.TProfile;
import de.jottyfan.camporganizer.db.jooq.tables.records.TCampRecord;
import de.jottyfan.camporganizer.db.jooq.tables.records.TPersonRecord;
import de.jottyfan.camporganizer.db.jooq.tables.records.TProfileRecord;
import de.jottyfan.camporganizer.db.jooq.tables.records.TRssRecord;
import de.jottyfan.camporganizer.module.camplist.LambdaResultWrapper;
import de.jottyfan.camporganizer.module.mail.MailBean;
@ -140,26 +141,30 @@ public class PersonRepository {
LambdaResultWrapper lrw = new LambdaResultWrapper();
jooq.transaction(t -> {
SelectConditionStep<TProfileRecord> sql0 = jooq.selectFrom(T_PROFILE).where(T_PROFILE.USERNAME.eq(registrator));
LOGGER.debug(sql0.toString());
Integer fkRegistrator = sql0.fetchOne(T_PROFILE.PK);
// get old accept value for comparison
SelectConditionStep<TPersonRecord> sql = jooq.selectFrom(T_PERSON).where(T_PERSON.PK.eq(bean.getPk()));
LOGGER.debug(sql.toString());
TPersonRecord r = sql.fetchOne();
SelectConditionStep<TPersonRecord> sql1 = jooq.selectFrom(T_PERSON).where(T_PERSON.PK.eq(bean.getPk()));
LOGGER.debug(sql1.toString());
TPersonRecord r = sql1.fetchOne();
lrw.putBoolean("acceptOld", r == null ? null : r.getAccept());
lrw.putBoolean("acceptNew", bean.getAccept());
Integer fkCamp = r == null ? null : r.getFkCamp();
String email = r.getEmail(); // use the old one, too
lrw.putString("oldEmail", email);
SelectConditionStep<TCampRecord> sql0 = jooq.selectFrom(T_CAMP).where(T_CAMP.PK.eq(fkCamp));
LOGGER.debug(sql0.toString());
TCampRecord rc = sql0.fetchOne();
SelectConditionStep<TCampRecord> sql2 = jooq.selectFrom(T_CAMP).where(T_CAMP.PK.eq(fkCamp));
LOGGER.debug(sql2.toString());
TCampRecord rc = sql2.fetchOne();
String campName = rc == null ? null : rc.getName();
LocalDateTime arrive = rc == null ? null : rc.getArrive();
String campNameWithYear = new StringBuilder(campName == null ? "" : campName).append(" ")
.append(arrive == null ? "" : arrive.format(DateTimeFormatter.ofPattern("YYYY"))).toString();
lrw.putString("campNameWithYear", campNameWithYear);
UpdateConditionStep<TPersonRecord> sql1 = jooq
UpdateConditionStep<TPersonRecord> sql3 = jooq
// @formatter:off
.update(T_PERSON)
.set(T_PERSON.FORENAME, bean.getForename())
@ -174,10 +179,11 @@ public class PersonRepository {
.set(T_PERSON.COMMENT, bean.getComment())
.set(T_PERSON.ACCEPT, bean.getAccept())
.set(T_PERSON.CAMPROLE, bean.getCamprole())
.set(T_PERSON.FK_REGISTRATOR, fkRegistrator)
.where(T_PERSON.PK.eq(bean.getPk()));
// @formatter:on
LOGGER.debug(sql1.toString());
lrw.add(sql1.execute());
LOGGER.debug(sql3.toString());
lrw.add(sql3.execute());
// always
StringBuilder buf = new StringBuilder("Eine Anmeldung für ");
@ -185,15 +191,15 @@ public class PersonRepository {
buf.append(" wurde von ").append(registrator);
buf.append(" korrigiert.");
InsertValuesStep2<TRssRecord, String, String> sql2 = DSL.using(t)
InsertValuesStep2<TRssRecord, String, String> sql4 = DSL.using(t)
// @formatter:off
.insertInto(T_RSS,
T_RSS.MSG,
T_RSS.RECIPIENT)
.values(buf.toString(), "registrator");
// @formatter:on
LOGGER.debug("{}", sql2.toString());
sql2.execute();
LOGGER.debug("{}", sql4.toString());
sql4.execute();
});
// send email to user instead of rss feed
@ -229,7 +235,7 @@ public class PersonRepository {
buf.append(bean.getSurname());
buf.append(" zur Freizeit ");
buf.append(campNameWithYear);
buf.append(" wurde bestätigt. Melde Dich jetzt unter https://www.onkelwernerfreizeiten.de/camporganizer an,");
buf.append(" wurde bestätigt. Melde Dich jetzt unter https://www.onkelwernerfreizeiten.de an,");
buf.append(" um die zu unterschreibenden Dokumente herunterzuladen, auszudrucken und uns zukommen zu lassen.");
sendMail = true;
}
@ -300,7 +306,11 @@ public class PersonRepository {
LocalDateTime created = r.get(T_PERSON.CREATED);
EnumCamprole role = r.get(T_PERSON.CAMPROLE);
String createdString = created.format(DateTimeFormatter.ofPattern("dd.MM.yyyy"));
if (bookerForename == null && bookerSurname == null) {
buf.append(String.format("angemeldet ohne Registrierung am %s\n", createdString));
} else {
buf.append(String.format("angemeldet von %s %s am %s\n", bookerForename, bookerSurname, createdString));
}
if (registratorForename != null || registratorSurname != null) {
buf.append(String.format("bearbeitet von %s %s\n", registratorForename, registratorSurname));
}

View File

@ -5,36 +5,29 @@ import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import de.jottyfan.camporganizer.module.confirmation.person.IPersonService;
/**
*
* @author jotty
*
*/
@Service
public class PersonService implements IPersonService {
public class PersonService {
@Autowired
private PersonRepository gateway;
@Override
public PersonBean getPerson(String username, Integer pk) {
return gateway.getPerson(username, pk);
}
@Override
public Integer updatePerson(PersonBean bean, String worker) {
return gateway.updatePerson(bean, worker);
}
@Override
public List<CampBean> getCamps(String username) {
return gateway.getCamps(username);
}
@Override
public String getAnnotations(Integer pk) {
return gateway.getAnnotations(pk);
}
}

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.TeacherAgeCheck;
import de.jottyfan.camporganizer.module.registration.validate.UnusedUsername;
/**
@ -19,6 +20,7 @@ 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.")
public class RegistrationBean implements Serializable {
private static final long serialVersionUID = 1L;

View File

@ -52,7 +52,7 @@ public class RegistrationController extends CommonController {
model.addAttribute("roles", EnumConverter.getRoles());
return "/registration/registration";
}
Boolean result = service.register(bean, (String) model.getAttribute("currentUser"));
Boolean result = service.register(bean, getCurrentUser());
return result ? "/registration/success" : "/error";
}

View File

@ -112,31 +112,6 @@ public class RegistrationRepository {
if (bean.getRegisterInKeycloak() && !loginNotYetInUse) {
throw new DataAccessException("login already in use: " + bean.getLogin());
}
// TODO: move to bean validator instead:
// check for valid birthdate of teachers
LocalDateTime birthDate = bean.getBirthDate().atStartOfDay();
if (EnumCamprole.teacher.equals(bean.getCampRole())) {
SelectConditionStep<Record2<Integer, DayToSecond>> sql = jooq
// @formatter:off
.select(T_CAMP.MAX_AGE,
DSL.localDateTimeDiff(T_CAMP.DEPART, birthDate).as("teacherAge"))
.from(T_CAMP)
.where(T_CAMP.PK.eq(bean.getFkCamp()));
// @formatter:on
LOGGER.debug(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);
double totalYears = currentTeacherAge.getTotalDays() / 365.25; // in years
int years = (int) totalYears;
if (years < minTeacherAge) {
throw new DataAccessException("Als Mitarbeiter bist Du leider zu jung für diese Freizeit.");
}
}
// end of check
Integer fkProfile = null;
if (loginNotYetInUse) {
String oldPassword = new StrongPasswordEncryptor().encryptPassword(bean.getPassword());
@ -410,4 +385,34 @@ public class RegistrationRepository {
LOGGER.debug("{}", sql.toString());
sql.execute();
}
/**
* check if the age of the teacher in campRole fits to the valid ones
*
* @param birthDate the birth date of the person
* @param fkCamp the ID of the camp
* @param campRole the role; if it is a teacher, check for the age
* @return true if it fits, false otherwise
*/
public Boolean checkAgeOfTeacher(LocalDate birthDate, Integer fkCamp, EnumCamprole campRole) {
LocalDateTime birthDateDay = birthDate.atStartOfDay();
if (EnumCamprole.teacher.equals(campRole)) {
SelectConditionStep<Record2<Integer, DayToSecond>> sql = jooq
// @formatter:off
.select(T_CAMP.MAX_AGE,
DSL.localDateTimeDiff(T_CAMP.DEPART, birthDateDay).as("teacherAge"))
.from(T_CAMP)
.where(T_CAMP.PK.eq(fkCamp));
// @formatter:on
LOGGER.debug(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);
double totalYears = currentTeacherAge.getTotalDays() / 365.25; // in years
int years = (int) totalYears;
return years >= minTeacherAge;
} else {
return true;
}
}
}

View File

@ -0,0 +1,31 @@
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 javax.validation.Constraint;
import javax.validation.Payload;
/**
*
* @author jotty
*
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = TeacherAgeCheckValidator.class)
@Documented
public @interface TeacherAgeCheck {
String message() default "teacher is too young";
String field();
String fkCamp();
String campRole();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,58 @@
package de.jottyfan.camporganizer.module.registration.validate;
import java.text.DateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.datetime.DateFormatter;
import de.jottyfan.camporganizer.db.jooq.enums.EnumCamprole;
import de.jottyfan.camporganizer.module.registration.RegistrationRepository;
/**
*
* @author jotty
*
*/
public class TeacherAgeCheckValidator implements ConstraintValidator<TeacherAgeCheck, Object> {
private String field;
private String fkCamp;
private String campRole;
private String message;
@Autowired
private RegistrationRepository gateway;
public void initialize(TeacherAgeCheck tac) {
this.field = tac.field();
this.fkCamp = tac.fkCamp();
this.campRole = tac.campRole();
this.message = tac.message();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
Object birthDate = new BeanWrapperImpl(value).getPropertyValue(field);
Object campId = new BeanWrapperImpl(value).getPropertyValue(fkCamp);
Object roleInCamp = new BeanWrapperImpl(value).getPropertyValue(campRole);
Boolean result = true;
if (birthDate != null && campId != null) {
LocalDate bd = (LocalDate) birthDate;
Integer camp = (Integer) campId;
EnumCamprole role = (EnumCamprole) roleInCamp;
result = gateway.checkAgeOfTeacher(bd, camp, role);
if (!result) {
context.buildConstraintViolationWithTemplate(message).addPropertyNode(field).addConstraintViolation()
.disableDefaultConstraintViolation();
}
}
return result;
}
}

View File

@ -17,13 +17,11 @@
</div>
<div class="row mb-2">
<label for="inputForename" class="col-sm-2 col-form-label">Vorname</label>
<div class="col-sm-10">
<div class="col-sm-4">
<input type="text" th:field="*{forename}" class="inputForename form-control" />
</div>
</div>
<div class="row mb-2">
<label for="inputSurname" class="col-sm-2 col-form-label">Nachname</label>
<div class="col-sm-10">
<div class="col-sm-4">
<input type="text" th:field="*{surname}" class="inputSurname form-control" />
</div>
</div>
@ -35,25 +33,21 @@
</div>
<div class="row mb-2">
<label for="inputZip" class="col-sm-2 col-form-label">PLZ</label>
<div class="col-sm-10">
<div class="col-sm-2">
<input type="text" th:field="*{zip}" class="inputZip form-control" />
</div>
</div>
<div class="row mb-2">
<label for="inputCity" class="col-sm-2 col-form-label">Ort</label>
<div class="col-sm-10">
<div class="col-sm-6">
<input type="text" th:field="*{city}" class="inputCity form-control" />
</div>
</div>
<div class="row mb-2">
<label for="inputBirthdate" class="col-sm-2 col-form-label">Geburtstag (TODO)</label>
<div class="col-sm-10">
<label for="inputBirthdate" class="col-sm-2 col-form-label">Geburtstag</label>
<div class="col-sm-4">
<input type="date" th:field="*{birthdate}" class="inputBirthdate form-control" />
</div>
</div>
<div class="row mb-2">
<label for="inputSex" class="col-sm-2 col-form-label">Geschlecht</label>
<div class="col-sm-10">
<div class="col-sm-4">
<select class="form-select" th:field="*{sex}">
<option value="male">männlich</option>
<option value="female">weiblich</option>
@ -62,13 +56,11 @@
</div>
<div class="row mb-2">
<label for="inputPhone" class="col-sm-2 col-form-label">Telefon</label>
<div class="col-sm-10">
<div class="col-sm-4">
<input type="text" th:field="*{phone}" class="inputPhone form-control" />
</div>
</div>
<div class="row mb-2">
<label for="inputEmail" class="col-sm-2 col-form-label">E-Mail</label>
<div class="col-sm-10">
<div class="col-sm-4">
<div class="input-group">
<input type="email" th:field="*{email}" class="inputEmail form-control" />
<a class="btn btn-outline-secondary" th:href="'mailto:' + *{email}"><i class="fas fa-envelope-square"></i></a>

View File

@ -35,7 +35,7 @@
</div>
<div class="col-sm-6 rowdist">
<span class="error" th:each="error : ${#fields.errors('birthDate')}">[[${error}]]<br /></span>
<input type="date" placeholder="Geburtsdatum" th:field="*{birthDate}" th:class="${'form-control ' + (#fields.hasErrors('birthDate') ? 'inputerror' : '')}" />
<input type="date" placeholder="Geburtsdatum" th:field="*{birthDate}" th:class="${'form-control ' + (#fields.hasErrors('birthDate') ? 'inputerror' : '')}" onfocus="this.type = 'date'" onblur="this.type = 'text'" />
</div>
</div>
<div class="row">