Example #1
0
    def test_event_get(self):
        event = CalendarIdCreatedEvent(str(uuid.uuid4()), 1, 1)
        EventEvent.append_event(event)

        with session_scope(self.DBSession) as session:
            session.add(CalendarId(
                user_id=1,
                id=1))

        now = datetime.datetime.now()
        post_data = json.dumps(
            dict(name='Evento',
                 location=[44.6368, 10.5697],
                 start_time=strftime((now + datetime.timedelta(days=1))),
                 end_time=strftime((now + datetime.timedelta(days=1, hours=1))),
                 recurrence_rule='MONTHLY',
                 next_is_base=False,
                 until=strftime(datetime.datetime(year=2018, month=2, day=1)),
                 flex=True,
                 flex_duration=1800))

        self.client.post('/users/1/calendars/1/events', data=post_data, content_type='application/json')
        self.simulate_eventual_consistency()

        response = self.client.get('/users/1/calendars/1/events/1')
        self.assertEqual(response.status_code, 200)
    def test_event_delete(self):
        event = CalendarIdCreatedEvent(str(uuid.uuid4()), 1, 1)
        EventEvent.append_event(event)

        with session_scope(self.DBSession) as session:
            session.add(CalendarId(
                user_id=1,
                id=1))

        now = datetime.datetime.now()
        post_data = json.dumps(
            dict(name='Pranzo Dalla Nonna',
                 location=[44.6368, 10.5697],
                 start_time=strftime(now),
                 end_time=strftime(now + datetime.timedelta(hours=1)),
                 recurrence_rule='DAILY',
                 next_is_base=False,
                 until=strftime(now + datetime.timedelta(days=3)),
                 flex=True,
                 flex_duration=1800))

        self.client.post('/users/1/calendars/1/events', data=post_data, content_type='application/json')
        self.simulate_eventual_consistency()

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

        with session_scope(self.DBSession) as session:
            self.assertIsNone(session.query(Event).first())
Example #3
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.
        EventEvent.append_event(event)
        event_published = publish_event(get_sending_channel(), event, routing_key)

        if not event_published:
            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:
        session.rollback()
        logger.error(f'Could not append to event store event {event.toJSON()}')
        raise SQLAlchemyError

    finally:
        session.close()
 def test_add_event(self):
     with session_scope(self.EventStoreSession) as session:
         event = CalendarIdCreatedEvent(str(uuid.uuid4()), 1, 1)
         j_event = event.toJSON()
         EventEvent.append_event(event)
         q = session.query(EventEvent).first()
         self.assertEqual(q.event, j_event)
         self.assertEqual(
             json.loads(q.event)['type'], CALENDAR_ID_CREATED_EVENT)
    def test_event_create(self):

        event = CalendarIdCreatedEvent(str(uuid.uuid4()), 1, 1)
        EventEvent.append_event(event)

        with session_scope(self.DBSession) as session:
            session.add(CalendarId(user_id=1, id=1))

        now = datetime.datetime.now()
        post_data = json.dumps(
            dict(name='Pranzo Dalla Nonna',
                 location=[44.6368, 10.5697],
                 start_time=strftime(now),
                 end_time=strftime(now + datetime.timedelta(hours=1)),
                 recurrence_rule='DAILY',
                 next_is_base=False,
                 until=strftime(now + datetime.timedelta(days=3)),
                 flex=True,
                 flex_duration=1800))

        response = self.client.post('/users/1/calendars/1/events',
                                    data=post_data,
                                    content_type='application/json')
        self.simulate_eventual_consistency()
        self.assertEqual(response.status_code, 201)

        now = datetime.datetime.now()
        post_data = json.dumps(
            dict(name='Football Match',
                 location=[44.6368, 10.5697],
                 start_time=strftime(now + datetime.timedelta(days=3)),
                 end_time=strftime(now + datetime.timedelta(days=4)),
                 recurrence_rule='NORMAL',
                 next_is_base=False,
                 until=None,
                 flex=None,
                 flex_duration=None))

        response = self.client.post('/users/1/calendars/1/events',
                                    data=post_data,
                                    content_type='application/json')
        self.simulate_eventual_consistency()
        self.assertEqual(response.status_code, 400)  # the event overlaps

        now = datetime.datetime.now()
        post_data = json.dumps(
            dict(name='Football Match',
                 location=[44.6368, 10.5697],
                 start_time=strftime(now + datetime.timedelta(days=10)),
                 end_time=strftime(now + datetime.timedelta(days=12)),
                 recurrence_rule='NORMAL',
                 next_is_base=False,
                 until=None,
                 flex=None,
                 flex_duration=None))

        response = self.client.post('/users/1/calendars/1/events',
                                    data=post_data,
                                    content_type='application/json')
        self.simulate_eventual_consistency()
        self.assertEqual(response.status_code, 201)
