Commit c992ed4f authored by Martin Lowe's avatar Martin Lowe 🇨🇦 Committed by Martin Lowe
Browse files

Move db models, fix data loader + DDL for state, add validation for

complete

Validation for complete action makes use of hibernate/persistence
validation mechanisms, calling the logic in service before posting to
the mail service.
parent cc04d876
......@@ -93,6 +93,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-qute</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
......
......@@ -29,12 +29,13 @@ import org.eclipsefoundation.core.model.RequestWrapper;
import org.eclipsefoundation.persistence.dao.PersistenceDao;
import org.eclipsefoundation.persistence.model.RDBMSQuery;
import org.eclipsefoundation.persistence.service.FilterService;
import org.eclipsefoundation.react.model.Address;
import org.eclipsefoundation.react.model.Contact;
import org.eclipsefoundation.react.model.MembershipForm;
import org.eclipsefoundation.react.model.FormOrganization;
import org.eclipsefoundation.react.model.FormWorkingGroup;
import org.eclipsefoundation.react.dto.Address;
import org.eclipsefoundation.react.dto.Contact;
import org.eclipsefoundation.react.dto.FormOrganization;
import org.eclipsefoundation.react.dto.FormWorkingGroup;
import org.eclipsefoundation.react.dto.MembershipForm;
import org.eclipsefoundation.react.namespace.ContactTypes;
import org.eclipsefoundation.react.namespace.FormState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -74,8 +75,7 @@ public class DataLoader {
public void init(@Observes StartupEvent ev) {
// if running in dev mode, preload a bunch of data using dao
LOGGER.debug("Current mode: {}", ProfileManager.getActiveProfile());
if (config.isEnabled()
&& config.getDataLoaderProfiles().contains(ProfileManager.getActiveProfile())) {
if (config.isEnabled() && config.getDataLoaderProfiles().contains(ProfileManager.getActiveProfile())) {
RequestWrapper wrap = new RequestWrapper();
List<MembershipForm> forms = new ArrayList<>(config.getFormCount());
for (int i = 0; i < config.getFormCount(); i++) {
......@@ -87,8 +87,10 @@ public class DataLoader {
mf.setSigningAuthority(Math.random() > 0.5);
mf.setRegistrationCountry("CA");
mf.setVatNumber(RandomStringUtils.randomNumeric(10));
mf.setPurchaseOrderRequired(Math.random() > 0.5 ?"yes": "no");
mf.setDateCreated(Math.random() > 0.5 ? System.currentTimeMillis() + r.nextInt(10000): System.currentTimeMillis() - r.nextInt(10000));
mf.setPurchaseOrderRequired(Math.random() > 0.5 ? "yes" : "no");
mf.setDateCreated(Math.random() > 0.5 ? System.currentTimeMillis() + r.nextInt(10000)
: System.currentTimeMillis() - r.nextInt(10000));
mf.setState(FormState.INPROGRESS);
forms.add(mf);
}
......@@ -99,6 +101,7 @@ public class DataLoader {
List<Contact> contacts = new ArrayList<>(forms.size() * ContactTypes.values().length);
List<FormWorkingGroup> wgs = new ArrayList<>();
for (MembershipForm mf : forms) {
// generate an org for form
FormOrganization o = new FormOrganization();
o.setForm(mf);
o.setLegalName(RandomStringUtils.randomAlphabetic(4, 10));
......@@ -112,36 +115,9 @@ public class DataLoader {
a.setOrganization(o);
o.setAddress(a);
organizations.add(o);
for (int j = 0; j < ContactTypes.values().length; j++) {
// randomly skip contacts
if (Math.random() > 0.5) {
continue;
}
Contact c = new Contact();
c.setForm(mf);
c.setTitle("Sample Title");
c.setfName(RandomStringUtils.randomAlphabetic(4, 10));
c.setlName(RandomStringUtils.randomAlphabetic(4, 10));
c.setType(ContactTypes.values()[j]);
c.setEmail(RandomStringUtils.randomAlphabetic(4, 10));
contacts.add(c);
}
// randomly create WG entries
while (true) {
if (Math.random() > 0.5) {
break;
}
FormWorkingGroup wg = new FormWorkingGroup();
wg.setWorkingGroupID(config.getWorkingGroups().get(r.nextInt(config.getWorkingGroups().size())));
wg.setParticipationLevel(
config.getParticipationLevels().get(r.nextInt(config.getParticipationLevels().size())));
// get a random instance of time
Instant inst = Instant.now().minus(r.nextInt(1000000), ChronoUnit.SECONDS);
wg.setEffectiveDate(new Date(inst.getEpochSecond()));
wg.setContact(generateContact(mf, Optional.empty()));
wg.setForm(mf);
wgs.add(wg);
}
// generate contacts + wgs for form
contacts.addAll(generateContacts(mf));
wgs.addAll(generateWorkingGroups(mf));
}
organizations = dao.add(new RDBMSQuery<>(wrap, filters.get(FormOrganization.class)), organizations);
contacts = dao.add(new RDBMSQuery<>(wrap, filters.get(Contact.class)), contacts);
......@@ -152,6 +128,39 @@ public class DataLoader {
}
}
private List<FormWorkingGroup> generateWorkingGroups(MembershipForm form) {
List<FormWorkingGroup> wgs = new ArrayList<>();
// randomly create WG entries
while (true) {
if (Math.random() > 0.5) {
break;
}
FormWorkingGroup wg = new FormWorkingGroup();
wg.setWorkingGroupID(config.getWorkingGroups().get(r.nextInt(config.getWorkingGroups().size())));
wg.setParticipationLevel(
config.getParticipationLevels().get(r.nextInt(config.getParticipationLevels().size())));
// get a random instance of time
Instant inst = Instant.now().minus(r.nextInt(1000000), ChronoUnit.SECONDS);
wg.setEffectiveDate(new Date(inst.getEpochSecond()));
wg.setContact(generateContact(form, Optional.empty()));
wg.setForm(form);
wgs.add(wg);
}
return wgs;
}
private List<Contact> generateContacts(MembershipForm form) {
List<Contact> out = new ArrayList<>();
for (int j = 0; j < ContactTypes.values().length; j++) {
// randomly skip contacts
if (Math.random() > 0.5) {
continue;
}
out.add(generateContact(form, Optional.of(ContactTypes.values()[j])));
}
return out;
}
private Contact generateContact(MembershipForm form, Optional<ContactTypes> type) {
Contact c = new Contact();
c.setForm(form);
......@@ -159,8 +168,17 @@ public class DataLoader {
c.setfName(RandomStringUtils.randomAlphabetic(4, 10));
c.setlName(RandomStringUtils.randomAlphabetic(4, 10));
c.setType(type.orElse(ContactTypes.WORKING_GROUP));
c.setEmail(RandomStringUtils.randomAlphabetic(4, 10));
c.setEmail(generateEmail());
return c;
}
private String generateEmail() {
StringBuilder sb = new StringBuilder();
sb.append(RandomStringUtils.randomAlphabetic(4, 10));
sb.append("@");
sb.append(RandomStringUtils.randomAlphabetic(4, 10));
sb.append(".");
sb.append(RandomStringUtils.randomAlphabetic(2));
return sb.toString();
}
}
......@@ -9,7 +9,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.react.model;
package org.eclipsefoundation.react.dto;
import java.util.Objects;
......
......@@ -9,7 +9,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.react.model;
package org.eclipsefoundation.react.dto;
import java.util.Objects;
......@@ -27,6 +27,7 @@ import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.ws.rs.core.MultivaluedMap;
import org.eclipsefoundation.core.namespace.DefaultUrlParameterNames;
......@@ -72,7 +73,7 @@ public class Contact extends BareNode implements TargetedClone<Contact> {
@NotBlank(message = "Job title cannot be blank")
@JsonbProperty(value = "job_title")
private String title;
@NotBlank(message = "Contact type cannot be blank")
@NotNull(message = "Contact type cannot be empty")
@Enumerated(EnumType.STRING)
private ContactTypes type;
......
......@@ -9,7 +9,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.react.model;
package org.eclipsefoundation.react.dto;
import java.util.Objects;
......
......@@ -9,7 +9,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.react.model;
package org.eclipsefoundation.react.dto;
import java.util.Date;
import java.util.Objects;
......
......@@ -9,7 +9,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.react.model;
package org.eclipsefoundation.react.dto;
import java.util.Objects;
......@@ -21,6 +21,7 @@ import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.ws.rs.core.MultivaluedMap;
import org.eclipsefoundation.core.namespace.DefaultUrlParameterNames;
......@@ -46,7 +47,7 @@ public class MembershipForm extends BareNode implements TargetedClone<Membership
private String userID;
@NotBlank(message = "Membership level cannot be blank")
private String membershipLevel;
@NotBlank(message = "Signing authority cannot be blank")
@NotNull(message = "Signing authority cannot be null")
private boolean signingAuthority;
@NotBlank(message = "Purchase order state cannot be blank")
private String purchaseOrderRequired;
......@@ -54,7 +55,7 @@ public class MembershipForm extends BareNode implements TargetedClone<Membership
private String registrationCountry;
@SortableField
private Long dateCreated;
@NotBlank(message = "The form state cannot be blank")
@NotNull(message = "The form state cannot be null")
private FormState state;
/** @return the id */
......
......@@ -9,7 +9,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.react.model;
package org.eclipsefoundation.react.dto;
/**
* Indicates the the object should be able to clone itself into Target Object. This allows for entity refs retrieved
......
package org.eclipsefoundation.react.model;
import javax.validation.ConstraintViolation;
import org.eclipsefoundation.persistence.dto.BareNode;
public class ConstraintViolationWrapFactory {
public <T extends BareNode> ConstraintViolationWrap build(T object, ConstraintViolation<T> inner) {
return new ConstraintViolationWrap(object.getId(), object.getClass().getSimpleName(), inner.getInvalidValue(),
inner.getPropertyPath().toString());
}
public class ConstraintViolationWrap {
private Object rootID;
private String type;
private Object value;
private String path;
ConstraintViolationWrap(Object rootID, String type, Object value, String path) {
this.type = type;
this.rootID = rootID;
this.value = value;
this.path = path;
}
/**
* @return the rootID
*/
public Object getRootID() {
return rootID;
}
/**
* @return the type
*/
public String getType() {
return type;
}
/**
* @return the value
*/
public Object getValue() {
return value;
}
/**
* @return the path
*/
public String getPath() {
return path;
}
}
}
......@@ -14,7 +14,6 @@ package org.eclipsefoundation.react.model;
import java.util.List;
import java.util.Objects;
public class WorkingGroup {
private String documentID;
......@@ -27,7 +26,6 @@ public class WorkingGroup {
this.levels = levels;
}
public WorkingGroup() {
}
......
......@@ -21,7 +21,7 @@ import org.eclipsefoundation.core.model.RequestWrapper;
import org.eclipsefoundation.core.service.CachingService;
import org.eclipsefoundation.persistence.dao.PersistenceDao;
import org.eclipsefoundation.persistence.service.FilterService;
import org.eclipsefoundation.react.model.MembershipForm;
import org.eclipsefoundation.react.dto.MembershipForm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......
......@@ -30,8 +30,8 @@ import javax.ws.rs.core.Response;
import org.eclipsefoundation.core.helper.CSRFHelper;
import org.eclipsefoundation.core.namespace.DefaultUrlParameterNames;
import org.eclipsefoundation.persistence.model.RDBMSQuery;
import org.eclipsefoundation.react.model.Contact;
import org.eclipsefoundation.react.model.MembershipForm;
import org.eclipsefoundation.react.dto.Contact;
import org.eclipsefoundation.react.dto.MembershipForm;
import org.eclipsefoundation.react.namespace.MembershipFormAPIParameterNames;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
......
......@@ -30,8 +30,8 @@ import javax.ws.rs.core.Response;
import org.eclipsefoundation.core.helper.CSRFHelper;
import org.eclipsefoundation.core.namespace.DefaultUrlParameterNames;
import org.eclipsefoundation.persistence.model.RDBMSQuery;
import org.eclipsefoundation.react.model.FormOrganization;
import org.eclipsefoundation.react.model.MembershipForm;
import org.eclipsefoundation.react.dto.FormOrganization;
import org.eclipsefoundation.react.dto.MembershipForm;
import org.eclipsefoundation.react.namespace.MembershipFormAPIParameterNames;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
......
......@@ -12,10 +12,13 @@
package org.eclipsefoundation.react.request;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.persistence.NoResultException;
import javax.inject.Inject;
import javax.validation.Validator;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
......@@ -29,14 +32,18 @@ import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.eclipsefoundation.core.helper.CSRFHelper;
import org.eclipsefoundation.core.namespace.DefaultUrlParameterNames;
import org.eclipsefoundation.persistence.dto.BareNode;
import org.eclipsefoundation.persistence.model.RDBMSQuery;
import org.eclipsefoundation.react.model.Contact;
import org.eclipsefoundation.react.model.FormOrganization;
import org.eclipsefoundation.react.model.FormWorkingGroup;
import org.eclipsefoundation.react.model.MembershipForm;
import org.eclipsefoundation.react.dto.Contact;
import org.eclipsefoundation.react.dto.FormOrganization;
import org.eclipsefoundation.react.dto.FormWorkingGroup;
import org.eclipsefoundation.react.dto.MembershipForm;
import org.eclipsefoundation.react.model.ConstraintViolationWrapFactory;
import org.eclipsefoundation.react.model.ConstraintViolationWrapFactory.ConstraintViolationWrap;
import org.eclipsefoundation.react.namespace.FormState;
import org.eclipsefoundation.react.namespace.MembershipFormAPIParameterNames;
import org.eclipsefoundation.react.service.MailerService;
......@@ -58,6 +65,8 @@ import io.quarkus.security.Authenticated;
public class MembershipFormResource extends AbstractRESTResource {
public static final Logger LOGGER = LoggerFactory.getLogger(MembershipFormResource.class);
@Inject
Validator validator;
@Inject
MailerService mailer;
......@@ -163,7 +172,7 @@ public class MembershipFormResource extends AbstractRESTResource {
MultivaluedMap<String, String> params = new MultivaluedMapImpl<>();
params.add(DefaultUrlParameterNames.ID.getName(), formID);
// retrieve the possible cached object
// retrieve the membership form for the current post
List<MembershipForm> results = dao.get(new RDBMSQuery<>(wrap, filters.get(MembershipForm.class), params));
if (results == null) {
return Response.serverError().build();
......@@ -174,23 +183,40 @@ public class MembershipFormResource extends AbstractRESTResource {
// dont send email if force param is not true and form already submitted
return Response.status(204).build();
}
// send the form to the mailing service
MembershipForm mf = results.get(0);
mailer.sendToFormAuthor(mf);
// retrieve all of the info needed to post the form email
MultivaluedMap<String, String> extraparams = new MultivaluedMapImpl<>();
extraparams.add(MembershipFormAPIParameterNames.FORM_ID.getName(), formID);
List<FormOrganization> org = dao.get(new RDBMSQuery<>(wrap, filters.get(FormOrganization.class), extraparams));
List<FormOrganization> orgs = dao.get(new RDBMSQuery<>(wrap, filters.get(FormOrganization.class), extraparams));
List<FormWorkingGroup> wgs = dao.get(new RDBMSQuery<>(wrap, filters.get(FormWorkingGroup.class), extraparams));
List<Contact> contacts = dao.get(new RDBMSQuery<>(wrap, filters.get(Contact.class), extraparams));
// send the membership team email message
mailer.sendToMembershipTeam(mf, !org.isEmpty() ? org.get(0) : null, wgs, contacts);
// validate form elements
Set<ConstraintViolationWrap> violations = new HashSet<>();
violations.addAll(recordViolations(results));
violations.addAll(recordViolations(orgs));
violations.addAll(recordViolations(wgs));
violations.addAll(recordViolations(contacts));
// check that there are no validation violations for form elements
if (!violations.isEmpty()) {
return Response.status(Status.BAD_REQUEST.getStatusCode()).entity(violations).build();
}
// send the forms to the mailing service
mailer.sendToFormAuthor(mf);
mailer.sendToMembershipTeam(mf, !orgs.isEmpty() ? orgs.get(0) : null, wgs, contacts);
// update the state and push the update
mf.setState(FormState.SUBMITTED);
return Response.ok(dao.add(new RDBMSQuery<>(wrap, filters.get(MembershipForm.class)), Arrays.asList(mf)))
.build();
}
private <T extends BareNode> Set<ConstraintViolationWrap> recordViolations(List<T> items) {
ConstraintViolationWrapFactory factory = new ConstraintViolationWrapFactory();
return items.stream()
.flatMap(item -> validator.validate(item).stream().map(violation -> factory.build(item, violation)))
.collect(Collectors.toSet());
}
}
......@@ -30,9 +30,9 @@ import javax.ws.rs.core.Response;
import org.eclipsefoundation.core.helper.CSRFHelper;
import org.eclipsefoundation.core.namespace.DefaultUrlParameterNames;
import org.eclipsefoundation.persistence.model.RDBMSQuery;
import org.eclipsefoundation.react.model.Contact;
import org.eclipsefoundation.react.model.MembershipForm;
import org.eclipsefoundation.react.model.FormWorkingGroup;
import org.eclipsefoundation.react.dto.Contact;
import org.eclipsefoundation.react.dto.FormWorkingGroup;
import org.eclipsefoundation.react.dto.MembershipForm;
import org.eclipsefoundation.react.namespace.MembershipFormAPIParameterNames;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
......
......@@ -2,10 +2,10 @@ package org.eclipsefoundation.react.service;
import java.util.List;
import org.eclipsefoundation.react.model.Contact;
import org.eclipsefoundation.react.model.FormOrganization;
import org.eclipsefoundation.react.model.FormWorkingGroup;
import org.eclipsefoundation.react.model.MembershipForm;
import org.eclipsefoundation.react.dto.Contact;
import org.eclipsefoundation.react.dto.FormOrganization;
import org.eclipsefoundation.react.dto.FormWorkingGroup;
import org.eclipsefoundation.react.dto.MembershipForm;
/**
* Interface defining emails that need to be generated and sent as part of the submission of the membership forms to the
......
......@@ -7,10 +7,10 @@ import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipsefoundation.react.model.Contact;
import org.eclipsefoundation.react.model.FormOrganization;
import org.eclipsefoundation.react.model.FormWorkingGroup;
import org.eclipsefoundation.react.model.MembershipForm;
import org.eclipsefoundation.react.dto.Contact;
import org.eclipsefoundation.react.dto.FormOrganization;
import org.eclipsefoundation.react.dto.FormWorkingGroup;
import org.eclipsefoundation.react.dto.MembershipForm;
import org.eclipsefoundation.react.service.MailerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......
......@@ -8,6 +8,7 @@ CREATE TABLE `MembershipForm` (
`signingAuthority` bit(1) NOT NULL,
`userID` varchar(255) DEFAULT NULL,
`vatNumber` varchar(255) DEFAULT NULL,
`state` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
......@@ -43,7 +44,7 @@ CREATE TABLE `FormWorkingGroup` (
`form_id` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UK_1gt3qh8yu1jpky54o38igvfrq` (`contact_id`),
UNIQUE KEY `UK_2qrha8ti59jcxnvi2h8b9d2u8` (`form_id`),
KEY `UK_2qrha8ti59jcxnvi2h8b9d2u8` (`form_id`),
CONSTRAINT `FK8mhoi37sufequy7nnn19811rv` FOREIGN KEY (`form_id`) REFERENCES `MembershipForm` (`id`),
CONSTRAINT `FKmnoygtwlsvh31vb4volo8odct` FOREIGN KEY (`contact_id`) REFERENCES `Contact` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
......
......@@ -10,7 +10,7 @@ import javax.json.bind.Jsonb;
import org.eclipsefoundation.core.config.JsonBConfig;
import org.eclipsefoundation.core.helper.CSRFHelper;
import org.eclipsefoundation.react.model.Contact;
import org.eclipsefoundation.react.dto.Contact;
import org.eclipsefoundation.react.namespace.ContactTypes;
import org.eclipsefoundation.react.test.helper.AuthHelper;
import org.eclipsefoundation.react.test.helper.SchemaNamespaceHelper;
......
......@@ -10,8 +10,8 @@ import javax.json.bind.Jsonb;
import org.eclipsefoundation.core.config.JsonBConfig;
import org.eclipsefoundation.core.helper.CSRFHelper;
import org.eclipsefoundation.react.model.Address;
import org.eclipsefoundation.react.model.FormOrganization;
import org.eclipsefoundation.react.dto.Address;
import org.eclipsefoundation.react.dto.FormOrganization;
import org.eclipsefoundation.react.test.helper.AuthHelper;
import org.eclipsefoundation.react.test.helper.SchemaNamespaceHelper;
import org.hamcrest.text.IsEmptyString;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment