Exemple #1
0
def event(encrypted_token, config, branch_id, date, time):
    return d(config=config,
             queryStringParameters=d(
                 token=encrypted_token,
                 branch_id=branch_id,
                 date=date,
                 time=time,
             ))
Exemple #2
0
def user_data(first_name, last_name, email, phone, date_of_birth):
    return d({
        'dob': date_of_birth,
        'email': email,
        'first_name': first_name,
        'last_name': last_name,
        'phone': phone,
    })
Exemple #3
0
def match_and_notify_user(config, user, input_data):
    user_filters = user.filters

    def _day_filter(d):
        day_name = calendar.day_name[arrow.get(d).weekday()].lower()
        return day_name in user_filters.days

    email_ctx = []
    for branch_name in user_filters.branches:
        if branch_name not in input_data['branches']:
            continue

        branch = input_data['branches'][branch_name]
        available_dates = list(filter(_day_filter, branch['available_dates']))
        if not available_dates:
            continue

        email_ctx.append(
            dict(branch,
                 available_dates={
                     date: branch['available_dates'][date]
                     for date in available_dates
                 }))

    if not email_ctx:
        log.info('No available slots after filter')
        return

    encrypted_token = token.encrypt(
        config.encrypt_key,
        d(user=user,
          service=d(publicId=input_data.publicId,
                    name=input_data.name,
                    qpId=input_data.qpId)))
    template = templates.generate_email(config, user, email_ctx,
                                        encrypted_token)
    subject = f'{len(email_ctx)} Locations Have An Available ICBC Timeslot!'
    if len(email_ctx) == 1:
        subject = f'{email_ctx[0]["name"]} Has An Available ICBC Timeslot!'

    log.info(
        ses.send_email(config.sender_email,
                       user.email,
                       subject,
                       template,
                       bcc_recipients=[config.sender_email]))
Exemple #4
0
def test_handler_existing_appointment(
        appointment_reserve_stub, appointment_check_multiple_fail_stub,
        appointment_confirm_stub, appointments_search_stub,
        appointment_cancel_stub, configuration_stub, appointment_confirm_json,
        event):
    output = book.handler(event, None)
    assert output == d(statusCode=200,
                       body=json.dumps(appointment_confirm_json))
Exemple #5
0
def config(user_data, email, encrypt_key, api_gateway):
    with patch('pycbc.config.s3') as s3_mock:
        s3_mock.get_object.return_value = {'Body': StringIO('{}')}
        yield pycbc_config.load({
            'config': {
                'users': [
                    d(user_data,
                      filters=d(services=['learners'],
                                days=['saturday', 'sunday'],
                                branches=['Burnaby']))
                ],
                'encrypt_key':
                encrypt_key,
                'api_gateway':
                api_gateway,
                'sender_email':
                email,
            }
        })
Exemple #6
0
def available_timeslots(input_data: Input):
    input_data = _set_defaults(input_data)
    pycbc = client.WebBookingClient()

    services = filter(
        lambda s:
        (not input_data.service_ids or s.qpId in input_data.service_ids),
        pycbc.services_search())

    results = []
    for service in services:
        log.info(f'Searching for branches for {service}')
        branches_for_service = pycbc.branches_service_search(service.publicId)

        output = d(service, branches={})
        branches_to_select = set(input_data.branches)
        before_date = arrow.now().shift(months=input_data.month_offset)
        for branch in filter(lambda b: b.name in branches_to_select,
                             branches_for_service):
            log.info(f'Searching for dates for branch {branch}')
            branch_dates = pycbc.branches_dates_search(service.publicId,
                                                       branch.id)
            available_dates = (data.date for data in branch_dates
                               if arrow.get(data.date) <= before_date)

            available_datetimes = d()
            for _, date in zip(range(input_data.max_times), available_dates):
                available_datetimes[date] = [
                    t.time for t in pycbc.branches_times_search(
                        service.publicId, branch.id, date)
                ]

            if not available_datetimes:
                continue

            output.branches[branch['name']] = d(
                branch, available_dates=available_datetimes)
        if output.branches:
            results.append(output)

    return results
