added pairing CSV; fixed agenda

This commit is contained in:
2023-06-19 17:31:26 -04:00
parent 03fa19734b
commit 6471493170
5 changed files with 301 additions and 8 deletions

View File

@@ -2,11 +2,16 @@ package com.poststats.golf.api;
import com.brianlong.util.FlexMap;
import com.poststats.api.Constants;
import com.poststats.formatter.PersonFormatter;
import com.poststats.formatter.PersonsFormatter;
import com.poststats.golf.api.model.EventRound;
import com.poststats.golf.api.model.EventRoundPairing;
import com.poststats.golf.api.model.EventRoundPairingOrder;
import com.poststats.golf.formatter.GolfCourseFormatter;
import com.poststats.golf.service.CourseService;
import com.poststats.golf.service.EventRoundPairingService;
import com.poststats.golf.service.EventRoundService;
import com.poststats.golf.service.PersonService;
import com.poststats.transformer.impl.DaoConverter;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
@@ -19,11 +24,24 @@ import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.Response.Status;
import jakarta.ws.rs.core.StreamingOutput;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -40,6 +58,9 @@ public class EventRoundApi {
@PathParam("eventId")
private long eventId;
@Inject
private PersonService golferService;
@Inject
private EventRoundService roundService;
@@ -139,7 +160,7 @@ public class EventRoundApi {
@ApiResponse(responseCode = "200", description = "Success"),
@ApiResponse(
responseCode = "404",
description = "An event with the specified ID or any event rounds could not be found"
description = "An event or its round with the specified ID could not be found"
)
})
public List<EventRoundPairing> getPairings(@PathParam("eroundId")
@@ -152,6 +173,51 @@ public class EventRoundApi {
return this.converter.convertValue(rows, EventRoundPairing.class);
}
@GET
@Path("/round/{eroundId:[0-9]+}/pairings/csv")
@Produces("text/csv")
@Operation(summary = "Retrieves limited meta-data about all pairings for a single event round in the CSV format.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "Success"),
@ApiResponse(
responseCode = "404",
description = "An event or its round with the specified ID could not be found"
)
})
public StreamingOutput getPairingsAsCsv(@PathParam("eroundId")
long eroundId, @QueryParam("orderBy")
List<EventRoundPairingOrder> orderBys, @QueryParam("lastNameFirst")
boolean lastNameFirst) {
this.logger.debug("getPairingsAsCsv({}, {})", eroundId, lastNameFirst);
List<? extends FlexMap> rows = this.pairingService.getParticipantsByRoundId(eroundId);
if (rows == null || rows.isEmpty())
throw new WebApplicationException("No pairings found", Status.NOT_FOUND);
this.logger.debug("Found {} pairings in round: {}", rows.size(), eroundId);
this.golferService.injectDeep("personID", rows, "golfer");
PersonFormatter personFormatter = new PersonFormatter();
personFormatter.withNameReversal(lastNameFirst);
GolfCourseFormatter courseFormatter = new GolfCourseFormatter();
for (FlexMap row : rows) {
FlexMap golfer = row.get("golfer", FlexMap.class);
FlexMap person = golfer.get("person", FlexMap.class);
row.put("name", personFormatter.format(person));
FlexMap pairing = row.get("pairing", FlexMap.class);
this.courseService.inject("courseID", pairing, "course");
FlexMap course = pairing.get("course", FlexMap.class);
if (course != null)
pairing.put("courseName", courseFormatter.format(course));
}
this.sort(rows, orderBys);
return this.toCsv(rows, lastNameFirst);
}
@GET
@Path("/round/{eroundId:[0-9]+}/pairing/{pairingID:[0-9]+}")
@Produces(Constants.V1_JSON)
@@ -179,4 +245,135 @@ public class EventRoundApi {
return this.converter.convertValue(row, EventRoundPairing.class);
}
private void sort(List<? extends FlexMap> persons, List<EventRoundPairingOrder> orderBys) {
final List<EventRoundPairingOrder> orders = new LinkedList<>((orderBys == null || orderBys.isEmpty())
? Arrays.asList(EventRoundPairingOrder.Pairing, EventRoundPairingOrder.FormattedName)
: orderBys);
this.logger.debug("Sorting by: {}", orders);
ListIterator<EventRoundPairingOrder> i = orders.listIterator();
while (i.hasNext()) {
EventRoundPairingOrder order = i.next();
if (order == EventRoundPairingOrder.Pairing) {
i.remove();
i.add(EventRoundPairingOrder.TeeTime);
i.add(EventRoundPairingOrder.Course);
i.add(EventRoundPairingOrder.CourseNine);
i.add(EventRoundPairingOrder.HoleNumber);
}
}
Collections.sort(persons, new Comparator<FlexMap>() {
@Override
public int compare(FlexMap person1, FlexMap person2) {
if (person1 == null && person2 == null) {
return 0;
} else if (person1 == null) {
return -1;
} else if (person2 == null) {
return 1;
} else {
for (EventRoundPairingOrder order : orders) {
int compare = 0;
FlexMap pairing1 = person1.get("pairing", FlexMap.class);
FlexMap pairing2 = person2.get("pairing", FlexMap.class);
switch (order) {
case FormattedName:
compare = person1.getString("name").compareTo(person2.getString("name"));
break;
case LastName:
compare = person1.get("golfer", FlexMap.class).get("person", FlexMap.class)
.getString("lname").compareTo(person2.get("golfer", FlexMap.class)
.get("person", FlexMap.class).getString("lname"));
break;
case FirstName:
compare = person1.get("golfer", FlexMap.class).get("person", FlexMap.class)
.getString("fname").compareTo(person2.get("golfer", FlexMap.class)
.get("person", FlexMap.class).getString("fname"));
break;
case Course:
if (!pairing1.isNotEmpty("course") && !pairing2.isNotEmpty("course")) {
} else if (!pairing1.isNotEmpty("course")) {
return -1;
} else if (!pairing2.isNotEmpty("course")) {
return 1;
} else {
compare = StringUtils.compare(
pairing1.get("course", FlexMap.class).getString("nine"),
pairing2.get("course", FlexMap.class).getString("nine"));
}
break;
case CourseNine:
if (!pairing1.isNotEmpty("nine") && !pairing2.isNotEmpty("nine")) {
} else if (!pairing1.isNotEmpty("nine")) {
return -1;
} else if (!pairing2.isNotEmpty("nine")) {
return 1;
} else {
compare = StringUtils.compare(pairing1.get("nine", FlexMap.class).getString("nine"),
pairing2.get("nine", FlexMap.class).getString("nine"));
}
break;
case HoleNumber:
compare = compareTo(pairing1.getByte("number"), pairing2.getByte("number"));
break;
case TeeTime:
compare = compareTo(pairing1.getTime("teetime"), pairing2.getTime("teetime"));
break;
default:
throw new IllegalArgumentException();
}
if (compare != 0)
return compare;
}
return 0;
}
}
});
}
private StreamingOutput toCsv(List<? extends FlexMap> persons, boolean lastNameFirst) {
return new StreamingOutput() {
@Override
public void write(OutputStream output) throws IOException {
PersonsFormatter<FlexMap> personsFormatter = new PersonsFormatter<>();
personsFormatter.withNameReversal(lastNameFirst);
PrintStream pstream = new PrintStream(output);
CSVPrinter personsCsvPrinter = new CSVPrinter(pstream, CSVFormat.DEFAULT);
try {
personsCsvPrinter.printRecord("Name", "Tee Time", "Course", "Course Nine", "Hole Number");
for (FlexMap eperson : persons) {
FlexMap pairing = eperson.get("pairing", FlexMap.class);
FlexMap nine = pairing.get("nine", FlexMap.class);
personsCsvPrinter.printRecord(eperson.getString("name"), pairing.getTime("teetime"),
pairing.getString("courseName"), nine == null ? null : nine.getString("nine"),
pairing.getByte("number"));
}
personsCsvPrinter.flush();
} finally {
personsCsvPrinter.close();
}
}
};
}
private <T extends Comparable<T>> int compareTo(T c1, T c2) {
if (c1 == null && c2 == null) {
return 0;
} else if (c1 == null) {
return -1;
} else if (c2 == null) {
return 1;
} else {
return c1.compareTo(c2);
}
}
}

