Exemplo n.º 1
0
def full_active_message_render(event, context):
    """
  Render active message pane - expects event to have body with current user and list of message groupings
  """
    logger.debug("Event: {} type: {}", event, type(event))
    logger.debug("Context: {}", context)
    body = event["body"]
    logger.debug("body: {}", body)
    for attribute in ["username", "all_messages"]:
        if attribute not in body:
            error_message = "Improper message format: `{}' missing from message JSON".format(
                attribute)
            logger.debug(error_message)
            return _build_response(400, error_message)
    username = body["username"]
    all_messages = body["all_messages"]
    env = jinja2.Environment(
        loader=S3Loader('chat-application-upload-bucket-11097', logger),
        autoescape=jinja2.select_autoescape(['html', 'xml']))
    template = env.get_template('jinja_message_template.html.j2')
    logger.debug("template: {}", template)
    done = template.render(current_user=username, all_messages=all_messages)
    logger.debug("template_done: '{}', template_type: {}", done, type(done))
    # encoded_payload = base64.b64encode(done.encode('utf-8')).decode('utf-8')
    # logger.debug("encoded: '{}' type: {}".format(encoded_payload, type(encoded_payload)))
    return _build_response(200, done)
Exemplo n.º 2
0
def logout(event, context):
    """
  Handles a logout request via WebSocket
  """
    logger.info("Login request via WebSocket")
    logger.debug("event: {}", str(event))
    connectionID = event["requestContext"].get("connectionId")
    logger.debug("connectionID: {}", connectionID)

    # Query the connections Table and remove the current user
    connections_table = dynamodb.Table(definitions.Connections.TABLE_NAME)
    local_var = ":user"
    result = connections_table.update_item(
        Key={definitions.Connections.CONNECTION_ID: connectionID},
        UpdateExpression="set {} = {}".format(definitions.Connections.USERNAME,
                                              local_var),
        ExpressionAttributeValues={local_var: None},
        ReturnValues="UPDATED_OLD")
    logger.debug("Result: {}", result)
    # If there was a current user, remove the connection ID from the user Table
    if result['ResponseMetadata'][
            'HTTPStatusCode'] == 200 and 'Attributes' in result:
        if definitions.Connections.USERNAME in result['Attributes']:
            logger.debug(
                result['Attributes'][definitions.Connections.USERNAME])
            if result['Attributes'][
                    definitions.Connections.USERNAME] is not None:
                username = result['Attributes'][
                    definitions.Connections.USERNAME]
                users_table = dynamodb.Table(definitions.Users.TABLE_NAME)
                local_var = ":connID"
                result = users_table.update_item(
                    Key={definitions.Users.USERNAME: username},
                    UpdateExpression="delete {} {}".format(
                        definitions.Users.CONNECTION_ID, local_var),
                    ExpressionAttributeValues={local_var: set([connectionID])},
                    ReturnValues="UPDATED_NEW")
                logger.debug("Updated result: {}", result)
    else:
        logger.error("Connections table not properly updated - result: {}",
                     result)
        _send_to_connection(
            connectionID,
            _build_response_detailed(500, action, "Internal server error"),
            event)
        return _build_response(500, "Internal server error")
    # Send the login page to the client
    obj = s3.Object('chat-application-upload-bucket-11097', 'login_page.html')
    body = obj.get()["Body"].read().decode("utf-8")
    _send_to_connection(connectionID,
                        _build_response_detailed(200, action, body), event)
    return _build_response(200, "Disconnection success")
def connection_manager(event, context):
    """
  Handles CONNECT and DISCONNECT for WebSocket
  """
    connectionID = event["requestContext"].get("connectionId")
    logger.debug("event: {}", str(event))
    logger.debug("connectionID: {}", connectionID)

    if event["requestContext"]["eventType"] == "CONNECT":
        logger.info("Incoming connect request")

        # Add the connection to the database
        connections_table = dynamodb.Table(definitions.Connections.TABLE_NAME)
        connections_table.put_item(
            Item={definitions.Connections.CONNECTION_ID: connectionID})
        return _build_response(200, "Connection success")
    if event["requestContext"]["eventType"] == "DISCONNECT":
        logger.info("Incoming disconnect request")

        # Remove the connection from the database
        connections_table = dynamodb.Table(definitions.Connections.TABLE_NAME)
        result = connections_table.delete_item(
            Key={definitions.Connections.HASH_KEY: connectionID},
            ReturnValues="ALL_OLD")
        logger.debug(result)
        # Removes connectionID from userTable on abrupt disconnect (no logout)
        if result['ResponseMetadata'][
                'HTTPStatusCode'] == 200 and 'Attributes' in result:
            if definitions.Connections.USERNAME in result['Attributes']:
                if result['Attributes'][
                        definitions.Connections.USERNAME] is not None:
                    username = result['Attributes'][
                        definitions.Connections.USERNAME]
                    users_table = dynamodb.Table(definitions.Users.TABLE_NAME)
                    local_var = ":connID"
                    result = users_table.update_item(
                        Key={definitions.Users.USERNAME: username},
                        UpdateExpression="delete {} {}".format(
                            definitions.Users.CONNECTION_ID, local_var),
                        ExpressionAttributeValues={
                            local_var: set([connectionID])
                        },
                        ReturnValues="UPDATED_NEW")
                    logger.debug("Updated result: {}", result)
        else:
            logger.error("Connections table not properly updated - result: {}",
                         result)
            return _build_response(500, "Internal server error")
        return _build_response(200, "Disconnection success")
    logger.error("Unexpected eventType for connection manager")
    return _build_response(500, "Unexpected eventType")
def default_message(event, context):
    """
  Return an error when unexpected WebSocket event occurs
  """
    error_message = "Unexpected WebSocket event"
    logger.info(error_message)
    return _build_response(400, error_message)
Exemplo n.º 5
0
def ping(event, context):
    """
    Basic connection check
    """
    logger.info("Ping requested.")
    logger.debug("OS Environ: {}", os.environ)
    return _build_response(200, "PONG!")
def fetch_create_group_page(event, context):
    """
  Endpoint to get the create group page
  """
    logger.info("Group creation page via WebSocket")
    logger.debug("event: {}", str(event))
    connectionID = event["requestContext"].get("connectionId")
    logger.debug("connectionID: {}", connectionID)

    # Validate that user is logged in

    connections_table = dynamodb.Table(definitions.Connections.TABLE_NAME)
    response = connections_table.query(
        ProjectionExpression=definitions.Connections.USERNAME,
        KeyConditionExpression=boto3.dynamodb.conditions.Key(
            definitions.Connections.CONNECTION_ID).eq(connectionID))
    logger.debug("response: {}", response)
    items = response.get("Items", [])
    logger.debug("items: {}", items)
    if len(items) > 0:
        item = items[0]
    else:
        logger.error(
            "user password query returned not even an empty set items: {}",
            items)
        _send_to_connection(
            connectionID, _build_response_detailed(500, action,
                                                   "Server error"), event)
        return _build_response(500, "Server error")
    if definitions.Connections.USERNAME not in item:
        _send_to_connection(
            connectionID, _build_response_detailed(401, action,
                                                   "Not logged in"), event)
        return _build_response(401, "Not logged in")

    # username = item[definitions.Connections.USERNAME]

    obj = s3.Object('chat-application-upload-bucket-11097',
                    'create_group_page.html')
    body = obj.get()["Body"].read().decode("utf-8")
    _send_to_connection(connectionID,
                        _build_response_detailed(200, action, body), event)
    return _build_response(200, "Create Group page fetched")
