예제 #1
0
def get_reminder(reminder_id):
    """
    Retrieves a specific reminder by id
    """
    try:
        rm = Reminder.query.filter_by(id=reminder_id).one()
    except NoResultFound:
        log.info({"message": "no reminder with id {}".format(reminder_id)})
        return Response(response=json.dumps({"message":
                                             "unknown reminder id"}),
                        status=404,
                        content_type='application/json')
    else:
        res = {
            'reminder_id': rm.id,
            'contact_number': rm.contact_num,
            'appt_user_dt': str(rm.appt_user_dt),
            'appt_sys_dt': str(rm.appt_sys_dt),
            'notify_sys_dt': str(rm.notify_sys_dt),
            'will_attend': rm.will_attend,
            'location': rm.location,
            'participant': rm.participant,
            'reminder_sent': rm.reminder_sent,
            'confirm_sent': rm.confirm_sent
        }
        return Response(response=json.dumps(res),
                        status=200,
                        content_type='application/json')
예제 #2
0
def send_reminder(self, reminder_id):
    """
    Retrieves the reminder from the database, passes the message content to the
    Flowroute SMS client for dispatch. If it fails, the task is re-queued.
    """
    try:
        appt = Reminder.query.filter_by(id=reminder_id).one()
    except NoResultFound:
        log.error(
            {"message": "Received unknown appointment with id {}.".format(
             reminder_id), "reminder_id": reminder_id})
        return
    msg_body = create_message_body(appt)
    message = Message(
        to=appt.contact_num,
        from_=FLOWROUTE_NUMBER,
        content=msg_body)
    try:
        sms_controller.create_message(message)
    except Exception as e:
        strerr = vars(e).get('response_body', None)
        log.critical({"message": "Raised an exception sending SMS",
                      "exc": e, "strerr": strerr, "reminder_id": reminder_id})
        raise self.retry(exc=e)
    else:
        log.info(
            {"message": "Reminder sent to {} for reminder_id {}".format(
             appt.contact_num, reminder_id),
             "reminder_id": reminder_id})
        appt.reminder_sent = True
        db_session.add(appt)
        db_session.commit()
예제 #3
0
def inbound_handler():
    """
    The inbound request handler for consuming HTTP wrapped SMS content from
    Flowroute's messaging service.

    Validates proper message content, and takes time to clear the expired
    reminders before continuing. Retrieves the reminder based on the
    sender, and marks the 'will_attend' attribute according to whether
    there is 'yes' or 'no' (case insensitive) anywhere in the message.
    Responds to the user with a confirmation message and returns 200.
    """
    req = request.json
    # Take the time to clear out any past reminders
    try:
        virtual_tn = req['to']
        assert len(virtual_tn) <= 18
        sms_from = req['from']
        assert len(sms_from) <= 18
        req['body']
    except (TypeError, KeyError, AssertionError) as e:
        msg = ("Malformed inbound message: {}".format(req))
        log.error({"message": msg, "status": "failed", "exc": str(e)})
        return Response('There was an issue parsing your request.', status=400)
    else:
        Reminder.clean_expired()
        try:
            appt = Reminder.query.filter_by(
                contact_num=sms_from).one()
        except NoResultFound:
            msg = "No existing un-responded reminder for contact {}.".format(
                sms_from)
            log.info({"message": msg})
            return Response(status=200)
        else:
            message = req['body'].upper()
            if 'YES' in message:
                appt.will_attend = True
                confirm = True
            elif 'NO' in message:
                appt.will_attend = False
                confirm = False
            else:
                confirm = None
            db_session.add(appt)
            try:
                send_reply.apply_async((appt.id,), {'confirm': confirm})
            except ConnectionError as e:
                log.critical({"message": "unable to connect to redis",
                              "exc": type(e)})
                db_session.rollback()
                return Response(status=500)
            else:
                db_session.commit()
                log.info({"message":
                          ("successfully recorded response from {}, scheduled "
                           "SMS confirmation for appointment {}").format(
                               sms_from, appt.id),
                          "reminder_id": appt.id})
                return Response(status=200)
