Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions amplify-migration-apps/mood-board/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ button that returns random emojis and tracks clicks via Kinesis analytics.
- Board and MoodItem models use public auth (API key) - anyone can create/read/update/delete.
- Authenticated users can invoke the getRandomEmoji Lambda and read Kinesis events.
- Kinesis analytics tracks "Surprise Me" button clicks with timestamps.
- Kinesis stream trigger automatically counts processed events in a DynamoDB table.

## Install Dependencies

Expand Down Expand Up @@ -82,6 +83,7 @@ GraphQL API with schema containing:
- _MoodItem_ model with title, description, image, and board reference.
- _getRandomEmoji_ query that returns random emojis by invoking a Lambda function using the `@function` directive.
- _getKinesisEvents_ query that reads events from Kinesis stream using a Lambda function.
- _KinesisEventCount_ model for tracking the number of events processed by the Kinesis trigger.

```console
amplify add api
Expand Down Expand Up @@ -197,6 +199,52 @@ REGION
? Do you want to edit the local lambda function now? No
```

### Function (moodboardKinesisTrigger)

Node.js Lambda function triggered by the Kinesis stream. When records arrive in the stream,
this function fires automatically and increments a global event counter in the `KinesisEventCount`
DynamoDB table via the GraphQL API. Configured with mutation access to the API.

```console
amplify add function
```

```console
? Select which capability you want to add: Lambda function (serverless function)
? Provide an AWS Lambda function name: moodboardKinesisTrigger
? Choose the runtime that you want to use: NodeJS
? Choose the function template that you want to use: Lambda trigger
? What event source do you want to associate with Lambda trigger? Amazon Kinesis Stream
? Choose a Kinesis event source option Use Analytics category kinesis stream in the current Amplify project
Selected resource moodboardKinesis

✅ Available advanced settings:
- Resource access permissions
- Scheduled recurring invocation
- Lambda layers configuration
- Environment variables configuration
- Secret values configuration

? Do you want to configure advanced settings? Yes
? Do you want to access other resources in this project from your Lambda function? Yes
? Select the categories you want this function to have access to. api
? Select the operations you want to permit on moodboard Mutation

You can access the following resource attributes as environment variables from your Lambda function
API_MOODBOARD_GRAPHQLAPIENDPOINTOUTPUT
API_MOODBOARD_GRAPHQLAPIIDOUTPUT
API_MOODBOARD_GRAPHQLAPIKEYOUTPUT
ENV
REGION

? Do you want to invoke this function on a recurring schedule? No
? Do you want to enable Lambda layers for this function? No
? Do you want to configure environment variables for this function? No
? Do you want to configure secret values this function can access? No
✔ Choose the package manager that you want to use: · NPM
? Do you want to edit the local lambda function now? No
```

## Configure

