private String[] setCustomTime(String requestUid, String userInput, User user) {
   LocalDateTime parsedTime = eventUtil.parseDateTime(userInput);
   userLogger.recordUserInputtedDateTime(
       user.getUid(), userInput, "vote-custom", UserInterfaceType.USSD);
   eventRequestBroker.updateEventDateTime(user.getUid(), requestUid, parsedTime);
   final String dateTimePrompt = "at " + parsedTime.format(dateTimeFormat);
   return new String[] {eventRequestBroker.load(requestUid).getName(), dateTimePrompt};
 }
 private String[] adjustSubject(String requestUid, String userInput, User user) {
   String dateTime;
   eventRequestBroker.updateName(user.getUid(), requestUid, userInput);
   EventRequest vote = eventRequestBroker.load(requestUid);
   if (vote.getEventStartDateTime().isBefore(Instant.now().plus(7, ChronoUnit.MINUTES))) {
     // user is manipulating an "instant" vote so need to reset the counter, else may expire before
     // send
     eventRequestBroker.updateEventDateTime(
         user.getUid(), requestUid, LocalDateTime.now().plusMinutes(7L));
     dateTime = getMessage(thisSection, "confirm", "time.instant", user);
   } else {
     // need a quick way to do "at" in i18n
     dateTime = "at " + vote.getEventDateTimeAtSAST().format(dateTimeFormat);
   }
   return new String[] {userInput, dateTime};
 }
  private String[] setStandardTime(String requestUid, String time, User user) {

    final ZonedDateTime proposedDateTime;
    final String dateTimePrompt = getMessage(thisSection, "confirm", "time." + time, user);

    ZonedDateTime zonedNow = Instant.now().atZone(DateTimeUtil.getSAST());

    switch (time) {
      case "instant":
        proposedDateTime = zonedNow.plusMinutes(7L).truncatedTo(ChronoUnit.SECONDS);
        break;
      case "hour":
        proposedDateTime = zonedNow.plusHours(1L);
        break;
      case "day":
        proposedDateTime = zonedNow.plusDays(1L);
        break;
      case "week":
        proposedDateTime = zonedNow.plusWeeks(1L);
        break;
      default:
        // this should never be called, but need it else Java throws error -- defaulting to instant
        proposedDateTime = zonedNow.plusMinutes(7L);
        break;
    }

    eventRequestBroker.updateEventDateTime(
        user.getUid(), requestUid, proposedDateTime.toLocalDateTime());
    EventRequest voteRequest = eventRequestBroker.load(requestUid);
    return new String[] {voteRequest.getName(), dateTimePrompt};
  }
  /*
  Send out and confirm it has been sent
   */
  @RequestMapping(value = path + "send")
  @ResponseBody
  public Request voteSend(
      @RequestParam(value = phoneNumber) String inputNumber,
      @RequestParam(value = entityUidParam) String requestUid)
      throws URISyntaxException {

    User user = userManager.findByInputNumber(inputNumber, null);
    USSDMenu menu;

    try {
      String createdUid = eventRequestBroker.finish(user.getUid(), requestUid, true);
      Event vote = eventBroker.load(createdUid);
      log.info(
          "Vote details confirmed! Closing date and time: "
              + vote.getEventDateTimeAtSAST().format(dateTimeFormat));
      menu = new USSDMenu(getMessage(thisSection, "send", promptKey, user), optionsHomeExit(user));
      return menuBuilder(menu);
    } catch (EventStartTimeNotInFutureException e) {
      final String messageKey = USSDSection.VOTES.toKey() + "send.err.past.";
      menu = new USSDMenu(getMessage(messageKey + promptKey, user));
      menu.setFreeText(false);
      menu.addMenuOption(
          voteMenus + "send-reset" + entityUidUrlSuffix + requestUid,
          getMessage(messageKey + "yes", user));
      menu.addMenuOption(backVoteUrl("time", requestUid), getMessage(messageKey + "no", user));
      return menuBuilder(menu);
    }
  }
  @RequestMapping(value = path + "change-vote")
  @ResponseBody
  public Request changeVoteDo(
      @RequestParam(value = phoneNumber) String inputNumber,
      @RequestParam(value = entityUidParam) String eventUid,
      @RequestParam(value = "response") String response)
      throws URISyntaxException {

    final User user = userManager.findByInputNumber(inputNumber, null);
    final Event vote = eventBroker.load(eventUid);

    USSDMenu menu;
    if (vote.getEventStartDateTime().isBefore(Instant.now())) {
      menu = new USSDMenu(getMessage(thisSection, "change", "error", user));
    } else {
      // todo: replace this hack once responses are handled better
      EventRSVPResponse voteResponse =
          "abstain".equals(response)
              ? EventRSVPResponse.MAYBE
              : EventRSVPResponse.fromString(response);
      eventLogBroker.rsvpForEvent(vote.getUid(), user.getUid(), voteResponse);
      menu = new USSDMenu(getMessage(thisSection, "change", "done", response, user));
    }

    menu.addMenuOption(
        voteMenus + "details" + entityUidUrlSuffix + eventUid + "&back=open",
        getMessage(thisSection, "change", optionsKey + "back", user));
    menu.addMenuOptions(optionsHomeExit(user));

    return menuBuilder(menu);
  }
  /*
  Third menu asks the user when the vote will close. Options are "instant vote", i.e., 5 minutes, versus "one day",
  versus "custom".
   */
  @RequestMapping(value = path + "time")
  @ResponseBody
  public Request votingTime(
      @RequestParam(value = phoneNumber) String inputNumber,
      @RequestParam(value = entityUidParam) String requestUid,
      @RequestParam(value = userInputParam) String issue,
      @RequestParam(value = interruptedFlag, required = false) boolean interrupted,
      @RequestParam(value = revisingFlag, required = false) boolean revising)
      throws URISyntaxException {

    User user = userManager.findByInputNumber(inputNumber, saveVoteMenu("time", requestUid));

    if (!interrupted && !revising) eventRequestBroker.updateName(user.getUid(), requestUid, issue);

    USSDMenu menu = new USSDMenu(getMessage(thisSection, "time", promptKey, user));

    String nextUrl =
        voteMenus + "confirm" + entityUidUrlSuffix + requestUid + "&field=standard&time=";
    String optionKey = voteKey + ".time." + optionsKey;

    menu.addMenuOption(nextUrl + "instant", getMessage(optionKey + "instant", user));
    menu.addMenuOption(nextUrl + "hour", getMessage(optionKey + "hour", user));
    menu.addMenuOption(nextUrl + "day", getMessage(optionKey + "day", user));
    menu.addMenuOption(nextUrl + "week", getMessage(optionKey + "week", user));
    menu.addMenuOption(
        voteMenus + "time_custom" + entityUidUrlSuffix + requestUid,
        getMessage(optionKey + "custom", user));

    return menuBuilder(menu);
  }
  @RequestMapping(value = "/update/read/{phoneNumber}/{code}", method = RequestMethod.POST)
  public ResponseEntity<ResponseWrapper> updateReadStatus(
      @PathVariable("phoneNumber") String phoneNumber,
      @PathVariable("code") String code,
      @RequestParam("uid") String uid)
      throws Exception {

    User user = userManagementService.findByInputNumber(phoneNumber);
    Notification notification = notificationService.loadNotification(uid);

    log.info(
        "updating notification read status for user uid : {}, notification uid : {}",
        user.getUid(),
        uid);
    if (!notification.getTarget().equals(user)) {
      return RestUtil.accessDeniedResponse();
    }

    if (notification.isRead() && notification.isViewedOnAndroid()) {
      log.info("Trying to update notification when already read");
      return RestUtil.errorResponse(HttpStatus.ALREADY_REPORTED, RestMessage.ALREADY_UPDATED);
    } else {
      notificationService.updateNotificationsViewedAndRead(Collections.singleton(uid));
      return RestUtil.messageOkayResponse(RestMessage.NOTIFICATION_UPDATED);
    }
  }
 @RequestMapping(value = path + "send-reset")
 public Request voteSendResetTime(
     @RequestParam(value = phoneNumber) String inputNumber,
     @RequestParam(value = entityUidParam) String requestUid)
     throws URISyntaxException {
   User user = userManager.findByInputNumber(inputNumber, null);
   setStandardTime(requestUid, "instant", user);
   eventRequestBroker.finish(user.getUid(), requestUid, true);
   return menuBuilder(
       new USSDMenu(getMessage(thisSection, "send", promptKey, user), optionsHomeExit(user)));
 }
 @RequestMapping(value = path + "reminder-do")
 @ResponseBody
 public Request sendVoteReminderDo(
     @RequestParam(value = phoneNumber) String inputNumber,
     @RequestParam(value = entityUidParam) String eventUid)
     throws URISyntaxException {
   // use meeting reminder functions
   User user = userManager.findByInputNumber(inputNumber, null);
   eventBroker.sendManualReminder(user.getUid(), eventUid);
   return menuBuilder(
       new USSDMenu(
           getMessage(thisSection, "reminder-do", promptKey, user), optionsHomeExit(user)));
 }
 private USSDMenu setVoteGroupAndInitiateRequest(
     String menuPrompt,
     String interruptedRequestUid,
     String groupUid,
     String subsequentMenu,
     String paramsToPassForward,
     User user) {
   String requestUid;
   if (StringUtils.isEmpty(interruptedRequestUid)) {
     VoteRequest voteRequest = eventRequestBroker.createEmptyVoteRequest(user.getUid(), groupUid);
     requestUid = voteRequest.getUid();
   } else {
     requestUid = interruptedRequestUid;
   }
   cacheManager.putUssdMenuForUser(user.getPhoneNumber(), saveVoteMenu("issue", requestUid));
   return new USSDMenu(
       menuPrompt,
       voteMenus + subsequentMenu + entityUidUrlSuffix + requestUid + paramsToPassForward);
 }
  @RequestMapping(value = "/list/since/{phoneNumber}/{code}", method = RequestMethod.GET)
  public ResponseEntity<ResponseWrapper> getNotificationsSince(
      @PathVariable String phoneNumber,
      @PathVariable String code,
      @RequestParam(value = "createdSince", required = false) Long createdSince) {

    User user = userManagementService.findByInputNumber(phoneNumber);
    Instant intervalStart = createdSince == null ? null : Instant.ofEpochMilli(createdSince);
    List<Notification> notifications =
        notificationService.fetchAndroidNotificationsSince(user.getUid(), intervalStart);
    List<NotificationDTO> notificationDTOs =
        notifications
            .stream()
            .filter(NotificationDTO::isNotificationOfTypeForDTO)
            .map(NotificationDTO::convertToDto)
            .collect(Collectors.toList());
    NotificationWrapper wrapper = new NotificationWrapper(notificationDTOs);
    return RestUtil.okayResponseWithData(RestMessage.NOTIFICATIONS, wrapper);
  }