Add many to many connection in amplify graphql - graphql

I have a time consuming problem and no idea what I can test more.
This is working, but I need the in Recruiter for positions an array. But then I have a many to many connection and nothing is working anymore. Is there a nice way to solve it?
Here is my code:
type Position #model #auth(rules: []) {
id: ID!
title: String!
candidates: [Candidate]! #connection(name: "PositionCandidates")
interestedRecruiters: [Recruiter]! #connection(name: "PositionRecruiter")
}
type Candidate #model #auth(rules: []) {
id: ID!
firstname: String!
lastname: String!
email: AWSEmail!
birthday: AWSDate!
position: Position! #connection(name: "PositionCandidates")
}
type Recruiter #model #auth(rules: []) {
id: ID!
firstname: String!
lastname: String!
email: AWSEmail!
birthday: AWSDate!
positions: Position! #connection(name: "PositionRecruiter")
}
thanks !

To work with many-to-many connections with amplify/app sync you have to create a 'Join' table (type). You can think of the join table as a table where you store the connections.
I didn't completely understand from your example what you wanted the result to be, so I'll try to explain with an alternate example:
Lets say you have a system where multiple users can be the creators of a document, and a user also can be one of the creators of multiple documents. To accomplish this you must create three dynamoDb tables (or three types is the schema).
type User #model {
id: ID!
name: String
documents: [UserDocument] #connection(name: "UserDocumentConnection")
}
type Document #model {
id: ID!
title: String
content: String
users: [UserDocument] #connection(name: "DocumentUserConnection")
}
type UserDocument #model {
id: ID!
user: User #connection(name: "UserDocumentConnection")
document: Document #connection(name: "DocumentUserConnection")
}
Then you have a table containing all your users, a table containing all your Documents, and a table containing all the connections between users and documents.
So let's say you have a users that is creating a new document. Then you first create the document, then when it is created and you have received the document back from appsync with the new id of the document, then you must create a new object in the UserDocument table, containing the id of the user and the id of the document. You can then also add more users on the document by adding more items to the UserDocument table.
I hope this will help you to the correct path forwards.

Related

How Dynamo DB manages Many to Many relationship?

I have 2 tables User and Activity with a Many to Many relationship:
type User #model #auth(rules: [{allow: public}]) {
id: ID!
activities: [Activity] #manyToMany(relationName: "UserActivity")
}
type Activity #model #auth(rules: [{allow: public}]) {
id: ID!
users: [User] #manyToMany(relationName: "UserActivity")
}
From Dynamo DB how should I add my JSON data to connect these 2 tables?
The relationship #manyToMany, as documented here, is completely different from all others, it configures a "join table" between two models.
Basically, after push your new GraphQL schema, in Dynamo DB you'll find a new table:
type UserActivity {
id: ID!
userID: ID!
activityID: ID!
user: User!
activity: Activity!
createdAt: AWSDateTime!
updatedAt: AWSDateTime!
}
Here you have to provide 2 different ID (userID and activityID) every time you want to join these 2 tables.
So you don't have to add IDs to single tables (User, Activity) but only to the joined table (UserActivity)

Add auth to many to many Amplify GraphQL DataStore connection

I am beginning a project with Amplify DataStore and modeling data that has a many-to-many relationship that also requires authorization. I'm having a hard time finding examples of M2M with auth, there are none in the documentation.
Here is a MTM example from the DataStore docs, I want to prevent users that are not PostEditors from being able to load Posts. Currently the plan is to use Cognito, but open to other solutions. How would this be achieved?
type Post #model {
id: ID!
title: String!
editors: [PostEditor] #connection(keyName: "byPost", fields: ["id"])
}
type PostEditor
#model(queries: null)
#key(name: "byPost", fields: ["postID", "editorID"])
#key(name: "byEditor", fields: ["editorID", "postID"]) {
id: ID!
postID: ID!
editorID: ID!
post: Post! #connection(fields: ["postID"])
editor: User! #connection(fields: ["editorID"])
}
type User #model {
id: ID!
username: String!
posts: [PostEditor] #connection(keyName: "byEditor", fields: ["id"])
}

Do I have to create a new type to define array of objects in GraphQL schemas?

