AppSync / Apollo - Subscription - Update nested array within a model - apollo-client

For a Chat app I am trying to update the Apollo cache for a nested array, so that when a message is received for any conversation, the user can see the latest message in real time.
I have the following AppSync schema.
type Conversation
#model
#auth(rules: [
{ allow: owner, ownerField: "users", operations: [create, read, delete, update] }
{ allow: private, provider: iam, operations: [read, create, update, delete] }
]) {
messages: [Message] #hasMany(indexName: "messagesByConversation", limit: 1)
users: [String]
createdAt: AWSDateTime
updatedAt: AWSDateTime
}
type Message
#model(mutations: { create: "createMessage", update: "updateMessage" })
#auth(rules: [
{ allow: groups, groups: ["Users"] }
{ allow: owner, operations: [read, create, update], ownerField: "users" }
{ allow: private, provider: iam }
]) {
conversationId: ID! #index(name: "messagesByConversation", sortKeyFields: ["createdAt"], queryField: "messagesByConversation")
text: String
users: [String]!
receiver: ID!
sender: ID!
createdAt: AWSDateTime!
updatedAt: AWSDateTime
}
type Subscription {
onCreateMessageForSenderUser(sender: String!): Message #aws_subscribe(mutations: ["createMessage"])
onCreateMessageForReceiverUser(receiver: String!): Message #aws_subscribe(mutations: ["createMessage"])
}
Which has the nested model messages: [Message]
In the Apollo Cache, I can see a new typename is created called __ ModelMessageConnection to facilitate the connection
I have a subscription that listens for new messages (in this case when my user is the sender).
The trouble I am facing, is how can I update the cache so when the subscription for a new message is received, it updates for the conversation (the ModelMessageConnection)
const { data: senderMessageData } = useSubscription(
gql(SENDER_MESSAGE_SUBSCRIPTION),
{
skip: !subData?.sub,
variables: {
sender: subData?.sub
},
onSubscriptionData: (options) => {
options.client.cache.modify({
//TODO: do something here?
});
}
}
);

Related

Amplify - Custom status code from Graphql lambda function

I am currently using a lambda function attached to my graphql createUser.
schema.graphql
type User
#model(queries: { get: null }, mutations: null, subscriptions: null)
#auth(rules: [
{ allow: groups, groups: ["ADMIN"] }
])
{
id: ID!
first_name: String!
last_name: String!
email: String!
}
createUser(input: CreateUser!): User
#function(name: "createUser-${env}")
#auth(rules: [
{ allow: groups, groups: ["ADMIN"] }
])
I'm trying to return a 401 status code from my lambda but it's not working, how can I do that ?

Authorization error when updating a GraphQL object using aws-amplify

I'm experiencing issues when using aws amplify to generate a graphql API
My model has mainly two objects (User and messages):
User object:
type User
#model(subscriptions: null)
#auth(rules: [{ allow: owner, ownerField: "id" }]) {
id: ID!
email: String!
username: String!
avatar: S3Object
name: String
conversations: [Conversation] #manyToMany(relationName: "UserConversations")
messages: [Message!]! #hasMany(indexName: "byAuthor", fields: ["id"])
createdAt: String
updatedAt: String
}
Message object:
type Message
#model(subscriptions: null)
#auth(
rules: [
{
allow: owner
ownerField: "authorId"
operations: [create, update, delete]
}
]
) {
id: ID!
author: User #belongsTo(fields: ["authorId"])
authorId: ID! #index(name: "byAuthor", sortKeyFields: ["content"])
content: String!
conversation: Conversation! #belongsTo(fields: ["messageConversationId"])
messageConversationId: ID!
#index(name: "byConversation", sortKeyFields: ["createdAt"])
createdAt: String
updatedAt: String
}
There's a hasMany/belongsTo relationship between the two and auth rules on both.
After I signin to the API and try to create a user object through the JS API, i'm getting the following error
'Not Authorized to access messages on type ModelMessageConnection'
await AuthAPI.graphql(
graphqlOperation(createUser, {
input: {
id,
username,
email,
name,
},
})
);
This is actually due to the message rule that was missing the read action. Changing the object model to the code below fixed it
type Message
#model(subscriptions: null)
#auth(
rules: [
{
allow: owner
ownerField: "authorId"
operations: [create, update, delete, read]
}
]
) {
id: ID!
author: User #belongsTo(fields: ["authorId"])
authorId: ID! #index(name: "byAuthor", sortKeyFields: ["content"])
content: String!
conversation: Conversation! #belongsTo(fields: ["messageConversationId"])
messageConversationId: ID!
#index(name: "byConversation", sortKeyFields: ["createdAt"])
createdAt: String
updatedAt: String
}

How to model Many to Many relationship for Amplify GraphQL Schema on Same Table

