AWS AppSync Subscription fails during handshake - apollo

I'm trying to set up a graphql subscription but I'm getting the error:
Unhandled GraphQL subscription error Error: Error during subscription handshake
I'm using AWS Cognito User Pools for the authorisation.
To create the Subscription I'm using:
this.subscription = this.props.client.subscribe({ query: gql(onCreateVehicle) }).subscribe({
next: response => {
console.log(response.data.onCreateVehicle);
},
error: error => {
console.warn(error);
}
});
'onCreateVehicle' was automatically generated by Amplify and looks like this:
export const onCreateVehicle = `subscription OnCreateVehicle($owner: String!) {
onCreateVehicle(owner: $owner) {
id
name
events {
items {
id
name
date
isDeleted
owner
}
nextToken
}
sessions {
items {
id
name
isDeleted
createdAt
owner
}
nextToken
}
isDeleted
owner
}
}
`;
Request resolver:
{
"version": "2018-05-29",
"payload": {}
}
Response resolver:
## [Start] Determine request authentication mode **
#if( $util.isNullOrEmpty($authMode) && !$util.isNull($ctx.identity) && !$util.isNull($ctx.identity.sub) && !$util.isNull($ctx.identity.issuer) && !$util.isNull($ctx.identity.username) && !$util.isNull($ctx.identity.claims) && !$util.isNull($ctx.identity.sourceIp) && !$util.isNull($ctx.identity.defaultAuthStrategy) )
#set( $authMode = "userPools" )
#end
## [End] Determine request authentication mode **
## [Start] Check authMode and execute owner/group checks **
#if( $authMode == "userPools" )
## No Static Group Authorization Rules **
## [Start] Owner Authorization Checks **
#set( $isOwnerAuthorized = false )
## Authorization rule: { allow: owner, ownerField: "owner", identityClaim: "cognito:username" } **
#set( $allowedOwners0 = $util.defaultIfNull($ctx.args.owner, null) )
#set( $identityValue = $util.defaultIfNull($ctx.identity.claims.get("username"),
$util.defaultIfNull($ctx.identity.claims.get("cognito:username"), "___xamznone____")) )
#if( $util.isList($allowedOwners0) )
#foreach( $allowedOwner in $allowedOwners0 )
#if( $allowedOwner == $identityValue )
#set( $isOwnerAuthorized = true )
#end
#end
#end
#if( $util.isString($allowedOwners0) )
#if( $allowedOwners0 == $identityValue )
#set( $isOwnerAuthorized = true )
#end
#end
## [End] Owner Authorization Checks **
## [Start] Throw if unauthorized **
#if( !($isStaticGroupAuthorized == true || $isOwnerAuthorized == true) )
$util.unauthorized()
#end
## [End] Throw if unauthorized **
#end
## [End] Check authMode and execute owner/group checks **
$util.toJson(null)
I know it has something to do with authorisation as it didn't throw the handshake error when I removed the #auth from the schema.graphql. Should there be something in the request resolver to handle the authorisation?
Adam

I just had to pass the 'owner' value through the variables object.
subscribeToVehicles: async () => props.data.subscribeToMore({
document: gql(onCreateVehicle),
variables: {
owner: (await Auth.currentSession()).getIdToken().payload.sub
},
...

Related

How to use query operation with app sync auto generated resolver?

Below is the autogenerated resolver of appsync with DynamoDB. I am unable to understand what object I have to pass to use the Query operation.
#set( $limit = $util.defaultIfNull($context.args.limit, 100) )
#set( $ListRequest = {
"version": "2018-05-29",
"limit": $limit
} )
#if( $context.args.nextToken )
#set( $ListRequest.nextToken = $context.args.nextToken )
#end
#if( $context.args.filter )
#set( $ListRequest.filter = $util.parseJson("$util.transform.toDynamoDBFilterExpression($ctx.args.filter)") )
#end
#if( !$util.isNull($modelQueryExpression)
&& !$util.isNullOrEmpty($modelQueryExpression.expression) )
$util.qr($ListRequest.put("operation", "Query"))
$util.qr($ListRequest.put("query", $modelQueryExpression))
#if( !$util.isNull($ctx.args.sortDirection) && $ctx.args.sortDirection == "DESC" )
#set( $ListRequest.scanIndexForward = false )
#else
#set( $ListRequest.scanIndexForward = true )
#end
#else
$util.qr($ListRequest.put("operation", "Scan"))
#end
$util.toJson($ListRequest)
I have tried to pass the below query but got the all data of table. I have also tried with simple filter scan it's working and returning the data but I want to use index.
let modelQueryExpression = {
"expression": "#bookingtype = :bookingtype AND #bookinguserid = :bookinguserid",
"expressionNames": {
"#bookinguserid": "bookinguserid",
"#bookingtype": "bookingtype",
},
"expressionValues": {
":bookingtype": {
"S": JSON.parse(user).id,
},
":parent": {
"S": 'Trip',
},
}
}
const bookingdata = await API.graphql({ query: listBookings, variables: { modelQueryExpression } });

