My goal is to write an app where users can add a list of Items (lets say 20-30) to every Entry... There would be one entry per day and the number of items would be different each time.
I have two GraphQL types which look like this:
type Entry #model #auth(rules: [{allow: public}]) {
id: ID!
date: String!
entries: [Item] #hasMany
}
type Item #model #auth(rules: [{allow: public}]) {
id: ID!
name: String!
received: Boolean!
quantity: String!
hazardRating: Int!
entry: Entry #belongsTo
comments: [Comment] #hasMany
}
type Comment #auth(rules: [{allow: public}]) #model {
id: ID!
item: Item #belongsTo
content: String!
}
I want to write a mutation which adds an Item to an existing Entry:
I have tried this:
mutation createItem{
createItem( input:{name: "Sodium Hydroxide", received: true, quantity: "1L", hazardRating: 3, entry: {id = "7a59cfca-db53-4f15-8ae6-c37e025b2a44", date = "21 October 2022" }) {
id
name
received
quantity
hazardRating
entry {
id
date
}
}
}
but I get the error message "entry" does not exist on type Item...
How would I write a mutation that would add an Item to an existing Entry?
Is it even possible to do so or is it only possible to add all the Items per Entry at the same time?
I was missing references in Item.
In order that Item is recognised as part of Entry - you have to add entryID: ID! #index(name: "byEntry", sortKeyFields: ["name"]) into Item and also an indexName parameter into entry: (indexName: "byEntry", fields: ["id"])
Unlike GraphQL v1 - v2 fails to do this automatically when you start a schema from scratch so you have to make sure each type references each other.
The whole scheme looks like this:
type Entry #model #auth(rules: [{allow: public}]) {
id: ID!
date: String!
items: [Item] #hasMany(indexName: "byEntry", fields: ["id"])
}
type Item #model #auth(rules: [{allow: public}]) {
id: ID!
entryID: ID! #index(name: "byEntry", sortKeyFields: ["name"])
name: String!
amount: String!
hazardRating: Int!
comments: [Comment] #hasMany(indexName: "byItem", fields: ["id"])
}
type Comment #model #auth(rules: [{allow: public}]) {
id: ID!
itemID: ID! #index(name: "byItem" sortKeyFields: ["content"])
content: String!
}
Related
The schemas are as follows:
type User #model #auth(rules: [{allow: public}]) {
id: ID!
userEmail: AWSEmail!
userName: String!
userPassword: String!
userGoals: [Goal] #hasMany(indexName: "byUser", fields: ["id"])
}
type Goal #model #auth(rules: [{allow: public}]) {
id: ID!
goalStart: AWSDateTime!
goalEnd: AWSDateTime
goalCategory: CategoryTypes!
goalCurrentDuration: DurationBeat!
goalTargetDuration: DurationBeat!
goalPercentage: Float
userID: ID! #index(name: "byUser")
goalActivities: [Activity] #hasMany(indexName: "byGoal", fields: ["id"])
goalOfUser: User! #belongsTo
}
I save a Goal using:
Future<void> createGoal(String userID, CategoryTypes category,
DurationBeat targetDuration) async {
final Goal goal = Goal(
userID: userID,
goalCategory: category,
goalTargetDuration: targetDuration,
goalCurrentDuration: DurationBeat(
durationHours: 0, durationMinutes: 0, durationSeconds: 0),
goalPercentage: 0,
goalStart: _today, //Local datetime variable
goalEnd: null, //Null when the goal is current. Date assigned when the user sets new goal.
goalOfUser: global.currentUser //Globally accessible user instance
);
await Amplify.DataStore.save(goal);
}
When this function runs, nothing shows up in the database. (It saved correctly when I omit goalOfUser, but I made it a required field to establish a bi-directional relationship)
Finally, I request the data back using this function
Future<Goal> getLatestGoal(CategoryTypes category, String userId) async {
final record = await Amplify.DataStore.query(Goal.classType,
where: Goal.USERID
.eq(userId)
.and(Goal.GOALCATEGORY.eq(category))
.and(Goal.GOALEND.eq(null)),
sortBy: [Goal.GOALEND.ascending()]);
return record.first;
}
Surprisingly, this function returns the data that I "saved".
Goal {id=710ddbeb-89b5-4bcd-9623-8232b4e8a545, goalStart=2022-07-23T04:00:00.000000000Z, goalEnd=null, goalCategory=FITNESS, goalCurrentDuration=DurationBeat {durationSeconds=0, durationMinutes=0, durationHours=0}, goalTargetDuration=DurationBeat {durationSeconds=0, durationMinutes=5, durationHours=1}, goalPercentage=0.0, userID=190c7bd1-02ad-4ab1-970d-49b8e6f7a9f8, goalOfUser=User {id=190c7bd1-02ad-4ab1-970d-49b8e6f7a9f8, userEmail=charlesrichardsonusa#gmail.com, userName=Charbo, userPassword=password, createdAt=2022-07-19T20:23:09.797000000Z, updatedAt=2022-07-23T05:06:18.321000000Z}, createdAt=null, updatedAt=null}
I noticed that the attribute createdAt is null. I assume this means that it is only saved locally, but I do not understand why it wont sync remotely.
My question is:
How can I debug the Amplify.DataStore.save() function to determine exactly what is going wrong?
OR
Point out what I did wrong. I cant find the typo.
I'm new to AWS Amplify, and have created a GraphQL API for a project that I'm working on in order to learn it & AppSync. Essentially I have a schema that looks like the following:
type User #model {
id: ID! #primaryKey
boards: [Board] #hasMany
createdAt: String!
updatedAt: String!
}
type Board #model {
id: ID! #primaryKey
createdBy: User!
title: String!
}
and I'm trying to run the following query in the AppSync console:
query MyQuery {
listUsers {
items {
boards {
items {
title
}
}
}
}
}
But for some reason I keep seeing this error:
Filter Expression can only contain non-primary key attributes: Primary key attribute: userBoardsId
I've specified the primary key in both models, and I'm aware that AppSync generates the 'userBoardsId' foreign key, but I'm not sure why it's causing an issue.
Have you tried like this? Since boards is array, you need to add items
query MyQuery {
listUsers {
items {
boards {
items {
title
}
}
}
}
}
EDIT:
type User #model {
id: ID! #primaryKey
boards: [Board] #hasMany (indexName: "byUser")
createdAt: String!
updatedAt: String!
}
type Board #model {
id: ID! #primaryKey
userID: ID! #index(name: "byUser")
createdBy: User
title: String!
}
I'm not sure if I'm doing this correctly with the connections in AppSync GraphQL.
This is what my graphql models look like:
type User #model #auth(rules: [{ allow: owner, ownerField: "username" }]) {
id: ID!
username: String!
email: String!
userType: UserType
}
enum UserType {
TEACHER
CREATOR
}
type Teacher #model {
id: ID!
userId: ID
name: String!
activations: [Activation]
#connection(keyName: "activationsByTeacherId", fields: ["id"])
creators: [TeacherCreatorPartnership]
#connection(name: "TeacherCreatorPartnership")
}
type Creator #model {
id: ID!
userId: ID
name: String!
email: String!
username: String
teachers: [TeacherCreatorPartnership] #connection(name: "CreatorTeacherPartnership")
posts: [Post] #connection(name: "CreatorPosts")
activations: [CreatorActivations] #connection(name: "CreatorActivations")
}
type TeacherCreatorPartnership #model(queries: null) {
id: ID!
teacher: Teacher! #connection(name: "TeacherCreatorPartnership")
creator: Creator! #connection(name: "CreatorTeacherPartnership")
}
type CreatorActivations #model(queries: null) {
id: ID!
creator: Creator! #connection(name: "CreatorActivations")
activation: Activation! #connection(name: "ActivationCreators")
}
type Activation
#model
#key(
name: "activationsByTeacherId"
fields: ["teacherId"]
queryField: "activationsByTeacherId"
)
#auth(
rules: [
{ allow: groups, groups: ["Admin"] }
{
allow: owner
ownerField: "teacherId"
operations: [create, update, delete]
}
{ allow: private, operations: [read] }
{ allow: public, operations: [read] }
]
) {
id: ID!
teacherId: ID!
title: String!
teacher: Teacher #connection(fields: ["teacherId"])
creators: [CreatorActivations] #connection(name: "ActivationCreators")
}
The idea is that when user signs in with Amplify, they'll go through an onboarding process and choose whether they're a creator or a teacher.
This works fine, but the problem is if a signed-in user wants to create a new Activation.
I'm not sure if the graphql on the Activation model is set correctly, perhaps the auth key is wrong?
This is how I'm processing create
const createNewActivation = async () => {
if (!title || !content || !location) return;
const newId = uuid();
activation.id = newId;
const user = await Auth.currentAuthenticatedUser();
try {
await API.graphql({
query: createActivation,
variables: {
input: {
...activation,
teacherId: user.attributes.sub,
},
},
authMode: "AMAZON_COGNITO_USER_POOLS",
});
} catch (error) {
console.log("Error: problem creating activation: ", error);
}
};
I've also set up a lambda function so that when user confirms their account from sign up, it will save the user information in DynamoDB with an ID, the username, and email.
Edit: Got it working but...
So, I was able to save the data, but I had to change the schema in my Activation model from:
{
allow: owner
ownerField: "teacherId"
operations: [create, update, delete]
}
to just
{
allow: owner
}
Not sure why I can't set the owner to teacherId? Wouldn't I need this so I can make proper connections to Teacher's model with teachId field?
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.
Today I tried to add a 'name' field to the 'User' type of my AWS GraphQL schema. After running "amplify push" and "code gen", I received an error from AWS saying that 'owner' was null. After comparing my git history side by side, I found that codegen deleted the autogenerated owner field in my queries while simultaneously adding two new fields, 'createdAt' and 'updatedAt'. Unsure how/why this was happening, I reverted to an earlier git commit where everything was working fine and pushed to amplify. All my files were reverted back to a commit where everything was working perfectly. However, I am still receiving errors that 'owner' is null. GraphQL schema and screenshots of terminal are below. Happy to provide other files (queries, mutations, etc.) if helpful.
type Image {
bucket: String!
region: String!
key: String!
}
type Circle
#model
#auth(rules: [
{ allow: owner, operations: [create, delete] }
]) {
id: ID!
name: String!
username: String!
password: String
bio: String
avatar: Image
favors: [CircleFavor] #connection(name: "CirclesFavors")
users: [CircleUser] #connection(name: "CirclesUsers")
pendingUsers: [User] #connection
tags: [String]
admin: User #connection
isPrivate: Boolean!
poster: ID!
}
type Favor
#model
#auth(rules: [
{ allow: owner, operations: [create, delete] }
]) {
id: ID!
name: String
description: String
location: String
deadline: AWSDateTime
circles: [CircleFavor] #connection(name: "FavorsCircles")
price: Float!
}
type CircleFavor
#model(queries: { get: "getCircleFavor" })
#auth(rules: [
{ allow: owner, operations: [create, delete] }
]) {
id: ID!
circle: Circle! #connection(name: "CirclesFavors")
favor: Favor! #connection(name: "FavorsCircles")
poster: User! #connection(name: "UsersFavors")
}
type User
#model(
queries: { get: "getUser" }
mutations: { create: "registerUser", update: "updateUser" }
subscriptions: null
)
#auth(rules: [
{ allow: owner, operations: [create, delete] }
]) {
id: ID!
username: String!
bio: String
email: AWSEmail
isRegistered: Boolean
orders: [Order] #connection(name: "UsersOrders")
circles: [CircleUser] #connection(name: "UsersCircles")
favors: [CircleFavor] #connection(name: "UsersFavors")
}
type CircleUser
#model(queries: { get: "getCircleUser" })
#auth(rules: [
{ allow: owner, operations: [create, delete] }
]) {
id: ID!
circle: Circle! #connection(name: "CirclesUsers")
user: User! #connection(name: "UsersCircles")
}
type Order
#model(
queries: null
mutations: { create: "createOrder" }
subscriptions: null
)
#auth(rules: [
{ allow: owner, operations: [create, delete] }
]) {
id: ID!
favor: Favor #connection
claimer: User #connection(name: "UsersOrders")
additional_costs: Float
isCompletedByClaimer: Boolean!
isApprovedByPoster: ApprovalStatus!
}
enum ApprovalStatus {
PENDING
APPROVED
DISPUTED
}
Bad news: You dropped those fields from the database. The owner data is gone, which is why it's coming back NULL now that you re-added it to the schema.