I am using GraphQl API in an Amplify project and having some difficulty understanding how to model a many to many relationship between users. I get how to add a join table between two other tables. But now sure how to do it for the same table.
This is what I have but I'm almost certain it's not correct. I want each user to be able to add other Users as 'relations':
type User
#model
#auth(rules: [{ allow: owner, operations: [create, delete, update] }]) {
id: ID!
cognitoId: ID!
username: String!
registered: Boolean
contacts: [UserContacts] #connection(keyName: "byContact", fields: ["id"])
contactOfOtherUsers: [UserContacts] #connection(keyName: "byContact2", fields: ["id"])
}
type UserContacts
#model
#auth(rules: [{ allow: owner, operations: [create, delete, update] }])
#key(name: "byContact", fields: ["userID", "contactID"])
#key(name: "byContact2", fields: ["contactID", "userID"]) {
id: ID!
userID: ID!
contactID: ID!
user: User! #connection(fields: ["userID"])
contact: User! #connection(fields: ["contactID"])
}
I'm pretty new to Amplify and not really sure what approach to take.
What seems very wrong to me is the contactOfOtherUsers field in User. It is redundant but not sure how else to link the join table.
Maybe too late but this might work. Disclaimer: This would apply to Amplify GraphQL Transformer v1, which still works but it is deprecated as it was replaced by GraphQL Transformer v2
type User
#model
#auth(rules: [{ allow: owner}]) {
id: ID!
cognitoId: ID!
username: String!
registered: Boolean
contacts: [UserContact] #connection(keyName: "byContact", fields: ["id"])
}
type UserContact
#model
#auth(
rules: [
{ allow: owner }
{
allow: owner
ownerField: "contacts"
operations: [update, delete, read]
}
]
) #key(name: "byContact", fields: ["contactOneID", "contactTwoID"]){
id: ID!
contactOneID: ID!
contactTwoID: ID!
contactOne: User! #connection(fields: ["contactOneID"])
contactTwo: User! #connection(fields: ["contactTwoID"])
contacts: [ID]
}

In an AppSync #auth rule, how do I set an ownerField to a property in an array?

Imagine I have the following AppSync GraphQL schema with a User type, and a Post type with an editors field set to an array of Users:
type User
#model
#auth(rules: [
{ allow: owner }
])
{
id: ID!
owner: String!
username: String!
}
type Post
#model
#auth(rules: [
{ allow: owner },
# Can I do this?
# { allow: owner, ownerField: "editors.owner", operations: [update] }
])
{
id: ID!
owner: String!
title: String!
content: String
editors: [User]
}
How do I create an #auth rule to give update permissions to the Users in the editors array?
If you're using the amazon Cognito user pool you should set the editor type inside Post to be an array of String and set the values to the Cognito ids of the users that you want to have access. This is explained in the amplify cli documentation.
To have the editors to be of type User I suggest you to create another paramether named differently (for example editorUsers) and connect it to the User model as described here
Your schema should look like this:
type User
#model
#key(name: "byUser", fields: ["username"])
#auth(rules: [
{ allow: owner }
])
{
id: ID!
owner: String!
username: String!
}
type Post
#model
#auth(rules: [
{ allow: owner },
{ allow: owner, ownerField: "editors", operations: [update] }
])
{
id: ID!
owner: String!
title: String!
content: String
editors: [String]
editorsUsers: [User] #connection(keyName: "byUser", fields: ["id"])
}

GraphQL: Subscription not firing when mutation run

So, I'm testing subscriptions on Graphcool and would appreciate some clarification on how exactly they work.
I have a one to many relationship from Posts on Comments:
Schema
type Posts {
caption: String!
comments: [Comments!]! #relation(name: "PostsOnComments")
createdAt: DateTime!
displaysrc: String!
id: ID!
likes: Int
updatedAt: DateTime!
}
type Comments {
createdAt: DateTime!
id: ID!
posts: Posts #relation(name: "PostsOnComments")
text: String!
updatedAt: DateTime!
user: String!
}
The subscription I run in Graphcool is as follows:
subscription CreatedDeletedComments {
Comments(
filter: {
mutation_in: [CREATED, DELETED]
}
) {
mutation
node {
id
user
text
}
}
}
If I run the following in my React app, a created notification is fired:
return this.props.client.mutate({
mutation: gql`
mutation createComment ($id: ID, $textVal: String!, $userVal: String!) {
createComments (postsId: $id, text: $textVal, user: $userVal){
id
text
user
}
}
`,
variables: {
"id": postID,
"textVal": textVal,
"userVal": userVal
},
// forceFetch: true,
})
But if I run the following, no deleted notification is fired:
return this.props.client.mutate({
mutation: gql`
mutation removeComment ($id: ID!, $cid: ID!) {
removeFromPostsOnComments (postsPostsId: $id, commentsCommentsId: $cid){
postsPosts {
id
displaysrc
likes
comments {
id
text
user
}
}
}
}
`,
variables: {
"id": postID,
"cid": commentID
},
// forceFetch: true,
})
What am I overlooking here?
With the subscription
subscription CreatedDeletedComments {
Comments(
filter: {
mutation_in: [CREATED, DELETED]
}
) {
mutation
node {
id
user
text
}
}
}
you are subscribing to comment nodes being created or deleted. However, with the mutation removeFromPostsOnComments, you are not deleting any comment nodes. Instead, you are only deleting the connection between a post and a comment.
You can adjust your mutation request to delete the comment entirely instead of disconnecting it from the post:
return this.props.client.mutate({
mutation: gql`
mutation removeComment ($cid: ID!) {
deleteComment(id: $cid) {
id
}
}
`,
variables: {
"cid": commentID
},
// forceFetch: true,
})
If you don't want to delete the comment entirely but still want to hide it in your app, you could have a boolean field deleted that acts as a soft deletion marker.
Then you could subscribe to UPDATED comments instead of DELETED comments and check if the field deleted was updated. Refer to the
docs for more information on how to do that with updatedFields.
Subscriptions for relations is also already part of our roadmap.

Resources