Example #1
0
def trigger(email):

    phone_tokens_collection.delete_many({
        '$or': [{
            '$and': [
                {
                    'timestamp_issued': {
                        '$lt': timing.get_current_time(offset_minutes=-3)
                    }
                },
                {
                    'phone_number': ''
                },
            ]
        }, {
            'email': email
        }]
    })

    # Generate new token
    # By including the existing tokens a duplicate token error is impossible,
    # however the token generation might take longer and is non-deterministic
    existing_tokens = [
        document['token'] for document in list(
            phone_tokens_collection.find({}, {
                '_id': 0,
                'token': 1
            }))
    ]
    token = tokening.generate_random_key(length=5,
                                         numeric=True,
                                         existing_tokens=existing_tokens)

    # Create new token record
    new_record = {
        'email': email,
        'token': token,
        'timestamp_issued': timing.get_current_time(),
        'phone_number': '',
    }
    phone_tokens_collection.insert_one(new_record)

    # Trigger token-email
    return formatting.status('ok', token=token)
Example #2
0
def create_account(params_dict):

    email = params_dict["account"]["email"]
    password = params_dict["account"]["password"]
    zip_code = params_dict["account"]["zip_code"]
    country = params_dict["account"]["country"]

    current_timestamp = timing.get_current_time()
    new_helper = {
        'email': email,
        'account': {
            'register_date': timing.datetime_to_string(current_timestamp),
            'email_verified': False,
            'phone_number': '',
            'phone_number_verified': False,
            'hashed_password': tokening.hash_password(password),
            'zip_code': zip_code,
            'country': country,
        },
        'filter': {
            'call_type': {
                'only_local': False,
                'only_global': False,
            },
            'language': {
                'german': False,
                'english': False,
            },
        },
        'forward': {
            'online': False,
            'stay_online_after_call': False,
            'schedule_active': False,
            'schedule': [],
            'last_modified': current_timestamp
        }
    }

    try:
        # inserting helper document
        helper_id = helper_accounts_collection.insert_one(
            new_helper).inserted_id
    except DuplicateKeyError as e:
        # If two people sign up exactla at once
        # (verfication done but inserting fails for one)
        print(f'DuplicateKeyError: {e}')
        return formatting.status('email already taken')

    # Send verification email and add verification record
    email_verification.trigger(email)

    # login and return email/api_key dict
    return helper_authentication.helper_login_password(email, password)
Example #3
0
def modify_forward(params_dict):

    # params_dict["forward"] has already been validated

    params_dict["forward"].update({'last_modified': timing.get_current_time()})

    helper_accounts_collection.update_one(
        {'email': params_dict["email"]},
        {'$set': {
            'forward': params_dict["forward"]
        }})

    return formatting.status("ok")
Example #4
0
def verify(token, phone_number):
    record = phone_tokens_collection.find_one({
        'token': token,
        'timestamp_issued': {
            '$gt': timing.get_current_time(offset_minutes=-3)
        },
    })

    if record is None:
        return formatting.status('token invalid'), 400

    phone_tokens_collection.update_one(
        {'token': token}, {'$set': {
            'phone_number': phone_number
        }})

    return formatting.status('ok'), 200
Example #5
0
def fulfill_call(call_id, helper_id):
    # call_id and agent_id are assumed to be valid

    current_timestamp = timing.get_current_time()

    # Change call formatting.status
    call_update = {
        'status': 'fulfilled',
        'timestamp_fulfilled': current_timestamp
    }
    calls_collection.update_one({'_id': ObjectId(call_id)},
                                {'$set': call_update})

    new_behavior_log = {
        'helper_id': ObjectId(helper_id),
        'call_id': ObjectId(call_id),
        'timestamp': current_timestamp,
        'action': 'fulfilled',
    }
    helper_behavior_collection.insert_one(new_behavior_log)
Example #6
0
def add_call(caller_id, language, call_type='', zip_code=''):

    current_timestamp = timing.get_current_time()

    # local is boolean
    new_call = {
        'caller_id': ObjectId(caller_id),
        'call_type': [call_type],
        'zip_code': zip_code,
        'language': language,
        'feedback_granted': False,
        'confirmed': False,
        'helper_id': 0,
        'status': 'pending',
        'comment': '',
        'timestamp_received': current_timestamp,
        'timestamp_accepted': current_timestamp,
        'timestamp_fulfilled': current_timestamp,
    }
    call_id = calls_collection.insert_one(new_call).inserted_id

    return formatting.status('ok', call_id=call_id)
Example #7
0
def reject_call(call_id, helper_id):
    # Change call formatting.status
    call_update_dict_1 = {
        "$set": {
            'status': 'pending',
            'helper_id': 0,
            'comment': '',
        }
    }

    # accepted-match if local call was accepted from local queue (successful)
    # accepted-mismatch if local call was accepted from global/urgent queue (not successful)

    call_update_dict_2 = {
        "$pull": {
            "call_type": {
                "$in": ["forwarded", "accepted-match", "accepted-mismatch"]
            },
        }
    }

    operations = [
        UpdateOne({"_id": ObjectId(call_id)}, call_update_dict_1),
        UpdateOne({"_id": ObjectId(call_id)}, call_update_dict_2)
    ]
    calls_collection.bulk_write(operations)

    enqueue.enqueue(call_id)

    new_behavior_log = {
        'helper_id': ObjectId(helper_id),
        'call_id': ObjectId(call_id),
        'timestamp': timing.get_current_time(),
        'action': 'rejected',
    }
    helper_behavior_collection.insert_one(new_behavior_log)
