Ejemplo n.º 1
0
    def it_gets_admin_users(self, db, db_session, sample_user):
        user = create_user(email='*****@*****.**', name='Sam Black', access_area='admin')

        admin_users = dao_get_admin_users()

        assert len(admin_users) == 1
        assert admin_users == [user]
        assert admin_users[0].is_admin()
Ejemplo n.º 2
0
    def it_creates_admin_user_if_email_is_admin_in_env_var(self, mocker, client, db_session):
        data = {'email': '*****@*****.**', 'name': 'Admin User', 'access_area': ',email,'}
        response = client.post(
            url_for('user.create_user'),
            data=json.dumps(data),
            headers=[('Content-Type', 'application/json'), create_authorization_header()]
        )
        assert response.status_code == 201

        json_resp = json.loads(response.get_data(as_text=True))
        assert json_resp['access_area'] == USER_ADMIN
        assert dao_get_admin_users()[0].email == data['email']
Ejemplo n.º 3
0
def send_message():
    data = request.get_json(force=True)
    current_app.logger.info('send_message: %r', data)

    validate(data, post_send_message_schema)

    emails_to = [user.email for user in dao_get_admin_users()]

    status_code = send_smtp_email(emails_to,
                                  'Web message: {}'.format(data['reason']),
                                  data['message'],
                                  from_email=data['email'],
                                  from_name=data['name'])

    return jsonify({
        'message':
        'Your message was sent'
        if status_code == 200 else 'An error occurred sending your message'
    })
Ejemplo n.º 4
0
def send_event_email_reminder():
    current_app.logger.info('Task send_event_email_reminder received: {}')

    for event in dao_get_future_events():
        if event.event_state != APPROVED:
            continue
        event.event_dates.sort(key=lambda k: k.event_datetime)

        time_to_send = (event.event_dates[0].event_datetime - timedelta(weeks=2)) < datetime.today()

        if time_to_send and not dao_get_email_by_event_id(event.id):
            subject = f"Event: {event.title} email reminder"
            message = f"Please <a href='{current_app.config['FRONTEND_ADMIN_URL']}/emails'>"\
                f"login</a> to create an email for {event.title}"
            message_html = get_email_html(BASIC, message=message)

            for user in dao_get_admin_users():
                status_code = send_smtp_email(user.email, subject, message_html)
                if status_code != 200:
                    current_app.logger.error(
                        f"Problem sending reminder email {subject} for {user.id}, status code: {status_code}")