예제 #4
0
def send_reply(self, reminder_id, confirm=True):
    """
    Retrieves the reminder from the database, and sends either a confirmation
    or an unparsable message response to the Flowroute SMS client. If that fails,
    the task is re-queued.
    """
    try:
        appt = Reminder.query.filter_by(id=reminder_id).one()
    except NoResultFound:
        log.error({
            "message":
            "Received unknown appointment with id {}.".format(reminder_id),
            "reminder_id":
            reminder_id
        })
        return
    if confirm is None:
        msg_content = UNPARSABLE_RESPONSE
    elif confirm is True:
        msg_content = CONFIRMATION_RESPONSE
    elif confirm is False:
        msg_content = CANCEL_RESPONSE
    message = Message(to=appt.contact_num,
                      from_=FLOWROUTE_NUMBER,
                      content=msg_content)
    try:
        sms_controller.create_message(message)
    except Exception as e:
        strerr = vars(e).get('response_body', None)
        log.critical({
            "message": "Raised an exception sending SMS",
            "exc": e,
            "strerr": strerr,
            "reminder_id": reminder_id
        })
        raise self.retry(exc=e)
    else:
        appt.confirm_sent = True
        db_session.add(appt)
        db_session.commit()
        log.info({
            "message":
            "Confirmation sent to {} for reminder_id {}".format(
                appt.contact_num, reminder_id),
            "reminder_id":
            reminder_id
        })
예제 #5
0
def remove_reminder(reminder_id):
    """
    The deletion route for Reminders - takes a reminder_id as an argument
    """
    try:
        reminder = Reminder.query.filter_by(id=reminder_id).one()
    except NoResultFound:
        log.info({"message": "no reminder with id {}".format(reminder_id)})
        return Response(
            response=json.dumps({"message": "unknown reminder id"}),
            status=404, content_type='application/json')
    else:
        db_session.delete(reminder)
        db_session.commit()
        msg = "Successfully deleted reminder with id {}.".format(reminder_id)
        log.info({"message": msg})
        return Response(
            response=json.dumps({"message": msg, "reminder_id": reminder_id}),
            status=200, content_type='application/json')
예제 #6
0
def send_reminder(self, reminder_id):
    """
    Retrieves the reminder from the database, passes the message content to the
    Flowroute SMS client for dispatch. If it fails, the task is re-queued.
    """
    try:
        appt = Reminder.query.filter_by(id=reminder_id).one()
    except NoResultFound:
        log.error({
            "message":
            "Received unknown appointment with id {}.".format(reminder_id),
            "reminder_id":
            reminder_id
        })
        return
    msg_body = create_message_body(appt)
    message = Message(to=appt.contact_num,
                      from_=FLOWROUTE_NUMBER,
                      content=msg_body)
    try:
        sms_controller.create_message(message)
    except Exception as e:
        strerr = vars(e).get('response_body', None)
        log.critical({
            "message": "Raised an exception sending SMS",
            "exc": e,
            "strerr": strerr,
            "reminder_id": reminder_id
        })
        raise self.retry(exc=e)
    else:
        log.info({
            "message":
            "Reminder sent to {} for reminder_id {}".format(
                appt.contact_num, reminder_id),
            "reminder_id":
            reminder_id
        })
        appt.reminder_sent = True
        db_session.add(appt)
        db_session.commit()
예제 #7
0
def get_reminder(reminder_id):
    """
    Retrieves a specific reminder by id
    """
    try:
        rm = Reminder.query.filter_by(id=reminder_id).one()
    except NoResultFound:
        log.info({"message": "no reminder with id {}".format(reminder_id)})
        return Response(
            response=json.dumps({"message": "unknown reminder id"}),
            status=404, content_type='application/json')
    else:
        res = {'reminder_id': rm.id, 'contact_number': rm.contact_num,
               'appt_user_dt': str(rm.appt_user_dt), 'appt_sys_dt':
               str(rm.appt_sys_dt), 'notify_sys_dt': str(rm.notify_sys_dt),
               'will_attend': rm.will_attend, 'location': rm.location,
               'participant': rm.participant, 'reminder_sent':
               rm.reminder_sent, 'confirm_sent': rm.confirm_sent}
        return Response(
            response=json.dumps(res),
            status=200, content_type='application/json')
