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_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 #4
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 #5
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 #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()
Example #7
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 #8
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')))
Example #9
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')))