Ejemplo n.º 5
0
def update_event(event_id):
    data = request.get_json(force=True)

    current_app.logger.info('Update event: {}'.format(data))

    validate(data, post_update_event_schema)

    try:
        event = dao_get_event_by_id(event_id)
    except NoResultFound:
        raise InvalidRequest('event not found: {}'.format(event_id), 400)

    errs = []
    event_dates = []
    event_data = {}

    if data.get('event_state') == REJECTED:
        new_rejects = [
            r for r in data.get('reject_reasons') if not r.get('id')
        ]
        if not new_rejects:
            raise InvalidRequest('rejected event requires new reject reason',
                                 400)
    elif data.get('event_state') == APPROVED:
        if data.get('reject_reasons'):
            rejects = [
                r for r in data.get('reject_reasons') if not r.get('resolved')
            ]
            if rejects:
                raise InvalidRequest(
                    'approved event should not have any reject reasons', 400)

    data_event_dates = data.get('event_dates')

    if data_event_dates:
        serialized_event_dates = event.serialize_event_dates()

        data_event_dates__dates = [e['event_date'] for e in data_event_dates]
        serialized_event_dates__dates = [
            e['event_datetime'] for e in serialized_event_dates
        ]

        diff_add = set(data_event_dates__dates).difference(
            serialized_event_dates__dates)
        intersect = set(data_event_dates__dates).intersection(
            serialized_event_dates__dates)

        dates_to_add = [
            e for e in data_event_dates if e['event_date'] in diff_add
        ]
        dates_to_update = [
            e for e in data_event_dates if e['event_date'] in intersect
        ]

        for _date in dates_to_add:
            speakers = []
            for s in _date.get('speakers', []):
                speaker = dao_get_speaker_by_id(s['speaker_id'])
                speakers.append(speaker)

            e = EventDate(event_id=event_id,
                          event_datetime=_date['event_date'],
                          end_time=_date.get('end_time'),
                          speakers=speakers)

            current_app.logger.info('Adding event date: {}'.format(
                _date['event_date']))

            dao_create_event_date(e)

            if _date['event_date'] not in [
                    _e.event_datetime for _e in event_dates
            ]:
                event_dates.append(e)

        for _date in sorted(dates_to_update, key=lambda k: k['event_date']):
            speakers = []
            for s in _date['speakers']:
                speaker = dao_get_speaker_by_id(s['speaker_id'])
                speakers.append(speaker)
            db_event_date = [
                e for e in event.event_dates if e.event_datetime.strftime(
                    '%Y-%m-%d %H:%M') == _date['event_date']
            ][0]
            db_event_date.speakers = speakers

            if _date['event_date'] not in [
                    _e.event_datetime for _e in event_dates
            ]:
                event_dates.append(db_event_date)

    if data.get('reject_reasons'):
        for reject_reason in data.get('reject_reasons'):
            if reject_reason.get('id'):
                reject_data = {
                    'reason': reject_reason['reason'],
                    'resolved': reject_reason.get('resolved') or False
                }

                dao_update_reject_reason(reject_reason.get('id'),
                                         **reject_data)
            else:
                rr = RejectReason(event_id=event_id,
                                  reason=reject_reason['reason'],
                                  resolved=reject_reason.get('resolved')
                                  or False,
                                  created_by=reject_reason.get('created_by'))
                dao_create_reject_reason(rr)

    event_data = {}
    for k in data.keys():
        if hasattr(Event, k) and k not in ['reject_reasons']:
            event_data[k] = data[k]

    if event_dates:
        event_data['event_dates'] = event_dates
    elif data_event_dates == []:
        raise InvalidRequest('{} needs an event date'.format(event_id), 400)

    if event_data.get('fee') and event_data.get('fee') > 0:
        update_data = {
            'fee': event_data.get('fee'),
            'conc_fee': event_data.get('conc_fee'),
            'multi_day_fee': event_data.get('multi_day_fee') or 0,
            'multi_day_conc_fee': event_data.get('multi_day_conc_fee') or 0,
            'event_type_id': event_data.get('event_type_id'),
        }
        db_data = {
            'fee': event.fee,
            'conc_fee': event.conc_fee,
            'multi_day_fee': event.multi_day_fee,
            'multi_day_conc_fee': event.multi_day_conc_fee,
            'event_type_id': str(event.event_type.id),
        }

        if update_data != db_data:
            if data.get('event_state') in [READY, APPROVED]:
                paypal_tasks.create_update_paypal_button_task.apply_async(
                    (str(event_id), ))

    res = dao_update_event(event_id, **event_data)

    if res:
        image_data = data.get('image_data')

        image_filename = data.get('image_filename')

        if current_app.config['STORAGE'].startswith('None'):
            current_app.logger.warn('Storage not setup')
        else:
            storage = Storage(current_app.config['STORAGE'])
            if image_data:
                event_year = str(
                    event.event_dates[0].event_datetime).split('-')[0]
                target_image_filename = '{}/{}'.format(event_year,
                                                       str(event_id))

                if data.get('event_state') != APPROVED:
                    target_image_filename += '-temp'

                storage.upload_blob_from_base64string(
                    image_filename, target_image_filename,
                    base64.b64decode(image_data))

                unix_time = time.time()
                image_filename = '{}?{}'.format(target_image_filename,
                                                unix_time)
            elif image_filename:
                image_filename_without_cache_buster = image_filename.split(
                    '?')[0]
                if not storage.blob_exists(
                        image_filename_without_cache_buster):
                    raise InvalidRequest(
                        '{} does not exist'.format(
                            image_filename_without_cache_buster), 400)

        if image_filename:
            if data.get('event_state') == APPROVED:
                if '-temp' in image_filename:
                    q_pos = image_filename.index('-temp?')
                    image_filename = image_filename[0:q_pos]
                    storage.rename_image(image_filename + '-temp',
                                         image_filename)
                else:
                    current_app.logger.warn(
                        f"No temp file to rename: {image_filename}")

            event.image_filename = image_filename
            dao_update_event(event.id, image_filename=image_filename)

        json_event = event.serialize()

        if data.get('event_state') == READY:
            emails_to = [admin.email for admin in dao_get_admin_users()]

            message = 'Please review this event for publishing <a href="{}">{}</a>'.format(
                '{}/events/{}'.format(current_app.config['FRONTEND_ADMIN_URL'],
                                      event_id), event.title)

            status_code = send_smtp_email(
                emails_to, '{} is ready for review'.format(event.title),
                message)
            if status_code != 200:
                errs.append(f"Problem sending admin email {status_code}")
        elif data.get('event_state') == REJECTED:
            emails_to = [user.email for user in dao_get_users()]

            message = '<div>Please correct this event <a href="{}">{}</a></div>'.format(
                '{}/events/{}'.format(current_app.config['FRONTEND_ADMIN_URL'],
                                      event_id), event.title)

            message += '<ol>'
            for reject_reason in [
                    rr for rr in json_event.get('reject_reasons')
                    if not rr.get('resolved')
            ]:
                message += '<li>{}</li>'.format(reject_reason['reason'])
            message += '</ol>'

            status_code = send_smtp_email(
                emails_to,
                '{} event needs to be corrected'.format(event.title), message)
            if status_code != 200:
                errs.append(f"Problem sending smtp emails: {status_code}")

        json_event['errors'] = errs
        return jsonify(json_event), 200

    raise InvalidRequest('{} did not update event'.format(event_id), 400)