How to get the last inserted item from dynamodb, after that create new item using Aws appsync

I am using aws appsync and dynomoDB. I have a table called Payment and I want to create each item with an unique sequential number that is PaymentNo.
If we want to achive this, we have to get last inserted item in order to update the PaymentNO count. Actually, I don't know, "how to get the last inserted item from table in the same resolver?
could anyone help me to write a resolver for this ?
schema
type Payment #model(subscriptions: null) {
id: ID!
paid_amount: Float
paid_date: AWSDate
description: String
PaymentNO: Int
}
My request resolver
## [Start] Set default values. **
$util.qr($context.args.input.put("id", $util.defaultIfNull($ctx.args.input.id, $util.autoId())))
#set( $createdAt = $util.time.nowISO8601() )
## Automatically set the createdAt timestamp. **
$util.qr($context.args.input.put("createdAt", $util.defaultIfNull($ctx.args.input.createdAt, $createdAt)))
## Automatically set the updatedAt timestamp. **
$util.qr($context.args.input.put("updatedAt", $util.defaultIfNull($ctx.args.input.updatedAt, $createdAt)))
## [End] Set default values. **
## [Start] Prepare DynamoDB PutItem Request. **
$util.qr($context.args.input.put("__typename", "FeePayment"))
#if( $modelObjectKey )
#set( $condition = {
"expression": "",
"expressionNames": {},
"expressionValues": {}
} )
#foreach( $entry in $modelObjectKey.entrySet() )
#if( $velocityCount == 1 )
$util.qr($condition.put("expression", "attribute_not_exists(#keyCondition$velocityCount)"))
#else
$util.qr($condition.put("expression", "$condition.expression AND attribute_not_exists(#keyCondition$velocityCount)"))
#end
$util.qr($condition.expressionNames.put("#keyCondition$velocityCount", "$entry.key"))
#end
#else
#set( $condition = {
"expression": "attribute_not_exists(#id)",
"expressionNames": {
"#id": "id"
},
"expressionValues": {}
} )
#end
#if( $context.args.condition )
#set( $condition.expressionValues = {} )
#set( $conditionFilterExpressions = $util.parseJson($util.transform.toDynamoDBConditionExpression($context.args.condition)) )
$util.qr($condition.put("expression", "($condition.expression) AND $conditionFilterExpressions.expression"))
$util.qr($condition.expressionNames.putAll($conditionFilterExpressions.expressionNames))
$util.qr($condition.expressionValues.putAll($conditionFilterExpressions.expressionValues))
#end
#if( $condition.expressionValues && $condition.expressionValues.size() == 0 )
#set( $condition = {
"expression": $condition.expression,
"expressionNames": $condition.expressionNames
} )
#end
{
"version": "2018-05-29",
"operation": "PutItem",
"key": #if( $modelObjectKey ) $util.toJson($modelObjectKey) #else {
"id": $util.dynamodb.toDynamoDBJson($ctx.args.input.id)
} #end,
"attributeValues": $util.dynamodb.toMapValuesJson($context.args.input),
"condition": $util.toJson($condition)
}
## [End] Prepare DynamoDB PutItem Request. **
response resolver
#if( $ctx.error )
$util.error($ctx.error.message, $ctx.error.type)
#else
$util.toJson($ctx.result)
#end

How to write Upsert mutation query(insert or update) in AWS DynamoDB AppSync resolver