Example #6
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()
def build_event_aggregate():
    """
    This builds the event aggregate by chronologically applying events in the event store.
    :return: the aggregate status.
    """
    aggregate_status = {}
    event_list = []

    try:
        results = EventEvent.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 == CALENDAR_ID_CREATED_EVENT:

            if user_id not in aggregate_status:
                aggregate_status[user_id] = \
                    {'calendars': {}}

            aggregate_status[user_id]['calendars'].update(
                {str(event_info['id']): {
                     'events': {}
                 }})

        elif event_type == CALENDAR_ID_DELETED_EVENT:
            aggregate_status[user_id]['calendars'].pop(str(event_info['id']))

        elif event_type == USER_CALENDARS_DELETED_EVENT:
            try:
                aggregate_status.pop(user_id)
            except KeyError:
                pass  # user had no calendars

        else:
            calendar_id = str(event_info['calendar_id'])
            event_id = str(event_info['id'])

            if event_type == EVENT_CREATED_EVENT or event_type == EVENT_MODIFIED_EVENT:

                event_info.pop('user_id')
                event_info.pop('calendar_id')
                event_info.pop('id')
                event_info['start_time'] = strptime(event_info['start_time'])
                event_info['end_time'] = strptime((event_info['end_time']))

                aggregate_status[user_id]['calendars'][calendar_id][
                    'events'].update(
                        {event_id: {
                            **event_info, 'recurrences': {}
                        }})

                start_time = event_info['start_time']
                end_time = event_info['end_time']

                if event_info['recurrence_rule'] == 'NORMAL':
                    aggregate_status[user_id]['calendars'][calendar_id][
                        'events'][event_id]['recurrences'].update({
                            '1':
                            dict(start_time=start_time, end_time=end_time)
                        })
                else:
                    rec_rule = event_info['recurrence_rule']
                    until = strptime(event_info['until'])
                    start_occurrences, end_occurrences = generate_occurrences(
                        RRULE[rec_rule]['name'], start_time, end_time, until)

                    for index, (s_time, e_time) in enumerate(
                            zip(start_occurrences, end_occurrences), 1):
                        aggregate_status[user_id]['calendars'][calendar_id][
                            'events'][event_id]['recurrences'].update({
                                str(index): {
                                    'start_time': s_time,
                                    'end_time': e_time
                                }
                            })

            elif event_type == EVENT_DELETED_EVENT:
                aggregate_status[user_id]['calendars'][calendar_id][
                    'events'].pop(event_id)

    return aggregate_status
Example #8
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')))
Example #9
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')))
    def test_aggregate_builder(self):
        event1 = CalendarIdCreatedEvent(uuid=str(uuid.uuid4()), user_id=1, id=1)
        event2 = CalendarIdCreatedEvent(uuid=str(uuid.uuid4()), user_id=1, id=2)

        now = datetime.datetime.now()
        event3 = EventCreatedEvent(
            user_id=1,
            calendar_id=1,
            id=1,
            name='Meeting',
            location=[44.6368, 10.5697],
            start_time=strftime(now),
            end_time=strftime(now + datetime.timedelta(hours=1)),
            recurrence_rule='NORMAL',
            next_is_base=False,
            until=None,
            flex=None,
            flex_duration=None
        )

        now = datetime.datetime.now()
        event4 = EventCreatedEvent(
            user_id=1,
            calendar_id=1,
            id=2,
            name='Cena Natalizia',
            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
        )

        now = datetime.datetime.now()
        event5 = 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
        )

        now = datetime.datetime.now()
        event6 = EventCreatedEvent(
            user_id=1,
            calendar_id=2,
            id=1,
            name='For lab',
            location=[44.6368, 10.5697],
            start_time=strftime(now),
            end_time=strftime(now + datetime.timedelta(hours=1)),
            recurrence_rule='NORMAL',
            next_is_base=False,
            until=None,
            flex=None,
            flex_duration=None
        )

        event7 = CalendarIdCreatedEvent(uuid=str(uuid.uuid4()), user_id=2, id=1)

        now = datetime.datetime.now()
        event8 = EventCreatedEvent(
            user_id=2,
            calendar_id=1,
            id=1,
            name='Cinema',
            location=[44.6368, 10.5697],
            start_time=strftime(now),
            end_time=strftime(now + datetime.timedelta(hours=1)),
            recurrence_rule='NORMAL',
            next_is_base=False,
            until=None,
            flex=None,
            flex_duration=None
        )

        event9 = EventDeletedEvent(
            user_id=1,
            calendar_id=1,
            id=2
        )

        EventEvent.append_event(event1)
        EventEvent.append_event(event2)
        EventEvent.append_event(event3)
        EventEvent.append_event(event4)
        EventEvent.append_event(event5)
        EventEvent.append_event(event6)
        EventEvent.append_event(event7)
        EventEvent.append_event(event8)
        EventEvent.append_event(event9)
        aggregate_status = build_event_aggregate()

        self.assertEqual(len(aggregate_status['1']['calendars'].keys()), 2)
        self.assertEqual(aggregate_status['1']['calendars']['1']['events']['1']['name'], 'Lol finals')
        self.assertEqual(len(aggregate_status['1']['calendars']['1']['events']['1']['recurrences'].keys()), 11)
        self.assertEqual(aggregate_status['2']['calendars']['1']['events']['1']['name'], 'Cinema')
        self.assertTrue('2' not in aggregate_status['1']['calendars']['1']['events'])

        event10 = CalendarIdDeletedEvent(uuid=str(uuid.uuid4()), user_id=1, id=1)

        EventEvent.append_event(event10)
        aggregate_status = build_event_aggregate()

        self.assertEqual(len(aggregate_status['1']['calendars'].keys()), 1)