コード例 #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_calendar_delete(self):
        default_vehicles = ['bus', 'subway', 'train', 'tram', 'car', 'walking', 'bike', 'taxi', 'enjoy', 'mobike']
        default_preferences = dict(vehicles=default_vehicles, personal_vehicles=[])

        with session_scope(self.DBSession) as session:
            session.add(GlobalPreferences(
                user_id=1,
                preferences=default_preferences))

        CalendarEvent.append_event(GlobalPreferencesCreatedEvent(str(uuid.uuid4()), 1, default_preferences))

        post_data = json.dumps(dict(name='Home', description='Home sweet home', base=[50, 50],
                                    color=[243, 250, 152],
                                    active=True, carbon=True,
                                    preferences=[dict(
                                        name='bus',
                                        time=['19:00', '20:30'],
                                        mileage=10
                                    )]))
        self.client.post('/users/1/calendars', data=post_data, content_type='application/json')
        self.simulate_eventual_consistency()

        self.client.delete('/users/1/calendars/1')
        self.simulate_eventual_consistency()

        with session_scope(self.DBSession) as session:
            self.assertIsNone(session.query(Calendar).filter(Calendar.user_id == 1, Calendar.id == 1).first())
コード例 #3
0
    def test_global_preferences_modify(self):
        default_vehicles = ['bus', 'subway', 'train', 'tram', 'car', 'walking', 'bike', 'taxi', 'enjoy', 'mobike']
        default_preferences = dict(vehicles=default_vehicles, personal_vehicles=[])

        with session_scope(self.DBSession) as session:
            session.add(GlobalPreferences(
                user_id=1,
                preferences=default_preferences))

        CalendarEvent.append_event(GlobalPreferencesCreatedEvent(str(uuid.uuid4()), 1, default_preferences))

        new_preferences = json.dumps(dict(
            vehicles=['bus', 'subway'],
            personal_vehicles=[
                dict(
                    name="tesla",
                    type='car',
                    location=(13, 14),
                    active=True
                )
            ])
        )
        response = self.client.put('/users/1/preferences', data=new_preferences, content_type='application/json')
        self.assertTrue(response.status_code, 200)
        self.assertEqual(json.loads(response.data)['id'], 1)
        self.simulate_eventual_consistency()

        with session_scope(self.DBSession) as session:
            self.assertEqual(
                session.query(GlobalPreferences).filter(GlobalPreferences.user_id == 1).first().preferences,
                json.loads(new_preferences))

        response = self.client.put('/users/10/preferences', data=new_preferences, content_type='application/json')
        self.assertTrue(response.status_code, 404)
コード例 #4
0
    def test_all_calendars_get(self):
        default_vehicles = [
            'bus', 'subway', 'train', 'tram', 'car', 'walking', 'bike', 'taxi',
            'enjoy', 'mobike'
        ]
        default_preferences = dict(vehicles=default_vehicles,
                                   personal_vehicles=[])

        with session_scope(self.DBSession) as session:
            session.add(
                GlobalPreferences(user_id=1, preferences=default_preferences))

        CalendarEvent.append_event(
            GlobalPreferencesCreatedEvent(str(uuid.uuid4()), 1,
                                          default_preferences))

        post_data = json.dumps(
            dict(name='Home',
                 description='Home sweet home',
                 base=[50, 50],
                 color=[243, 250, 152],
                 active=True,
                 carbon=True,
                 preferences=[
                     dict(name='bus', time=['19:00', '20:30'], mileage=10)
                 ]))
        self.client.post('/users/1/calendars',
                         data=post_data,
                         content_type='application/json')
        self.simulate_eventual_consistency()

        post_data = json.dumps(
            dict(name='Job',
                 description='',
                 base=[50, 50],
                 color=[243, 250, 152],
                 active=True,
                 carbon=True,
                 preferences=[
                     dict(name='bus', time=['19:00', '20:30'], mileage=10)
                 ]))
        self.client.post('/users/1/calendars',
                         data=post_data,
                         content_type='application/json')
        self.simulate_eventual_consistency()

        response = self.client.get('/users/1/calendars')
        self.assertEqual(
            json.loads(response.data)['calendars'][0]['name'], 'Home')
        self.assertEqual(
            json.loads(response.data)['calendars'][1]['name'], 'Job')

        response = self.client.get('/users/128/calendars')
        self.assertEqual(response.status_code, 404)
コード例 #5
0
    def test_add_event(self):
        with session_scope(self.EventStoreSession) as session:
            event = GlobalPreferencesCreatedEvent(str(uuid.uuid4()), 1, dict(
                vehicles=['bus'],
                personal_vehicles=[dict(
                    name='tesla',
                    type='car',
                    location=(44.700546, 8.035837),
                    active=True
                )]))

            j_event = event.toJSON()
            CalendarEvent.append_event(event)
            q = session.query(CalendarEvent).first()
            self.assertEqual(q.event, j_event)
            self.assertEqual(json.loads(q.event)['type'], PREFERENCES_CREATED_EVENT)
