package gov.nysenate.openleg.controller.api.bill;
import com.google.common.collect.Range;
import gov.nysenate.openleg.client.response.base.BaseResponse;
import gov.nysenate.openleg.client.response.base.DateRangeListViewResponse;
import gov.nysenate.openleg.client.view.bill.BaseBillIdView;
import gov.nysenate.openleg.client.view.bill.SimpleBillInfoView;
import gov.nysenate.openleg.client.view.updates.UpdateDigestModelView;
import gov.nysenate.openleg.client.view.updates.UpdateDigestView;
import gov.nysenate.openleg.client.view.updates.UpdateTokenModelView;
import gov.nysenate.openleg.client.view.updates.UpdateTokenView;
import gov.nysenate.openleg.controller.api.base.BaseCtrl;
import gov.nysenate.openleg.controller.api.base.InvalidRequestParamEx;
import gov.nysenate.openleg.dao.base.LimitOffset;
import gov.nysenate.openleg.dao.base.PaginatedList;
import gov.nysenate.openleg.dao.base.SortOrder;
import gov.nysenate.openleg.dao.bill.data.BillUpdatesDao;
import gov.nysenate.openleg.model.bill.BaseBillId;
import gov.nysenate.openleg.model.bill.BillUpdateField;
import gov.nysenate.openleg.model.updates.UpdateDigest;
import gov.nysenate.openleg.model.updates.UpdateToken;
import gov.nysenate.openleg.model.updates.UpdateType;
import gov.nysenate.openleg.service.bill.data.BillDataService;
import gov.nysenate.openleg.util.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;
import java.time.LocalDateTime;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static gov.nysenate.openleg.controller.api.base.BaseCtrl.BASE_API_PATH;
import static java.util.stream.Collectors.toList;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
/**
* Bill Updates API
*/
@RestController
@RequestMapping(value = BASE_API_PATH + "/bills", method = RequestMethod.GET, produces = APPLICATION_JSON_VALUE)
public class BillUpdatesCtrl extends BaseCtrl
{
private static final Logger logger = LoggerFactory.getLogger(BillUpdatesCtrl.class);
@Autowired protected BillUpdatesDao billUpdatesDao;
@Autowired protected BillDataService billData;
/**
* Updated Bills API
* -----------------
*
* Return a list of bill ids that have changed during a specified date/time range.
* Usages:
* (GET) /api/3/bills/updates/ (last 7 days)
* (GET) /api/3/bills/updates/{from} (from date to now)
* (GET) /api/3/bills/updates/{from}/{to}
*
* Where 'from' and 'to' are ISO date times.
*
* Request Params: detail (boolean) - Show update digests within each token.
* summary (boolean) - Return bill infos instead of just the bill id.
* type (string) - Update type (processed, published) Default: published
* filter (string) - Filter updates by a BillUpdateField value
* limit, offset (int) - Paginate
* order (string) - Order by update date
*
* Expected Output: List of UpdateTokenView<BaseBillId> or UpdateDigestView<BaseBillId> if detail = true.
*/
@RequestMapping(value = "/updates")
public BaseResponse getRecentUpdates(WebRequest request) {
return getUpdatesDuring(LocalDateTime.now().minusDays(7), LocalDateTime.now(), request);
}
@RequestMapping(value = "/updates/{from:.*\\.?.*}")
public BaseResponse getUpdatesFrom(@PathVariable String from, WebRequest request) {
LocalDateTime fromDateTime = parseISODateTime(from, "from");
LocalDateTime toDateTime = LocalDateTime.now();
return getUpdatesDuring(fromDateTime, toDateTime, request);
}
@RequestMapping(value = "/updates/{from:.*\\.?.*}/{to:.*\\.?.*}")
public BaseResponse getUpdatesDuring(@PathVariable String from, @PathVariable String to, WebRequest request) {
LocalDateTime fromDateTime = parseISODateTime(from, "from");
LocalDateTime toDateTime = parseISODateTime(to, "to");
return getUpdatesDuring(fromDateTime, toDateTime, request);
}
/**
* Bill Update Digests API
* ------------------------
*
* Return update digests for a bill during a given date/time range
* Usages:
* (GET) /api/3/bills/{sessionYear}/{printNo}/updates
* (GET) /api/3/bills/{sessionYear}/{printNo}/updates/{from}
* (GET) /api/3/bills/{sessionYear}/{printNo}/updates/{from}/{to}
*
* Where 'from' and 'to' are ISO date times.
*
* Request Params: filter (string) - Filter updates by a BillUpdateField
* type (string) - Update type (processed, published) Default: published
*
* Expected Output: List of UpdateDigestView<BaseBillId>
*/
@RequestMapping(value = "/{sessionYear:[\\d]{4}}/{printNo}/updates")
public BaseResponse getUpdatesForBill(@PathVariable int sessionYear, @PathVariable String printNo, WebRequest request) {
return getUpdatesForBillDuring(sessionYear, printNo, DateUtils.LONG_AGO.atStartOfDay(),
LocalDateTime.now(), request);
}
@RequestMapping(value = "/{sessionYear:[\\d]{4}}/{printNo}/updates/{from:.*\\.?.*}")
public BaseResponse getUpdatesForBill(@PathVariable int sessionYear, @PathVariable String printNo,
@PathVariable String from,
WebRequest request) {
LocalDateTime fromDateTime = parseISODateTime(from, "from");
return getUpdatesForBillDuring(sessionYear, printNo, fromDateTime, LocalDateTime.now(), request);
}
@RequestMapping(value = "/{sessionYear:[\\d]{4}}/{printNo}/updates/{from:.*\\.?.*}/{to:.*\\.?.*}")
public BaseResponse getUpdatesForBillDuring(@PathVariable int sessionYear, @PathVariable String printNo,
@PathVariable String from, @PathVariable String to, WebRequest request) {
LocalDateTime fromDateTime = parseISODateTime(from, "from");
LocalDateTime toDateTime = parseISODateTime(to, "to");
return getUpdatesForBillDuring(sessionYear, printNo, fromDateTime, toDateTime, request);
}
/** --- Internal --- */
private BaseResponse getUpdatesDuring(LocalDateTime from, LocalDateTime to, WebRequest request) {
// Fetch params
LimitOffset limOff = getLimitOffset(request, 50);
Range<LocalDateTime> updateRange = getOpenRange(from, to, "from", "to");
boolean detail = getBooleanParam(request, "detail", false);
boolean summary = getBooleanParam(request, "summary", false);
SortOrder sortOrder = getSortOrder(request, SortOrder.ASC);
String filter = request.getParameter("filter");
UpdateType updateType = getUpdateTypeFromParam(request);
BillUpdateField fieldFilter = getUpdateFieldFromParam(filter);
if (!detail) {
PaginatedList<UpdateToken<BaseBillId>> updateTokens =
billUpdatesDao.getUpdates(updateRange, updateType, fieldFilter, sortOrder, limOff);
return DateRangeListViewResponse.of(updateTokens.getResults().stream()
.map(token ->
(!summary) ? new UpdateTokenView(token, new BaseBillIdView(token.getId()))
: new UpdateTokenModelView(token, new BaseBillIdView(token.getId()),
new SimpleBillInfoView(billData.getBillInfo(token.getId())))
).collect(toList()), updateRange, updateTokens.getTotal(), limOff);
}
else {
PaginatedList<UpdateDigest<BaseBillId>> updateDigests =
billUpdatesDao.getDetailedUpdates(updateRange, updateType, fieldFilter, sortOrder, limOff);
return DateRangeListViewResponse.of(updateDigests.getResults().stream()
.map(digest ->
(!summary) ? new UpdateDigestView(digest, new BaseBillIdView(digest.getId()))
: new UpdateDigestModelView(digest, new BaseBillIdView(digest.getId()),
new SimpleBillInfoView(billData.getBillInfo(digest.getId())))
)
.collect(toList()), updateRange, updateDigests.getTotal(), limOff);
}
}
private BaseResponse getUpdatesForBillDuring(int sessionYear, String printNo, LocalDateTime from, LocalDateTime to,
WebRequest request) {
BillUpdateField filterField = getUpdateFieldFromParam(request.getParameter("filter"));
SortOrder sortOrder = getSortOrder(request, SortOrder.ASC);
LimitOffset limOff = getLimitOffset(request, 50);
Range<LocalDateTime> updateRange = getOpenRange(from, to, "from", "to");
UpdateType updateType = getUpdateTypeFromParam(request);
PaginatedList<UpdateDigest<BaseBillId>> digests = billUpdatesDao.getDetailedUpdatesForBill(
getBaseBillId(printNo, sessionYear, "printNo"), updateRange, updateType, filterField, sortOrder, limOff);
return DateRangeListViewResponse.of(digests.getResults().stream()
.map(digest -> new UpdateDigestView(digest, new BaseBillIdView(digest.getId())))
.collect(toList()), updateRange, digests.getTotal(), limOff);
}
private BillUpdateField getUpdateFieldFromParam(String filter) {
BillUpdateField fieldFilter = null;
if (filter != null && !filter.isEmpty()) {
try {
fieldFilter = BillUpdateField.valueOf(filter.toUpperCase());
}
catch (IllegalArgumentException ex) {
String validFields = Stream.of(BillUpdateField.values())
.map(b -> b.name().toLowerCase())
.collect(Collectors.joining(", "));
throw new InvalidRequestParamEx(filter, "filter", "string",
"Filter must be one of the following fields: " + validFields);
}
}
return fieldFilter;
}
}