Search code examples
javascriptaws-lambdaamazon-dynamodbaws-api-gateway

Insert/Update Lambda works but calling it from API GW fails as the payload is being passed


I've got a Dynamo DB table:

Partition key: FingerPrint (String)
Sort key: IpAddress (String)
LastUpdated (Number)
TimesUpdated (Number)
Votes (string)

And I have this Lambda to insert or update items:

const AWS = require('aws-sdk');
// As request in comments:
console.log('Event:', event);

exports.handler = async (event) => {

  // Get the FingerPrint and IpAddress from the event
  const fingerPrint = event.FingerPrint;
  var ipAddress = '';
  try {
    ipAddress = event.client_ip;
    //ipAddress = event.identity.sourceIp;
  } catch (error) {
  }
  if (!ipAddress) {
    ipAddress = '100.100.100.100';
  }
  
  console.log('FingerPrint:', fingerPrint);
  console.log('IpAddress:', ipAddress);
  console.log('Votes:', event.Votes);

  // IT FAILS HERE!!
  if (!fingerPrint || !ipAddress || !event.Votes) {
    console.log('Missing required properties');
    // Handle the missing properties error appropriately
    return;
  }

  // Create a DynamoDB client
  const ddb = new AWS.DynamoDB();

  // Get the item from the table
  const params = {
    TableName: 'SRHTop100',
    Key: {
      'FingerPrint': { 'S': fingerPrint },
      'IpAddress': { 'S': ipAddress },
    },
  };

  console.log('Get Params:', params);
  const item = await ddb.getItem(params).promise();

  
  // Get the 8 digit date
  const getCurrentDate = () => {
    const date = new Date();
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, '0');
    const day = String(date.getDate()).padStart(2, '0');
    return `${year}${month}${day}`;
  };
  const lastUpdated = parseInt(getCurrentDate());

  // Check if the item exists
  if (item.Item) {
    const existingItem = item.Item;
    // Update the TimesUpdated field, votes, and lastUpdated
    existingItem.TimesUpdated.N = (parseInt(existingItem.TimesUpdated.N) + 1).toString();
    existingItem.LastUpdated.N = lastUpdated.toString();
    existingItem.Votes.S = event.Votes.toString();

    // Update the item in the table
    const updateParams = {
      TableName: 'SRHTop100',
      Key: {
        'FingerPrint': { 'S': fingerPrint },
        'IpAddress': { 'S': ipAddress },
      },
      UpdateExpression: 'SET TimesUpdated = :updated, LastUpdated = :lastUpdated, Votes = :votes',
      ExpressionAttributeValues: {
        ':updated': { 'N': existingItem.TimesUpdated.N },
        ':lastUpdated': { 'N': existingItem.LastUpdated.N },
        ':votes': { 'S': existingItem.Votes.S },
      },
    };
    console.log('Update Params:', updateParams);
    await ddb.updateItem(updateParams).promise();
  } else {
    // Create a new item in the table
    const newItem = {
      'FingerPrint': { 'S': fingerPrint },
      'IpAddress': { 'S': ipAddress },
      'LastUpdated': { 'N': lastUpdated.toString() },
      'TimesUpdated': { 'N': '1' },
      'Votes': { 'S': event.Votes.toString() },
    };
    const createParams = {
      TableName: 'SRHTop100',
      Item: newItem,
    };
    
    console.log('Create Params:', params);
    await ddb.putItem(createParams).promise();
  }
  console.log('Done');
};

Calling this method in Lambda with this payload works:

{
    "FingerPrint": "2163923702",
    "Votes": "{ '12': 1 }"
}

enter image description here

The problems when I call the Lambda via the API GW, Testing it fails with:

{"message": "Internal server error"}

enter image description here

I can see in the CloudWatch logs:

INFO FingerPrint: undefined
INFO IpAddress: 100.100.100.100
INFO Votes: undefined
INFO Missing required properties

As per the comments after printing out the event this is what I get:

2023-06-14T14:41:31.347Z    8b34c7b8-1934-48b3-b416-a29844b96533    INFO    Event: {
  resource: '/srhTop100Lambda',
  path: '/srhTop100Lambda',
  httpMethod: 'POST',
  headers: null,
  multiValueHeaders: null,
  queryStringParameters: null,
  multiValueQueryStringParameters: null,
  pathParameters: null,
  stageVariables: null,
  requestContext: {
    resourceId: 'fbpc7m',
    resourcePath: '/srhTop100Lambda',
    httpMethod: 'POST',
    extendedRequestId: 'Gg2wMEliywMFvog=',
    requestTime: '14/Jun/2023:14:41:30 +0000',
    path: '/srhTop100Lambda',
    accountId: '456567667775',
    protocol: 'HTTP/1.1',
    stage: 'test-invoke-stage',
    domainPrefix: 'testPrefix',
    requestTimeEpoch: 1686753690532,
    requestId: 'c1942d7f-9c29-4ed6-8f21-8e7de67139cd',
    identity: {
      cognitoIdentityPoolId: null,
      cognitoIdentityId: null,
      apiKey: 'test-invoke-api-key',
      principalOrgId: null,
      cognitoAuthenticationType: null,
      userArn: 'arn:aws:iam::456567667775:user/jezza.thompson',
      apiKeyId: 'test-invoke-api-key-id',
      userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Edg/114.0.1823.41',
      accountId: '456567667775',
      caller: 'AIDAWUTMNXQ75DPQPXNSV',
      sourceIp: 'test-invoke-source-ip',
      accessKey: 'ASIAWUTMNXQ7SFXRZFZQ',
      cognitoAuthenticationProvider: null,
      user: 'AIDAWUTMNXQ75DPQPXNSV'
    },
    domainName: 'testPrefix.testDomainName',
    apiId: 'lxpz295o9e'
  },
  body: '{\r\n' +
    '    "FingerPrint": "2163923702",\r\n' +
    '    "Votes": \r\n' +
    `        "{ '12': 2 }"\r\n` +
    '}',
  isBase64Encoded: false
}

I don't have any Mapping Templates and I'm not sure what's going on and why the Payload isn't being sent.


Solution

  • You are trying to parse event.Fingerprint but the FingerPrint is held in body:

    body: '{\r\n' +
        '    "FingerPrint": "2163923702",\r\n' +
        '    "Votes": \r\n' +
        `        "{ '12': 2 }"\r\n` +
        '}',
    

    So you need to parse the event.body and subtract your data from there.

    const requestBody = JSON.parse(event.body);
    var fingerPrint = requestBody.FingerPrint;
    var votes = requestBody.Votes;