Example #1
0
def handle_login():
    """
    Login endpoint. It is used for logging a user in.
    If the email and the password passed in the request body are valid ones it returns a
    valid token for the authentication in the API gateway.

    """
    data = request.get_json()

    if validate_request_paylaod(data):
        email = data['email']
        password = data['password']
        user_id = get_user_id_by_credentials(email, password)

        if user_id:
            # retrieves a new token for this user
            KONG_JWT_API = f"http://{KONG_IP}:8001/consumers/{email}/jwt"
            response = requests.post(KONG_JWT_API, data={"algorithm": "HS256"})
            data = response.json()
            headers = {"typ": "JWT", "alg": "HS256"}
            claims = {"iss": data['key']}
            token = jwt.encode(claims, data['secret'], headers=headers)

            if not response.status_code == 201:
                logger.error(f'Could not build token for {request}')
                return ko(dict(error='Could not build token'))

            logger.info(f"Generated JWT Token: {token} for user {user_id}")
            return ok(jsonify(dict(auth_token=token.decode(), id=user_id)))

    return bad_request(jsonify(dict(error='invalid_data')))
Example #2
0
def handle_get_event(user_id, calendar_id, id):
    """
    This query endpoint returns an event resource.
    :return: a json object with the event info on a 200 response status code.
    """
    cal_id = get_user_calendar_id(user_id, calendar_id)

    if not cal_id:
        return not_found(jsonify(dict(error='Calendar not found')))

    event, event_recurrences = get_event(user_id, calendar_id, id)

    if not event:
        return not_found(jsonify(dict(error='Event not found')))

    response = dict(name=event.name,
                    location=event.location,
                    start_time=strftime(event.start_time),
                    end_time=strftime(event.end_time),
                    next_is_base=event.next_is_base,
                    recurrence_rule=event.recurrence_rule.value,
                    until=strftime(event.until),
                    flex=event.flex,
                    flex_duration=event.flex_duration,
                    recurrences=[])

    for recurrence in event_recurrences:
        response['recurrences'].append(
            dict(recurrence_id=recurrence.id,
                 start_time=strftime(recurrence.start_time),
                 end_time=strftime(recurrence.end_time)))

    return ok(jsonify(response))
def handle_get_global_preferences(user_id):
    """
    This query endpoint returns the global preferences of a user.
    :return: a json object with the global preferences on a 200 response status code.
    """
    global_preferences = get_user_global_preferences(user_id)

    if global_preferences:

        response = dict(global_preferences.preferences)

        return ok(jsonify(response))

    return not_found(jsonify(dict(error='User not found')))
Example #4
0
def handle_get(user_id):
    """
    This is the only endpoint query side. It is used for retrieving a user resource data.
    :return: a json object with the user info on a 200 response status code.
    """
    user = get_user_by_id(user_id)

    if user:
        return ok(
            jsonify(
                dict(id=user.id,
                     email=user.email,
                     first_name=user.first_name,
                     last_name=user.last_name)))

    return not_found(jsonify(dict(error='User not found')))
Example #5
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()
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')))
Example #7
0
def handle_get_schedule(user_id):
    """
    This query endpoint returns the user schedule.
    :return: a json object with the list of all the recurrences in the user schedule on a 200 response status code.
    """
    recurrences_and_names = get_schedule(user_id)

    response = dict(recurrences=[])
    for recurrence, name in recurrences_and_names:
        response['recurrences'].append(
            dict(user_id=user_id,
                 calendar_id=recurrence.calendar_id,
                 event_id=recurrence.event_id,
                 id=recurrence.id,
                 start_time=strftime(recurrence.start_time),
                 end_time=strftime(recurrence.end_time),
                 event_name=name))
    return ok(jsonify(response))
Example #8
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()
Example #9
0
def handle_get_all_calendar_events(user_id, calendar_id):
    """
    This query endpoint returns all the events of a user calendar.
    :return: a json object listing all the events in the user calendar on a 200 response status code.
    """
    cal_id = get_user_calendar_id(user_id, calendar_id)

    if not cal_id:
        return not_found(jsonify(dict(error='Calendar not found')))

    events = get_all_calendar_events(user_id, calendar_id)

    response = {}

    for event, event_recurrences in events:
        response[str(event.id)] = dict(
            name=event.name,
            location=event.location,
            start_time=strftime(event.start_time),
            end_time=strftime(event.end_time),
            next_is_base=event.next_is_base,
            recurrence_rule=event.recurrence_rule.value,
            until=strftime(event.until),
            flex=event.flex,
            flex_duration=event.flex_duration,
            recurrences=dict()
        )

        for recurrence in event_recurrences:
            response[str(event.id)]['recurrences'].update(
                {
                    str(recurrence.id): dict(
                        start_time=strftime(recurrence.start_time),
                        end_time=strftime(recurrence.end_time)
                    )
                }
            )

    return ok(jsonify(response))
def handle_get_all_calendars(user_id):
    """
    This query endpoint returns all the calendars of a given user.
    :return: a json object with a list of all the calendars on a 200 response status code.
    """
    if not (get_user_global_preferences(user_id)):
        return not_found(jsonify(dict(error='User not found')))

    calendars = get_all_user_calendars(user_id)
    response = dict(calendars=[])

    for calendar in calendars:
        response['calendars'].append(
            dict(id=calendar.id,
                 name=calendar.name,
                 description=calendar.description,
                 base=calendar.base,
                 color=calendar.color,
                 active=calendar.active,
                 carbon=calendar.carbon,
                 preferences=calendar.preferences))

    return ok(jsonify(response))
Example #11
0
def handle_get_calendar(user_id, id):
    """
    This query endpoint return a calendar of a given user.
    :return: a json object with the calendar info on a 200 response status code.
    """
    if not (get_user_global_preferences(user_id)):
        return not_found(jsonify(dict(error='User not found')))

    calendar = get_calendar(user_id, id)

    if calendar:

        response = dict(name=calendar.name,
                        description=calendar.description,
                        base=calendar.base,
                        color=calendar.color,
                        active=calendar.active,
                        carbon=calendar.carbon,
                        preferences=calendar.preferences)

        return ok(jsonify(response))

    return not_found(jsonify(dict(error='Calendar not found')))
Example #12
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')))
Example #13
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()
Example #14
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')))