Пример #1
0
def add_and_publish_event(event, routing_key):
    """
    Adds the event to the event store and publish it on the event bus.
    :param event: the event.
    :param routing_key: the routing key for topic-matching.
    """
    session = EventStoreSession()

    try:
        # both of these are wrapped inside a unique try catch because this must be an atomic operation.
        CalendarEvent.append_event(event)
        event_published = publish_event(get_sending_channel(), event,
                                        routing_key)

        if not event_published:  # message cannot be routed
            logger.error(f'Could not publish event {event.toJSON()}')
            session.rollback()
            raise SQLAlchemyError

        else:
            session.commit()
            logger.info(f'Added and published event {event.toJSON()}')

    except SQLAlchemyError as e:
        session.rollback()
        logger.error(f'Could not append to event store event {event.toJSON()}')
        raise e

    finally:
        session.close()
Пример #2
0
    def test_gp_modify(self):
        event = GlobalPreferencesCreatedEvent(
            str(uuid.uuid4()), 1,
            dict(vehicles=[
                'bus', 'subway', 'train', 'tram', 'car', 'walking', 'bike',
                'taxi', 'enjoy', 'mobike'
            ]))
        publish_event(self.app.sending_channel, event, PREFERENCES_CREATED)
        self.simulate_eventual_consistency()

        event = CalendarCreatedEvent(
            user_id=1,
            id=1,
            name='Home',
            description='',
            base=[35.1324, 36.1234],
            color=[243, 250, 152],
            active=True,
            carbon=True,
            preferences=[dict(name='bus', time=None, mileage=10)])
        publish_event(self.app.sending_channel, event, CALENDAR_CREATED)
        self.simulate_eventual_consistency()

        event = GlobalPreferencesModifiedEvent(1, dict(vehicles=['mobike']))
        publish_event(self.app.sending_channel, event, PREFERENCES_MODIFIED)
        self.simulate_eventual_consistency()

        with session_scope(self.DBSession) as session:
            self.assertEqual(
                session.query(Calendar).filter(
                    Calendar.user_id == 1,
                    Calendar.id == 1).first().preferences, [])
Пример #3
0
def handle_delete(user_id, id):
    """
    Delete calendar endpoint. It deletes a calendar.
    """
    aggregate_status = build_calendar_aggregate()

    if aggregate_status == -1:
        logger.error(
            f'Could not build calendar aggregate for request: {request}')
        return ko(jsonify(dict(error='cannot build calendar aggregate')))

    if not (str(user_id) in aggregate_status.keys()):
        return not_found(jsonify(dict(error='invalid user')))

    if not (str(id) in aggregate_status[str(user_id)]['calendars'].keys()):
        return not_found(jsonify(dict(error='invalid calendar')))

    event = CalendarDeletedEvent(user_id=user_id, id=id)

    session = EventStoreSession()

    try:

        # both of these are wrapped inside a unique try catch because this must be an atomic operation.
        CalendarEvent.append_event(event)
        event_published = publish_event(get_sending_channel(), event,
                                        CALENDAR_DELETED)

        if not event_published:
            logger.error(
                f'Could not publish event on event bus for request: {request}')
            session.rollback()
            return ko(
                jsonify(dict(error='could not publish event on event bus')))

        else:
            session.commit()
            logger.info(f'Deleted calendar for user {user_id} with id: {id}')
            return ok(jsonify(dict(user_id=user_id, id=id)))

    except SQLAlchemyError:
        session.rollback()
        logger.error(f'Could not append to event store for request: {request}')
        return ko(jsonify(dict(error='could not append to event store')))

    finally:
        session.close()