```console
Expand All @@ -223,6 +271,8 @@ amplify push
├───────────┼──────────────────────────┼───────────┼───────────────────┤
│ Function │ moodboardKinesisReader │ Create │ awscloudformation │
├───────────┼──────────────────────────┼───────────┼───────────────────┤
│ Function │ moodboardKinesisTrigger │ Create │ awscloudformation │
├───────────┼──────────────────────────┼───────────┼───────────────────┤
│ Analytics │ moodboardKinesis │ Create │ awscloudformation │
└───────────┴──────────────────────────┴───────────┴───────────────────┘

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import { data } from './data/resource';
import { storage } from './storage/resource';
import { moodboardGetRandomEmoji } from './function/moodboardGetRandomEmoji/resource';
import { moodboardKinesisReader } from './function/moodboardKinesisReader/resource';
import { moodboardKinesisTrigger } from './function/moodboardKinesisTrigger/resource';
import { KinesisEventSource } from 'aws-cdk-lib/aws-lambda-event-sources';
import { StartingPosition } from 'aws-cdk-lib/aws-lambda';
import { Stream } from 'aws-cdk-lib/aws-kinesis';
import { defineBackend } from '@aws-amplify/backend';
import { defineAnalytics } from './analytics/resource';
import { Duration, aws_iam } from 'aws-cdk-lib';
Expand All @@ -13,6 +17,7 @@ const backend = defineBackend({
storage,
moodboardGetRandomEmoji,
moodboardKinesisReader,
moodboardKinesisTrigger,
});
const analytics = defineAnalytics(backend);
const cfnUserPool = backend.auth.resources.cfnResources.cfnUserPool;
Expand Down Expand Up @@ -84,3 +89,29 @@ backend.moodboardKinesisReader.resources.lambda.addToRolePolicy(
resources: [analytics.kinesisStreamArn],
})
);
backend.moodboardKinesisTrigger.resources.cfnResources.cfnFunction.functionName = `moodboardKinesisTrigger-${branchName}`;
backend.moodboardKinesisTrigger.addEnvironment(
'API_MOODBOARD_GRAPHQLAPIKEYOUTPUT',
backend.data.apiKey!
);
backend.moodboardKinesisTrigger.addEnvironment(
'API_MOODBOARD_GRAPHQLAPIENDPOINTOUTPUT',
backend.data.graphqlUrl
);
backend.moodboardKinesisTrigger.addEnvironment(
'API_MOODBOARD_GRAPHQLAPIIDOUTPUT',
backend.data.apiId
);
backend.data.resources.graphqlApi.grantMutation(
backend.moodboardKinesisTrigger.resources.lambda
);
const kinesisStream = Stream.fromStreamArn(
backend.moodboardKinesisTrigger.resources.lambda.stack,
'KinesisStream',
analytics.kinesisStreamArn
);
backend.moodboardKinesisTrigger.resources.lambda.addEventSource(
new KinesisEventSource(kinesisStream, {
startingPosition: StartingPosition.LATEST,
})
);
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ type Board @model @auth(rules: [{ allow: public }]) {
moodItems: [MoodItem] @hasMany(indexName: "byBoard", fields: ["id"])
}

type KinesisEventCount @model @auth(rules: [{ allow: public }]) {
id: ID!
processedAt: AWSDateTime!
}

type Query {
getRandomEmoji: String @function(name: "moodboardGetRandomEmoji-${branchName}") @auth(rules: [{ allow: private }])
getKinesisEvents: AWSJSON @function(name: "moodboardKinesisReader-${branchName}") @auth(rules: [{ allow: private }])
Expand All @@ -28,8 +33,9 @@ export const data = defineData({
//The "branchname" variable needs to be the same as your deployment branch if you want to reuse your Gen1 app tables
branchName: 'x',
modelNameToTableNameMapping: {
MoodItem: 'MoodItem-pd6mhagtyveltorgdvv7movvqq-x',
Board: 'Board-pd6mhagtyveltorgdvv7movvqq-x',
MoodItem: 'MoodItem-g26hrobfy5b5pniveska5ylg4u-x',
Board: 'Board-g26hrobfy5b5pniveska5ylg4u-x',
KinesisEventCount: 'KinesisEventCount-g26hrobfy5b5pniveska5ylg4u-x',
},
},
],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"key1": "value1",
"key2": "value2",
"key3": "value3"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/* Amplify Params - DO NOT EDIT
API_MOODBOARD_GRAPHQLAPIENDPOINTOUTPUT
API_MOODBOARD_GRAPHQLAPIIDOUTPUT
API_MOODBOARD_GRAPHQLAPIKEYOUTPUT
ANALYTICS_MOODBOARDKINESIS_KINESISSTREAMARN
ENV
REGION
Amplify Params - DO NOT EDIT */

const https = require('https');
const url = require('url');

const GRAPHQL_ENDPOINT = process.env.API_MOODBOARD_GRAPHQLAPIENDPOINTOUTPUT;
const API_KEY = process.env.API_MOODBOARD_GRAPHQLAPIKEYOUTPUT;

const createKinesisEventCount = /* GraphQL */ `
mutation CreateKinesisEventCount($input: CreateKinesisEventCountInput!) {
createKinesisEventCount(input: $input) {
id
}
}
`;

function graphqlRequest(query, variables) {
const endpoint = url.parse(GRAPHQL_ENDPOINT);
const body = JSON.stringify({ query, variables });

return new Promise((resolve, reject) => {
const req = https.request(
{
hostname: endpoint.hostname,
path: endpoint.path,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': API_KEY,
'Content-Length': Buffer.byteLength(body),
},
},
(res) => {
let data = '';
res.on('data', (chunk) => (data += chunk));
res.on('end', () => resolve(JSON.parse(data)));
},
);
req.on('error', reject);
req.write(body);
req.end();
});
}

exports.handler = async (event) => {
console.log('Kinesis trigger fired with', event.Records.length, 'records');

for (const record of event.Records) {

Check failure

Code scanning / CodeQL

Unused loop iteration variable Error

For loop variable record is not used in the loop body.

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused variable record.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
await graphqlRequest(createKinesisEventCount, {
input: {
processedAt: new Date().toISOString(),
},
});
}

console.log('Logged', event.Records.length, 'events');
return { statusCode: 200 };
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { defineFunction } from '@aws-amplify/backend';

const branchName = process.env.AWS_BRANCH ?? 'sandbox';

export const moodboardKinesisTrigger = defineFunction({
entry: './index.js',
name: `moodboardKinesisTrigger-${branchName}`,
timeoutSeconds: 25,
memoryMB: 128,
environment: { ENV: `${branchName}`, REGION: 'us-east-1' },
runtime: 22,
});
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"PolicyName": "moodboardKinesis-auth-policy-gen2-x",
"Roles": [
{
"Ref": "referencetoamplifymoodboarde2esandbox799a4d1a84authNestedStackauthNestedStackResourceCF49B256Outputsamplifymoodboarde2esandbox799a4d1a84authamplifyAuthauthenticatedUserRole65FDEE87Ref"
"Ref": "referencetoamplifymoodboarde2esandbox685dc54d14authNestedStackauthNestedStackResourceAFCFFB8EOutputsamplifymoodboarde2esandbox685dc54d14authamplifyAuthauthenticatedUserRoleECA75B03Ref"
}
]
},
Expand Down Expand Up @@ -49,7 +49,7 @@
"PolicyName": "moodboardKinesis-unauth-policy-gen2-x",
"Roles": [
{
"Ref": "referencetoamplifymoodboarde2esandbox799a4d1a84authNestedStackauthNestedStackResourceCF49B256Outputsamplifymoodboarde2esandbox799a4d1a84authamplifyAuthunauthenticatedUserRoleC0B7612DRef"
"Ref": "referencetoamplifymoodboarde2esandbox685dc54d14authNestedStackauthNestedStackResourceAFCFFB8EOutputsamplifymoodboarde2esandbox685dc54d14authamplifyAuthunauthenticatedUserRole92D42D98Ref"
}
]
},
Expand All @@ -62,7 +62,7 @@
"CDKMetadata": {
"Type": "AWS::CDK::Metadata",
"Properties": {
"Analytics": "v2:deflate64:H4sIAAAAAAAA/yXHUQrCMAwA0LPsv421MC+wfxV3AIltBlnXTpbUIWN3H+LX43nwrQPX4Co2xGQnfsF2JVGKvWJIBld5bokLCQt0Q+l1IcyGMf92nycO3910Q7lVfVfdTZkjwSinj/dwvoBrRmG2Sy3KmeDx9wDnXMcSdwAAAA=="
"Analytics": "v2:deflate64:H4sIAAAAAAAA/yXHUQqDMAwA0LP432ZdwBP4v415AKlthFhbh0knQ7z7GPt6PARsHbjG72JDTHbhEY4biVLs1Ydk/C7DkbiQsEA3lV438tmwz7891oXD5zTdVO5VX1VPU9ZIMMvljQh4BdfMwmy3WpQzwfPvF8PdMhZ3AAAA"
},
"Metadata": {
"aws:cdk:path": "amplify-moodboard-e2e-sandbox-x/analytics/CDKMetadata/Default"
Expand All @@ -80,7 +80,7 @@
"kinesisStreamShardCount": {
"Value": "1"
},
"amplifymoodboarde2esandbox799a4d1a84analyticsmoodboardKinesisKinesisStream1161343BArn": {
"amplifymoodboarde2esandbox685dc54d14analyticsmoodboardKinesisKinesisStream0B86977BArn": {
"Value": "arn:aws:kinesis:us-east-1:123456789012:stream/moodboardKinesis-gen2-x"
}
},
Expand Down Expand Up @@ -351,10 +351,10 @@
}
},
"Parameters": {
"referencetoamplifymoodboarde2esandbox799a4d1a84authNestedStackauthNestedStackResourceCF49B256Outputsamplifymoodboarde2esandbox799a4d1a84authamplifyAuthauthenticatedUserRole65FDEE87Ref": {
"referencetoamplifymoodboarde2esandbox685dc54d14authNestedStackauthNestedStackResourceAFCFFB8EOutputsamplifymoodboarde2esandbox685dc54d14authamplifyAuthauthenticatedUserRoleECA75B03Ref": {
"Type": "String"
},
"referencetoamplifymoodboarde2esandbox799a4d1a84authNestedStackauthNestedStackResourceCF49B256Outputsamplifymoodboarde2esandbox799a4d1a84authamplifyAuthunauthenticatedUserRoleC0B7612DRef": {
"referencetoamplifymoodboarde2esandbox685dc54d14authNestedStackauthNestedStackResourceAFCFFB8EOutputsamplifymoodboarde2esandbox685dc54d14authamplifyAuthunauthenticatedUserRole92D42D98Ref": {
"Type": "String"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": "us-east-1:edbce570-5f19-4755-9803-52e7b3e0c56d"
"cognito-identity.amazonaws.com:aud": "us-east-1:3a5eeecb-0ea0-4cbe-93f4-2094213caf65"
},
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "authenticated"
Expand Down Expand Up @@ -54,7 +54,7 @@
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": "us-east-1:edbce570-5f19-4755-9803-52e7b3e0c56d"
"cognito-identity.amazonaws.com:aud": "us-east-1:3a5eeecb-0ea0-4cbe-93f4-2094213caf65"
},
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "unauthenticated"
Expand Down Expand Up @@ -92,7 +92,7 @@
"CDKMetadata": {
"Type": "AWS::CDK::Metadata",
"Properties": {
"Analytics": "v2:deflate64:H4sIAAAAAAAA/02LwQrCMBBEv6X3dK0BvUtPXkQqniUmq26bbqBZLRLy71Kk2tPMvJnRoDcVVIUZY2ldV3q6QjpgFHQnMbZTZoyXZMOdSQKkc8ThGIJX9Y1/fja1J2RZVn+yd8hC8p7Py9wEjzsRYx89smRFpoc0wWk4ac5ZcXAIbVy9tIb1FqqijUTl8GShHqH56gf10GUazgAAAA=="
"Analytics": "v2:deflate64:H4sIAAAAAAAA/02LwQrCMBBEv6X3dK0BP0B68iLS4llisuq26Qaa1SIh/y5Fqj3NzJsZDXpXQVWYKZbW9aWnK6QjRkHXirG9MlO8JBvuTBIgnSOOpxC8qm/884upPSHLuvqTg0MWkvdyXucmeNyLGPsYkCUrMgOkGc7DWXPOioND6OLmpTXoLVRFF4nK8clCA0Lz1Q/RUZAezgAAAA=="
},
"Metadata": {
"aws:cdk:path": "amplify-moodboard-e2e-sandbox-x/auth/CDKMetadata/Default"
Expand Down Expand Up @@ -367,20 +367,20 @@
}
},
"Outputs": {
"amplifymoodboarde2esandbox799a4d1a84authamplifyAuthUserPoolC3F32E8CRef": {
"Value": "us-east-1_K3ZcFPo5Q"
"amplifymoodboarde2esandbox685dc54d14authamplifyAuthUserPool390B0381Ref": {
"Value": "us-east-1_5iU0nP22B"
},
"amplifymoodboarde2esandbox799a4d1a84authamplifyAuthUserPoolAppClientFBE59DFARef": {
"Value": "7jnfsfml09seoak5857srhn920"
"amplifymoodboarde2esandbox685dc54d14authamplifyAuthUserPoolAppClient54FF5C73Ref": {
"Value": "6dkse6kuhg779dhl6glmefrmrq"
},
"amplifymoodboarde2esandbox799a4d1a84authamplifyAuthIdentityPoolB80FD358Ref": {
"Value": "us-east-1:edbce570-5f19-4755-9803-52e7b3e0c56d"
"amplifymoodboarde2esandbox685dc54d14authamplifyAuthIdentityPoolA541B351Ref": {
"Value": "us-east-1:3a5eeecb-0ea0-4cbe-93f4-2094213caf65"
},
"amplifymoodboarde2esandbox799a4d1a84authamplifyAuthauthenticatedUserRole65FDEE87Ref": {
"Value": "amplify-moodboard26041500-amplifyAuthauthenticatedU-inPzqwEs0wDw"
"amplifymoodboarde2esandbox685dc54d14authamplifyAuthauthenticatedUserRoleECA75B03Ref": {
"Value": "amplify-moodboard26041519-amplifyAuthauthenticatedU-hG08sRlsZcsb"
},
"amplifymoodboarde2esandbox799a4d1a84authamplifyAuthunauthenticatedUserRoleC0B7612DRef": {
"Value": "amplify-moodboard26041500-amplifyAuthunauthenticate-2CR87a5ajww1"
"amplifymoodboarde2esandbox685dc54d14authamplifyAuthunauthenticatedUserRole92D42D98Ref": {
"Value": "amplify-moodboard26041519-amplifyAuthunauthenticate-Six3YvIOxgGy"
}
}
}
Loading
Loading