Skip to content

Commit

Permalink
Sample function now uses DynamoDb
Browse files Browse the repository at this point in the history
  • Loading branch information
instantiator committed Jan 8, 2023
1 parent 0e4a2c6 commit 5d0e163
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 21 deletions.
42 changes: 39 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,16 +102,52 @@ You may need to use the `--region` parameter if your default region is not set t

`cognito-client-id` and `cognito-user-pool-id` can be obtained from the deployed stack outputs.

Use the `IdToken` output from this script as the `Authorization: Bearer` heading value in calls to the API Gateway. eg.
You could use the `IdToken` output from this script as the `Authorization: Bearer` heading value in calls to the API Gateway. eg.

```shell
curl -H "Authorization: Bearer <IdToken>" https://gateway.endpoint.uri.etc.
curl -H "Authorization: Bearer <IdToken>" https://endpoint.uri.etc.
```

For subsequent calls, you can use the output from `get-user-id-token.sh`, eg.
For all subsequent calls where you need a token, you can use the output from `get-user-id-token.sh`, eg.

```shell
export ID_TOKEN=$(auth-scripts/get-user-id-token.sh -u <username> -p <password> -ci <cognito-client-id>)
curl -H "Authorization: Bearer $ID_TOKEN" https://endpoint.uri.etc.
```

## Exercising the functions

To illustrate the flexibility, `FunctionOne` actually contains two handlers, and the template deploys two functions - one for each.

For convenience of exploration, each deployed function is also mapped to two endpoints (one with auth, one without). When developing your application, give some though to which endpoints need authentication and which don't.

| Handler | Endpoint | Method | Notes |
|-|-|-|-|
| `FunctionOne.HandlerRoot:Handle` | /FunctionOne/ | Any | Returns "ok". |
| `FunctionOne.HandlerRoot:Handle` | /FunctionOne/noauth | Any | As above, but no auth required. |
| `FunctionOne.HandlerNotes:Handle` | /FunctionOne/notes/ | Get | Returns a count of the number of notes in the database table. |
| `FunctionOne.HandlerNotes:Handle` | /FunctionOne/notes/ | Post | Adds a new note to the database table. |
| `FunctionOne.HandlerNotes:Handle` | /FunctionOne/notes/noauth | Get | As above, but no auth required. |
| `FunctionOne.HandlerNotes:Handle` | /FunctionOne/notes/noauth | Post | As above, but no auth required. |

### Quick test

Retrieve an id token for this user:

```shell
export ID_TOKEN=$(auth-scripts/get-user-id-token.sh -u <test-user-email> -p <password> -ci <client-id>)
```

POST to the notes endpoint, to add a new note to the table:

```shell
curl -H "Authorization: Bearer $ID_TOKEN" -X POST https://endpoint.uri.etc/staging/FunctionOne/notes
```

GET from the notes endpoint, to see how many notes there are:

```shell
curl -H "Authorization: Bearer $ID_TOKEN" https://endpoint.uri.etc/staging/FunctionOne/notes
```

## Misc notes
Expand Down
1 change: 1 addition & 0 deletions src/SampleFunctions/FunctionOne/FunctionOne.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@
<PackageReference Include="Amazon.Lambda.Core" Version="2.1.0" />
<PackageReference Include="Amazon.Lambda.RuntimeSupport" Version="1.8.2" />
<PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="2.3.0" />
<PackageReference Include="AWSSDK.DynamoDBv2" Version="3.7.101.20" />
</ItemGroup>
</Project>
63 changes: 63 additions & 0 deletions src/SampleFunctions/FunctionOne/HandlerNotes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System;
using System.Runtime.Intrinsics.X86;
using Amazon;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DocumentModel;
using Amazon.DynamoDBv2.Model;
using Amazon.Lambda.APIGatewayEvents;
using Amazon.Lambda.Core;
using Amazon.Lambda.Serialization.SystemTextJson;
using Amazon.Runtime;

namespace FunctionOne;

