I'm currently working on a react project that uses a one to many relationship for a schema model.
My schema is defined as below for the children field, I want it to be a recursive model that is the same as Response.
type Response #model #auth(rules: [{ allow: owner }]) {
id: ID!
problem: String!
model: String!
basic: String
groupify: String
cognify: String
technify: AWSJSON
temperature: Float!
response: String!
bookmarked: Boolean!
preference: String!
children: [Response] #hasMany(fields: ["id"])
}
I want to update a response with the children and create a new response without any children initially but I'm not entirely sure how to do that.
I'm following documentation via this https://docs.amplify.aws/lib/graphqlapi/mutate-data/q/platform/js/ but I can't find an example for my purpose.
I have a function to create a vehicle in my amplify project:
const createNewVehicle = async () => {
const { year, make, model, vinNumber } = searchedVehicleInfo || {};
try {
await API.graphql({
query: createVehicle,
variables: { input: { year, make, model, vinNumber } },
authMode: 'AMAZON_COGNITO_USER_POOLS',
});
} catch (err) {
console.log(err);
return null;
}
};
This function creates a vehicle in my dynamoDB table utilizing the API module exposed by amplify. The authMode property saves the Cognito user's username in the API request as well. The vehicle's graphql model looks like this:
type Vehicle
#model
#auth(
rules: [
{
allow: owner
ownerField: "username"
operations: [create, read, update, delete]
}
{ allow: public, operations: [read] }
]
) {
id: ID!
year: Int!
make: String!
model: String!
vinNumber: String!
image: String
username: String
#index(name: "vehiclesByUsername", queryField: "vehiclesByUsername")
receipts: [Receipt] #hasMany(indexName: "byVehicle", fields: ["id"])
}
A team member created his own AWS account and set up his own environment to be used in amplify. This team member created an IAM user and applied the AdminAccess-Amplify role to his user. When he uses this function, the vehicle is created, but it saves the username in a weird way. If his username is test12 it will save the vehicle with a username like this 6e0b3347-5dae-4106-aed6-8ec5c87fde52::test12. So when I try to grab vehicles by username, none come up because his username is test12 and not 6e0b3347-5dae-4106-aed6-8ec5c87fde52::test12.
How can I get it to save just the username and not all the extra information?
There really isn't enough documentation on this either in the AWS docs or in the Github, so hopefully someone here has tackled a similar issue.
I have a react app with backend api hosted on AWS, using appsync, dynamoDB, and cognito-user-pools. My IAM policies are set up to allow unauth users read-only permission to some public tables. I tried the public api key but that didn't do anything. I'm trying to get the IAM unauth role permissions set up but even when I experimentally added literally every service and every action to the unauth role, I still get "no current user" when attempting to make the API call without logging in.
Use case is for public author pages, where information about an author along with their currently available books is listed. Users should not have to sign in to see this page, an author should be able to drop a link to the page to anyone, whether they have a login for the app or not.
This is my graphql schema for the relevant types, it gets no errors:
type PublicBook #model #auth(rules: [{ allow: owner, operations: [create, update, delete], provider: userPools },
{allow: public, operations: [read], provider: iam}])
#key(name:"byPublicWorld", fields: ["publicWorldId", "indexOrder"])
#key(name:"byPublicSeries", fields: ["publicSeriesId", "indexOrder"]){
id: ID!
publicWorldId: ID
publicSeriesId: ID
indexOrder: Int!
cover: FileObject #connection
description: String
amazon: String
ibooks: String
smashwords: String
kobo: String
goodreads: String
audible: String
barnesnoble: String
sample: String
}
type PublicSeries #model #auth(rules: [{ allow: owner, operations: [create, update, delete], provider: userPools },
{allow: public, operations: [read], provider: iam}])
#key(name:"byPublicWorld", fields: ["publicWorldId", "indexOrder"]){
id: ID!
publicWorldId: ID!
indexOrder: Int!
logo: FileObject #connection
description: String
genre: String
books: [PublicBook]#connection(keyName:"byPublicSeries", fields: ["id"])
}
type PublicWorld #model #auth(rules: [{ allow: owner, operations: [create, update, delete], provider: userPools },
{allow: public, operations: [read], provider: iam}])
#key(name:"byAuthorPage", fields: ["authorPageId", "indexOrder"]){
id: ID!
authorPageId: ID!
logo: FileObject #connection
description: String
genre: String
indexOrder: Int!
series: [PublicSeries]#connection(keyName:"byPublicWorld", fields: ["id"])
books: [PublicBook]#connection(keyName:"byPublicWorld", fields: ["id"])
}
type AuthorPage #model #auth(rules: [{ allow: owner, operations: [create, update, delete], provider: userPools },
{allow: public, operations: [read], provider: iam}])
#key(name:"byPenName", fields: ["penId"])
#key(name:"byPenDisplayName", fields: ["penDisplayName"], queryField: "authorPageByPen"){
id: ID!
authorName: String
penDisplayName: String
penId: ID!
bio: String
photo: FileObject #connection
logo: FileObject #connection
penFBProfile: String
penFBGroup: String
penFBPage: String
penTwitter: String
penInstagram: String
penAmazon: String
penWebsite: String
penNewsletter: String
penGoodreads: String
penPatreon: String
posts: [AuthorPost]#connection(keyName:"byAuthorPage", fields: ["id"])
worlds: [PublicWorld]#connection(keyName:"byAuthorPage", fields: ["id"])
}
type AuthorPost #model #auth(rules: [{ allow: owner, operations: [create, update, delete], provider: userPools },
{allow: public, operations: [read], provider: iam}])
#key(name:"byAuthorPage", fields: ["authorPageId", "timeCreated"]){
id: ID!
authorPageId: ID!
timeCreated: AWSTimestamp!
content: String!
title: String!
subtitle: String
type: PostType!
}
Each of these types is set to owner/cognito permissions for creating, updating, and deleting, and then there is a public auth using iam to read. Seems straight forward enough.
The main type here is Author page, and I have the query set up to pull all the connected relevant cascading information. When logged in, this works fine and shows an author page with all the bits and bobs:
export const authorPageByPen = /* GraphQL */ `
query AuthorPageByPen(
$penDisplayName: String
$sortDirection: ModelSortDirection
$filter: ModelAuthorPageFilterInput
$limit: Int
$nextToken: String
) {
authorPageByPen(
penDisplayName: $penDisplayName
sortDirection: $sortDirection
filter: $filter
limit: $limit
nextToken: $nextToken
) {
items {
id
authorName
penDisplayName
penId
bio
photo {
location
}
logo {
location
}
penFBProfile
penFBGroup
penFBPage
penTwitter
penInstagram
penAmazon
penWebsite
penNewsletter
penGoodreads
penPatreon
posts {
nextToken
startedAt
}
worlds {
nextToken
startedAt
}
_version
_deleted
_lastChangedAt
createdAt
updatedAt
owner
}
nextToken
startedAt
}
}
`;
On the page itself (although in production this just happens at app.js and persists throughout the app), I'm pulling current credentials and logging them to make sure that some kind of IAM identity is being created, and it appears to be:
accessKeyId: "BUNCHANUMBERSKEY"
authenticated: false
expiration: Thu Mar 04 2021 13:18:04 GMT-0700 (Mountain Standard Time) {}
identityId: "us-west-2:48cd766c-4854-4cc6-811a-f82127670041"
secretAccessKey: "SecretKeyBunchanumbers"
sessionToken:"xxxxxbunchanumbers"
That identityId on line 4 is present in my identity pool as an unauth identity, so it is getting back to the pool, which seems to be what's supposed to happen.
So, this identity pool has two roles associated with it, which is standard: auth and unauth, and my Unauthenticated Identities Setting has the box for Enable Access to Unauthenticated Identities checked.
In my unauth role, I've got the following as the inline policy json:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"appsync:GraphQL"
],
"Resource": [
"arn:aws:appsync:us-west-2:MyAccountID:apis/MyAppsyncApiId/types/Mutation/fields/authorPageByPen"
]
}
]
}
I wasn't sure if this needed to be mutation, or query, or what, so I've tried them all. I tried them in combination with 'fields' and with 'index', I've tried writing the JSON, and adding the policies from the inline editor, which gives me the following which also does not work:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "appsync:GraphQL",
"Resource": [
"arn:aws:appsync:us-west-2:MyAccountID:apis/MyAppSyncAPIId/types/AuthorPage/fields/authorPageByPen",
"arn:aws:appsync:us-west-2:MyAccountID:apis/MyAppSyncAPIID"
]
}
]
}
What bit am I missing here? I could understand getting some error about not being allowed to access a resource, but the only error that logs is No Current User, and that happens immediately after the log showing the user.
Update:
Running the query from the Appsync console works fine with IAM and no logged in user. In the page itself, I'm using the following function to call the author page (I'm using routes):
const pullAuthorPage = async () => {
try{
const authorPageData = await API.graphql(graphqlOperation(authorPageByPen, { penDisplayName: props.match.params.id.toLowerCase() }))
console.log(JSON.stringify(authorPageData, null, 2));
setState({...authorPageData.data.authorPageByPen.items[0]})
} catch (error) {
console.log(error);
}
}
What I thought would happen with this is that if there is no authenticated user logged in, this will run using the unauth user credentials. Is that not the case? And if so, how should I change it?
Following AWS documentation (https://aws-amplify.github.io/docs/cli-toolchain/graphql > Many-To-Many Connections), I try to understand the workaround example they provide for many to many connections (which seems not supported yet by Amplify).
The schema is:
type Post #model {
id: ID!
title: String!
editors: [PostEditor] #connection(name: "PostEditors")
}
# Create a join model and disable queries as you don't need them
# and can query through Post.editors and User.posts
type PostEditor #model(queries: null) {
id: ID!
post: Post! #connection(name: "PostEditors")
editor: User! #connection(name: "UserEditors")
}
type User #model {
id: ID!
username: String!
posts: [PostEditor] #connection(name: "UserEditors")
}
Using AWS AppSync Console, so far I'm able to:
Create a user using this mutation:
mutation {
createUser(input:{
username: "theUserName"
}){
username
}
}
Create a post using this mutation:
mutation {
createPost(input: {
title: "second post"
}){
title
}
}
But I don't understand how to add multiple editors to a post? So far, I'm able to add editors to a post using PostEditor join, but in their example, there is this statement (which I don't understand very well), so I don't think this is the good approach:
# Create a join model and disable queries as you don't need them
# and can query through Post.editors and User.posts
So I guess that using this join model to perform mutation is not what I have to do. Nevertheless, to be able to create this relation between a post and an editor, I created a mutation (retrieving "postEditorPostId" and "postEditorEditorId" from both previous mutations):
mutation {
createPostEditor(input:{
postEditorPostId: "XXX-XXX-XXX"
postEditorEditorId: "YYY-YYY-YYY"
}){
post {
title
}
editor {
username
posts {
items {
post {
title
}
}
}
}
}
}
Do I need to perform this previous mutation everytime I add a new editor (so the mutation will remain the same but "postEditorEditorId" will change? it seems obviously not a scalable approach, if for example the UI allows an admin to add 50 or more new editors (so it will need 50 mutations).
Finally I can get the information I need using this query:
query{
getUser(id: "YYY-YYY-YYY"){
username
posts {
items {
post {
title
}
}
}
}
}
Is there a better way (I suppose) to add editors to a post?
edit:
Using a promise, I am able to add multiple editors to a post, but it involves to execute as mutation as mutations as there are users:
const users = [{id: "U1", username: "user1"}, {id: "U2", username: "user2"}];
const post = { id: "P1", title: "Post 1" };
/*
After creating two users and a post using the approriate mutations
Using the CreatePost join below to make user1 and user2 editor on Post 1
*/
function graphqlCreatePostEditor(editorID) {
return new Promise((resolve, reject) => {
resolve(
API.graphql(graphqlOperation(createPostEditor, {
input: {
postID: post.id,
}
}))
)
})
}
let promises = users.map(user=> {
return graphqlCreatePostEditor(user.id)
.then(e => {
console.log(e)
return e;
})
});
Promise.all(promises)
.then(results => {
console.log(results)
})
.catch(e => {
console.error(e);
})
Still looking if there is a way to pass an array in a sigle mutation.
For simplicity sake, I'm lets go with a User model and a Project model where a user can have many projects and belong to many projects.
Note: The creation of join table as I've described it here is for the Amplify JS API for React / React Native / JavaScript
User model
type User #model {
id: ID!
username: String!
projects: [UserProject] #connection(name: "UserProject")
}
Project model
type Project #model {
id: ID!
project_title: String!
users: [UserProject] #connection(name: "ProjectUser")
}
Join table
type UserProject #model {
id: ID!
user: User #connection(name: "UserProject")
project: Project #connection(name: "ProjectUser")
}
Creation of Join table
Prerequisite: Fetch both user.id and project.id however you want to do that.
const UserProjectDetails = {
userProjectUserId: user.id
userProjectProjectId: project.id
};
API.graphql({ query: mutations.createUserProject, variables: {input: UserProjectDetails}})
And there you have it.
This article on dev.to was also pretty straight to the point:
https://dev.to/norrischebl/modeling-relationships-join-table-graphql-aws-amplify-appsync-1n5f
I have a UI component that generates a mongo schema like this
{
content: String,
date: { type: Date, default: Date.now },
author: {
type: Schema.Types.ObjectId,
ref: 'User'
}
}
the idea is take this schema and generate the end-point to get the info, the question is where do you recomend storage the schema in mongoDB or in the files somehow run programmatically
yo angular-fullstack:endpoint mySchema
Thank You