Пример #4
0
def handle_preferences(user_id):
    """
    Global preferences modify endpoint. It modifies user global preferences.
    """
    aggregate_status = build_calendar_aggregate()

    if aggregate_status == -1:
        logger.error(f'Could not build user aggregate for request: {request}')
        return ko(jsonify(dict(error='cannot build user aggregate')))

    if not (str(user_id) in aggregate_status.keys()):
        return not_found(jsonify(dict(error='invalid user')))

    data = request.get_json()

    if validate_global_preferences(data):  # checks the request payload is correctly formed

        event = GlobalPreferencesModifiedEvent(user_id=user_id, preferences=data)
        session = EventStoreSession()

        try:

            # both of these are wrapped inside a unique try catch because this must be an atomic operation.
            CalendarEvent.append_event(event)
            event_published = publish_event(get_sending_channel(), event, PREFERENCES_MODIFIED)

            if not event_published:
                logger.error(f'Could not publish event on event bus for request: {request}')
                session.rollback()
                return ko(jsonify(dict(error='could not publish event on event bus')))

            else:
                session.commit()
                logger.info(f'Modified global preferences of user with id: {user_id}')
                return ok(jsonify(dict(id=user_id)))

        except SQLAlchemyError:
            session.rollback()
            logger.error(f'Could not append to event store for request: {request}')
            return ko(jsonify(dict(error='could not append to event store')))

        finally:
            session.close()

    return bad_request(jsonify(dict(error='invalid data')))
Пример #5
0
def handle_delete(user_id):
    """
    Delete user endpoint. It deletes a user resource.
    """
    aggregate_status = build_user_aggregate()

    if aggregate_status == -1:
        logger.error(f'Could not build user aggregate for request: {request}')
        return ko(jsonify(dict(error='cannot build user aggregate')))

    if not (str(user_id) in aggregate_status.keys()):
        return not_found(jsonify(dict(error='invalid user')))

    event = UserDeletedEvent(id=user_id)

    session = EventStoreSession()
    try:
        UserEvent.append_event(event)
        event_published = publish_event(get_sending_channel(), event,
                                        USER_DELETED)

        if not event_published:
            logger.error(
                f'Could not publish event on event bus for request: {request}')
            session.rollback()
            return ko(
                jsonify(dict(error='could not publish event on event bus')))

        else:
            session.commit()
            logger.info(f'Deleted user with id: {user_id}')
            return ok(jsonify(dict(id=user_id)))

    except SQLAlchemyError:
        session.rollback()
        logger.error(f'Could not append to event store for request: {request}')
        return ko(jsonify(dict(error='could not append to event store')))

    finally:
        session.close()
Пример #6
0
    def test_messages(self):
        event = UserCreatedEvent(1, '*****@*****.**',
                                 hash_pass('test_password'), 'test_first_name',
                                 'test_last_name')
        publish_event(self.app.sending_channel, event, 'user.created')
        event = UserModifiedEvent(1, '*****@*****.**', hash_pass('test_password'),
                                  'test_first_name', 'test_last_name')
        publish_event(self.app.sending_channel, event, USER_MODIFIED)
        self.simulate_eventual_consistency()

        with session_scope(self.DBSession) as session:
            self.assertEqual(
                session.query(User).filter(User.id == 1).first().email,
                '*****@*****.**')

        event = UserDeletedEvent(1)
        publish_event(self.app.sending_channel, event, USER_DELETED)
        self.simulate_eventual_consistency()

        with session_scope(self.DBSession) as session:
            self.assertIsNone(session.query(User).filter(User.id == 1).first())
Пример #7
0
def handle_modify(user_id, id):
    """
    Calendar modify endpoint. It modifies a calendar resource.
    """
    data = request.get_json()
    aggregate_status = build_calendar_aggregate()

    if aggregate_status == -1:
        logger.error(
            f'Could not build calendar aggregate for request: {request}')
        return ko(jsonify(dict(error='cannot build calendar aggregate')))

    if not (str(user_id) in aggregate_status.keys()):
        return not_found(jsonify(dict(error='invalid user')))

    if not (str(id) in aggregate_status[str(user_id)]['calendars'].keys()):
        return not_found(jsonify(dict(error='invalid calendar')))

    user_global_preferences = aggregate_status[str(user_id)]['preferences']

    # validate the request payload and check if the preferences are applicable given the global one
    # in the current aggregate status
    if validate_calendar_data(data) and validate_calendar_preferences(
            data['preferences'], user_global_preferences):

        user_calendars = aggregate_status[str(user_id)]['calendars']

        user_calendars.pop(str(id))  # pop for preserving idempotency

        if data['name'] in ((value['name']
                             for value in user_calendars.values())):
            return bad_request(jsonify(dict(error='invalid data')))

        data['user_id'] = user_id
        data['id'] = id

        event = CalendarModifiedEvent(**data)

        session = EventStoreSession()

        try:

            # both of these are wrapped inside a unique try catch because this must be an atomic operation.
            CalendarEvent.append_event(event)
            event_published = publish_event(get_sending_channel(), event,
                                            CALENDAR_MODIFIED)

            if not event_published:
                logger.error(
                    f'Could not publish event on event bus for request: {request}'
                )
                session.rollback()
                return ko(
                    jsonify(
                        dict(error='could not publish event on event bus')))

            else:
                session.commit()
                logger.info(
                    f'Modified calendar for user {user_id} with id: {id}')
                return ok(jsonify(dict(user_id=user_id, id=id)))

        except SQLAlchemyError:
            session.rollback()
            logger.error(
                f'Could not append to event store for request: {request}')
            return ko(jsonify(dict(error='could not append to event store')))

        finally:
            session.close()

    return bad_request(jsonify(dict(error='invalid data')))