Exemple #7
0
def load(event):
    event_override = event.get('config', d())
    env_prefix = event_override.get(
        'env_prefix', os.getenv('ENV_PREFIX', 'PYCBC_'))
    s3_bucket = event_override.get(
        's3_bucket', os.getenv(f'{env_prefix}S3_BUCKET', 'pycbc'))
    s3_filename = event_override.get(
        's3_filename',
        os.getenv(f'{env_prefix}S3_FILENAME', 'pycbc-config.yaml')
    )
    return json.loads(json.dumps(reduce(
        _merge,
        [
            deepcopy(_DEFAULTS),
            _from_s3(s3_bucket, s3_filename),
            _from_env(env_prefix),
            event_override,
            {'s3_bucket': s3_bucket, 's3_filename': s3_filename}
        ])
    ))
Exemple #8
0
def handler(event, context):
    config = load(event)
    logging.config.dictConfig(config.logging)

    try:
        query_params = event['queryStringParameters']
        query_token = query_params.pop('token')

        payload = token.decrypt(config.encrypt_key, query_token, ttl=3600)
    except InvalidToken:
        return {
            'statusCode': 400,
            'body': 'token expired',
        }
    except Exception as exc:
        log.exception(exc)
        return {
            'statusCode': 401,
            'body': 'UNAUTHORIZED',
        }
    log.info(f'Payload: {payload}')

    branch = d(branch_id=query_params['branch_id'],
               date=query_params['date'],
               time=query_params['time'])
    try:
        reservation = reserve(payload.service, branch, payload.user)
        return {
            'statusCode': 200,
            'body': json.dumps(reservation),
        }
    except Exception as exc:
        log.exception(exc)
        return {
            'statusCode': 500,
            'body': 'failed to book appointment',
        }
Exemple #9
0
def _set_defaults(input_data):
    return d(service_ids=input_data.get('service_ids', []),
             month_offset=input_data.get('month_offset', 1),
             max_times=input_data.get('max_times', 3),
             branches=input_data.get('branches', []),
             debug=input_data.get('debug', False))
Exemple #10
0
def _from_env(prefix):
    env_vars = (k for k in os.environ if k.startswith(prefix))
    return d({
        k[len(prefix):].lower(): os.environ[k] for k in env_vars
    })
Exemple #11
0
def _yaml_load(data):
    yaml.add_constructor(
        _mapping_tag,
        lambda loader, node: d(loader.construct_pairs(node)),
    )
    return yaml.load(data, Loader=yaml.FullLoader)
Exemple #12
0
s3 = boto3.client('s3')
_mapping_tag = yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG
_DEFAULTS = d({
    'users': [],
    'encrypt_key': Fernet.generate_key().decode('utf-8'),
    'api_gateway': None,
    'sender_email': None,
    'logging': d({
        'version': 1,
        'formatters': d({
            'default': d({
                'format': '%(asctime)-15s - %(levelname)-7s - %(message)s',
            }),
        }),
        'handlers': d({
            'console': d({
                'class': 'logging.StreamHandler',
                'formatter': 'default',
                'level': 'DEBUG',
                'stream': 'ext://sys.stderr',
            }),
        }),
        'loggers': d({
            'pycbc': d({
                'handlers': ['console'],
                'level': 'INFO',
            })
        })
    })
})

Exemple #13
0
def test_loads():
    assert json.loads('{"test": {"test": 1}}') == d(test=d(test=1))
Exemple #14
0
def test_handler(appointment_reserve_stub, appointment_check_multiple_stub,
                 appointment_confirm_stub, configuration_stub, event,
                 appointment_confirm_json):
    output = book.handler(event, None)
    assert output == d(statusCode=200,
                       body=json.dumps(appointment_confirm_json))