Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/adding query #210

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,19 @@ custom:
attributeType: S
action: GetItem
cors: true
- dynamodb:
path: /dynamodb
method: get
tableName: { Ref: 'YourTable' }
indexName: 'myIndex'
hashKey:
queryStringParam: id # use query string parameter
attributeType: S
rangeKey:
queryStringParam: sort
attributeType: S
action: Query
cors: true
- dynamodb:
path: /dynamodb/{id}
method: delete
Expand Down Expand Up @@ -644,6 +657,35 @@ custom:
#set($item = $input.path('$.Item')){ "Item": $item }

```
#### Using Query with Index(GSI)
If you want to use GSI to get some information, you must use the Query action and add the attribute "indexName"
with the name of the Index that you want to use.
When you use it, the action on Dynamo will transform in Query action and it will use the HashKey and, if provided, the RangeKey to create the query unsing the equal operator.

You can change the operator for other allowed by AWS, just add the attribute "queryOperator" to the specific
key (Rash or Hange) information:

```yaml
- dynamodb:
path: /dynamodb
method: query
tableName: { Ref: 'YourTable' }
indexName: 'myIndex'
hashKey:
queryStringParam: id # use query string parameter
attributeType: S
queryOperator: ">"
rangeKey:
queryStringParam: sort
attributeType: S
queryOperator: "<"
action: Query
cors: true