def get_group(event, context):
    """
  Return a selected group
  """
    logger.info("Entering a group")
    connectionID = event["requestContext"].get("connectionId")
    logger.debug("connectionID: {}", connectionID)

    # Validate the connection is logged in

    connections_table = dynamodb.Table(definitions.Connections.TABLE_NAME)
    response = connections_table.query(
        ProjectionExpression=definitions.Connections.USERNAME,
        KeyConditionExpression=boto3.dynamodb.conditions.Key(
            definitions.Connections.CONNECTION_ID).eq(connectionID))
    logger.debug("response: {}", response)
    items = response.get("Items", [])
    logger.debug("items: {}", items)
    if len(items) >= 0:
        item = items[0]
    else:
        logger.error(
            "user password query returned not even an empty set items: {}",
            items)
        _send_to_connection(
            connectionID, _build_response_detailed(500, action,
                                                   "Server error"), event)
        return _build_response(500, "Server error")
    if definitions.Connections.USERNAME not in item:
        _send_to_connection(
            connectionID, _build_response_detailed(401, action,
                                                   "Not logged in"), event)
        return _build_response(401, "Not logged in")

    username = item[definitions.Connections.USERNAME]

    default_fetch = 20  # Default number of messages to fetch

    body = _fetch_body(event, logger)
    for attribute in [definitions.Groups.GROUP_ID]:
        if attribute not in body:
            error_message = "Improper message format: `{}' missing from message JSON".format(
                attribute)
            logger.debug(error_message)
            _send_to_connection(
                connectionID,
                _build_response_detailed(400, action, error_message), event)
            return _build_response(400, error_message)

    group = body[definitions.Groups.GROUP_ID]

    # # Validate the connection is logged in

    # connections_table = dynamodb.Table(definitions.Connections.TABLE_NAME)
    # response = connections_table.query(
    #   ProjectionExpression=definitions.Connections.USERNAME,
    #   KeyConditionExpression=boto3.dynamodb.conditions.Key(definitions.Connections.CONNECTION_ID).eq(connectionID)
    # )
    # logger.debug("response: {}".format(response))
    # items = response.get("Items", [])
    # logger.debug("items: {}".format(items))
    # if (len(items) > 0):
    #   item = items[0]
    # else:
    #   logger.error("user password query returned not even an empty set items: {}".format(items))
    #   _send_to_connection(connectionID, _build_response_detailed(500, action, "Server error"), event)
    #   return _build_response(500, "Server error")
    # if definitions.Connections.USERNAME not in item:
    #   _send_to_connection(connectionID, _build_response_detailed(401, action, "Not logged in"), event)
    #   return _build_response(401, "Not logged in")

    # username = item[definitions.Connections.USERNAME]

    # Validate the user is a member of the group

    groups_table = dynamodb.Table(definitions.Groups.TABLE_NAME)
    response = groups_table.query(
        # ProjectionExpression="{}, {}".format(definitions.Groups.USERNAME, definitions.Groups.NICKNAME),
        KeyConditionExpression=boto3.dynamodb.conditions.Key(
            definitions.Groups.GROUP_ID).eq(group))
    items = response.get("Items", [])
    logger.debug("items: {}", str(items))
    ok = False
    # current_user_exclusion_list = []
    # current_user_left_timestamp = -1
    for item in items:
        if item[definitions.Groups.USERNAME] == username:
            ok = True
            current_user_exclusion_list = item[definitions.Groups.EXCLUSION_LIST] if definitions.Groups.EXCLUSION_LIST in item else []
            current_user_left = bool(current_user_exclusion_list) and len(
                current_user_exclusion_list[-1]) == 1
            # Note if the current user has left the group
            # if definitions.Groups.EXCLUSION_LIST in item and len(item[definitions.Groups.EXCLUSION_LIST][-1]) == 1:
            # current_user_left = True
            # current_user_left_timestamp = item[definitions.Groups.EXCLUSION_LIST]
            break
    if not ok:
        _send_to_connection(
            connectionID,
            _build_response_detailed(403, action, "Not a member of group"),
            event)
        return _build_response(403, "Not a member of group")

    logger.debug(
        "current_user_left: {} current_user_exclusion_list: {} ".format(
            current_user_left, current_user_exclusion_list))

    # Group table info to get the group nickname & list of users

    nickname = items[0][definitions.Groups.NICKNAME]
    current_group_users = [
        item[definitions.Groups.USERNAME] for item in items
        if not (definitions.Groups.EXCLUSION_LIST in item
                and len(item[definitions.Groups.EXCLUSION_LIST][-1]) == 1)
    ]
    # current_user_left = username not in current_group_users
    # definitions.Groups.LEFT: item[definitions.Groups.LEFT] if definitions.Groups.LEFT in item else False

    logger.debug("nickname: {} current_group_users: {}", nickname,
                 current_group_users)

    logger.debug("group: {}", group)
    messages_table = dynamodb.Table(definitions.Messages.TABLE_NAME)
    local_name_1 = ":group_id"
    local_name_2_template = ":timestmp{}_{}"
    # local_name_2 = ":timestmp"
    if current_user_left:
        expression_attribute_values = {
            definitions.Messages.HASH_KEY: local_name_1
        }
        key_condition_expression = [[
            "{} = {} and (".format(definitions.Messages.HASH_KEY, local_name_1)
        ]]
        for i, exclusion_item in enumerate(current_user_exclusion_list):
            exclusion_pair = []
            temp_local_name_2 = local_name_2_template.format(i, 0)
            message = "{} <= {}".format(definitions.Messages.RANGE_KEY,
                                        temp_local_name_2)
            expression_attribute_values[temp_local_name_2] = exclusion_item[0]
            if len(exclusion_item) > 1:
                exclusion_pair.append(message)
                temp_local_name_2 = local_name_2_template.format(i, 1)
                exclusion_pair.append("{} >= {}".format(
                    definitions.Messages.RANGE_KEY, temp_local_name_2))
                expression_attribute_values[
                    temp_local_name_2] = exclusion_item[1]
            else:
                message += ")"
                exclusion_pair.append(message)

        logger.debug("KeyConditionExpression before join: {}",
                     key_condition_expression)
        key_condition_expression = " and ".join(
            " or ".join(item) for item in key_condition_expression)
        logger.debug("KeyConditionExpression after join: {}",
                     key_condition_expression)
        logger.debug("ExpressionAttributeValues: {}",
                     expression_attribute_values)
        response = messages_table.query(
            KeyConditionExpression=key_condition_expression,
            ExpressionAttributeValues=expression_attribute_values,
            Limit=default_fetch,
            ScanIndexForward=False)
        # items = response.get("Items", [])
        # logger.debug("items: {}".format(str(items)))
        # if len(items) > default_fetch:
        #   items = items[-default_fetch:]
        # logger.debug("items: {}".format(str(items)))
    else:
        response = messages_table.query(
            KeyConditionExpression="{} = {}".format(
                definitions.Messages.HASH_KEY, local_name_1),
            ExpressionAttributeValues={local_name_1: group},
            Limit=default_fetch,
            ScanIndexForward=False)
    items = response.get("Items", [])
    logger.debug("items: {}", str(items))
    # How many new messages were actually taken
    messages = [
        {
            definitions.Messages.USERNAME:
            item[definitions.Messages.USERNAME]
            if definitions.Messages.USERNAME in item else None,
            definitions.Messages.CONTENT:
            item[definitions.Messages.CONTENT],
            definitions.Messages.TIMESTAMP:
            datetime.fromtimestamp(item[definitions.Messages.TIMESTAMP] //
                                   1000000000).strftime('%m/%d/%Y %H:%M'),
            #definitions.Messages.INIT: x[definitions.Messages.INIT] if definitions.Messages.INIT in x else False
        } for item in items
    ]
    messages.reverse()
    memo_username = None
    all_messages = []
    temp_list = []
    # init_set = False
    for message in messages:
        if memo_username is None:
            memo_username = message[definitions.Messages.USERNAME]
            if temp_list:
                all_messages.append(temp_list[:])
                temp_list.clear()
            # if message[definitions.Messages.INIT] == True:
            #   init_set = True
        elif memo_username != message[definitions.Messages.USERNAME]:
            memo_username = message[definitions.Messages.USERNAME]
            all_messages.append(temp_list[:])
            temp_list.clear()
            # if init_set == True:
            #   init_set = False
        temp_list.append(message)
    if temp_list:
        all_messages.append(temp_list)

    logger.debug("Messages: {}", str(messages))
    logger.debug("All Messages: {}", str(all_messages))
    # Send data to requesting client (render)
    params = {
        "body": {
            "left": current_user_left,
            "nickname": nickname,
            "group_users": current_group_users,
            "username": username,
            "all_messages": all_messages,
            "groupID": group
        }
    }
    lambda_client = boto3.client("lambda")
    invoke_response = lambda_client.invoke(
        FunctionName="chat-application-dev-activePaneRender",
        InvocationType="RequestResponse",
        Payload=json.dumps(params))
    payload = invoke_response['Payload'].read().decode()
    logger.debug("Payload: '{}' type: {}", payload, type(payload))
    payload_dict = json.loads(payload)
    logger.debug("Payload_dict: '{}' type: {}", payload_dict,
                 type(payload_dict))
    body = {"group": group, "html": payload_dict["body"]}
    _send_to_connection(connectionID,
                        _build_response_detailed(200, action, body), event)
    return _build_response(200, "Sent {} messages".format(len(messages)))