Пример #8
0
def handle_recurrence_delete(user_id, calendar_id, event_id, id):
    """
    This endpoint deletes a recurrence of an event.
    """
    aggregate_status = build_event_aggregate()

    if aggregate_status == -1:
        logger.error(
            f'Could not build calendar aggregate for request: {request}')
        return ko(jsonify(dict(error='cannot build calendar aggregate')))

    if not (str(user_id) in aggregate_status.keys()):
        return not_found(jsonify(dict(error='invalid user')))

    if not (str(calendar_id)
            in aggregate_status[str(user_id)]['calendars'].keys()):
        return not_found(jsonify(dict(error='invalid calendar')))

    if not (str(event_id) in aggregate_status[str(user_id)]['calendars'][str(
            calendar_id)]['events'].keys()):
        return not_found(jsonify(dict(error='invalid event')))

    if not (str(id) in aggregate_status[str(user_id)]['calendars'][str(
            calendar_id)]['events'][str(event_id)]['recurrences'].keys()):
        return not_found(jsonify(dict(error='invalid recurrence')))

    data = dict()
    data['user_id'] = user_id
    data['calendar_id'] = calendar_id
    data['event_id'] = event_id
    data['id'] = id

    event = RecurrenceDeletedEvent(**data)

    session = EventStoreSession()

    try:

        # both of these are wrapped inside a unique try catch because this must be an atomic operation.
        EventEvent.append_event(event)
        event_published = publish_event(get_sending_channel(), event,
                                        RECURRENCE_DELETED)

        if not event_published:
            logger.error(
                f'Could not publish event on event bus for request: {request}')
            session.rollback()
            return ko(
                jsonify(dict(error='could not publish event on event bus')))

        else:
            session.commit()
            logger.info(
                f'Deleted reccurece for user {user_id} on calendar id {calendar_id} on event id {event_id} with id {id}'
            )
            return ok(
                jsonify(dict(user_id=user_id, calendar_id=calendar_id, id=id)))

    except SQLAlchemyError:
        session.rollback()
        logger.error(f'Could not append to event store for request: {request}')
        return ko(jsonify(dict(error='could not append to event store')))

    finally:
        session.close()
Пример #9
0
def handle_modify(user_id, calendar_id, id):
    """
    This endpoint modifies an event resource.
    """
    data = request.get_json()
    aggregate_status = build_event_aggregate()

    if aggregate_status == -1:
        logger.error(
            f'Could not build calendar aggregate for request: {request}')
        return ko(jsonify(dict(error='cannot build calendar aggregate')))

    if not (str(user_id) in aggregate_status.keys()):
        return not_found(jsonify(dict(error='invalid user')))

    if not (str(calendar_id)
            in aggregate_status[str(user_id)]['calendars'].keys()):
        return not_found(jsonify(dict(error='invalid calendar')))

    if not (str(id) in aggregate_status[str(user_id)]['calendars'][str(
            calendar_id)]['events'].keys()):
        return not_found(jsonify(dict(error='invalid event')))

    aggregate_status[str(user_id)]['calendars'][str(
        calendar_id)]['events'].pop(str(id))
    user_schedule = aggregate_status[str(user_id)]['calendars']

    if validate_event(data) and event_can_be_inserted(data, user_schedule):

        if 'until' not in data:
            data['until'] = None

        if 'flex' not in data:
            data['flex'] = None
            data['flex_duration'] = None

        data['user_id'] = user_id
        data['calendar_id'] = calendar_id
        data['id'] = id

        event = EventModifiedEvent(**data)

        session = EventStoreSession()

        try:

            # both of these are wrapped inside a unique try catch because this must be an atomic operation.
            EventEvent.append_event(event)
            event_published = publish_event(get_sending_channel(), event,
                                            EVENT_MODIFIED)

            if not event_published:
                logger.error(
                    f'Could not publish event on event bus for request: {request}'
                )
                session.rollback()
                return ko(
                    jsonify(
                        dict(error='could not publish event on event bus')))

            else:
                session.commit()
                logger.info(
                    f'Modified event for user {user_id} on calendar id {calendar_id} with id {id}'
                )
                return ok(
                    jsonify(
                        dict(user_id=user_id, calendar_id=calendar_id, id=id)))

        except SQLAlchemyError:
            session.rollback()
            logger.error(
                f'Could not append to event store for request: {request}')
            return ko(jsonify(dict(error='could not append to event store')))

        finally:
            session.close()

    return bad_request(jsonify(dict(error='invalid data')))