Example #8
0
def dequeue(helper_id,
            zip_code=None,
            only_local=None,
            only_global=None,
            german=None,
            english=None):

    current_timestamp = timing.get_current_time()

    if only_local and only_global:
        return formatting.status(
            'invalid function call - only_local = only_global = True')

    # Step 1) Find the helpers zip_code

    if any([e is None] for e in
           [zip_code, only_local, only_global, english, german]) is None:
        helper = helper_accounts_collection.find_one(
            {'_id': ObjectId(helper_id)})
        if helper is None:
            return formatting.status('helper_id invalid')

        zip_code = helper['zip_code'] if (zip_code is None) else zip_code
        only_local = helper['filter_type_local'] if (
            only_local is None) else only_local
        only_global = helper['filter_type_global'] if (
            only_global is None) else only_global
        german = helper['filter_language_german'] if (
            german is None) else german
        english = helper['filter_language_english'] if (
            english is None) else english

    language_list = []
    language_list += ['german'] if german else []
    language_list += ['english'] if english else []

    zip_codes_list = fetching.get_adjacent_zip_codes(zip_code) if (
        zip_code != '') else []

    projection_dict = {}

    # Step 2) Find Call

    if only_local:
        filter_dict = {
            'call_type': {
                "$elemMatch": {
                    "$eq": 'local'
                }
            },
            'zip_code': {
                '$in': zip_codes_list
            },
            'language': {
                '$in': language_list
            }
        }
        call = call_queue.find_one_and_delete(
            filter_dict,
            projection_dict,
            sort=[('timestamp_received', 1)],
        )

    elif only_global:
        filter_dict = {
            'call_type': {
                "$elemMatch": {
                    "$eq": 'global'
                }
            },
            'language': {
                '$in': language_list
            }
        }
        call = call_queue.find_one_and_delete(
            filter_dict,
            projection_dict,
            sort=[('timestamp_received', 1)],
        )

    else:
        # 1. Urgent Queue
        filter_dict = {
            'timestamp_received': {
                '$lt':
                timing.get_current_time(offset_seconds=-global_timeout_seconds)
            }
        }
        call = call_queue.find_one_and_delete(
            filter_dict,
            projection_dict,
            sort=[('timestamp_received', 1)],
        )

        # 2. Local Queue
        if call is None:
            filter_dict = {
                'call_type': {
                    "$elemMatch": {
                        "$eq": 'local'
                    }
                },
                'zip_code': {
                    '$in': zip_codes_list
                },
                'language': {
                    '$in': language_list
                }
            }
            call = call_queue.find_one_and_delete(
                filter_dict,
                projection_dict,
                sort=[('timestamp_received', 1)],
            )

        # 3. Global Queue
        if call is None:
            # Or chain needed so that calls in other regions which
            # are not in the global queue yet get assigned
            filter_dict = {
                '$or': [{
                    'call_type': {
                        "$elemMatch": {
                            "$eq": 'local'
                        }
                    },
                    'timestamp_received': {
                        '$lt':
                        timing.get_current_time(
                            offset_seconds=-local_timeout_seconds)
                    }
                }, {
                    'call_type': {
                        "$elemMatch": {
                            "$eq": 'global'
                        }
                    }
                }]
            },
            call = call_queue.find_one_and_delete(
                filter_dict,
                projection_dict,
                sort=[('timestamp_received', 1)],
            )

    if call is None:
        return formatting.status('currently no call available')

    call_id = call['call_id']

    # Step 3) Update call (helper_id, formatting.status, timestamp_accepted)

    call_update_dict_1 = {
        "$set": {
            'status': 'accepted',
            'helper_id': ObjectId(helper_id),
            'timestamp_accepted': current_timestamp
        }
    }

    print(f"call = {call}")

    # accepted-match if local call was accepted from local queue (successful) or global call
    # accepted-mismatch if local call was matched with non-local helper

    if "local" in call["call_type"] and call["zip_code"] not in zip_codes_list:
        new_call_type = "accepted-mismatch"
    else:
        new_call_type = "accepted-match"

    call_update_dict_2 = {
        "$push": {
            "call_type": new_call_type,
        }
    }

    operations = [
        UpdateOne({'_id': ObjectId(call_id)}, call_update_dict_1),
        UpdateOne({'_id': ObjectId(call_id)}, call_update_dict_2)
    ]
    calls_collection.bulk_write(operations)

    # Step 4) Add helper behavior (helper_id, call_id, timestamp, action='accepted'
    new_behavior_log = {
        'helper_id': ObjectId(helper_id),
        'call_id': ObjectId(call_id),
        'timestamp': current_timestamp,
        'action': 'accepted',
    }
    helper_behavior_collection.insert_one(new_behavior_log)

    return formatting.status('ok')