예제 #8
0
def send_reply(self, reminder_id, confirm=True):
    """
    Retrieves the reminder from the database, and sends either a confirmation
    or an unparsable message response to the Flowroute SMS client. If that fails,
    the task is re-queued.
    """
    try:
        appt = Reminder.query.filter_by(id=reminder_id).one()
    except NoResultFound:
        log.error(
            {"message": "Received unknown appointment with id {}.".format(
             reminder_id), "reminder_id": reminder_id})
        return
    if confirm is None:
        msg_content = UNPARSABLE_RESPONSE
    elif confirm is True:
        msg_content = CONFIRMATION_RESPONSE
    elif confirm is False:
        msg_content = CANCEL_RESPONSE
    message = Message(
        to=appt.contact_num,
        from_=FLOWROUTE_NUMBER,
        content=msg_content)
    try:
        sms_controller.create_message(message)
    except Exception as e:
        strerr = vars(e).get('response_body', None)
        log.critical({"message": "Raised an exception sending SMS",
                      "exc": e, "strerr": strerr, "reminder_id": reminder_id})
        raise self.retry(exc=e)
    else:
        appt.confirm_sent = True
        db_session.add(appt)
        db_session.commit()
        log.info(
            {"message": "Confirmation sent to {} for reminder_id {}".format(
             appt.contact_num, reminder_id),
             "reminder_id": reminder_id})
예제 #9
0
def remove_reminder(reminder_id):
    """
    The deletion route for Reminders - takes a reminder_id as an argument
    """
    try:
        reminder = Reminder.query.filter_by(id=reminder_id).one()
    except NoResultFound:
        log.info({"message": "no reminder with id {}".format(reminder_id)})
        return Response(response=json.dumps({"message":
                                             "unknown reminder id"}),
                        status=404,
                        content_type='application/json')
    else:
        db_session.delete(reminder)
        db_session.commit()
        msg = "Successfully deleted reminder with id {}.".format(reminder_id)
        log.info({"message": msg})
        return Response(response=json.dumps({
            "message": msg,
            "reminder_id": reminder_id
        }),
                        status=200,
                        content_type='application/json')
예제 #10
0
def configure_app(app=app):
    if DEBUG_MODE:
        try:
            log.info({"message": "attempting to destroy db"})
            destroy_db()
        except OperationalError:
            log.info({"message": "nothing to destroy, table doesn't exist"})
        app.debug = DEBUG_MODE
        app.config.update(SQLALCHEMY_DATABASE_URI=TEST_DB)
    else:
        app.config.update(SQLALCHEMY_DATABASE_URI=DB)
    try:
        init_db()
    except OperationalError:
        log.info({"message": "database already exists... moving on."})
    return app
예제 #11
0
def add_reminder():
    """
    Adds a new reminder, and schedules an sms message handling celery task.
    If the redis is unavailable and unable to schedule a task the reminder
    is removed and an internal error is raised, otherwise the new reminder
    id is returned.

    Required:
    contact_number : string (The users phone number)
    appointment_time : string (The datetime of the appointment) "YYYY-MM-DDTHH:mmZ"
    notify_window : integer (The hours before the appointment to send reminder)

    Optional:
    location : string (Where the appointment is)
    participant : string (Who the appoinment is with)
    """
    body = request.json
    try:
        contact_num = str(body['contact_number'])
        appt_dt = arrow.get(body['appointment_time'], "YYYY-MM-DDTHH:mmZ")
        notify_win = int(body['notify_window'])
        location = body.get('location', None)
        participant = body.get('participant', None)
    except (KeyError, ParserError):
        raise InvalidAPIUsage(
            ("Required arguments: 'contact_number' (str), "
             "'appointment_time' (str) eg. '2016-01-01T13:00+02:00', "
             "'notify_window' (int)"))
    else:
        Reminder.clean_expired()
        reminder = Reminder(contact_num, appt_dt, notify_win,
                            location, participant)
        db_session.add(reminder)
    try:
        db_session.commit()
    except IntegrityError:
        msg = ("Unable to create a new reminder. Duplicate "
               "contact_number {}.".format(contact_num))
        log.error({"message": msg})
        return Response(json.dumps({"message": msg}), status=400,
                        content_type="application/json")
    else:
        try:
            send_reminder.apply_async(args=[reminder.id],
                                      eta=reminder.notify_sys_dt)
        except ConnectionError as e:
            log.critical({"message": "unable to connect to redis",
                          "exc": type(e)})
            db_session.delete(reminder)
            db_session.commit()
            return Response(json.dumps(
                {"message": ("Unable to create a new reminder."
                             " Redis is unreachable."),
                 "exc": "RedisConnectionError"}),
                status=500, content_type="application/json")

        msg = "Successfully created a reminder with id {}.".format(reminder.id)
        log.info({"message": msg})
        content = json.dumps({"message": msg, "reminder_id": reminder.id})
        return Response(content, status=200,
                        content_type="application/json")