Пример #10
0
def handle_create(user_id, calendar_id):
    """
    This endpoint creates a new event resource
    """
    data = request.get_json()
    aggregate_status = build_event_aggregate()

    if aggregate_status == -1:
        logger.error(
            f'Could not build calendar aggregate for request: {request}')
        return ko(jsonify(dict(error='cannot build calendar aggregate')))

    if not (str(user_id) in aggregate_status.keys()):
        return not_found(jsonify(dict(error='invalid user')))

    if not (str(calendar_id)
            in aggregate_status[str(user_id)]['calendars'].keys()):
        return not_found(jsonify(dict(error='invalid calendar')))

    user_schedule = aggregate_status[str(user_id)]['calendars']

    # checks if the request payload is formed correctly and if the event can be inserted in the user schedule
    # given the current aggregate status
    if validate_event(data) and event_can_be_inserted(data, user_schedule):
        calendar_events = aggregate_status[str(user_id)]['calendars'][str(
            calendar_id)]['events']
        if not calendar_events:
            new_id = 1
        else:
            new_id = int(max(calendar_events, key=int)) + 1

        if 'until' not in data:
            data['until'] = None

        if 'flex' not in data:
            data['flex'] = None
            data['flex_duration'] = None

        data['user_id'] = user_id
        data['calendar_id'] = calendar_id
        data['id'] = new_id

        event = EventCreatedEvent(**data)

        session = EventStoreSession()

        try:
            # both of these are wrapped inside a unique try catch because this must be an atomic operation.
            EventEvent.append_event(event)
            event_published = publish_event(get_sending_channel(), event,
                                            EVENT_CREATED)

            if not event_published:
                logger.error(
                    f'Could not publish event on event bus for request: {request}'
                )
                session.rollback()
                return ko(
                    jsonify(
                        dict(error='could not publish event on event bus')))

            else:
                session.commit()
                logger.info(
                    f'Created event for user {user_id} on calendar id {calendar_id} with id {new_id}'
                )
                return created(
                    jsonify(
                        dict(user_id=user_id,
                             calendar_id=calendar_id,
                             id=new_id)))

        except SQLAlchemyError:
            session.rollback()
            logger.error(
                f'Could not append to event store for request: {request}')
            return ko(jsonify(dict(error='could not append to event store')))

        finally:
            session.close()

    return bad_request(jsonify(dict(error='invalid data')))