View File

@@ -0,0 +1,20 @@
package com.poststats.golf.api.model;
import com.fasterxml.jackson.annotation.JsonProperty;
public enum EventRoundPairingOrder {
@JsonProperty("name")
FormattedName, @JsonProperty("lastName")
LastName, @JsonProperty("firstName")
FirstName, @JsonProperty("teetime")
TeeTime, @JsonProperty("course")
Course, @JsonProperty("nine")
CourseNine, @JsonProperty("holeNumber")
HoleNumber,
// an alias for teetime,course,nine,holeNumber
@JsonProperty("pairing")
Pairing
}

View File

@@ -114,8 +114,9 @@ public class EventAgendaJob {
throws CacheRetrievalException, SQLException, MessagingException, IOException {
boolean doEmailLink = Boolean.TRUE.equals(agenda.getBoolean("sendEmailWithLink"));
boolean doEmail = Boolean.TRUE.equals(agenda.getBoolean("sendEmailWithContent"));
boolean doText = Boolean.TRUE.equals(agenda.getBoolean("sendTextWithLink"));
if (!doText && !doEmailLink && !doEmail)
boolean doTextLink = Boolean.TRUE.equals(agenda.getBoolean("sendTextWithLink"));
boolean doText = Boolean.TRUE.equals(agenda.getBoolean("sendTextWithContent"));
if (!doTextLink && !doText && !doEmailLink && !doEmail)
return;
Long eventId = agenda.getLong("eventID");
@@ -130,18 +131,21 @@ public class EventAgendaJob {
+ this.weekdayFormatter.format(day);
Set<Long> textedPersonIDs = Collections.emptySet();
if (doText)
if (doTextLink) {
textedPersonIDs = this.textLink(eventId, documentId, recipientIDs, subject);
} else if (doText) {
textedPersonIDs = this.text(eventId, documentId, recipientIDs, subject);
}
if (doEmailLink)
if (doEmailLink) {
this.emailLink(eventId, documentId, recipientIDs, textedPersonIDs, subject);
if (doEmail) {
} else if (doEmail) {
this.email(eventId, documentId, recipientIDs, textedPersonIDs, subject);
}
}
private Set<Long> text(long eventID, long documentID, Map<Long, BigInteger> recipientIDs, String subject)
private Set<Long> textLink(long eventID, long documentID, Map<Long, BigInteger> recipientIDs, String subject)
throws SQLException, MessagingException {
this.logger.debug("Sending agenda links by text with document ID: {}", documentID);
@@ -168,6 +172,45 @@ public class EventAgendaJob {
return textedPersonIDs;
}
private Set<Long> text(long eventID, long documentID, Map<Long, BigInteger> recipientIDs, String subject)
throws SQLException, MessagingException, IOException {
this.logger.debug("Sending agenda links by text with document ID: {}", documentID);
List<Contact> recipients = this.personService.getContacts(recipientIDs.keySet(), false, true);
NameValuePair[] params = new NameValuePair[] {
new BasicNameValuePair("n", "documentAgendaMarkdown"),
new BasicNameValuePair("eventID", String.valueOf(eventID)),
new BasicNameValuePair("documentID", String.valueOf(documentID))
};
Set<Long> textedPersonIDs = new HashSet<Long>(recipientIDs.size());
for (Contact recipient : recipients) {
long personId = recipient.getPersonId();
this.logger.debug("Sending agenda to: {}", personId);
HttpUriRequest request = RequestBuilder.get(this.baseGolfUrl).addParameters(params)
.addParameter("epersonID", recipientIDs.get(personId).toString()).build();
CloseableHttpClient hclient = HttpClientBuilder.create().build();
CloseableHttpResponse response = hclient.execute(request);
try {
if (response.getStatusLine().getStatusCode() / 100 == 2) {
String markdown = IOUtils.toString(response.getEntity().getContent(), "utf-8");
this.texter.send(Arrays.asList(recipient), subject, markdown.trim());
textedPersonIDs.add(personId);
} else {
this.logger.warn("The URL could not be loaded properly: {}", request.getURI());
}
} finally {
response.close();
}
}
return textedPersonIDs;
}
private void emailLink(long eventID, long documentID, Map<Long, BigInteger> recipientIDs,
Set<Long> excludePersonIDs, String subject) throws SQLException, MessagingException {
this.logger.debug("Sending agenda links by email with document ID: {}", documentID);

View File

@@ -10,4 +10,6 @@ public interface EventRoundPairingService {
List<? extends FlexMap> getByRoundId(long eroundId);
List<? extends FlexMap> getParticipantsByRoundId(long eroundId);
}

View File

@@ -72,4 +72,35 @@ public class EventRoundPairingServiceDAO implements EventRoundPairingService {
)
private StatementProvider sqlSelectByRoundId;
@Override
public List<DataSet> getParticipantsByRoundId(long eroundId) {
try {
FlexPreparedStatement fps = this.sqlSelectParticipantsByRoundId.buildPreparedStatement();
try {
fps.setIntegerU(1, eroundId);
return fps.executeQuery().getAllRows(new FlexManyToOneDef("pairingID", "pairing"),
new FlexManyToOneDef("nineID", "pairing", "nine"));
} finally {
fps.close();
}
} catch (SQLException se) {
throw new ServiceException(se);
}
}
@Inject
@NonTransactionalProvider
@GolfProvider
@Statement(
sql = "SELECT EPR.epersonID, EP.personID, "
+ " ERP.pairingID, ERP.teetime, ERP.eroundID, ERP.courseID, ERP.number, "
+ " CN.nineID, CN.nine, CN.courseID "
+ "FROM ~g~.EventRoundPairing ERP "
+ " INNER JOIN ~g~.EventPersonRound EPR ON (ERP.pairingID=EPR.pairingID) "
+ " INNER JOIN ~g~.EventPerson EP ON (EPR.epersonID=EP.epersonID) "
+ " LEFT JOIN ~g~.CourseNine CN ON (ERP.nineID=CN.nineID) "
+ "WHERE ERP.eroundID=? "
)
private StatementProvider sqlSelectParticipantsByRoundId;
}