예제 #12
0
def add_reminder():
    """
    Adds a new reminder, and schedules an sms message handling celery task.
    If the redis is unavailable and unable to schedule a task the reminder
    is removed and an internal error is raised, otherwise the new reminder
    id is returned.

    Required:
    contact_number : string (The users phone number)
    appointment_time : string (The datetime of the appointment) "YYYY-MM-DDTHH:mmZ"
    notify_window : integer (The hours before the appointment to send reminder)

    Optional:
    location : string (Where the appointment is)
    participant : string (Who the appoinment is with)
    """
    body = request.json
    try:
        contact_num = str(body['contact_number'])
        appt_dt = arrow.get(body['appointment_time'], "YYYY-MM-DDTHH:mmZ")
        notify_win = int(body['notify_window'])
        location = body.get('location', None)
        participant = body.get('participant', None)
    except (KeyError, ParserError):
        raise InvalidAPIUsage(
            ("Required arguments: 'contact_number' (str), "
             "'appointment_time' (str) eg. '2016-01-01T13:00+02:00', "
             "'notify_window' (int)"))
    else:
        Reminder.clean_expired()
        reminder = Reminder(contact_num, appt_dt, notify_win, location,
                            participant)
        db_session.add(reminder)
    try:
        db_session.commit()
    except IntegrityError:
        msg = ("Unable to create a new reminder. Duplicate "
               "contact_number {}.".format(contact_num))
        log.error({"message": msg})
        return Response(json.dumps({"message": msg}),
                        status=400,
                        content_type="application/json")
    else:
        try:
            send_reminder.apply_async(args=[reminder.id],
                                      eta=reminder.notify_sys_dt)
        except ConnectionError as e:
            log.critical({
                "message": "unable to connect to redis",
                "exc": type(e)
            })
            db_session.delete(reminder)
            db_session.commit()
            return Response(json.dumps({
                "message": ("Unable to create a new reminder."
                            " Redis is unreachable."),
                "exc":
                "RedisConnectionError"
            }),
                            status=500,
                            content_type="application/json")

        msg = "Successfully created a reminder with id {}.".format(reminder.id)
        log.info({"message": msg})
        content = json.dumps({"message": msg, "reminder_id": reminder.id})
        return Response(content, status=200, content_type="application/json")
예제 #13
0
def inbound_handler():
    """
    The inbound request handler for consuming HTTP wrapped SMS content from
    Flowroute's messaging service.

    Validates proper message content, and takes time to clear the expired
    reminders before continuing. Retrieves the reminder based on the
    sender, and marks the 'will_attend' attribute according to whether
    there is 'yes' or 'no' (case insensitive) anywhere in the message.
    Responds to the user with a confirmation message and returns 200.
    """
    req = request.json
    # Take the time to clear out any past reminders
    try:
        virtual_tn = req['to']
        assert len(virtual_tn) <= 18
        sms_from = req['from']
        assert len(sms_from) <= 18
        req['body']
    except (TypeError, KeyError, AssertionError) as e:
        msg = ("Malformed inbound message: {}".format(req))
        log.error({"message": msg, "status": "failed", "exc": str(e)})
        return Response('There was an issue parsing your request.', status=400)
    else:
        Reminder.clean_expired()
        try:
            appt = Reminder.query.filter_by(contact_num=sms_from).one()
        except NoResultFound:
            msg = "No existing un-responded reminder for contact {}.".format(
                sms_from)
            log.info({"message": msg})
            return Response(status=200)
        else:
            message = req['body'].upper()
            if 'YES' in message:
                appt.will_attend = True
                confirm = True
            elif 'NO' in message:
                appt.will_attend = False
                confirm = False
            else:
                confirm = None
            db_session.add(appt)
            try:
                send_reply.apply_async((appt.id, ), {'confirm': confirm})
            except ConnectionError as e:
                log.critical({
                    "message": "unable to connect to redis",
                    "exc": type(e)
                })
                db_session.rollback()
                return Response(status=500)
            else:
                db_session.commit()
                log.info({
                    "message":
                    ("successfully recorded response from {}, scheduled "
                     "SMS confirmation for appointment {}").format(
                         sms_from, appt.id),
                    "reminder_id":
                    appt.id
                })
                return Response(status=200)