I am working on DynamoDB with the help of AppSync GraphQL queries.
I have a one DynamoDB table in which username is Partition key(hash key) and timestamp_value is sort key(range key).
I am saving two things against one item i.e. one is reading and second one is activity (like exercise, sports etc.) For adding this two things we have different UI screens. Both things might have same timestamp so it will save in one item.
So now I need a upsert (insert or update) query which can be used for both the above operations because when you try to insert new reading then it will check that item is present or not. If present then it will update or not then it will insert the item and same thing has to happen when user want to add a new activity.
I am confused with the documentation and I didn't find exact AppSync request mapping resolver for doing upsert operation.
Below is the PutItem request mapping resolver :-
{
"version": "2017-02-28",
"operation": "PutItem",
"key": {
"username": $util.dynamodb.toDynamoDBJson($ctx.identity.username),
"timestamp": $util.dynamodb.toDynamoDBJson($ctx.args.input.timestamp),
},
"attributeValues": $util.dynamodb.toMapValuesJson($ctx.args.input),
"condition": {
"expression": "attribute_not_exists(#timestamp)",
"expressionNames": {
"#timestamp": "timestamp",
},
},
}
And below is the UpdateItem request mapping resolver:-
{
"version": "2017-02-28",
"operation": "UpdateItem",
"key": {
"username": $util.dynamodb.toDynamoDBJson($ctx.identity.username),
"timestamp": $util.dynamodb.toDynamoDBJson($ctx.args.input.timestamp),
},
## Set up some space to keep track of things we're updating **
#set( $expNames = {} )
#set( $expValues = {} )
#set( $expSet = {} )
#set( $expAdd = {} )
#set( $expRemove = [] )
## Iterate through each argument, skipping keys **
#foreach( $entry in $util.map.copyAndRemoveAllKeys($ctx.args.input, ["username", "timestamp"]).entrySet() )
#if( $util.isNull($entry.value) )
## If the argument is set to "null", then remove that attribute from the item in DynamoDB **
#set( $discard = ${expRemove.add("#${entry.key}")} )
$!{expNames.put("#${entry.key}", "${entry.key}")}
#else
## Otherwise set (or update) the attribute on the item in DynamoDB **
$!{expSet.put("#${entry.key}", ":${entry.key}")}
$!{expNames.put("#${entry.key}", "${entry.key}")}
$!{expValues.put(":${entry.key}", $util.dynamodb.toDynamoDB($entry.value))}
#end
#end
## Start building the update expression, starting with attributes we're going to SET **
#set( $expression = "" )
#if( !${expSet.isEmpty()} )
#set( $expression = "SET" )
#foreach( $entry in $expSet.entrySet() )
#set( $expression = "${expression} ${entry.key} = ${entry.value}" )
#if ( $foreach.hasNext )
#set( $expression = "${expression}," )
#end
#end
#end
## Continue building the update expression, adding attributes we're going to ADD **
#if( !${expAdd.isEmpty()} )
#set( $expression = "${expression} ADD" )
#foreach( $entry in $expAdd.entrySet() )
#set( $expression = "${expression} ${entry.key} ${entry.value}" )
#if ( $foreach.hasNext )
#set( $expression = "${expression}," )
#end
#end
#end
## Continue building the update expression, adding attributes we're going to REMOVE **
#if( !${expRemove.isEmpty()} )
#set( $expression = "${expression} REMOVE" )
#foreach( $entry in $expRemove )
#set( $expression = "${expression} ${entry}" )
#if ( $foreach.hasNext )
#set( $expression = "${expression}," )
#end
#end
#end
## Finally, write the update expression into the document, along with any expressionNames and expressionValues **
"update": {
"expression": "${expression}",
#if( !${expNames.isEmpty()} )
"expressionNames": $utils.toJson($expNames),
#end
#if( !${expValues.isEmpty()} )
"expressionValues": $utils.toJson($expValues),
#end
},
"condition": {
"expression": "SET attribute_exists(#username) AND attribute_not_exists(#timestamp)",
"expressionNames": {
"#username": "username",
"#timestamp": "timestamp",
},
}
}
So how can I update the resolver so that I can do upsert operation?

How to avoid creating duplicate items in an array when posting an AWS Amplify GraphQL mutation