public class HandlerNotes
{
[LambdaSerializer(typeof(DefaultLambdaJsonSerializer))]
public async Task<APIGatewayProxyResponse> Handle(APIGatewayProxyRequest request)
{
var tableName = Environment.GetEnvironmentVariable("NOTES_TABLE");
var region = RegionEndpoint.GetBySystemName(Environment.GetEnvironmentVariable("AWS_REGION"));

Console.WriteLine($"Region: {region}");
Console.WriteLine($"Table: {tableName}");

AmazonDynamoDBConfig clientConfig = new AmazonDynamoDBConfig()
{
RegionEndpoint = region,
};
var dbClient = new AmazonDynamoDBClient(clientConfig);

string? output;
switch (request.HttpMethod.ToLower())
{
case "get":
var scanRequest = new ScanRequest { TableName = tableName };
var response = await dbClient.ScanAsync(scanRequest);
output = $"{tableName}: {response.Count}";
break;
case "post":
var id = Guid.NewGuid().ToString();
var putRequest = new PutItemRequest
{
TableName = tableName,
Item = new Dictionary<string, AttributeValue>()
{
{ "Id", new AttributeValue { S = id }},
{ "Note", new AttributeValue { S = $"Note with id: {id}" }},
}
};
var putResponse = await dbClient.PutItemAsync(putRequest);
output = $"put code {putResponse.HttpStatusCode}";
break;
default:
throw new ArgumentException($"Method {request.HttpMethod} not implemented.");
}

return new APIGatewayProxyResponse()
{
StatusCode = 200,
Body = output ?? "(null)"
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace FunctionOne;

public class HandlerOne
public class HandlerRoot
{
[LambdaSerializer(typeof(DefaultLambdaJsonSerializer))]
public async Task<APIGatewayProxyResponse> Handle(APIGatewayProxyRequest request)
Expand All @@ -17,4 +17,3 @@ public async Task<APIGatewayProxyResponse> Handle(APIGatewayProxyRequest request
};
}
}

54 changes: 38 additions & 16 deletions template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,47 +34,69 @@ Resources:
# Parameters:
# Location: s3://bucket/swagger.yaml
Auth:
DefaultAuthorizer: CognitoAuthorizer
#DefaultAuthorizer: CognitoAuthorizer
Authorizers:
CognitoAuthorizer:
UserPoolArn: !GetAtt UserPool.Arn

FunctionOne:
FunctionOneRoot:
Type: AWS::Serverless::Function
Properties:
Description: A simple example includes a .NET Core WebAPI App with DynamoDB table.
Description: A simple serverless function - returns ok.
CodeUri: ./src/SampleFunctions/FunctionOne/
Handler: FunctionOne::FunctionOne.HandlerOne::Handle
Handler: FunctionOne::FunctionOne.HandlerRoot::Handle
Runtime: dotnet6
MemorySize: 1024
Events:
RootResourceNoAuth:
Type: Api
Properties:
Path: /FunctionOne/noauth
Method: get
RestApiId: !Ref ApiGateway
RootResource:
Type: Api
Properties:
Path: /FunctionOne
Method: get
RestApiId: !Ref ApiGateway
Auth: # optional per endpoint
Authorizer: CognitoAuthorizer

FunctionOneNotes:
Type: AWS::Serverless::Function
Properties:
Description: A simple serverless function - GET to count notes, POST to add a note.
CodeUri: ./src/SampleFunctions/FunctionOne/
Handler: FunctionOne::FunctionOne.HandlerNotes::Handle
Runtime: dotnet6
MemorySize: 1024
Environment:
Variables:
SAMPLE_TABLE: !Ref DatabaseTable
NOTES_TABLE: !Ref NotesDbTable
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref DatabaseTable
TableName: !Ref NotesDbTable
Events:
ProxyResource:
NotesResource:
Type: Api
Properties:
Path: /FunctionOne/{proxy+}
Method: get # was ANY
Path: /FunctionOne/notes
Method: ANY
RestApiId: !Ref ApiGateway
Auth: # optional per endpoint
Authorizer: CognitoAuthorizer
RootResource:
NotesResourceNoAuth:
Type: Api
Properties:
Path: /FunctionOne
Method: get # was ANY
Path: /FunctionOne/notes/noauth
Method: ANY
RestApiId: !Ref ApiGateway
Auth: # optional per endpoint
Authorizer: CognitoAuthorizer

DatabaseTable:
NotesDbTable:
Type: AWS::Serverless::SimpleTable
Properties:
TableName: simple-table
TableName: notes
PrimaryKey:
Name: Id
Type: String
Expand Down

0 comments on commit 5d0e163

Please sign in to comment.