Exemplo n.º 8
0
def create_group(event, context):
  """
  Endpoint to create a chat group
  """
  logger.info("Group creation via WebSocket")
  logger.debug("event: {}", str(event))
  connectionID = event["requestContext"].get("connectionId")
  logger.debug("connectionID: {}", connectionID)

  # Validate that user is logged in

  connections_table = dynamodb.Table(definitions.Connections.TABLE_NAME)
  response = connections_table.query(
    ProjectionExpression=definitions.Connections.USERNAME,
    KeyConditionExpression=boto3.dynamodb.conditions.Key(definitions.Connections.CONNECTION_ID).eq(connectionID)
  )
  logger.debug("response: {}", response)
  items = response.get("Items", [])
  logger.debug("items: {}", items)
  if len(items) > 0:
    item = items[0]
  else:
    logger.error("user password query returned not even an empty set items: {}", items)
    _send_to_connection(connectionID, _build_response_detailed(500, action, "Server error"), event)
    return _build_response(500, "Server error")
  if definitions.Connections.USERNAME not in item:
    _send_to_connection(connectionID, _build_response_detailed(401, action, "Not logged in"), event)
    return _build_response(401, "Not logged in")

  username = item[definitions.Connections.USERNAME]

  # Check for required components
  body = _fetch_body(event, logger)
  for attribute in [definitions.Groups.NICKNAME, "users"]:
    if attribute not in body:
      error_message = "Improper message format: `{}' missing from message JSON".format(attribute)
      logger.debug(error_message)
      return _build_response(400, error_message)

  nickname = body[definitions.Groups.NICKNAME]
  users = json.loads(body["users"])
  logger.debug("nickname: '{}', username: '******', users: '{}', users_type: {}", nickname, username, users, type(users))

  # Validate that all users exist
  users_table = dynamodb.Table(definitions.Users.TABLE_NAME)
  invalid_users = []
  online_users = []
  for user in users:
    if user == username:
      invalid_users.append(user)
      continue
    response = users_table.query(
      ProjectionExpression="{}, {}".format(
        definitions.Users.USERNAME,
        definitions.Users.CONNECTION_ID
      ),
      KeyConditionExpression=boto3.dynamodb.conditions.Key(definitions.Users.USERNAME).eq(user)
    )
    logger.debug("response: {}", response)
    items = response.get("Items", [])
    logger.debug("items: {}", items)
    if len(items) > 0:
      item = items[0]
    else:
      invalid_users.append(user)
    # get all the connection IDs for online users
    if definitions.Users.CONNECTION_ID in item:
      online_users.extend(item[definitions.Users.CONNECTION_ID])
    if definitions.Users.USERNAME not in item:
      invalid_users.append(user)

  logger.debug("Invalid users: {}", invalid_users)

  if len(invalid_users) > 0:
    _send_to_connection(connectionID, _build_response_detailed(400, action, invalid_users), event)
    return _build_response(400, "Invalid users {}".format(invalid_users))

  groups_table = dynamodb.Table(definitions.Groups.TABLE_NAME)
  group_id = secrets.token_urlsafe(16)
  timestamp = time.time_ns()
  logger.debug("Generated group_id: {} Timestamp: {}", group_id, timestamp)
  logger.debug("Users before if: {}", users)
  if len(users) > 0:
    while True:
      try:
        groups_table.put_item(
          Item={
            definitions.Groups.GROUP_ID: group_id,
            definitions.Groups.USERNAME: username,
            definitions.Groups.JOIN_TIMESTAMP: timestamp,
            definitions.Groups.NICKNAME: nickname,
            # TODO Check if this does anything or if we need it
            definitions.Groups.EXCLUSION_LIST: list()
          },
          ConditionExpression='attribute_not_exists({}) AND attribute_not_exists({})'.format(
            definitions.Groups.GROUP_ID,
            definitions.Groups.USERNAME
          )
        )
        break
      except botocore.exceptions.ClientError as cle:
        # ConditionalCheckFailedException is okay, rest are not
        logger.debug("Exception raised")
        if cle.response['Error']['Code'] != 'ConditionalCheckFailedException':
          error_message = "Unexpected exception: {}".format(cle)
          logger.debug(error_message)
          _send_to_connection(connectionID, _build_response_detailed(500, action, "Server Error"), event)
          return _build_response(500, error_message)
        group_id = secrets.token_urlsafe(16)
        logger.debug("Regenerated group_id: {}", group_id)
        continue
    logger.debug("Users before iteration: {}", users)
    for user in users:
      try:
        groups_table.put_item(
          Item={
            definitions.Groups.GROUP_ID: group_id,
            definitions.Groups.USERNAME: user,
            definitions.Groups.JOIN_TIMESTAMP: timestamp,
            definitions.Groups.NICKNAME: nickname,
            # TODO: Check if this does anything / if we need it
            definitions.Groups.EXCLUSION_LIST: list()
          },
          ConditionExpression='attribute_not_exists({}) AND attribute_not_exists({})'.format(
            definitions.Groups.GROUP_ID,
            definitions.Groups.USERNAME
          )
        )
      except botocore.exceptions.ClientError as cle:
        # ConditionalCheckFailedException is okay, rest are not
        logger.debug(cle)
        logger.debug("Exception raised - there should not be an issue adding users")
        _send_to_connection(connectionID, _build_response_detailed(500, action, "Server Error"), event)
        return _build_response(500, "Error adding additional users")
        # if e.response['Error']['Code'] != 'ConditionalCheckFailedException':
        #   raise
        # else:
        #   group_id = secrets.token_urlsafe(16)
        #   continue
  # alert everyone a group has been made? send the init memo to all groups
    logger.debug("group created")
    message = "Group created"
  else:
    logger.debug("Self group attempted")
    message = "Cannot make a self group"

  # Init memo

  messages_table = dynamodb.Table(definitions.Messages.TABLE_NAME)
  try:
    message = {
      definitions.Messages.USERNAME: None,
      definitions.Messages.GROUP_ID: group_id,
      definitions.Messages.TIMESTAMP: timestamp,
      definitions.Messages.CONTENT: "Group created at: {}".format(datetime.fromtimestamp(timestamp // 1000000000).strftime('%m/%d/%Y %H:%M')),
      definitions.Groups.NICKNAME: nickname
      # definitions.Messages.INIT: True
    }
    messages_table.put_item(
      Item=message,
      ConditionExpression='attribute_not_exists({}) AND attribute_not_exists({})'.format(
        definitions.Messages.GROUP_ID,
        definitions.Messages.TIMESTAMP
      )
    )
  except botocore.exceptions.ClientError:
    # Should be no errors as this is the first message to be inserted
    logger.debug("Exception raised - error inserting init message")
    _send_to_connection(connectionID, _build_response_detailed(500, action, "Server Error"), event)
    return _build_response(500, "Error inserting init message")

  # Send init message to all clients

  lambda_client = boto3.client("lambda")
  message[definitions.Messages.TIMESTAMP] = datetime.fromtimestamp(
    message[definitions.Messages.TIMESTAMP] // 1000000000
  ).strftime('%m/%d/%Y %H:%M')
  params = {
    "body": {
      "all_messages": [message]
    }
  }
  invoke_response = lambda_client.invoke(
    FunctionName="chat-application-dev-sideMessageRender",
    InvocationType="RequestResponse",
    Payload=json.dumps(params)
  )
  logger.debug("invoke_response: {}", invoke_response)
  payload = invoke_response['Payload'].read().decode()
  logger.debug("Payload: '{}' type: {}", payload, type(payload))
  payload_dict = json.loads(payload)
  side_message_html = payload_dict['body']
  logger.debug("online_users: {}", online_users)
  for online_user in online_users:
    _send_to_connection(online_user, _build_response_detailed(200, "init", side_message_html), event)

  # Success message to group creater

  _send_to_connection(connectionID, _build_response_detailed(200, action, "Group Created"), event)
  return _build_response(200, message)
def create_user(event, context):
    """
  Endpoint to create a chat user
  """
    logger.info("User creation via WebSocket")
    logger.debug("event: {}", str(event))
    connectionID = event["requestContext"].get("connectionId")
    logger.debug("connectionID: {}", connectionID)

    # Check for required components
    body = _fetch_body(event, logger)
    for attribute in [definitions.Users.USERNAME, definitions.Users.PASSWORD]:
        if attribute not in body:
            error_message = "Improper message format: `{}' missing from message JSON".format(
                attribute)
            logger.debug(error_message)
            _send_to_connection(
                connectionID,
                _build_response_detailed(400, action, error_message), event)
            return _build_response(400, error_message)

    username = body[definitions.Users.USERNAME]
    if username == "System":
        _send_to_connection(
            connectionID,
            _build_response_detailed(401, action, "Forbidden Username"), event)
        return _build_response(401, "Forbidden Username")
    password = body[definitions.Users.PASSWORD]
    logger.debug("username: '******', password: '******'", username, password)
    users_table = dynamodb.Table(definitions.Users.TABLE_NAME)
    try:
        users_table.put_item(
            Item={
                definitions.Users.USERNAME: username,
                definitions.Users.PASSWORD: password,
                definitions.Users.CONNECTION_ID: set([connectionID])
            },
            ConditionExpression='attribute_not_exists({})'.format(
                definitions.Users.USERNAME))
    except botocore.exceptions.ClientError as cle:
        # ConditionalCheckFailedException is okay, rest are not
        logger.debug("Exception raised: {}", str(cle))
        if cle.response['Error']['Code'] != 'ConditionalCheckFailedException':
            # TODO: Handle gracefully with a 500 response
            raise
        _send_to_connection(
            connectionID,
            _build_response_detailed(401, action, "Invalid username/password"),
            event)
        return _build_response(401, "Username not unique")
    # Updates the connections Table to have the username added
    local_var = ":user"
    connections_table = dynamodb.Table(definitions.Connections.TABLE_NAME)
    result = connections_table.update_item(
        Key={definitions.Connections.CONNECTION_ID: connectionID},
        UpdateExpression="set {} = {}".format(definitions.Connections.USERNAME,
                                              local_var),
        ExpressionAttributeValues={local_var: username},
        ReturnValues="UPDATED_NEW")
    if result['ResponseMetadata'][
            'HTTPStatusCode'] == 200 and 'Attributes' in result:
        logger.debug(result['Attributes'][definitions.Connections.USERNAME])
    else:
        logger.error("Connections table not properly updated - result: {}",
                     result)
        _send_to_connection(
            connectionID,
            _build_response_detailed(500, action, "Internal server error"),
            event)
        return _build_response(500, "Internal server error")
    # TODO: Send blank home screen to client
    _send_to_connection(connectionID,
                        _build_response_detailed(200, action, "Login Success"),
                        event)
    return _build_response(200, "User {} created".format(username))
def login(event, context):
    """
  Handles a login attempt via WebSocket
  """
    logger.info("Login request via WebSocket")
    logger.debug("event: {}", str(event))
    connectionID = event["requestContext"].get("connectionId")
    logger.debug("connectionID: {}", connectionID)

    # Validates username and password fields are present and fetches them
    body = _fetch_body(event, logger)
    for attribute in [definitions.Users.USERNAME, definitions.Users.PASSWORD]:
        if attribute not in body:
            error_message = "Improper message format: `{}' missing from message JSON".format(
                attribute)
            logger.debug(error_message)
            _send_to_connection(
                connectionID,
                _build_response_detailed(400, action, error_message), event)
            return _build_response(400, error_message)
    username = body[definitions.Users.USERNAME]
    password = body[definitions.Users.PASSWORD]
    logger.debug("username: '******', password: '******'", username, password)
    # Queries for the password to the username from the Table
    users_table = dynamodb.Table(definitions.Users.TABLE_NAME)
    response = users_table.query(
        ProjectionExpression=definitions.Users.PASSWORD,
        KeyConditionExpression=boto3.dynamodb.conditions.Key(
            definitions.Users.USERNAME).eq(username))
    logger.debug("response: {}", response)
    items = response.get("Items", [])
    logger.debug("items: {}", items)
    if len(items) > 0:
        item = items[0]
    else:
        # Case: User does not exist
        logger.debug("No user with name: {}", username)
        _send_to_connection(
            connectionID,
            _build_response_detailed(401, action, "Invalid username/password"),
            event)
        return _build_response(401, "Invalid username/password")
    # Validates username/password
    if definitions.Users.PASSWORD in item:
        correct_password = item[definitions.Users.PASSWORD]
        if (password != correct_password):
            _send_to_connection(
                connectionID,
                _build_response_detailed(401, action,
                                         "Invalid username/password"), event)
            return _build_response(401, "Invalid username/password")
    else:
        # Case: User record without a password field
        _send_to_connection(
            connectionID,
            _build_response_detailed(500, action, "Invalid user record"),
            event)
        return _build_response(401, "Invalid user record")
    # Updates the user to have this connectionID appended
    local_var = ":connID"
    result = users_table.update_item(
        Key={definitions.Users.USERNAME: username},
        UpdateExpression="add {} {}".format(definitions.Users.CONNECTION_ID,
                                            local_var),
        ExpressionAttributeValues={local_var: set([connectionID])},
        ReturnValues="UPDATED_NEW")
    if result['ResponseMetadata'][
            'HTTPStatusCode'] == 200 and 'Attributes' in result:
        logger.debug(result['Attributes'][definitions.Users.CONNECTION_ID])
    else:
        logger.error("User table not properly updated - result: {}", result)
        _send_to_connection(
            connectionID,
            _build_response_detailed(500, action, "Internal server error"),
            event)
        return _build_response(500, "Internal server error")
    # Updates the connections Table to have the username added
    local_var = ":user"
    connections_table = dynamodb.Table(definitions.Connections.TABLE_NAME)
    result = connections_table.update_item(
        Key={definitions.Connections.CONNECTION_ID: connectionID},
        UpdateExpression="set {} = {}".format(definitions.Connections.USERNAME,
                                              local_var),
        ExpressionAttributeValues={local_var: username},
        ReturnValues="UPDATED_NEW")
    if result['ResponseMetadata'][
            'HTTPStatusCode'] == 200 and 'Attributes' in result:
        logger.debug(result['Attributes'][definitions.Connections.USERNAME])
    else:
        logger.error("Connections table not properly updated - result: {}",
                     result)
        _send_to_connection(
            connectionID,
            _build_response_detailed(500, action, "Internal server error"),
            event)
        return _build_response(500, "Internal server error")
    # obj = s3.Object('chat-application-upload-bucket-11097', 'chat_template')
    _send_to_connection(connectionID,
                        _build_response_detailed(200, action, "Login Success"),
                        event)
    return _build_response(200, "Login successful")
def send_message(event, context):
    """
  Forward a message to all appropraite clients
  """
    logger.debug("environ var: {} type: {}", os.environ.get('IS_OFFLINE'),
                 type(os.environ.get('IS_OFFLINE')))
    logger.info("Message sent via WebSocket")
    logger.debug("event: {}", str(event))
    connectionID = event["requestContext"].get("connectionId")
    logger.debug("connectionID: {}", connectionID)

    # Validate that user is logged in

    connections_table = dynamodb.Table(definitions.Connections.TABLE_NAME)
    response = connections_table.query(
        ProjectionExpression=definitions.Connections.USERNAME,
        KeyConditionExpression=boto3.dynamodb.conditions.Key(
            definitions.Connections.CONNECTION_ID).eq(connectionID))
    logger.debug("response: {}", response)
    items = response.get("Items", [])
    logger.debug("items: {}", items)
    if len(items) > 0:
        item = items[0]
    else:
        logger.error(
            "user password query returned not even an empty set items: {}",
            items)
        _send_to_connection(
            connectionID, _build_response_detailed(500, action,
                                                   "Server error"), event)
        return _build_response(500, "Server error")
    if definitions.Connections.USERNAME not in item:
        _send_to_connection(
            connectionID, _build_response_detailed(401, action,
                                                   "Not logged in"), event)
        return _build_response(401, "Not logged in")

    username = item[definitions.Connections.USERNAME]

    # Check for required components
    body = _fetch_body(event, logger)
    for attribute in [
            definitions.Messages.CONTENT, definitions.Messages.GROUP_ID
    ]:
        if attribute not in body:
            error_message = "Improper message format: `{}' missing from message JSON".format(
                attribute)
            logger.debug(error_message)
            return _build_response(400, error_message)

    content = body[definitions.Messages.CONTENT]
    group = body[definitions.Messages.GROUP_ID]

    logger.debug("username: '******', content: '{}', group: '{}'", username,
                 content, group)

    # Validate that user is in group before sending message

    groups_table = dynamodb.Table(definitions.Groups.TABLE_NAME)
    response = groups_table.query(
        ProjectionExpression="{}, {}".format(definitions.Groups.USERNAME,
                                             definitions.Groups.NICKNAME),
        KeyConditionExpression=boto3.dynamodb.conditions.Key(
            definitions.Groups.GROUP_ID).eq(group))
    logger.debug("response: {}", response)
    users = response.get("Items", [])
    # logger.debug("items: {}".format(items))
    # users = items # [item[definitions.Groups.USERNAME] for item in items if definitions.Groups.USERNAME in item]
    logger.debug("users: {}", users)

    ok = False
    nickname = None
    for user in users:
        if definitions.Groups.USERNAME in user:
            if user[definitions.Groups.USERNAME] == username:
                ok = True
                nickname = user[definitions.Groups.NICKNAME]
                break
        else:
            logger.error("Invalid record {}", user)
            _send_to_connection(
                connectionID,
                _build_response_detailed(500, action, "Internal server error"),
                event)
            return _build_response(500, "Internal Server Error")
    if not ok:
        logger.debug("User: {} not a member of {} users: {}", username, group,
                     users)
        _send_to_connection(
            connectionID,
            _build_response_detailed(403, action,
                                     "user not a member of this group"), event)
        return _build_response(403, "user not member of group")

    messages_table = dynamodb.Table(definitions.Messages.TABLE_NAME)
    while True:
        timestamp = time.time_ns()
        logger.debug("Timestamp: {}", timestamp)
        try:
            messages_table.put_item(
                Item={
                    definitions.Messages.GROUP_ID: group,
                    definitions.Messages.TIMESTAMP: timestamp,
                    definitions.Messages.USERNAME: username,
                    definitions.Messages.CONTENT: content
                    # definitions.Messages.GROUP_NAME: nickname,
                    # definitions.Messages.INIT: False
                },
                ConditionExpression=
                'attribute_not_exists({}) AND attribute_not_exists({})'.format(
                    definitions.Messages.GROUP_ID,
                    definitions.Messages.TIMESTAMP))
            break
        except botocore.exceptions.ClientError as cle:
            # ConditionalCheckFailedException is okay (collision with timestamp), rest are not
            logger.debug("Exception raised")
            if cle.response['Error'][
                    'Code'] != 'ConditionalCheckFailedException':
                # TODO: Handle gracefully with a 500 response
                raise
            continue

    # Query all the users for their connectionIDs
    users_table = dynamodb.Table(definitions.Users.TABLE_NAME)
    user_connIDs = {}
    for user in users:
        response = users_table.query(
            ProjectionExpression=definitions.Users.CONNECTION_ID,
            KeyConditionExpression=boto3.dynamodb.conditions.Key(
                definitions.Users.USERNAME).eq(
                    user[definitions.Users.USERNAME]))
        logger.debug("response: {}", response)
        items = response.get("Items", [])
        logger.debug("items: {}", items)
        if len(items) > 0:
            item = items[0]
        else:
            logger.error(
                "user connection query returned not even an empty set items: {}",
                items)
            return _build_response(500, "Server error")
        if definitions.Users.CONNECTION_ID in item:
            conn_ID = item[definitions.Users.CONNECTION_ID]
            if conn_ID is not None:
                user_connIDs[user[definitions.Users.USERNAME]] = conn_ID
    logger.debug("user_connIDs: {}", user_connIDs)
    lambda_client = boto3.client("lambda")
    # Cycle through connectionID's sending out memo
    # TODO: send out to yourself first to lower latency for current user
    message = {
        definitions.Messages.USERNAME:
        username,
        definitions.Messages.GROUP_ID:
        group,
        definitions.Messages.TIMESTAMP:
        datetime.fromtimestamp(timestamp //
                               1000000000).strftime('%m/%d/%Y %H:%M'),
        definitions.Messages.CONTENT:
        content,
        definitions.Groups.NICKNAME:
        nickname
        # definitions.Messages.INIT: False
    }
    for user in user_connIDs:
        params = {"body": {"username": user, "all_messages": [[message]]}}
        invoke_response = lambda_client.invoke(
            FunctionName="chat-application-dev-fullActiveMessageRender",
            InvocationType="RequestResponse",
            Payload=json.dumps(params))
        logger.debug("invoke_response: {}", invoke_response)
        payload = invoke_response['Payload'].read().decode()
        logger.debug("Payload: '{}' type: {}", payload, type(payload))
        payload_dict = json.loads(payload)
        active_message_html = payload_dict['body']
        logger.debug("Payload_dict: '{}' type: {}", payload_dict,
                     type(payload_dict))
        params = {"body": {"all_messages": [message]}}
        invoke_response = lambda_client.invoke(
            FunctionName="chat-application-dev-sideMessageRender",
            InvocationType="RequestResponse",
            Payload=json.dumps(params))
        logger.debug("invoke_response: {}", invoke_response)
        payload = invoke_response['Payload'].read().decode()
        logger.debug("Payload: '{}' type: {}", payload, type(payload))
        payload_dict = json.loads(payload)
        side_message_html = payload_dict['body']
        logger.debug("item: {} body: {}", item, side_message_html)
        data = {
            "action": action,
            "statusCode": 200,
            "body": {
                "group": group,
                "sideMessage": side_message_html,
                "activeMessage": active_message_html
            }
        }
        logger.debug("Data: {}", data)
        for user_individual_conn_ID in user_connIDs[user]:
            _send_to_connection(user_individual_conn_ID, data, event)

    return _build_response(200, "Message distributed to all connections")
def signup(event, context):
    """
  Handles a register attempt via WebSocket
  """
    logger.info("Login request via WebSocket")
    logger.debug("event: {}", str(event))
    connectionID = event["requestContext"].get("connectionId")
    logger.debug("connectionID: {}", connectionID)

    # Validates username and password fields are present and fetches them
    body = _fetch_body(event, logger)
    for attribute in [definitions.Users.USERNAME, definitions.Users.PASSWORD]:
        if attribute not in body:
            error_message = "Improper message format: `{}' missing from message JSON".format(
                attribute)
            logger.debug(error_message)
            return _build_response(400, error_message)
    username = body[definitions.Users.USERNAME]
    password = body[definitions.Users.PASSWORD]
    logger.debug("username: '******', password: '******'", username, password)
    # Queries for the password to the username from the Table
    users_table = dynamodb.Table(definitions.Users.TABLE_NAME)
    response = users_table.query(
        ProjectionExpression=definitions.Users.USERNAME,
        KeyConditionExpression=boto3.dynamodb.conditions.Key(
            definitions.Users.USERNAME).eq(username))
    logger.debug("response: {}", response)
    items = response.get("Items", [])
    logger.debug("items: {}", items)
    # Check if username already exists
    if len(items) >= 0:
        item = items[0]
        if len(item) != 0:
            return _build_response(401, "Invalid username")
    else:
        logger.error("username query returned not even an empty set items: {}",
                     items)
        return _build_response(500, "Server error")
    # Creates the new user
    users_table.put_item(
        Item={
            definitions.Users.USERNAME: username,
            definitions.Users.PASSWORD: password,
            definitions.Users.CONNECTION_ID: [connectionID]
        })
    # Updates the connections Table to have the username added
    local_var = ":user"
    connections_table = dynamodb.Table(definitions.Connections.TABLE_NAME)
    result = connections_table.update_item(
        Key={definitions.Connections.CONNECTION_ID: connectionID},
        UpdateExpression="set {} = {}".format(definitions.Connections.USERNAME,
                                              local_var),
        ExpressionAttributeValues={local_var: username},
        ReturnValues="UPDATED_NEW")
    if result['ResponseMetadata'][
            'HTTPStatusCode'] == 200 and 'Attributes' in result:
        logger.debug(result['Attributes'][definitions.Connections.USERNAME])
    else:
        logger.error("Connections table not properly updated - result: {}",
                     result)
        return _build_response(500, "Internal server error")
    # TODO: Send blank home screen to client
    return _build_response(200, "Signup successful")
Exemplo n.º 13
0
def leave_group(event, context):
  """
  Leave a selected group
  """
  logger.info("Leaving a group")
  connectionID = event["requestContext"].get("connectionId")
  logger.debug("connectionID: {}", connectionID)

  # Validate the connection is logged in

  connections_table = dynamodb.Table(definitions.Connections.TABLE_NAME)
  response = connections_table.query(
    ProjectionExpression=definitions.Connections.USERNAME,
    KeyConditionExpression=boto3.dynamodb.conditions.Key(definitions.Connections.CONNECTION_ID).eq(connectionID)
  )
  logger.debug("response: {}", response)
  items = response.get("Items", [])
  logger.debug("items: {}", items)
  if len(items) >= 0:
    item = items[0]
  else:
    logger.error("user password query returned not even an empty set items: {}", items)
    _send_to_connection(connectionID, _build_response_detailed(500, action, "Server error"), event)
    return _build_response(500, "Server error")
  if definitions.Connections.USERNAME not in item:
    _send_to_connection(connectionID, _build_response_detailed(401, action, "Not logged in"), event)
    return _build_response(401, "Not logged in")

  username = item[definitions.Connections.USERNAME]

  # Validate passed parameters

  body = _fetch_body(event, logger)
  for attribute in [definitions.Groups.GROUP_ID]:
    if attribute not in body:
      error_message = "Improper message format: `{}' missing from message JSON".format(attribute)
      logger.debug(error_message)
      _send_to_connection(connectionID, _build_response_detailed(400, action, error_message), event)
      return _build_response(400, error_message)

  group = body[definitions.Groups.GROUP_ID]
  logger.debug("group_id: {}", group)

  # Validate user is a member of the group

  local_name = ":exclusion"
  #local_name_2 = ":leaveTimestmp"
  timestamp = time.time_ns()
  logger.debug("Leaving timestamp: {}", timestamp)
  groups_table = dynamodb.Table(definitions.Groups.TABLE_NAME)
  try:
    response = groups_table.update_item(
      Key={
        definitions.Groups.GROUP_ID: group,
        definitions.Groups.USERNAME: username
      },
      # TODO: List append for the exclusion list
      UpdateExpression="set {} = list_append({}, {})".format(
        definitions.Groups.EXCLUSION_LIST,
        definitions.Groups.EXCLUSION_LIST,
        local_name
      ),
      ConditionExpression='attribute_exists({})'.format(
        definitions.Groups.GROUP_ID
      ),
      ExpressionAttributeValues={
        local_name: [timestamp]
      },
      ReturnValues="UPDATED_NEW"
      # ProjectionExpression="{}, {}".format(definitions.Groups.USERNAME, definitions.Groups.NICKNAME),
      # KeyConditionExpression=boto3.dynamodb.conditions.Key(definitions.Groups.GROUP_ID).eq(group)
    )
    logger.debug("response: {}", response)
  except botocore.exceptions.ClientError as cle:
    # ConditionalCheckFailedException is okay, rest are not
    logger.debug("Exception raised")
    if cle.response['Error']['Code'] != 'ConditionalCheckFailedException':
      logger.debug("Unexpected exception: {}", cle)
      _send_to_connection(connectionID, _build_response_detailed(500, action, "Internal Server Error"), event)
      return _build_response(500, "Unexpected exception")
    logger.debug("User: {} not a member of {}", username, group)
    _send_to_connection(connectionID, _build_response_detailed(403, action, "user not a member of this group"), event)
    return _build_response(403, "user not member of group")

  _send_to_connection(connectionID, _build_response_detailed(200, action, "Successfully left group"), event)
  return _build_response(200, "Successfully left group")
def fetch_groups(event, context):
    """
  Fetch groups for side bar endpoint
  """
    logger.info("fetch groups request via WebSocket")
    logger.debug("event: {}", str(event))
    connectionID = event["requestContext"].get("connectionId")
    logger.debug("connectionID: {}", connectionID)

    # Validate that user is logged in
    connections_table = dynamodb.Table(definitions.Connections.TABLE_NAME)
    response = connections_table.query(
        ProjectionExpression=definitions.Connections.USERNAME,
        KeyConditionExpression=boto3.dynamodb.conditions.Key(
            definitions.Connections.CONNECTION_ID).eq(connectionID))
    logger.debug("response: {}", response)
    items = response.get("Items", [])
    logger.debug("items: {}", items)
    if len(items) > 0:
        item = items[0]
    else:
        logger.error(
            "user password query returned not even an empty set items: {}",
            items)
        _send_to_connection(
            connectionID, _build_response_detailed(500, action,
                                                   "Server error"), event)
        return _build_response(500, "Server error")
    if definitions.Connections.USERNAME not in item:
        _send_to_connection(
            connectionID, _build_response_detailed(401, action,
                                                   "Not logged in"), event)
        return _build_response(401, "Not logged in")

    username = item[definitions.Connections.USERNAME]

    groups_table = dynamodb.Table(definitions.Groups.TABLE_NAME)
    response = groups_table.scan(
        ProjectionExpression="{}, {}".format(definitions.Groups.GROUP_ID,
                                             definitions.Groups.NICKNAME),
        FilterExpression=boto3.dynamodb.conditions.Attr(
            definitions.Groups.USERNAME).eq(username))
    items = response.get("Items", [])
    logger.debug("items: {}", str(items))
    # How many groups there are
    item_len = len(items)
    logger.debug("groups: {}", item_len)
    last_messages = []
    # Query Last Message for each
    for group in items:
        local_name = ":grp"
        messages_table = dynamodb.Table(definitions.Messages.TABLE_NAME)

        # TODO: Need to do selective query here depending on if user has left the group or not

        response = messages_table.query(
            KeyConditionExpression="{} = {}".format(
                definitions.Messages.HASH_KEY, local_name),
            ExpressionAttributeValues={
                local_name: group[definitions.Groups.GROUP_ID]
            },
            Limit=1,
            ScanIndexForward=False)
        items = response.get("Items", [])
        logger.debug("items: {}", str(items))
        message = items[0]
        logger.debug("message: {}", str(message))
        last_messages.append({
            definitions.Messages.USERNAME:
            message[definitions.Messages.USERNAME]
            if definitions.Messages.USERNAME in message else None,
            definitions.Messages.GROUP_ID:
            group[definitions.Groups.GROUP_ID],
            definitions.Messages.TIMESTAMP:
            datetime.fromtimestamp(message[definitions.Messages.TIMESTAMP] //
                                   1000000000).strftime('%m/%d/%Y %H:%M'),
            definitions.Messages.CONTENT:
            message[definitions.Messages.CONTENT],
            definitions.Groups.NICKNAME:
            group[definitions.Groups.NICKNAME],
            # definitions.Messages.INIT: message[definitions.Messages.INIT] if definitions.Messages.INIT in message else False
        })
    logger.debug("last_messages before sort: {}", last_messages)
    last_messages.sort(key=lambda item: item[definitions.Messages.TIMESTAMP],
                       reverse=True)
    logger.debug("last_messages after sort: {}", last_messages)
    # Render messages
    params = {"body": {"all_messages": last_messages}}
    lambda_client = boto3.client("lambda")
    invoke_response = lambda_client.invoke(
        FunctionName="chat-application-dev-fetchChatRender",
        InvocationType="RequestResponse",
        Payload=json.dumps(params))
    payload = invoke_response['Payload'].read().decode()
    logger.debug("Payload: '{}' type: {}", payload, type(payload))
    payload_dict = json.loads(payload)
    logger.debug("Payload_dict: '{}' type: {}", payload_dict,
                 type(payload_dict))
    _send_to_connection(
        connectionID,
        _build_response_detailed(200, action, payload_dict["body"]), event)
    return _build_response(200, "Groups fetched")
Exemplo n.º 15
0
def get_recent_messages(event, context):
    """
  Return the recent N most messages specified by client
  """
    logger.info("Retrieving the N most recent messages")
    connectionID = event["requestContext"].get("connectionId")
    logger.debug("connectionID: {}", connectionID)

    # Validate the connection is logged in

    connections_table = dynamodb.Table(definitions.Connections.TABLE_NAME)
    response = connections_table.query(
        ProjectionExpression=definitions.Connections.USERNAME,
        KeyConditionExpression=boto3.dynamodb.conditions.Key(
            definitions.Connections.CONNECTION_ID).eq(connectionID))
    logger.debug("response: {}", response)
    items = response.get("Items", [])
    logger.debug("items: {}", items)
    if len(items) >= 0:
        item = items[0]
    else:
        logger.error(
            "user password query returned not even an empty set items: {}",
            items)
        _send_to_connection(
            connectionID, _build_response_detailed(500, action,
                                                   "Server error"), event)
        return _build_response(500, "Server error")
    if definitions.Connections.USERNAME not in item:
        _send_to_connection(
            connectionID, _build_response_detailed(401, action,
                                                   "Not logged in"), event)
        return _build_response(401, "Not logged in")

    username = item[definitions.Connections.USERNAME]

    default_fetch = 10  # Default number of messages to fetch

    body = _fetch_body(event, logger)
    for attribute in [definitions.Messages.GROUP_ID, "count"]:
        if attribute not in body:
            error_message = "Improper message format: `{}' missing from message JSON".format(
                attribute)
            logger.debug(error_message)
            return _build_response(400, error_message)

    group = body[definitions.Messages.GROUP_ID]
    count = body["count"]

    # Validate the user is a member of the group

    groups_table = dynamodb.Table(definitions.Groups.TABLE_NAME)
    response = groups_table.query(
        # ProjectionExpression="{}, {}".format(definitions.Groups.USERNAME, definitions.Groups.NICKNAME),
        KeyConditionExpression=boto3.dynamodb.conditions.Key(
            definitions.Groups.GROUP_ID).eq(group))
    items = response.get("Items", [])
    logger.debug("items: {}", str(items))
    ok = False
    # current_user_exclusion_list = []
    # current_user_left_timestamp = -1
    for item in items:
        if item[definitions.Groups.USERNAME] == username:
            ok = True
            current_user_exclusion_list = item[definitions.Groups.EXCLUSION_LIST] if definitions.Groups.EXCLUSION_LIST in item else []
            current_user_left = bool(current_user_exclusion_list) and len(
                current_user_exclusion_list[-1]) == 1
            # Note if the current user has left the group
            # if definitions.Groups.LEFT in item and item[definitions.Groups.LEFT] == True:
            #   current_user_left = True
            #   current_user_left_timestamp = item[definitions.Groups.LEFT_TIMESTAMP]
            break
    if not ok:
        _send_to_connection(
            connectionID,
            _build_response_detailed(403, action, "Not a member of group"),
            event)
        return _build_response(403, "Not a member of group")

    logger.debug("current_user_left: {} current_user_exclusion_list: {} ",
                 current_user_left, current_user_exclusion_list)

    logger.debug("group: {} count: {} count_type: {}", group, count,
                 type(count))
    count = int(count)
    message_number = count + default_fetch
    messages_table = dynamodb.Table(definitions.Messages.TABLE_NAME)
    local_name_1 = ":group_id"
    local_name_2_template = ":timestmp{}_{}"
    # local_name_2 = ":timestmp"
    if current_user_left:
        expression_attribute_values = {
            definitions.Messages.HASH_KEY: local_name_1
        }
        key_condition_expression = [[
            "{} = {} and (".format(definitions.Messages.HASH_KEY, local_name_1)
        ]]
        for i, exclusion_item in enumerate(current_user_exclusion_list):
            exclusion_pair = []
            temp_local_name_2 = local_name_2_template.format(i, 0)
            message = "{} <= {}".format(definitions.Messages.RANGE_KEY,
                                        temp_local_name_2)
            expression_attribute_values[temp_local_name_2] = exclusion_item[0]
            if len(exclusion_item) > 1:
                exclusion_pair.append(message)
                temp_local_name_2 = local_name_2_template.format(i, 1)
                exclusion_pair.append("{} >= {}".format(
                    definitions.Messages.RANGE_KEY, temp_local_name_2))
                expression_attribute_values[
                    temp_local_name_2] = exclusion_item[1]
            else:
                message += ")"
                exclusion_pair.append(message)

        logger.debug("KeyConditionExpression before join: {}",
                     key_condition_expression)
        key_condition_expression = " and ".join(
            " or ".join(item) for item in key_condition_expression)
        logger.debug("KeyConditionExpression after join: {}",
                     key_condition_expression)
        logger.debug("ExpressionAttributeValues: {}",
                     expression_attribute_values)
        response = messages_table.query(
            KeyConditionExpression=key_condition_expression,
            ExpressionAttributeValues=expression_attribute_values,
            Limit=message_number,
            ScanIndexForward=False)
        # response = messages_table.scan(
        #   FilterExpression="{} = {} AND {} <= {}".format(
        #     definitions.Messages.HASH_KEY,
        #     local_name_1,
        #     definitions.Messages.TIMESTAMP,
        #     local_name_2
        #   ),
        #   ExpressionAttributeValues={
        #     local_name_1: group,
        #     local_name_2: current_user_left_timestamp
        #   },
        #   # Limit=message_number,
        #   ScanIndexForward=False
        # )
        # items = response.get("Items", [])
        # logger.debug("items: {}".format(str(items)))
        # if len(items) > message_number:
        #   items = items[-message_number:]
        # logger.debug("items: {}".format(str(items)))
    else:
        response = messages_table.query(
            KeyConditionExpression="{} = {}".format(
                definitions.Messages.HASH_KEY, local_name_1),
            ExpressionAttributeValues={local_name_1: group},
            Limit=message_number,
            ScanIndexForward=False)
    items = response.get("Items", [])
    logger.debug("items: {}", str(items))
    # How many new messages were actually taken
    item_len = len(items)
    new_messages_count = item_len - count
    logger.debug("item_len: {} new_messages_count: {}", item_len,
                 new_messages_count)
    messages = []
    if (new_messages_count > 0):
        # Only get the newest messages
        # TODO Check if these are the newest messages? ScanIndexForward is reverse so these may be the oldest?
        items = items[:new_messages_count]
        logger.debug("New messages from items: {}", items)
        messages = [
            {
                definitions.Messages.USERNAME:
                item[definitions.Messages.USERNAME]
                if definitions.Messages.USERNAME in item else None,
                definitions.Messages.CONTENT:
                item[definitions.Messages.CONTENT],
                definitions.Messages.TIMESTAMP:
                datetime.fromtimestamp(item[definitions.Messages.TIMESTAMP] //
                                       1000000000).strftime('%m/%d/%Y %H:%M'),
                # definitions.Messages.INIT: x[definitions.Messages.INIT] if definitions.Messages.INIT in x else False
            } for item in items
        ]
        messages.reverse()
        memo_username = None
        all_messages = []
        temp_list = []
        # init_set = False
        for message in messages:
            if memo_username is None:
                memo_username = message[definitions.Messages.USERNAME]
                if temp_list:
                    all_messages.append(temp_list[:])
                    temp_list.clear()
            elif memo_username != message[definitions.Messages.USERNAME]:
                memo_username = message[definitions.Messages.USERNAME]
                all_messages.append(temp_list[:])
                temp_list.clear()
                # if init_set == True:
                #   init_set = False
            temp_list.append(message)
        if temp_list:
            all_messages.append(temp_list)
    else:
        logger.debug("No further new messages")
        return _build_response(200, "No further new messages")

    logger.debug("Messages: {}", str(messages))
    logger.debug("All Messages: {}", str(all_messages))

    # Send data to requesting client (render)

    params = {"body": {"username": username, "all_messages": all_messages}}
    lambda_client = boto3.client("lambda")
    invoke_response = lambda_client.invoke(
        FunctionName="chat-application-dev-fullActiveMessageRender",
        InvocationType="RequestResponse",
        Payload=json.dumps(params))
    payload = invoke_response['Payload'].read().decode()
    logger.debug("Payload: '{}' type: {}", payload, type(payload))
    payload_dict = json.loads(payload)
    logger.debug("Payload_dict: '{}' type: {}", payload_dict,
                 type(payload_dict))
    # body = {
    #   "group": group,
    #   "html": payload_dict["body"]
    # }
    _send_to_connection(
        connectionID,
        _build_response_detailed(200, action, payload_dict["body"]), event)
    return _build_response(
        200, "Sent {}-{} recent block of messages".format(item_len, count))