I am new to aws and graphql. I am working on appsync and trying to create relationships between two types. Following is my schema:
type BadCustomer {
id: ID!
name: String!
phone: AWSPhone
email: AWSEmail
streetAddress: String
dob: AWSDate
passportNumber: String
driversLicenceNumber: String
ipAddress: AWSIPAddress
}
type BadCustomerConnection {
items: [BadCustomer]
nextToken: String
}
input CreateBadCustomerInput {
name: String!
phone: AWSPhone
email: AWSEmail
streetAddress: String
dob: AWSDate
passportNumber: String
driversLicenceNumber: String
ipAddress: AWSIPAddress
}
input CreateDamageInput {
damage: Float!
comment: String
}
input CreateIndustryInput {
name: String!
}
input CreateSubscriberInput {
name: String!
email: AWSEmail!
phone: AWSPhone
streetAddress: String!
subscriberIndustryId: ID!
}
type Damage {
id: ID!
customer: BadCustomer!
industry: Industry!
victim: Subscriber!
damage: Float!
comment: String
}
type DamageConnection {
items: [Damage]
nextToken: String
}
input DeleteBadCustomerInput {
id: ID!
}
input DeleteDamageInput {
id: ID!
}
input DeleteIndustryInput {
id: ID!
}
input DeleteSubscriberInput {
id: ID!
}
type Industry {
id: ID!
name: String!
subscribers(
filter: TableSubscriberFilterInput,
sortDirection: ModelSortDirection,
limit: Int,
nextToken: String
): SubscriberConnection
}
type IndustryConnection {
items: [Industry]
nextToken: String
}
enum ModelSortDirection {
ASC
DESC
}
type Mutation {
createIndustry(input: CreateIndustryInput!): Industry
updateIndustry(input: UpdateIndustryInput!): Industry
deleteIndustry(input: DeleteIndustryInput!): Industry
createSubscriber(input: CreateSubscriberInput!): Subscriber
updateSubscriber(input: UpdateSubscriberInput!): Subscriber
deleteSubscriber(input: DeleteSubscriberInput!): Subscriber
createBadCustomer(input: CreateBadCustomerInput!): BadCustomer
updateBadCustomer(input: UpdateBadCustomerInput!): BadCustomer
deleteBadCustomer(input: DeleteBadCustomerInput!): BadCustomer
createDamage(input: CreateDamageInput!): Damage
updateDamage(input: UpdateDamageInput!): Damage
deleteDamage(input: DeleteDamageInput!): Damage
}
type Query {
getIndustry(id: ID!): Industry
listIndustries(filter: TableIndustryFilterInput, limit: Int, nextToken: String): IndustryConnection
getSubscriber(id: ID!): Subscriber
listSubscribers(filter: TableSubscriberFilterInput, limit: Int, nextToken: String): SubscriberConnection
getBadCustomer(id: ID!): BadCustomer
listBadCustomers(filter: TableBadCustomerFilterInput, limit: Int, nextToken: String): BadCustomerConnection
getDamage(id: ID!): Damage
listDamages(filter: TableDamageFilterInput, limit: Int, nextToken: String): DamageConnection
}
type Subscriber {
id: ID!
name: String!
email: AWSEmail!
phone: AWSPhone
streetAddress: String!
industry: Industry!
}
type SubscriberConnection {
items: [Subscriber]
nextToken: String
}
type Subscription {
onCreateIndustry(id: ID, name: String): Industry
#aws_subscribe(mutations: ["createIndustry"])
onUpdateIndustry(id: ID, name: String): Industry
#aws_subscribe(mutations: ["updateIndustry"])
onDeleteIndustry(id: ID, name: String): Industry
#aws_subscribe(mutations: ["deleteIndustry"])
onCreateSubscriber(
id: ID,
name: String,
email: AWSEmail,
phone: AWSPhone,
streetAddress: String
): Subscriber
#aws_subscribe(mutations: ["createSubscriber"])
onUpdateSubscriber(
id: ID,
name: String,
email: AWSEmail,
phone: AWSPhone,
streetAddress: String
): Subscriber
#aws_subscribe(mutations: ["updateSubscriber"])
onDeleteSubscriber(
id: ID,
name: String,
email: AWSEmail,
phone: AWSPhone,
streetAddress: String
): Subscriber
#aws_subscribe(mutations: ["deleteSubscriber"])
onCreateBadCustomer(
id: ID,
name: String,
phone: AWSPhone,
email: AWSEmail,
streetAddress: String
): BadCustomer
#aws_subscribe(mutations: ["createBadCustomer"])
onUpdateBadCustomer(
id: ID,
name: String,
phone: AWSPhone,
email: AWSEmail,
streetAddress: String
): BadCustomer
#aws_subscribe(mutations: ["updateBadCustomer"])
onDeleteBadCustomer(
id: ID,
name: String,
phone: AWSPhone,
email: AWSEmail,
streetAddress: String
): BadCustomer
#aws_subscribe(mutations: ["deleteBadCustomer"])
onCreateDamage(id: ID, damage: Float, comment: String): Damage
#aws_subscribe(mutations: ["createDamage"])
onUpdateDamage(id: ID, damage: Float, comment: String): Damage
#aws_subscribe(mutations: ["updateDamage"])
onDeleteDamage(id: ID, damage: Float, comment: String): Damage
#aws_subscribe(mutations: ["deleteDamage"])
}
input TableBadCustomerFilterInput {
id: TableIDFilterInput
name: TableStringFilterInput
phone: TableStringFilterInput
email: TableStringFilterInput
streetAddress: TableStringFilterInput
dob: TableStringFilterInput
passportNumber: TableStringFilterInput
driversLicenceNumber: TableStringFilterInput
ipAddress: TableStringFilterInput
}
input TableBooleanFilterInput {
ne: Boolean
eq: Boolean
}
input TableDamageFilterInput {
id: TableIDFilterInput
damage: TableFloatFilterInput
comment: TableStringFilterInput
}
input TableEventFilterInput {
id: TableIDFilterInput
name: TableStringFilterInput
where: TableStringFilterInput
when: TableStringFilterInput
description: TableStringFilterInput
}
input TableFloatFilterInput {
ne: Float
eq: Float
le: Float
lt: Float
ge: Float
gt: Float
contains: Float
notContains: Float
between: [Float]
}
input TableIDFilterInput {
ne: ID
eq: ID
le: ID
lt: ID
ge: ID
gt: ID
contains: ID
notContains: ID
between: [ID]
beginsWith: ID
}
input TableIndustryFilterInput {
id: TableIDFilterInput
name: TableStringFilterInput
}
input TableIntFilterInput {
ne: Int
eq: Int
le: Int
lt: Int
ge: Int
gt: Int
contains: Int
notContains: Int
between: [Int]
}
input TableStringFilterInput {
ne: String
eq: String
le: String
lt: String
ge: String
gt: String
contains: String
notContains: String
between: [String]
beginsWith: String
}
input TableSubscriberFilterInput {
id: TableIDFilterInput
name: TableStringFilterInput
email: TableStringFilterInput
phone: TableStringFilterInput
streetAddress: TableStringFilterInput
}
input UpdateBadCustomerInput {
id: ID!
name: String
phone: AWSPhone
email: AWSEmail
streetAddress: String
dob: AWSDate
passportNumber: String
driversLicenceNumber: String
ipAddress: AWSIPAddress
}
input UpdateDamageInput {
id: ID!
damage: Float
comment: String
}
input UpdateIndustryInput {
id: ID!
name: String
}
input UpdateSubscriberInput {
id: ID!
name: String
email: AWSEmail
phone: AWSPhone
streetAddress: String
subscriberIndustryId: ID
}
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
I want to setup a relationship between Subscribers and Industry. Each subscriber belongs to one industry. I have setup resolvers for Subscriber.industry and Industry.subscribers.
Subscriber.industry Resolver, Datasource Industry table:
{
"version": "2017-02-28",
"operation": "GetItem",
"key": {
"id": $util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.source.subscriberIndustryId, "___xamznone____"))
}
}
Industry.subscribers Resolver, Datasource Subscriber table:
#set( $limit = $util.defaultIfNull($context.args.limit, 10) )
{
"version": "2017-02-28",
"operation": "Query",
"query": {
"expression": "#connectionAttribute = :connectionAttribute",
"expressionNames": {
"#connectionAttribute": "id"
},
"expressionValues": {
":connectionAttribute": {
"S": "$context.source.id"
}
}
},
"scanIndexForward": #if( $context.args.sortDirection )
#if( $context.args.sortDirection == "ASC" )
true
#else
false
#end
#else
true
#end,
"filter": #if( $context.args.filter )
$util.transform.toDynamoDBFilterExpression($ctx.args.filter)
#else
null
#end,
"limit": $limit,
"nextToken": #if( $context.args.nextToken )
"$context.args.nextToken"
#else
null
#end
}
I have following queries:
query ListIndustries {
listIndustries {
items {
id
name
subscribers {
items {
id
name
streetAddress
}
}
}
}
}
query ListSubscribers {
listSubscribers{
items {
id
name
industry {
name
}
}
}
}
If i run ListSubscribers query on appsync console, it gives me the desired result, like:
{
"data": {
"listSubscribers": {
"items": [
{
"id": "d04d6ef4-95bf-4b01-9a6a-13f550dbfa8f",
"name": "John Doe",
"industry": {
"name": "Real Estate"
}
},
{
"id": "96fa3e60-fdcf-4a6d-a3b6-72ec2c4701d1",
"name": "Jane Doe",
"industry": {
"name": "Ecommerce"
}
},
{
"id": "413dd506-afd4-474e-a850-54a39eb1eeeb",
"name": "John Doe",
"industry": {
"name": "Real Estate"
}
}
]
}
}
}
But if I run ListIndustries query, it does not give me the list of subscribers in that industry, like:
{
"data": {
"listIndustries": {
"items": [
{
"id": "b904d8f3-b504-48e0-b28f-0f9ac1742186",
"name": "Real Estate",
"subscribers": {
"items": []
}
},
{
"id": "167b87f2-3342-4390-80af-df53ba3c6f73",
"name": "Ecommerce",
"subscribers": {
"items": []
}
}
]
}
}
}
Need someone who can help me out with this.
I will need a bit more information to be sure but it looks like the Industry.subscribers resolver may be misconfigured. Your query expression currently specifies:
"query": {
"expression": "#connectionAttribute = :connectionAttribute",
"expressionNames": {
"#connectionAttribute": "id"
},
"expressionValues": {
":connectionAttribute": {
"S": "$context.source.id"
}
}
}
// There is no "index" so this is trying to query the primary index.
Which says, "Query the primary index where the 'id' = $ctx.source.id". Based on the objects you pasted, it looks like your Subscriber objects use $util.autoId() and thus the subscriber 'id' will never equal the industry 'id'. If using the Amplify CLI's #connection directive, a GSI will have been created on the Subscriber table with a hash key called 'subscriberIndustryId' by default. The Industry.subscribers resolver should query that index where the "subscriberIndustryId = $ctx.source.id". If you are not using the CLI and #connection, you can also implement this pattern yourself.
E.G. this should do the trick:
"query": {
"expression": "#connectionAttribute = :connectionAttribute",
"expressionNames": {
"#connectionAttribute": "subscriberIndustryId"
},
"expressionValues": {
":connectionAttribute": {
"S": "$context.source.id"
}
}
},
"index": "gsi-IndustrySubscribers"
I missed index in Industry.subscribers resolver. Adding it fixed the problem.
Related
I have the following in my schema for my AWS Amplify project:
type DriveTime #model
#auth( rules: [
{allow: groups, groups: ["Admin", "Instructor"]},
{ allow: private, provider: iam }
]) {
id: ID!
start: AWSDateTime!
end: AWSDateTime!
openRegistration: AWSDateTime!
closeRegistration: AWSDateTime!
vehicle: Vehicle #connection(name: "VehicleDriveConnection")
instructor: Instructor #connection(name: "InstructorDriveConnection") #aws_cognito_user_pools #aws_iam
student: Student #connection(name: "StudentDriveConnection")
evaluation: DriveEvaluation #connection(name: "DriveEvaluationConnection")
}
I want to be able to list all drive times where the student connection is empty or null. I am able to get all driveTimes for a single student but not all driveTimes where there is no student.
Since I dont want students to be able to access drive times that are either not open for registration or already registered to another student I have added this to my schema:
type AvailableDriveTime {
id: ID!
start: AWSDateTime!
end: AWSDateTime!
openRegistration: AWSDateTime!
closeRegistration: AWSDateTime!
instructorFirstName: String!
instructorLastName: String!
}
type Query {
listAvailableDriveTimes(limit: Int, nextToken: String): AvailableDriveTimesConnection #function(name: "listAvailableDriveTimes-${env}") #aws_cognito_user_pools #aws_iam
}
And this is my current query in the Lambda resolver:
let currentDate = new Date();
const listDrives = `query ListDrives($limit: Int, $nextToken: String) {
listDriveTimes(limit: $limit, nextToken: $nextToken, filter: {and: {openRegistration: {le: "${currentDate.toISOString()}"}, closeRegistration: {ge: "${currentDate.toISOString()}"}}}) {
items {
id
start
end
openRegistration
closeRegistration
instructor {
firstName
lastName
}
student {
username
}
}
nextToken
}
}`
My current solution is sorting in the lambda resolver then returning the right data but it seems like there has to be a more efficient way.
Given the following schema:
input CreateSurveysInput {
uuid: String!
year: Int!
geocode: AWSJSON!
metadata: AWSJSON!
observations: AWSJSON!
report_url: String
survey_date: String!
video_url: String!
}
input CreateWaterQualityInput {
uuid: String!
}
input DeleteSurveysInput {
uuid: String!
year: Int!
}
input DeleteWaterQualityInput {
uuid: String!
}
type Mutation {
createSurveys(input: CreateSurveysInput!): Surveys
updateSurveys(input: UpdateSurveysInput!): Surveys
deleteSurveys(input: DeleteSurveysInput!): Surveys
createWaterQuality(input: CreateWaterQualityInput!): WaterQuality
updateWaterQuality(input: UpdateWaterQualityInput!): WaterQuality
deleteWaterQuality(input: DeleteWaterQualityInput!): WaterQuality
}
type Query {
getSurveys(year: Int!, uuid: String!): Surveys
listSurveys(filter: TableSurveysFilterInput, limit: Int, nextToken: String): SurveysConnection
getWaterQuality(uuid: String!): WaterQuality
listWaterQualities(filter: TableWaterQualityFilterInput, limit: Int, nextToken: String): WaterQualityConnection
}
type Subscription {
onCreateSurveys(
uuid: String,
year: Int,
geocode: AWSJSON,
metadata: AWSJSON,
observations: AWSJSON
): Surveys
#aws_subscribe(mutations: ["createSurveys"])
onUpdateSurveys(
uuid: String,
year: Int,
geocode: AWSJSON,
metadata: AWSJSON,
observations: AWSJSON
): Surveys
#aws_subscribe(mutations: ["updateSurveys"])
onDeleteSurveys(
uuid: String,
year: Int,
geocode: AWSJSON,
metadata: AWSJSON,
observations: AWSJSON
): Surveys
#aws_subscribe(mutations: ["deleteSurveys"])
onCreateWaterQuality(uuid: String): WaterQuality
#aws_subscribe(mutations: ["createWaterQuality"])
onUpdateWaterQuality(uuid: String): WaterQuality
#aws_subscribe(mutations: ["updateWaterQuality"])
onDeleteWaterQuality(uuid: String): WaterQuality
#aws_subscribe(mutations: ["deleteWaterQuality"])
}
type Surveys {
uuid: String!
year: Int!
geocode: AWSJSON!
metadata: AWSJSON!
observations: AWSJSON!
report_url: String
survey_date: String!
video_url: String!
}
type SurveysConnection {
items: [Surveys]
nextToken: String
}
input TableBooleanFilterInput {
ne: Boolean
eq: Boolean
}
input TableFloatFilterInput {
ne: Float
eq: Float
le: Float
lt: Float
ge: Float
gt: Float
contains: Float
notContains: Float
between: [Float]
}
input TableIDFilterInput {
ne: ID
eq: ID
le: ID
lt: ID
ge: ID
gt: ID
contains: ID
notContains: ID
between: [ID]
beginsWith: ID
}
input TableIntFilterInput {
ne: Int
eq: Int
le: Int
lt: Int
ge: Int
gt: Int
contains: Int
notContains: Int
between: [Int]
}
input TableStringFilterInput {
ne: String
eq: String
le: String
lt: String
ge: String
gt: String
contains: String
notContains: String
between: [String]
beginsWith: String
}
input TableSurveysFilterInput {
uuid: TableStringFilterInput
year: TableIntFilterInput
report_url: TableStringFilterInput
survey_date: TableStringFilterInput
video_url: TableStringFilterInput
}
input TableWaterQualityFilterInput {
uuid: TableStringFilterInput
}
input UpdateSurveysInput {
uuid: String!
year: Int!
geocode: AWSJSON
metadata: AWSJSON
observations: AWSJSON
report_url: String
survey_date: String
video_url: String
}
input UpdateWaterQualityInput {
uuid: String!
}
type WaterQuality {
uuid: String!
flow: AWSJSON!
free_chlorine: AWSJSON!
location: String!
ph: AWSJSON!
pressure: AWSJSON!
temperature: AWSJSON!
timestamp: Int!
}
type WaterQualityConnection {
items: [WaterQuality]
nextToken: String
}
...and the following resolver attached to the list query listWaterQualities:
{
"version": "2017-02-28",
"operation": "Scan",
"filter": #if($context.args.filter) $util.transform.toDynamoDBFilterExpression($ctx.args.filter) #else null #end,
"limit": $util.defaultIfNull($ctx.args.limit, 20),
"nextToken": $util.toJson($util.defaultIfNullOrEmpty($ctx.args.nextToken, null)),
}
$util.toJson($context.result.items)
...and the following db table structure (primary key uuid):
{
"flow": {
"raw": 7.1148501551630785,
"value": 113.83760248260926
},
"free_chlorine": {
"raw": 0.35,
"value": 0.35
},
"location": "mars",
"ph": {
"raw": 0.2,
"value": -6.15152
},
"pressure": {
"raw": 13248.528910641011,
"value": 86.19716484615098
},
"temperature": {
"raw": 684.7506156784981,
"value": 16.883287645632485
},
"timestamp": 1602381709752,
"uuid": "008b5a2ad27b42b8a311f021510fca87"
}
...and running the query in both the AppSync Console and via code. Im getting a timeout.
content-length: 1033
content-type: text/html
date: Sun, 11 Oct 2020 15:09:06 GMT
server: CloudFront
status: 504
via: 1.1 xxxxxxx.cloudfront.net (CloudFront)
x-amz-cf-id: xxxxxxx_2whgDivdLvdgGMRHz7O6g1d-5CSiBX0xVLXhIvfhQA==
x-amz-cf-pop: DFW3-C1
x-cache: Error from cloudfront
Apologies if this has already been addressed but i couldn't find a relevant solution, can anyone spot my error/issue. Something I am overlooking, or obvious as the cause of the error?
As always thanks for any and all feedback and thanks in advance!
Well, not looking for points here as its my mistake buuuut, should anyone else start to see these errors:
My issue ended up being the user role I had selected in the api data source:
Create or use an existing role
Allow AWS AppSync to securely interact with your data source.
I selected use existing and selected an IAM user associated with a different ddb table and thus the connection was never being made to the correct table and thus no data/timeout...
Sorry for waisting the space here...
I am trying to make a mutation but I am getting an error with the arguments.
In schema.graphql I have the following:
type User {
id: ID!
name: String!
email: String!
age: Int!
height: Float!
weight: Float!
goals: [String!]!
history: [Routine!]!
}
type Mutation {
signup(email: String!, password: String!, name: String!, age: Int!, height: Float!, weight: Float!, goals: [String!]!): AuthPayload
}
In schema.prisma:
model User {
id Int #id #default(autoincrement())
name String
email String #unique
password String
age Int
weight Float
height Float
goals String[]
history Routine[]
}
Then my mutation resolver is the following:
async function signup(parent, args, context, info) {
// 1
const password = await bcrypt.hash(args.password, 10)
// 2
const user = await context.prisma.user.create({ data: { ...args, password } })
// 3
const token = jwt.sign({ userId: user.id }, APP_SECRET)
// 4
return {
token,
user,
}
}
But when I try the function on GraphQL Playground for example with:
mutation {
signup(
name: "Esteban"
email: "esteban#gmail.com"
password: "Password"
age: 23
height: 1.84
weight: 70.0
goals: ["WEIGHT_LOSS"]
) {
token
user {
id
}
}
}
I get the following error:
{
"data": {
"signup": null
},
"errors": [
{
"message": "\nInvalid `context.prisma.user.create()` invocation in\n/Users/estebankramer1/Documents/ITBA/pf-proyf/server/src/resolvers/Mutation.js:10:42\n\n 6 // 1\n 7 const password = await bcrypt.hash(args.password, 10)\n 8 \n 9 // 2\n→ 10 const user = await context.prisma.user.create({\n data: {\n email: 'esteban#gmail.com',\n password: '$2a$10$hL1sMErBEcg98hpk5kRNpef2isI5d/O66zfRom8sQyThIHue7ku2O',\n name: 'Esteban',\n age: 23,\n height: 1.84,\n weight: 70,\n goals: [\n 'WEIGHT_LOSS'\n ]\n }\n })\n\nUnknown arg `0` in data.goals.0 for type UserCreategoalsInput. Available args:\n\ntype UserCreategoalsInput {\n set?: List<String>\n}\n\n",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"signup"
]
}
]
}
I don't know what I am doing wrong and I cannot find examples of Mutations with lists of strings.
It turns out I needed to change the mutation to the following:
async function signup(parent, args, context, info) {
// 1
const password = await bcrypt.hash(args.password, 10)
// 2
const user = await context.prisma.user.create({ data: {
...args,
goals: {
set: args.goals
},
password
} })
// 3
const token = jwt.sign({ userId: user.id }, APP_SECRET)
// 4
return {
token,
user,
}
}
Basically use a set operation.
I have no idea why, the documentation of Prisma is terrible. Maybe someone can clear things a bit?
I created a schema using the amplify cli and everything works as expected. The problem is when i try to perform a query on the events model it brings back comments as being null even though the event has comments. What confuses me more is when i query the comments alone they bring back their respective events but same thing does not work when i query for events. This is my schema below:
type Event #model {
id: ID!
title: String!
latitude: Float!
longitude: Float!
startDate: String!
endDate: String!
description: String!
coverUrl: String!
locationName: String!
Owner: User! #connection
attendies: [User!] #connection
photos: [Photo]
organization: Organization
comments: [Comment] #connection
}
type Comment #model {
id: ID!
user: User! #connection
event: Event! #connection
text: String!
}
The queries i performed are:
List Events
query listEvents {
listEvents {
items {
title
Owner {
username
}
comments {
items{
text
}
}
}
}
}
which brings back:
{
"data": {
"listEvents": {
"items": [
{
"title": "second Event",
"Owner": {
"username": "likono"
},
"comments": {
"items": []
}
}
]
}
}
}
and List Comments
query listComment{
listComments{
items{
text
event {
title
id
}
}
}
}
which brings back
{
"data": {
"listComments": {
"items": [
{
"text": "Second Comment Same User Same Event",
"event": {
"title": "second Event",
"id": "8bd6656b-b307-4e8f-ba65-84f75a4c2298"
}
}
]
}
}
}
Any help will be appreciated. Thanks.
I have been trying to run a mutation to create relations to two separate Types with not much success.
** SCHEMA **
(I have used "Create Resources" to create tables in DynamoDB)
type Comment {
eventId: ID!
commentId: String!
content: String
}
type CommentConnection {
items: [Comment]
nextToken: String
}
input CreateCommentInput {
eventId: ID!
commentId: String!
content: String
}
input CreateEventInput {
id: ID!
name: String
where: String
when: String
description: String
}
input DeleteCommentInput {
eventId: ID!
}
input DeleteEventInput {
id: ID!
}
type Event {
id: ID!
name: String
where: String
when: String
description: String
comments(limit: Int, nextToken: String): CommentConnection
}
type EventConnection {
items: [Event]
nextToken: String
}
type Mutation {
createEvent(input: CreateEventInput!): Event
updateEvent(input: UpdateEventInput!): Event
deleteEvent(input: DeleteEventInput!): Event
createComment(input: CreateCommentInput!): Comment
updateComment(input: UpdateCommentInput!): Comment
deleteComment(input: DeleteCommentInput!): Comment
commentOnEvent(input: commentOnEventInput!): Comment
}
type Query {
fetchEvent(id: ID!): Event
getEvent(id: ID!): Event
listEvents(first: Int, after: String): EventConnection
getComment(eventId: ID!): Comment
listComments(first: Int, after: String): CommentConnection
}
type Subscription {
onCreateEvent(
id: ID,
name: String,
where: String,
when: String,
description: String
): Event
#aws_subscribe(mutations: ["createEvent"])
onUpdateEvent(
id: ID,
name: String,
where: String,
when: String,
description: String
): Event
#aws_subscribe(mutations: ["updateEvent"])
onDeleteEvent(
id: ID,
name: String,
where: String,
when: String,
description: String
): Event
#aws_subscribe(mutations: ["deleteEvent"])
onCreateComment(eventId: ID, commentId: String, content: String): Comment
#aws_subscribe(mutations: ["createComment"])
onUpdateComment(eventId: ID, commentId: String, content: String): Comment
#aws_subscribe(mutations: ["updateComment"])
onDeleteComment(eventId: ID, commentId: String, content: String): Comment
#aws_subscribe(mutations: ["deleteComment"])
}
input UpdateCommentInput {
eventId: ID!
commentId: String
content: String
}
input UpdateEventInput {
id: ID!
name: String
where: String
when: String
description: String
}
input commentOnEventInput {
eventId: ID!
content: String
}
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
** MUTATIONS **
mutation #1:
mutation {
createEvent(input: {
id: "id8888"
name: "some event"
where: "Tokyo"
when: "tomorrow"
description: "desc for event"
})
{
id
name
}
}
mutation #1 gives:
{
"data": {
"createEvent": {
"id": "id8888",
"name": "some event"
}
}
}
mutation #2:
mutation {
commentOnEvent(input : {
eventId: "id8888"
commentId: "id2222"
content: "some content"
})
{
commentId
content
}
}
mutation #2 gives:
{
"data": {
"commentOnEvent": null
}
}
In the React sample created by AWS AppSync creates commentId automatically but I can't recreate that in manually created schema and resource.
I would like to know how I can establish relations on separate types and query them. Has anybody successfully done this??
Starting from the console's “Create resources” functionality lets talk through how this works. Assume we have this schema.
type Comment {
eventId: ID!
commentId: String!
content: String
}
type CommentConnection {
items: [Comment]
nextToken: String
}
input CreateCommentInput {
eventId: ID!
commentId: String!
content: String
}
input CreateEventInput {
id: ID!
name: String
where: String
when: String
description: String
}
input DeleteCommentInput {
eventId: ID!
}
input DeleteEventInput {
id: ID!
}
type Event {
id: ID!
name: String
where: String
when: String
description: String
comments(limit: Int, nextToken: String): CommentConnection
}
type EventConnection {
items: [Event]
nextToken: String
}
type Mutation {
createEvent(input: CreateEventInput!): Event
updateEvent(input: UpdateEventInput!): Event
deleteEvent(input: DeleteEventInput!): Event
createComment(input: CreateCommentInput!): Comment
updateComment(input: UpdateCommentInput!): Comment
deleteComment(input: DeleteCommentInput!): Comment
}
type Query {
getEvent(id: ID!): Event
listEvents(first: Int, after: String): EventConnection
getComment(eventId: ID!): Comment
listComments(first: Int, after: String): CommentConnection
}
type Subscription {
onCreateEvent(
id: ID,
name: String,
where: String,
when: String,
description: String
): Event
#aws_subscribe(mutations: ["createEvent"])
onUpdateEvent(
id: ID,
name: String,
where: String,
when: String,
description: String
): Event
#aws_subscribe(mutations: ["updateEvent"])
onDeleteEvent(
id: ID,
name: String,
where: String,
when: String,
description: String
): Event
#aws_subscribe(mutations: ["deleteEvent"])
onCreateComment(eventId: ID, commentId: String, content: String): Comment
#aws_subscribe(mutations: ["createComment"])
onUpdateComment(eventId: ID, commentId: String, content: String): Comment
#aws_subscribe(mutations: ["updateComment"])
onDeleteComment(eventId: ID, commentId: String, content: String): Comment
#aws_subscribe(mutations: ["deleteComment"])
}
input UpdateCommentInput {
eventId: ID!
commentId: String
content: String
}
input UpdateEventInput {
id: ID!
name: String
where: String
when: String
description: String
}
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
This is what the schema should look like after running Create Resources on the Event and Comment types. When going through the "Create Resources" flow with the Comment type you should choose the eventId as the table's hash key and the commentId as the sort key. For the Event type you can leave "id" as the single hash key. So what did that do for us?
First it created 2 DynamoDB tables to hold our objects of type Event and Comment. It then imported those tables as AppSync data sources and generated new schema parts including input objects, objects, and query and mutation fields and saved them to the schema. It also wired up resolvers specific to the new table you just defined and attached them to the newly generated query and mutation fields that implement common CRUD patterns. Unfortunately this does not yet understand relations so we have to add those ourselves. To do that let's first make the mutation to create relations as you have asked about and for completeness we will do a query as well.
As you have already done, you are going to need to add something like this to your schema
type Mutation {
commentOnEvent(input: CommentOnEventInput!): Comment
}
input CommentOnEventInput {
eventId: ID!
content: String
}
Save the schema and then click "Attach" on the Mutation.commentOnEvent field to add a resolver. Select the CommentTable data source we created earlier and from the mapping template put this:
{
"version" : "2017-02-28",
"operation" : "PutItem",
"key" : {
"eventId": $util.dynamodb.toDynamoDBJson($ctx.args.input.eventId),
"commentId": $util.dynamodb.toDynamoDBJson($util.autoId()),
},
"attributeValues" : $util.dynamodb.toMapValuesJson($ctx.args.input)
}
and for the response mapping template
$util.toJson($context.result)
Click save. Now you should be able to run a query like this:
mutation {
commentOnEvent(input: { eventId: "***", content: "A comment"}) {
eventId
content
}
}
Let's now add away to read data via a relation. E.G. I want to be able to run a query like this:
query {
getEvent(id: "***") {
id
comments(first: 5) {
items {
content
}
}
}
}
To do this lets first add the following parts to the schema.
type Event {
# add this to existing fields
comments(first: Int, after: String): CommentConnection
}
Click save then click "Attach" on the Event.comments field. Select the CommentTable data source again and then provide the following for the request mapping template.
# Event.comments.request.vtl
{
"version" : "2017-02-28",
"operation" : "Query",
"query" : {
"expression": "eventId = :eventId",
"expressionValues" : {
":eventId" : {
"S" : "${ctx.source.id}"
}
}
},
"limit": $util.defaultIfNull(${ctx.args.first}, 20),
"nextToken": $util.toJson($util.defaultIfNullOrBlank($ctx.args.after, null))
}
Notice the $ctx.source.id. Since we are resolving the Event.comments field, the $ctx.source is the instance of the Event type that we are resolving comments for. In effect this makes it so that anywhere we include { comments { ... } in a selection set on the Event type, only comments for the parent event will be fetched. And then you can return the paginated result object.
# Event.comments.response.vtl
# $ctx.result = { items: [...], nextToken: "..." }
$util.toJson($ctx.result)
This should do the trick. Now you can run both these queries and see the results.
mutation {
commentOnEvent(input: { eventId: "***", content: "A comment"}) {
eventId
content
}
}
query {
getEvent(id: "***") {
id
comments(first: 5) {
items {
content
}
}
}
}
Hope this helps.