コード例 #6
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()
コード例 #7
0
def build_calendar_aggregate():
    """
    This builds the calendar aggregate by chronologically applying events in the event store.
    :return: the aggregate status.
    """
    aggregate_status = {}
    event_list = []

    try:
        results = CalendarEvent.get_all_events()
    except SQLAlchemyError:
        return -1

    if not results:
        return {}

    for r in results:
        event_list.append(json.loads(r.event))

    for event in event_list:
        user_id = str(event['event_info']['user_id'])
        event_type = event['type']
        event_info = event['event_info']

        if event_type == PREFERENCES_CREATED_EVENT:
            aggregate_status[user_id] = event_info

        elif event_type == PREFERENCES_MODIFIED_EVENT:
            aggregate_status[user_id]['preferences'] = event_info[
                'preferences']

            global_preferences = aggregate_status[user_id]['preferences']

            if 'calendars' in aggregate_status[user_id]:
                for calendar in aggregate_status[user_id]['calendars'].values(
                ):
                    calendar[
                        'preferences'] = make_calendar_preferences_consistent(
                            global_preferences, calendar['preferences'])

        elif event_type == PREFERENCES_DELETED_EVENT:
            aggregate_status.pop(user_id)

        else:
            if 'calendars' not in aggregate_status[user_id].keys():
                aggregate_status[user_id].update(dict(calendars=dict()))

            id = str(event['event_info']['id'])
            event_info.pop('user_id', None)
            event_info.pop('id', None)

            if event_type == CALENDAR_CREATED_EVENT or event_type == CALENDAR_MODIFIED_EVENT:
                aggregate_status[user_id]['calendars'][id] = event_info

            elif event_type == CALENDAR_DELETED_EVENT:
                aggregate_status[user_id]['calendars'].pop(id)

    return aggregate_status
コード例 #8
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')))
コード例 #9
0
    def test_calendar_get(self):
        default_vehicles = [
            'bus', 'subway', 'train', 'tram', 'car', 'walking', 'bike', 'taxi',
            'enjoy', 'mobike'
        ]
        default_preferences = dict(vehicles=default_vehicles,
                                   personal_vehicles=dict())

        with session_scope(self.DBSession) as session:
            session.add(
                GlobalPreferences(user_id=1, preferences=default_preferences))

        CalendarEvent.append_event(
            GlobalPreferencesCreatedEvent(str(uuid.uuid4()), 1,
                                          default_preferences))

        response = self.client.get('/users/4/preferences')
        self.assertEqual(response.status_code, 404)

        response = self.client.get('/users/1/preferences')
        self.assertEqual(response.status_code, 200)
        self.assertEqual(
            json.loads(response.data)['vehicles'], default_vehicles)
コード例 #10
0
    def test_calendar_create(self):
        default_vehicles = [
            'bus', 'subway', 'train', 'tram', 'car', 'walking', 'bike', 'taxi',
            'enjoy', 'mobike'
        ]
        default_preferences = dict(vehicles=default_vehicles,
                                   personal_vehicles=[])

        with session_scope(self.DBSession) as session:
            session.add(
                GlobalPreferences(user_id=1, preferences=default_preferences))

        CalendarEvent.append_event(
            GlobalPreferencesCreatedEvent(str(uuid.uuid4()), 1,
                                          default_preferences))

        post_data = json.dumps(
            dict(name='Home',
                 description='',
                 base=[44.6381, 10.5726],
                 color=[243, 250, 0],
                 active=False,
                 carbon=False,
                 preferences=[dict(name='bus', time=None, mileage=None)]))
        response = self.client.post('/users/1/calendars',
                                    data=post_data,
                                    content_type='application/json')
        self.assertEqual(response.status_code, 201)
        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().name,
                'Home')

        post_data = json.dumps(
            dict(name='Job',
                 description='Job sweet job',
                 base=[50.20, 50],
                 color=[243, 250, 152],
                 active=True,
                 carbon=True,
                 preferences=[
                     dict(name='bus', time=['19:00', '20:30'], mileage=10)
                 ]))
        response = self.client.post('/users/1/calendars',
                                    data=post_data,
                                    content_type='application/json')
        self.assertEqual(response.status_code, 201)
        self.assertEqual(json.loads(response.data)['id'], 2)
        self.simulate_eventual_consistency()

        with session_scope(self.DBSession) as session:
            self.assertEqual(
                session.query(Calendar).filter(Calendar.user_id == 1,
                                               Calendar.id == 2).first().name,
                'Job')

        post_data = json.dumps(
            dict(name='Home',
                 description='second home',
                 base=[50, 50],
                 color=[243, 250, 152],
                 active=True,
                 carbon=True,
                 preferences=[
                     dict(name='bus', time=['19:00', '20:30'], mileage=10)
                 ]))
        response = self.client.post('/users/1/calendars',
                                    data=post_data,
                                    content_type='application/json')
        self.assertEqual(response.status_code, 400)  # name already present

        post_data = json.dumps(
            dict(name='Home',
                 description='second home',
                 base=[50, 50],
                 color=[243, 250, 152],
                 active=True,
                 carbon=True,
                 preferences=[
                     dict(name='bus', time=['19:00', '20:30'], mileage=10),
                     dict(name='tesla', time=None, mileage=None)
                 ]))
        response = self.client.post('/users/1/calendars',
                                    data=post_data,
                                    content_type='application/json')
        self.assertEqual(response.status_code,
                         400)  # tesla not in global preferences