Пример #11
0
def handle_create():
    """
    This is the create user endpoint. It creates a new user resource.
    """
    data = request.get_json()

    aggregate_status = build_user_aggregate()
    if aggregate_status == -1:
        logger.error(f'Could not build user aggregate for request: {request}')
        return ko(jsonify(dict(error='cannot build user aggregate')))

    if validate_request_payload(data):  # checks the data in the payload is formed correctly
        email = data['email']
        password = data['password']
        first_name = data['first_name']
        last_name = data['last_name']
        logger.info("Validated request payload")
        if validate_user_data(email, password, first_name, last_name):
            logger.info("Validated user data")

            # checks if the account can be created given the current aggregate status
            if is_unique_email(email, aggregate_status):
                logger.info("Email is indeed unique")
                if aggregate_status:
                    new_id = int(max(aggregate_status, key=int)) + 1
                else:
                    new_id = 1

                data['id'] = new_id
                data['password'] = hash_pass(password)

                event = UserCreatedEvent(**data)

                session = EventStoreSession()

                logger.info(data)
                try:

                    # both of these are wrapped inside a unique try catch because this must be an atomic operation.
                    UserEvent.append_event(event)
                    event_published = publish_event(get_sending_channel(), event, USER_CREATED)

                    # create a new consumer inside kong
                    kong_request = requests.post(KONG_CONSUMER_API, data={'username': email})
                    if not event_published or not kong_request.status_code == 201:
                        logger.error(f'Could not publish event on event bus for request: {request}')
                        session.rollback()
                        return ko(jsonify(dict(error='could not publish event on event bus')))

                    else:
                        session.commit()
                        logger.info(f'Created user with id: {new_id}')
                        logger.info(f"Added new consumer to kong with username {email}")
                        return created(jsonify(dict(id=new_id)))

                except SQLAlchemyError:
                    session.rollback()
                    logger.error(f'Could not append to event store for request: {request}')
                    return ko(jsonify(dict(error='could not append to event store')))

                except Exception as e:
                    logger.error(e)
                finally:
                    session.close()

    return bad_request(jsonify(dict(error='invalid data')))
Пример #12
0
    def test_events(self):
        event = CalendarIdCreatedEvent(str(uuid.uuid4()), 1, 1)
        publish_event(self.app.sending_channel, event, CALENDAR_ID_CREATED)
        self.simulate_eventual_consistency()

        now = datetime.datetime.now()

        event = EventCreatedEvent(
            user_id=1,
            calendar_id=1,
            id=1,
            name='Pranzo Dalla Nonna',
            location=[44.6368, 10.5697],
            start_time=strftime(now),
            end_time=strftime(now + datetime.timedelta(hours=1)),
            next_is_base=False,
            recurrence_rule='DAILY',
            until=strftime(now + datetime.timedelta(days=3)),
            flex=True,
            flex_duration=3600
        )
        publish_event(self.app.sending_channel, event, EVENT_CREATED)
        self.simulate_eventual_consistency()

        with session_scope(self.DBSession) as session:
            self.assertEqual(len(session.query(Recurrence).all()), 4)

        event = EventModifiedEvent(
            user_id=1,
            calendar_id=1,
            id=1,
            name='Lol finals',
            location=[44.6368, 10.5697],
            start_time=strftime(now),
            end_time=strftime(now + datetime.timedelta(hours=1)),
            next_is_base=False,
            recurrence_rule='DAILY',
            until=strftime(now + datetime.timedelta(days=10)),
            flex=True,
            flex_duration=3600
        )
        publish_event(self.app.sending_channel, event, EVENT_MODIFIED)
        self.simulate_eventual_consistency()

        with session_scope(self.DBSession) as session:
            self.assertEqual(len(session.query(Event).all()), 1)
            self.assertEqual(session.query(Event).first().name, 'Lol finals')
            self.assertEqual(len(session.query(Recurrence).all()), 11)

        event = EventDeletedEvent(
            user_id=1,
            calendar_id=1,
            id=1
        )
        publish_event(self.app.sending_channel, event, EVENT_DELETED)
        self.simulate_eventual_consistency()

        with session_scope(self.DBSession) as session:
            self.assertIsNone(session.query(Event).first())
            self.assertIsNone(session.query(Recurrence).first())

        event = EventCreatedEvent(
            user_id=1,
            calendar_id=1,
            id=1,
            name='Pranzo Dalla Nonna',
            location=[44.6368, 10.5697],
            start_time=strftime(now),
            end_time=strftime(now + datetime.timedelta(hours=1)),
            next_is_base=False,
            recurrence_rule='NORMAL',
            until=None,
            flex=None,
            flex_duration=None
        )
        publish_event(self.app.sending_channel, event, EVENT_CREATED)
        self.simulate_eventual_consistency()

        with session_scope(self.DBSession) as session:
            self.assertEqual(len(session.query(Recurrence).all()), 1)

        event = CalendarIdDeletedEvent(str(uuid.uuid4()), 1, 1)
        publish_event(self.app.sending_channel, event, CALENDAR_ID_DELETED)
        self.simulate_eventual_consistency()

        with session_scope(self.DBSession) as session:
            self.assertIsNone(session.query(Event).first())
            self.assertIsNone(session.query(Recurrence).first())