Ejemplo n.º 6
0
def paypal_ipn(params=None, allow_emails=True, replace_order=False):
    message = ''
    bypass_verify = False
    if not params:
        params = request.form.to_dict(flat=False)
    else:
        bypass_verify = True
    current_app.logger.info('IPN params: %r', params)

    def get_data(params):
        data = {}
        for key in params.keys():
            if isinstance(params[key], list):
                data[key] = params[key][0]
            else:
                data[key] = params[key]
        return data

    if bypass_verify or (current_app.config['TEST_VERIFY']
                         and current_app.config['ENVIRONMENT'] != 'live'):
        v_response = 'VERIFIED'
        current_app.logger.info('Test paypal verify')
    else:
        VERIFY_URL = current_app.config['PAYPAL_VERIFY_URL']

        params['cmd'] = '_notify-validate'
        headers = {
            'content-type': 'application/x-www-form-urlencoded',
            'user-agent': 'Python-IPN-Verification-Script'
        }
        current_app.logger.info("params: %r", params)  # debug

        r = requests.post(VERIFY_URL,
                          params=params,
                          headers=headers,
                          verify=True)
        r.raise_for_status()
        v_response = r.text
        if v_response == 'VERIFIED':
            current_app.logger.info('VERIFIED: %s', params['txn_id'])

    # Check return message and take action as needed
    if v_response == 'VERIFIED':
        status = product_message = delivery_message = error_message = ''
        diff = 0.0
        data = get_data(params)

        order_data, tickets, events, products, delivery_zones, errors = parse_ipn(
            data, replace_order)
        if 'payment already made' in (','.join(errors)):
            current_app.logger.info("Transaction payment already made %r",
                                    data['txn_id'])
            return "Duplicate transaction %s" % {data['txn_id']}

        order_data['params'] = json.dumps(params)
        if 'is_donation' in order_data.keys(
        ) and order_data['is_donation'] == "Donation":
            order_data['is_donation'] = True
            order_data['linked_txn_id'] = None

        order = Order(**order_data)
        dao_create_record(order)

        if order_data['payment_status'] != 'Completed':
            err_msg = f"Payment not Completed: {order_data['payment_status']}"
            errors = [err_msg]
        else:
            message = f"<p>Thank you for your order ({order.id})</p>"

            if order_data['txn_type'] == 'web_accept' and order_data[
                    'linked_txn_id']:
                linked_order = dao_get_order_with_txn_id(
                    order_data['linked_txn_id'])

                diff = linked_order.delivery_balance - Decimal(
                    order_data['payment_total'])
                if diff == 0:
                    status = statuses.DELIVERY_EXTRA_PAID
                elif diff > 0:
                    status = statuses.DELIVERY_EXTRA
                    current_app.logger.warning(
                        'Delivery balance not paid in full')
                elif diff < 0:
                    status = statuses.DELIVERY_REFUND
                    current_app.logger.warning('Delivery balance overpaid')

                dao_update_record(Order,
                                  linked_order.id,
                                  delivery_status=status,
                                  payment_total=linked_order.payment_total +
                                  Decimal(order_data['payment_total']),
                                  delivery_balance=abs(diff))

                order_data['delivery_status'] = status
                order_data['delivery_zone'] = linked_order.delivery_zone
                order_data['delivery_balance'] = abs(diff)

                _payment_total = _get_nice_cost(order.payment_total)
                _diff = _get_nice_cost(diff)
                if status == statuses.DELIVERY_EXTRA_PAID:
                    message += f"<div>Outstanding payment for order ({order_data['linked_txn_id']}) of &pound;" \
                        f"{_payment_total} for delivery to {order_data['delivery_zone']} has been paid.</div>"
                elif status == statuses.DELIVERY_EXTRA:
                    message += f"<div>Outstanding payment for order ({order_data['linked_txn_id']}) of &pound;" \
                        f"{_payment_total} for delivery to {order_data['delivery_zone']} has been " \
                        f"partially paid.</div><div>Not enough delivery paid, &pound;{_diff} due.</div>"
                elif status == statuses.DELIVERY_REFUND:
                    message += f"<p>You have overpaid for delivery on order ({order_data['linked_txn_id']}) " \
                        f"by &pound;{_diff}, please send a message to website admin if there is " \
                        "no refund within 5 working days.</p>"
            else:
                if products:
                    for product in products:
                        if product['type'] == BOOK:
                            book_to_order = BookToOrder(
                                book_id=product['book_id'],
                                order_id=order.id,
                                quantity=product['quantity'])
                            dao_create_book_to_order(book_to_order)

                            product_message += (
                                f'<tr><td>{product["title"]}</td><td> x {product["quantity"]}</td>'
                                f'<td> = {_get_nice_cost(product["price"] * product["quantity"])}</td></tr>'
                            )
                    product_message = f'<table>{product_message}</table>'
                    address_delivery_zone = None

                    if 'address_country_code' not in order_data:
                        delivery_message = "No address supplied. "
                        status = "missing_address"
                    else:
                        address_delivery_zone = get_delivery_zone(
                            order_data['address_country_code'])
                        admin_message = ""

                        total_cost = 0
                        for dz in delivery_zones:
                            _d = [
                                _dz for _dz in DELIVERY_ZONES
                                if _dz['name'] == dz
                            ]
                            if _d:
                                d = _d[0]
                                total_cost += d['price']
                                _price = _get_nice_cost(d['price'])
                                admin_message += f"<tr><td>{d['name']}</td><td>{_price}</td></tr>"
                            else:
                                errors.append(f'Delivery zone: {dz} not found')
                                admin_message += f"<tr><td>{dz}</td><td>Not found</td></tr>"
                        _total_cost = _get_nice_cost(total_cost)
                        _price = _get_nice_cost(address_delivery_zone['price'])
                        diff = total_cost - address_delivery_zone['price']

                        if diff != 0:
                            admin_message = f"<p>Order delivery zones: <table>{admin_message}" \
                                f"</table>Total: &pound;{_total_cost}</p>"

                            admin_message += "<p>Expected delivery zone: " \
                                f"{address_delivery_zone['name']} - &pound;{_price}</p>"

                            order_data['delivery_balance'] = _get_nice_cost(
                                diff)
                            if diff > 0:
                                status = "refund"
                                delivery_message = f"Refund of &pound;{order_data['delivery_balance']} " \
                                    "due as wrong delivery fee paid"
                            elif diff < 0:
                                _diff = _get_nice_cost(diff)

                                status = "extra"
                                delivery_message = "{}, &pound;{} due. ".format(
                                    "No delivery fee paid" if total_cost == 0
                                    else "Not enough delivery paid",
                                    order_data['delivery_balance'])

                            admin_message = f"Transaction ID: {order.txn_id}<br>Order ID: {order.id}" \
                                f"<br>{delivery_message}.{admin_message}"

                            for user in dao_get_admin_users():
                                send_smtp_email(user.email,
                                                f'New Acropolis {status}',
                                                admin_message)
                        else:
                            status = statuses.DELIVERY_PAID

                    dao_update_record(
                        Order,
                        order.id,
                        delivery_status=status,
                        delivery_zone=address_delivery_zone['name']
                        if address_delivery_zone else None,
                        delivery_balance=str(abs(diff)))

                    if delivery_message:
                        order_data['delivery_status'] = status
                        if status == 'refund':
                            delivery_message = f"<p>{delivery_message}, please send a message " \
                                "to website admin if there is no refund within 5 working days.</p>"
                        else:
                            order_data['delivery_zone'] = address_delivery_zone['name']\
                                if address_delivery_zone else None
                    else:
                        product_message = (
                            f'{product_message}<br><div>Delivery to: {order_data["address_street"]},'
                            f'{order_data["address_city"]}, '
                            f'{order_data["address_postal_code"]}, {order_data["address_country"]}</div>'
                        )

                if tickets:
                    for i, _ticket in enumerate(tickets):
                        _ticket['order_id'] = order.id
                        ticket = Ticket(**_ticket)
                        dao_create_record(ticket)
                        tickets[i]['ticket_id'] = ticket.id
                        tickets[i]['title'] = ticket.event.title

                    storage = Storage(current_app.config['STORAGE'])
                    for ticket in tickets:
                        link_to_post = '{}{}'.format(
                            current_app.config['API_BASE_URL'],
                            url_for('.use_ticket',
                                    ticket_id=ticket['ticket_id']))
                        img = pyqrcode.create(link_to_post)
                        buffer = io.BytesIO()
                        img.png(buffer, scale=2)

                        img_b64 = base64.b64encode(buffer.getvalue())
                        target_image_filename = '{}/{}'.format(
                            'qr_codes', str(ticket['ticket_id']))
                        storage.upload_blob_from_base64string(
                            'qr.code', target_image_filename, img_b64)

                        message += '<div><span><img src="{}/{}"></span>'.format(
                            current_app.config['IMAGES_URL'],
                            target_image_filename)

                        event_date = dao_get_event_date_by_id(
                            ticket['eventdate_id'])
                        minutes = ':%M' if event_date.event_datetime.minute > 0 else ''
                        message += "<div>{} on {}</div></div>".format(
                            ticket['title'],
                            event_date.event_datetime.strftime(
                                '%-d %b at %-I{}%p'.format(minutes)))

                        if event_date.event.remote_access:
                            message += f"<br><div>Meeting id: {event_date.event.remote_access}"
                            if event_date.event.remote_pw:
                                message += f", Password: {event_date.event.remote_pw}"
                            message += "</div>"
                            message += f"<div><a href='https://zoom.us/j/{event_date.event.remote_access}'>"\
                                "Join zoom event</a></div>"

        if errors:
            error_message = ''
            for error in errors:
                error_message += f"<div>{error}</div>"
                order.errors.append(OrderError(error=error))
            error_message = f"<p>Errors in order: {error_message}</p>"

        if status in [
                statuses.DELIVERY_EXTRA, statuses.DELIVERY_MISSING_ADDRESS,
                statuses.DELIVERY_NOT_PAID
        ]:
            _delivery_zone_balance = ''

            if 'delivery_balance' in order_data:
                _delivery_balance = _get_nice_cost(
                    order_data['delivery_balance'])
                _delivery_zone_balance = f"/{order_data['delivery_zone']}/{_delivery_balance}"\
                    if order_data['delivery_zone'] else ''

            delivery_message = (
                f"<p>{delivery_message}Please "
                f"<a href='{current_app.config['FRONTEND_URL']}/order/{order_data['delivery_status']}/"
                f"{order_data['txn_id']}{_delivery_zone_balance}'>complete</a> "
                "your order.</p>")

        if allow_emails:
            send_email(
                order.email_address, 'New Acropolis Order',
                message + product_message + delivery_message + error_message)
    else:
        if v_response == 'INVALID':
            current_app.logger.info('INVALID %r', params['txn_id'])
        else:
            current_app.logger.info('UNKNOWN response %r', params['txn_id'])

        data = get_data(params)
        data[
            'txn_id'] = f"XX-{v_response}_{int(datetime.utcnow().timestamp())}-{data['txn_id']}"

        order_data, tickets, events, products, delivery_zones, errors = parse_ipn(
            data)

        order_data['params'] = json.dumps(params)

        order = Order(**order_data)
        order.errors.append(OrderError(error=f"{v_response} verification"))
        dao_create_record(order)

    return 'Paypal IPN'
Ejemplo n.º 7
0
    def it_does_not_get_admin_user_if_no_admin(self, db, db_session, sample_user):
        admin_user = dao_get_admin_users()

        assert not admin_user