コード例 #11
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')))
コード例 #12
0
    def test_calendar_modify(self):
        default_vehicles = ['bus', 'subway', 'train', 'tram', 'car', 'walking', 'bike', 'taxi', 'enjoy',
                            'mobike']
        default_preferences = dict(vehicles=default_vehicles, personal_vehicles=[])

        with session_scope(self.DBSession) as session:
            session.add(GlobalPreferences(
                user_id=1,
                preferences=default_preferences))

        CalendarEvent.append_event(GlobalPreferencesCreatedEvent(str(uuid.uuid4()), 1, default_preferences))

        post_data = json.dumps(dict(name='Home', description='Home sweet home', base=[50, 50],
                                    color=[243, 250, 152],
                                    active=True, carbon=True,
                                    preferences=[dict(
                                        name='bus',
                                        time=['19:00', '20:30'],
                                        mileage=10
                              )]))
        self.client.post('/users/1/calendars', data=post_data, content_type='application/json')
        self.simulate_eventual_consistency()

        put_data = json.dumps(dict(name='Test', description='Test sweet Test', base=[50, 50],
                                   color=[243, 250, 152],
                                   active=True, carbon=True,
                                   preferences=[dict(
                                       name='bus',
                                       time=['19:00', '20:30'],
                                       mileage=10
                                   )]))
        response = self.client.put('/users/1/calendars/1', data=put_data, content_type='application/json')
        self.assertEqual(response.status_code, 200)
        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().name,
                'Test'
            )

        put_data = json.dumps(dict(name='Test', description='Test sweet Test', base=[50, 50],
                                   color=[243, 250, 152],
                                   active=True, carbon=True,
                                   preferences=[
                                       dict(
                                           name='bus',
                                           time=['19:00', '20:30'],
                                           mileage=10
                                       ),
                                       dict(
                                           name='tesla',
                                           time=['19:00', '20:30'],
                                           mileage=100
                                       )]
                                   ))
        response = self.client.put('/users/1/calendars/1', data=put_data, content_type='application/json')
        self.assertEqual(response.status_code, 400)  # there is no tesla in global preferences

        response = self.client.put('/users/1/calendars/3', data=put_data, content_type='application/json')
        self.assertEqual(response.status_code, 404)
        self.assertEqual(json.loads(response.data)['error'], 'invalid calendar')
コード例 #13
0
    def test_build_aggregate(self):
        event1 = GlobalPreferencesCreatedEvent(
            str(uuid.uuid4()), 1,
            dict(vehicles=['bus'],
                 personal_vehicles=[
                     dict(name='tesla',
                          type='car',
                          location=(44.700546, 8.035837),
                          active=True)
                 ]))

        event2 = CalendarCreatedEvent(
            user_id=1,
            id=1,
            name='Home',
            description='Home sweet home',
            base=[50, 50],
            color=[243, 250, 152],
            active=True,
            carbon=True,
            preferences=[dict(name='bus', time=['19:00', '20:30'], mileage=5)])

        event3 = CalendarCreatedEvent(
            user_id=1,
            id=2,
            name='Job',
            description='Job sweet job',
            base=[20, 20],
            color=[243, 250, 152],
            active=True,
            carbon=True,
            preferences=[dict(name='bus', time=['19:00', '20:30'], mileage=5)])

        event4 = CalendarModifiedEvent(user_id=1,
                                       id=1,
                                       name='Home',
                                       description='Home not so sweet home',
                                       base=[50, 50],
                                       color=[243, 250, 152],
                                       active=True,
                                       carbon=True,
                                       preferences=[
                                           dict(name='bus',
                                                time=['19:00', '20:30'],
                                                mileage=5),
                                           dict(name='tesla',
                                                time=['19:00', '20:30'],
                                                mileage=200)
                                       ])

        event5 = CalendarDeletedEvent(user_id=1, id=2)

        event6 = GlobalPreferencesModifiedEvent(
            1,
            dict(personal_vehicles=[
                dict(name='tesla',
                     type='car',
                     location=(44.700546, 8.035837),
                     active=True)
            ]))

        CalendarEvent.append_event(event1)
        CalendarEvent.append_event(event2)
        CalendarEvent.append_event(event3)
        CalendarEvent.append_event(event4)
        CalendarEvent.append_event(event5)
        CalendarEvent.append_event(event6)

        aggregate_status = build_calendar_aggregate()

        self.assertEqual(
            aggregate_status['1']['calendars']['1']['description'],
            'Home not so sweet home')
        self.assertFalse('2' in aggregate_status['1']['calendars'])
        self.assertTrue('bus' not in [
            vehicle['name'] for vehicle in aggregate_status['1']['calendars']
            ['1']['preferences']
        ])