Я создаю приложение React с помощью AWS Amplify.Я использую пользовательские пулы Cognito для аутентификации и серверную часть GraphQL AppSync для своей серверной части.
Я пытаюсь написать собственный преобразователь для пакетных мутаций.Вот схема, которую я использую:
type Todo @model @auth(rules: [{ allow: owner }]) {
id: ID!
title: String!
description: String
completed: Boolean
}
input CreateTodoInput {
id: ID
title: String!
description: String
completed: Boolean
}
type Mutation {
batchAddTodos(todos: [CreateTodoInput]): [Todo]
}
Эта схема включает аутентификацию для API GraphQL с использованием Пулы пользователя Cognito .
, чтобы эта пользовательская мутация работаланужно добавить пользовательских распознавателей .Я изменил amplify/api/<your-api-name>/stacks/CustomResources.json
, чтобы он содержал следующие ресурсы:
// Left everything as it was
"Resources": {
"EmptyResource": {
"Type": "Custom::EmptyResource",
"Condition": "AlwaysFalse"
},
"BatchAddTodosResolver": {
"Type": "AWS::AppSync::Resolver",
"Properties": {
"ApiId": {
"Ref": "AppSyncApiId"
},
"DataSourceName": "TodoTable",
"TypeName": "Mutation",
"FieldName": "batchAddTodos",
"RequestMappingTemplateS3Location": {
"Fn::Sub": [
"s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Mutation.batchAddTodos.req.vtl",
{
"S3DeploymentBucket": {
"Ref": "S3DeploymentBucket"
},
"S3DeploymentRootKey": {
"Ref": "S3DeploymentRootKey"
}
}
]
},
"ResponseMappingTemplateS3Location": {
"Fn::Sub": [
"s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Mutation.batchAddTodos.res.vtl",
{
"S3DeploymentBucket": {
"Ref": "S3DeploymentBucket"
},
"S3DeploymentRootKey": {
"Ref": "S3DeploymentRootKey"
}
}
]
}
}
}
},
// ... more code that I didn't touch
Для пользовательского распознавателя запросов я написал следующий шаблон:
#foreach($item in ${ctx.args.todos})
## [Start] Owner Authorization Checks **
#set( $isOwnerAuthorized = false )
## Authorization rule: { allow: "owner", ownerField: "owner", identityField: "cognito:username" } **
#set( $allowedOwners0 = $util.defaultIfNull($item.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
#if( $util.isNull($allowedOwners0) && (! $item.containsKey("owner")) )
$util.qr($item.put("owner", $identityValue))
#set( $isOwnerAuthorized = true )
#end
## [End] Owner Authorization Checks **
## [Start] Throw if unauthorized **
#if( !($isStaticGroupAuthorized == true || $isDynamicGroupAuthorized == true || $isOwnerAuthorized
== true) )
$util.unauthorized()
#end
## [End] Throw if unauthorized **
#end
#set($todosdata = [])
#foreach($item in ${ctx.args.todos})
$util.qr($item.put("createdAt", $util.time.nowISO8601()))
$util.qr($item.put("updatedAt", $util.time.nowISO8601()))
$util.qr($item.put("__typename", "Todo"))
$util.qr($item.put("id", $util.defaultIfNullOrBlank($item.id, $util.autoId())))
$util.qr($todosdata.add($util.dynamodb.toMapValues($item)))
#end
{
"version": "2018-05-29",
"operation": "BatchPutItem",
"tables": {
"TodoTable": $utils.toJson($todosdata)
}
}
В первом цикле я пыталсяубедитесь, что у пользователя есть доступ к созданным им задачам.Во втором цикле я добавил данные, которые добавляются резольверами, сгенерированными Amplify CLI.Это включает __typename
, временные метки и id
.
После этого я делаю запрос на создание ресурсов.Я следовал этому руководству для кода .Обратите внимание, что мне пришлось обновить версию до "2018-05-29"
.Код, сгенерированный Amplify CLI, обычно имеет версию "2017-02-28"
(я не знаю, имеет ли это значение).
Я также написал следующее сопоставление для распознавателя ответов:
#if ($ctx.error)
$util.appendError($ctx.error.message, $ctx.error.type, null, $ctx.result.data.unprocessedKeys)
#end
$util.toJson($ctx.result.data)
Это в основном говорит AppSync, что нужно возвращать данные и ошибки для всех необработанных элементов.
Сначала я попытался сделать запрос, используя React:
import API, { graphqlOperation } from '@aws-amplify/api';
// ... later
async function handleClick() {
const todoFixtures = [
{ id: 1, title: 'Get groceries', description: '', completed: false },
{ id: 2, title: 'Go to the gym', description: 'Leg Day', completed: true }
];
try {
const input = { todos: prepareTodos(todoFixtures) };
const res = await API.graphql(graphqlOperation(batchAddTodos, input));
console.log(res);
} catch (err) {
console.log('error ', err);
}
}
prepareTodos
просто избавился от id
полей и устанавливает пустые поля на null
(чтобы избежать криков DynamoDB на меня).Код внизу, поскольку он не имеет значения.
Поскольку это не удалось, я попытался выполнить мутацию через консоль AppSync:
mutation add {
batchAddTodos(todos: [
{title: "Hello", description: "Test", completed: false}
]) {
id title
}
}
Но обе попытки приводят к следующей ошибке:
{
"data": {
"batchAddTodos": null
},
"errors": [
{
"path": [
"batchAddTodos"
],
"data": null,
"errorType": "DynamoDB:AmazonDynamoDBException",
"errorInfo": null,
"locations": [
{
"line": 32,
"column": 3,
"sourceName": null
}
],
"message": "User: arn:aws:sts::655817346595:assumed-role/Todo-role-naona7ytt5drxazwmtp7a2uccy-batch/APPSYNC_ASSUME_ROLE is not authorized to perform: dynamodb:BatchWriteItem on resource: arn:aws:dynamodb:eu-central-1:655817346595:table/TodoTable (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: AccessDeniedException; Request ID: EP48SJPVMB9G9M69HPR0BO8SKJVV4KQNSO5AEMVJF66Q9ASUAAJG)"
},
{
"path": [
"batchAddTodos"
],
"locations": null,
"message": "Can't resolve value (/batchAddTodos) : type mismatch error, expected type LIST"
}
]
}
Это наводит меня на мысль, что код React является «правильным» или, по крайней мере, таким же неправильным, как код AppSync.Но я подозреваю, что ошибка находится где-то в сопоставлении шаблона решателя.Я просто не могу найти это.Что здесь не так?
Может быть, созданная роль предположения не поддерживает версию "2018-05-19"
?Вот код для роли, сгенерированной по умолчанию (я ее не написал):
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "appsync.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
},
"Policies": [
{
"PolicyName": "DynamoDBAccess",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:BatchGetItem",
"dynamodb:BatchWriteItem",
"dynamodb:PutItem",
"dynamodb:DeleteItem",
"dynamodb:GetItem",
"dynamodb:Scan",
"dynamodb:Query",
"dynamodb:UpdateItem"
],
"Resource": [
{
"Fn::Sub": [
"arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${tablename}",
{
"tablename": {
"Fn::If": [
"HasEnvironmentParameter",
{
"Fn::Join": [
"-",
[
"Todo",
{
"Ref": "GetAttGraphQLAPIApiId"
},
{
"Ref": "env"
}
]
]
},
{
"Fn::Join": [
"-",
[
"Todo",
{
"Ref": "GetAttGraphQLAPIApiId"
}
]
]
}
]
}
}
]
},
{
"Fn::Sub": [
"arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${tablename}/*",
{
"tablename": {
"Fn::If": [
"HasEnvironmentParameter",
{
"Fn::Join": [
"-",
[
"Todo",
{
"Ref": "GetAttGraphQLAPIApiId"
},
{
"Ref": "env"
}
]
]
},
{
"Fn::Join": [
"-",
[
"Todo",
{
"Ref": "GetAttGraphQLAPIApiId"
}
]
]
}
]
}
}
]
}
]
}
]
}
}
]
prepareTodos
:
const map = f => arr => arr.map(f);
const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);
const dissoc = prop => ({ [prop]: _, ...obj }) => obj;
const mapObj = f => obj =>
Object.keys(obj).reduce((acc, key) => ({ ...acc, [key]: f(obj[key]) }), {});
const replaceEmptyStringWithNull = x => (x === '' ? null : x);
const prepareTodos = map(
pipe(
dissoc('id'),
mapObj(replaceEmptyStringWithNull)
)
);
Редактировать: Iудалось решить несоответствие типов.В ответ я должен вернуть: $util.toJson($ctx.result.data.TodoTable)
.