Preface: I'm new to GraphQL and Amplify.
I have a user object that contains two arrays, one called preferred_genres, and another called preferred_characters. I have a profile page where the user can edit their profile and add items to these arrays.
The update mutation provided by AWS Amplify seems only to be adding elements to the array, never removing them. If on my UI I remove an item from the list and then submit the update mutation, the item is not removed from my backend. If I add an item to the array, the item is added on the backend, but I also end up with duplicate values for those already in the backend.
What I'm trying to do is overwrite the array on the backend with the data I'm submitting from the front end. Am I missing something obvious? Is there a more appropriate way of achieving this in GraphQL?
Schema
type User #model {
id: ID!
...
preferred_characters: [Character]
preferred_genres: [Genre]
...
}
DataStore's Conflict resolution documentation:
Updating Conflict Resolution:
One way is to update it as the documentation suggests (The way I did it).
I checked which files changed and the only important key to update can be found in amplify/backend/api/<yourApi>/transform.conf.json
From:
To:
A way to implement this would be to change your updateUser resolver to update or create these entries.
{
"version" : "2017-02-28",
"operation" : "UpdateItem",
"key" : {
"id" : $util.dynamodb.toDynamoDBJson($context.arguments.id)
},
## Set up some space to keep track of things you're updating **
#set( $expNames = {} )
#set( $expValues = {} )
#set( $expSet = {} )
#set( $expAdd = {} )
#set( $expRemove = [] )
## Increment "version" by 1 **
$!{expAdd.put("version", ":one")}
$!{expValues.put(":one", { "N" : 1 })}
## Iterate through each argument, skipping "id" and "expectedVersion" **
#foreach( $entry in $context.arguments.entrySet() )
#if( $entry.key != "id" && $entry.key != "expectedVersion" )
#if( (!$entry.value) && ("$!{entry.value}" == "") )
## If the argument is set to "null", then remove that attribute from the item in DynamoDB **
#set( $discard = ${expRemove.add("#${entry.key}")} )
$!{expNames.put("#${entry.key}", "$entry.key")}
#else
## Otherwise set (or update) the attribute on the item in DynamoDB **
$!{expSet.put("#${entry.key}", ":${entry.key}")}
$!{expNames.put("#${entry.key}", "$entry.key")}
$!{expValues.put(":${entry.key}", { "S" : "${entry.value}" })}
#end
#end
#end
## Start building the update expression, starting with attributes you're going to SET **
#set( $expression = "" )
#if( !${expSet.isEmpty()} )
#set( $expression = "SET" )
#foreach( $entry in $expSet.entrySet() )
#set( $expression = "${expression} ${entry.key} = ${entry.value}" )
#if ( $foreach.hasNext )
#set( $expression = "${expression}," )
#end
#end
#end
## Continue building the update expression, adding attributes you're going to ADD **
#if( !${expAdd.isEmpty()} )
#set( $expression = "${expression} ADD" )
#foreach( $entry in $expAdd.entrySet() )
#set( $expression = "${expression} ${entry.key} ${entry.value}" )
#if ( $foreach.hasNext )
#set( $expression = "${expression}," )
#end
#end
#end
## Continue building the update expression, adding attributes you're going to REMOVE **
#if( !${expRemove.isEmpty()} )
#set( $expression = "${expression} REMOVE" )
#foreach( $entry in $expRemove )
#set( $expression = "${expression} ${entry}" )
#if ( $foreach.hasNext )
#set( $expression = "${expression}," )
#end
#end
#end
## Finally, write the update expression into the document, along with any expressionNames and expressionValues **
"update" : {
"expression" : "${expression}"
#if( !${expNames.isEmpty()} )
,"expressionNames" : $utils.toJson($expNames)
#end
#if( !${expValues.isEmpty()} )
,"expressionValues" : $utils.toJson($expValues)
#end
},
"condition" : {
"expression" : "version = :expectedVersion",
"expressionValues" : {
":expectedVersion" : $util.dynamodb.toDynamoDBJson($context.arguments.expectedVersion)
}
}
}
More details
I stumbled upon this question in the Amplify GitHub issues about the AWSJSON scalar having a similar issue. The issue seems to be related to conflict resolution in the API.
I don't remember setting that up, but I tried amplify update API, chose my GraphQL API and the advanced options, declined any conflict resolution and ran amplify push.
Now values are saving as expected. When I remove an item from an array it's removed from the backend and visa-versa.

How I can receive parameter that when I send the message from postman

I send the parameter to api gateway by postman and receive the result.
So I try simple example in lambda,
exports.handler = (event, context, callback) => {
// TODO implement
const Idx = event.Idx * 2;
callback(null, Idx);
};
when I send Idx, It callback double of it.
I select raw in Body and doing that I can receive the result
But, I put "Content-Type" : "application/x-www-form-urlencoded" in Headers part, select x-www-form-urlencoded, it returns this.
{"message": "Could not parse request body into json: Unrecognized token \'Idx\': was expecting \'null\', \'true\', \'false\' or NaN\n at [Source: [B#331a45ec; line: 1, column: 5]"}
I want receive the data from postman by x-www-formurlencoded.
How I can do it?
If you know of it, please help me.
API Gateway is expecting JSON, but you're giving it urlencoded form data (i.e. a string). You need to add a mapping template in your API Gateway configuration:
{
"data": {
#foreach( $token in $input.path('$').split('&') )
#set( $keyVal = $token.split('=') )
#set( $keyValSize = $keyVal.size() )
#if( $keyValSize >= 1 )
#set( $key = $util.urlDecode($keyVal[0]) )
#if( $keyValSize >= 2 )
#set( $val = $util.urlDecode($keyVal[1]) )
#else
#set( $val = '' )
#end
"$key": "$val"#if($foreach.hasNext),#end
#end
#end
}
}
Source: https://stackoverflow.com/a/37956390/5794667