```

If used some reponse template customazitaion, be aware that the response is different from the GetItem
returning an array containing the atributte Items

### EventBridge

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@ custom:
queryStringParam: sort
attributeType: S
cors: true
- dynamodb:
path: /dynamodb/index/{indexRange}/{indexSort}
method: get
indexName: myTestIndex
tableName:
Ref: MyMuTestTable
action: Query
hashKey:
pathParam: indexSort
attributeType: S
rangeKey:
pathParam: indexRange
attributeType: S
cors: true
- dynamodb:
path: /dynamodb/{id}
method: delete
Expand All @@ -62,11 +76,27 @@ resources:
AttributeType: S
- AttributeName: sort
AttributeType: S
- AttributeName: indexRange
AttributeType: S
- AttributeName: indexSort
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
- AttributeName: sort
KeyType: RANGE
GlobalSecondaryIndexes:
- IndexName: myTestIndex
KeySchema:
- AttributeName: indexSort
KeyType: HASH
- AttributeName: indexRange
KeyType: RANGE
Projection:
ProjectionType: ALL
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
34 changes: 34 additions & 0 deletions __tests__/integration/dynamodb/multiple-integrations/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,40 @@ describe('Multiple Dynamodb Proxies Integration Test', () => {
})
})

it('should get correct response from dynamodb Query with index', async () => {
await putDynamodbItem(
tableName,
_.merge(
{},
{ [hashKeyAttribute]: hashKey, [rangeKeyAttribute]: sortKey },
{
message: { S: 'testtest' },
indexRange: { S: 'rangeTest' },
indexSort: { S: 'sortTest' }
}
)
)
const getEndpoint = `${endpoint}/dynamodb/index/rangeTest/sortTest`

const getResponse = await fetch(getEndpoint, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
})
expect(getResponse.headers.get('access-control-allow-origin')).to.deep.equal('*')
expect(getResponse.status).to.be.equal(200)

const item = await getResponse.json()
expect(item).to.be.deep.equal([
{
id: hashKey.S,
sort: sortKey.S,
message: 'testtest',
indexRange: 'rangeTest',
indexSort: 'sortTest'
}
])
})

it('should get correct response from dynamodb DeleteItem action endpoint', async () => {
await putDynamodbItem(
tableName,
Expand Down
6 changes: 4 additions & 2 deletions lib/apiGateway/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,12 +148,13 @@ const partitionKey = Joi.alternatives().try([
)
])

const allowedDynamodbActions = ['PutItem', 'GetItem', 'DeleteItem']
const allowedDynamodbActions = ['PutItem', 'GetItem', 'DeleteItem', 'Query']
const dynamodbDefaultKeyScheme = Joi.object()
.keys({
pathParam: Joi.string(),
queryStringParam: Joi.string(),
attributeType: Joi.string().required()
attributeType: Joi.string().required(),
queryOperator: Joi.string().valid(['=', '<', '<=', '>', '>='])
})
.xor('pathParam', 'queryStringParam')
.error(
Expand Down Expand Up @@ -298,6 +299,7 @@ const proxiesSchemas = {
.required(),
tableName: stringOrRef.required(),
condition: Joi.string(),
indexName: Joi.string(),
hashKey: dynamodbDefaultKeyScheme.required(),
rangeKey: dynamodbDefaultKeyScheme,
requestParameters,
Expand Down
82 changes: 81 additions & 1 deletion lib/apiGateway/validate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2209,7 +2209,7 @@ describe('#validateServiceProxies()', () => {
}

expect(() => serverlessApigatewayServiceProxy.validateServiceProxies()).to.throw(
'child "action" fails because ["action" must be one of [PutItem, GetItem, DeleteItem]'
'child "action" fails because ["action" must be one of [PutItem, GetItem, DeleteItem, Query]'
)
})

Expand Down Expand Up @@ -2271,6 +2271,86 @@ describe('#validateServiceProxies()', () => {

expect(() => serverlessApigatewayServiceProxy.validateServiceProxies()).to.not.throw()
})

it('should throw error if the "indexName" parameter is not a string', () => {
serverlessApigatewayServiceProxy.serverless.service.custom = {
apiGatewayServiceProxies: [
{
dynamodb: {
tableName: 'yourTable',
path: 'dynamodb',
method: 'put',
action: 'PutItem',
hashKey: { pathParam: 'id', attributeType: 'S' },
indexName: { a: 'b' }
}
}
]
}

expect(() => serverlessApigatewayServiceProxy.validateServiceProxies()).to.throw(
'child "indexName" fails because ["indexName" must be a string]'
)
})

it('should not throw error if the "indexName" parameter is a string', () => {
serverlessApigatewayServiceProxy.serverless.service.custom = {
apiGatewayServiceProxies: [
{
dynamodb: {
tableName: 'yourTable',
path: 'dynamodb',
method: 'put',
action: 'PutItem',
hashKey: { pathParam: 'id', attributeType: 'S' },
indexName: 'test'
}
}
]
}

expect(() => serverlessApigatewayServiceProxy.validateServiceProxies()).to.not.throw()
})

it('should not throw error if the "attributeType" parameter is one of the valid ones', () => {
serverlessApigatewayServiceProxy.serverless.service.custom = {
apiGatewayServiceProxies: [
{
dynamodb: {
tableName: 'yourTable',
path: 'dynamodb',
method: 'put',
action: 'PutItem',
hashKey: { pathParam: 'id', attributeType: 'S', queryOperator: '=' },
indexName: 'test'
}
}
]
}

expect(() => serverlessApigatewayServiceProxy.validateServiceProxies()).to.not.throw()
})

it('should throw error if the "attributeType" parameter is not of the valid ones', () => {
serverlessApigatewayServiceProxy.serverless.service.custom = {
apiGatewayServiceProxies: [
{
dynamodb: {
tableName: 'yourTable',
path: 'dynamodb',
method: 'put',
action: 'PutItem',
hashKey: { pathParam: 'id', attributeType: 'S', queryOperator: '?' },
indexName: 'test'
}
}
]
}

expect(() => serverlessApigatewayServiceProxy.validateServiceProxies()).to.throw(
'child "dynamodb" fails because [child "hashKey" fails because [child "queryOperator" fails because ["queryOperator" must be one of [=, <, <=, >, >=]]]]'
)
})
})

describe('eventbridge', () => {
Expand Down
7 changes: 3 additions & 4 deletions lib/package/dynamodb/compileIamRoleToDynamodb.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,13 @@ module.exports = {
}

const permissions = tableNameActions.map(({ tableName, action }) => {
const baiscArn =
'arn:${AWS::Partition}:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${tableName}'
return {
Effect: 'Allow',
Action: `dynamodb:${action}`,
Resource: {
'Fn::Sub': [
'arn:${AWS::Partition}:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${tableName}',
{ tableName }
]
'Fn::Sub': [action === 'Query' ? baiscArn + '/*' : baiscArn, { tableName }]
}
}
})
Expand Down
23 changes: 23 additions & 0 deletions lib/package/dynamodb/compileIamRoleToDynamodb.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,17 @@ describe('#compileIamRoleToDynamodb()', () => {
queryStringParam: 'id'
}
}
},
{
dynamodb: {
path: '/dynamodb/v1',
tableName: 'mytable',
method: 'get',
action: 'Query',
hashKey: {
queryStringParam: 'id'
}
}
}
]
}
Expand Down Expand Up @@ -126,6 +137,18 @@ describe('#compileIamRoleToDynamodb()', () => {
}
]
}
},
{
Effect: 'Allow',
Action: 'dynamodb:Query',
Resource: {
'Fn::Sub': [
'arn:${AWS::Partition}:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${tableName}/*',
{
tableName: 'mytable'
}
]
}
}
]
}
Expand Down
Loading