I’m trying to replicate a REST API that I’ve built in the past and one part that got me thinking was if one of my tables had an array of objects. So for example, I have a table called Profile and it contains arrays Experience and Education that would strictly be under Profile but has its own fields as well but not its own table.
While I was adding fields in GraphQL, I bumped into this not really having a solid solution other than to create new types and then associating them with relationships and then have a resolver or a front-end make sure a Profile is created first before the Experience/Education portion is. I’m not sure if it’s the right way to do it or if there is a better way to do it. Down below is a snippet of what I ended up using… looking at the admin page, there are created tables for Profile, Experience and Education which is expected. But is there a way to only have just Profile and accomplish something similar? Or is this more of a way of life with GraphQL?
type Profile {
id: ID! #id
handle: String!
company: String
website: String
location: String
status: String!
githubUsername: String
experience: [Experience!] #relation(link: INLINE)
education: [Education!] #relation(link: INLINE)
}
type Experience {
id: ID! #id
title: String!
company: String!
}
type Education {
id: ID! #id
title: String!
company: String!
}
In Prisma, you can use embedded types. You would drop the #relation directive and add #embedded directives to the types you're embedding:
type Profile {
id: ID! #id
handle: String!
company: String
website: String
location: String
status: String!
githubUsername: String
experience: [Experience!]
education: [Education!]
}
type Experience #embedded {
title: String!
company: String!
}
type Education #embedded {
title: String!
company: String!
}
However, this only possible if you're using MongoDB for your database and there's some specific limitations listed in the docs when using embedded types.

How to "upsert" an array/nested field when issuing update mutation in Graphcool / GraphQL?

I have a Post type than has a tag field which can be associated with many Tag entries (man-to-many relationship). The issue I am running into is when updating a Post - I need to both create and associate new Tag's for tags that do not yet exist, while preserving the existing Post->Tag relationships. Basically, what I am looking for is something akin to an upsert when issuing a mutation update on a nested one-to-many field.
Here is my schema:
type Post #model {
createdAt: DateTime!
createdBy: User #relation(name: "PostsByUser")
description: String #defaultValue(value: "''")
id: ID! #isUnique
tags: [Tag!]! #relation(name: "TagsOfPost")
...
}
type Tag #model {
id: ID! #isUnique
tag: String!
createdBy: User #relation(name: "TagsByUser")
createdAt: DateTime!
posts: [Post!]! #relation(name: "TagsOfPost")
}
This mutation works to update a Post and add new tags, but overwrite all of the existing values in the Post's tag field:
mutation updatePost(
$id: ID!
$createdById: ID!
$timestamp: DateTime!
$description: String
$tags: [PosttagsTag!]!
) {
updatePost(
id: $id
createdById: $createdById
timestamp: $timestamp
description: $description
tags: $tags
) {
id
timestamp
description
tags {
id
tag
}
createdBy {
id
username
}
}
}
I came across this post by #marktani but it's not clear how to implement the combined method he outlines:
Combined
You can also use tags and tagsIds in the same mutation, this would connect the new Tutorial node to all the tags in tagsIds and connect it to the new tags in tags. This is what you want to do if you want to only allow tags with a unique text, so for a new Tutorial, there will likely be some tags that already exists, and some that need to be created.
Is it currently impossible to do this with one mutation? Would a second mutation be required after updating the post with new tags to re-establish the associations between the Post and existing Tag ids, i.e., having to repeatedly call addToTagsOfPost(tagsTagId: ID!
postsPostId: ID!)? Thanks!
Ok, so there is currently a Graphcool bug where passing in both tags and tagsIds to the mutation will create and associate newly created Tags but will not add the tagsIds associations to existing Tags. I posted an issue on Graphcool's GitHub repo and they have acknowledged it - https://github.com/graphcool/framework/issues/1288

How to reference a list of types representing existing records?

Suppose I have two types, User and Permission, e.g.
type User {
id: ID!
firstName: String!
lastName: String!
email: String!
permissions: [Permission!]
}
type Permission {
id: ID!
name: String!
}
User has permissions field, which contains a list of associated Permission type records.
How do I structure a mutation query that updates which permissions user has?
I have simply used a list of ID type for the permissions parameter in the mutation
I have set permissions parameter to a list of GraphQLID.
type Mutation {
updateUser (
id: ID!
email: String
password: String
firstName: String
lastName: String
# List of Permission IDs
permissions: [ID!]
): User
}
The problem with this approach is that this does not define relation between Permission type and ID. Therefore, I have added "List of Permission IDs" comment.
Another approach would be to create a dedicated input type, e.g.
input PermissionInput {
id: ID!
}
And use that in instead of ID in the updateUser permissions parameter. However, this does not solve the reference issue.
Unfortunately, I can't add just a comment, but we solve the problem in the same way - but to ensure clarity and consistency of the shape of the data, we use specific id fields. I.e. we would define the input like this:
type Mutation {
updateUser (
id: ID!
email: String
password: String
firstName: String
lastName: String
permissionIds: [ID!]
): User
}
Pretty basic difference, but it clearly differentiates the input from the output, each field name is intuitive and there isn't much overhead.

Resources