How to query a list on AppSync GraphQL by a nested object - amazon-web-services

I am using AWS AppSync GraphQL and am trying to filter a list by a nested object's value.
My schema looks like this:
type Post #model {
id: ID
title: String
content: String
hidden: Boolean
}
type PinnedPost #model
{
id: ID!
userID: ID #index(name: "byUser", sortKeyFields: ["postID"])
user: User #hasOne (fields: ["userID"])
postID: ID
post: Post #hasOne (fields: ["postID"])
}
I would like to run a query to list the PinnedPost for a user, but filter out the hidden ones, like so:
const pinnedData = await API.graphql(graphqlOperation(
listPinnedPosts, {
filter: {
userID: {
eq: userInfo.attributes.sub
},
post: {
hidden: {
eq: false
},
}
}
}
))
I have updated the filterinput in my Schema through the AppSync Console to:
input ModelPinnedPostFilterInput {
id: ModelIDInput
userID: ModelIDInput
postID: ModelIDInput
post: ModelPostFilterInput
and: [ModelPinnedPostFilterInput]
or: [ModelPinnedPostFilterInput]
not: ModelPinnedPostFilterInput
}
There are no errors associated with it, but the nested filter is not being applied as it will return both true and false values for hidden.
This question was sort of answered before:
Appsync & GraphQL: how to filter a list by nested value
but it is not clear to me where I am supposed to edit the mapping template to allow this. How can I achieve this result?

Related

Amplify Datastore not saving but correctly querying relational model

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.

Filter Expression can only contain non-primary key attributes

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!
}

AWS Amplify GraphQL Query For Null Connections

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.

AWS Appsync Graphql query to get list of items returns empty array even though the dynamodb table has items in it

I'm running an Angular 11 application that is integrated with AWS Amplify and Appsync using GraphQL and dynamoDB for the backend.
This is my Graphql schema:-
type School
#model
#auth(
rules: [{ allow: owner, ownerField: "admins", operations: [update, read] }]
) {
id: ID!
name: String!
admins: [Member]
classes: [Class] #connection(name: "SchoolClasses")
members: [Member] #connection(name: "SchoolMembers")
}
type Class
#model
#auth(
rules: [{ allow: owner, ownerField: "admins", operations: [update, read] }]
) {
id: ID!
name: String!
school: School #connection(name: "SchoolClasses")
admins: [Member]
members: [Member] #connection(name: "ClassMembers")
}
type Member #model #auth(rules: [{ allow: owner }]) {
id: ID!
name: String!
school: School #connection(name: "SchoolMembers")
class: Class #connection(name: "ClassMembers")
}
This is my client definition:-
const client = new AWSAppSyncClient({
url: awsconfig.aws_appsync_graphqlEndpoint,
region: awsconfig.aws_appsync_region,
auth: {
type: awsconfig.aws_appsync_authenticationType,
jwtToken: async () =>
(await Auth.currentSession()).getAccessToken().getJwtToken(),
},
complexObjectsCredentials: () => Auth.currentCredentials(),
cacheOptions: {
dataIdFromObject: (obj: any) => `${obj.__typename}:${obj.myKey}`,
},
});
This is my query method:-
client
.query({
query: ListSchools,
})
.then((data: any) => {
console.log('data from listSchools ', data);
console.log(data.data.listSchools.items);
});
};
This is my query definition:-
import gql from 'graphql-tag';
export default gql`
query ListSchools(
$filter: ModelSchoolFilterInput
$limit: Int
$nextToken: String
) {
listSchools(filter: $filter, limit: $limit, nextToken: $nextToken) {
items {
id
name
admins {
id
name
createdAt
updatedAt
owner
}
classes {
nextToken
}
members {
nextToken
}
createdAt
updatedAt
}
nextToken
}
}
`;
The output for data in the console looks like this:-
{
"data":{
"listSchools":{
"items":[],
"nextToken":null,
"__typename":"ModelSchoolConnection"
}
},
"loading":false,
"networkStatus":7,
"stale":false
}
As you can see, the items is an empty array. But currently I have 3 items in my dynamoDB table:-
What am I doing wrong?
I have checked the regions to see if it is querying a different region, but it is checking the correct region, so I should be seeing the results. Also, wouldn't it throw an error if we're querying the wrong table?
I figured it out. The issue was in the GraphQL Schema definition where I had set the #auth paramter to only allow a certain admin to access the list, that's why I was getting back an empty array. I removed the #auth parameter and it now gives back the proper list of items.

AWS Amplify GraphQL - One to Many connections return empty list when queried

I've been following the AWS GraphQL CLI guide for setting up an API for my app, but am having trouble with connections.
The following is my current Graphql schema, with some attributes removed
type Employee #model {
id: ID!
employment: [Employment!] #connection(name: "byEmployeeIDByCompanyID", fields: ["id"])
}
type Company #model {
id: ID!
employees: [Employment!] #connection(name: "byCompanyIDByDateHired", fields: ["id"])
}
type Employment #model
#key(name: "byEmployeeIDByCompanyID", fields: ["employeeID", "companyID"], queryField: "employmentByEmployeeIDByCompanyID") {
id: ID!
companyID: ID!
employeeID: ID!
company: Company! #connection(fields: ["companyID"])
employee: Employee! #connection(fields: ["employeeID"])
}
When I query Employees or Companys, [Employment] always returns an empty array. Do I need to edit the resolvers for these fields? They should work out of the box, no?
From my understanding, using #key with 'name' and multiple 'fields' creates a secondary index on the table, and specifying that key with #connection tells the connection to use that key instead of the tables primary index. In the "byEmployeeIDByCompanyID" key, for example, employeeID is the partition key, and companyID is the sort key. A query on the "employmentByEmployeeIDByCompanyID" queryField with an employeeID but no companyID returns all the employments for a given employee, which is what I want, so why isn't the connection working?
I found success in editing the resolvers, so I'm going to go with this for now. For Employee.employment, I added:
"index": "byEmployeeIDByCompanyID",
to the request mapping template, and changed the query from:
{
"expression": "#partitionKey = :partitionKey",
"expressionNames": {
"#partitionKey": "id"
},
"expressionValues": {
":partitionKey": {
"S": "$context.source.id"
}
}
}
to
{
"expression": "#partitionKey = :partitionKey",
"expressionNames": {
"#partitionKey": "employeeID"
},
"expressionValues": {
":partitionKey": {
"S": "$context.source.id"
}
}
}