I'm working with GraphQl and Prisma for the first time and I am trying to make an update function in my resolver mutation file but I have been struggling with it. I have done some research but the things that I am finding don't work for my code. I always end up with errors. Could someone help me with this?
Schema:
type Service {
id: ID!
createdAt: DateTime!
updatedAt: DateTime!
name: String!
cost: Float!
description:String
hours: Int!
minutes: Int!
postedBy: User!
}
type Mutation {
postService(name: String!, cost:Float!,description:String!,hours: Int!, minutes: Int! ): Service!
deleteService(id: ID!): Service!
updateService(name: String!, cost:Float!,description:String!,hours: Int!, minutes: Int!): Service!
}
mutation resolver:
function updateService(parent, args, context, info) {
return context.prisma.mutation.updateService({
cost: args.cost,
name: args.name,
description: args.description,
hours: args.hours,
minutes: args.minutes,
postedBy: { connect: { id: userId } },
where: {
id: args.where.id
}
}, info)
}
query in prisma playground:
mutation{
updateService(
name: "Update test"
where:{
id : "ckaz82x01aqq50a42fkrz3uxu"
}
){id
name
hours
minutes}}
In the above mutation that you have posted, you have only passed name and not all of the variables.
Where as in your schema below, cost, description, hours, minutes are required.
updateService(name: String!, cost:Float!,description:String!,hours: Int!, minutes: Int!): Service!
Could you check if that's the error that you are receiving? If so, pass all the parameters and run the mutation again.
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 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?
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.
I have been trying to run a mutation to create relations to two separate Types with not much success.
** SCHEMA **
(I have used "Create Resources" to create tables in DynamoDB)
type Comment {
eventId: ID!
commentId: String!
content: String
}
type CommentConnection {
items: [Comment]
nextToken: String
}
input CreateCommentInput {
eventId: ID!
commentId: String!
content: String
}
input CreateEventInput {
id: ID!
name: String
where: String
when: String
description: String
}
input DeleteCommentInput {
eventId: ID!
}
input DeleteEventInput {
id: ID!
}
type Event {
id: ID!
name: String
where: String
when: String
description: String
comments(limit: Int, nextToken: String): CommentConnection
}
type EventConnection {
items: [Event]
nextToken: String
}
type Mutation {
createEvent(input: CreateEventInput!): Event
updateEvent(input: UpdateEventInput!): Event
deleteEvent(input: DeleteEventInput!): Event
createComment(input: CreateCommentInput!): Comment
updateComment(input: UpdateCommentInput!): Comment
deleteComment(input: DeleteCommentInput!): Comment
commentOnEvent(input: commentOnEventInput!): Comment
}
type Query {
fetchEvent(id: ID!): Event
getEvent(id: ID!): Event
listEvents(first: Int, after: String): EventConnection
getComment(eventId: ID!): Comment
listComments(first: Int, after: String): CommentConnection
}
type Subscription {
onCreateEvent(
id: ID,
name: String,
where: String,
when: String,
description: String
): Event
#aws_subscribe(mutations: ["createEvent"])
onUpdateEvent(
id: ID,
name: String,
where: String,
when: String,
description: String
): Event
#aws_subscribe(mutations: ["updateEvent"])
onDeleteEvent(
id: ID,
name: String,
where: String,
when: String,
description: String
): Event
#aws_subscribe(mutations: ["deleteEvent"])
onCreateComment(eventId: ID, commentId: String, content: String): Comment
#aws_subscribe(mutations: ["createComment"])
onUpdateComment(eventId: ID, commentId: String, content: String): Comment
#aws_subscribe(mutations: ["updateComment"])
onDeleteComment(eventId: ID, commentId: String, content: String): Comment
#aws_subscribe(mutations: ["deleteComment"])
}
input UpdateCommentInput {
eventId: ID!
commentId: String
content: String
}
input UpdateEventInput {
id: ID!
name: String
where: String
when: String
description: String
}
input commentOnEventInput {
eventId: ID!
content: String
}
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
** MUTATIONS **
mutation #1:
mutation {
createEvent(input: {
id: "id8888"
name: "some event"
where: "Tokyo"
when: "tomorrow"
description: "desc for event"
})
{
id
name
}
}
mutation #1 gives:
{
"data": {
"createEvent": {
"id": "id8888",
"name": "some event"
}
}
}
mutation #2:
mutation {
commentOnEvent(input : {
eventId: "id8888"
commentId: "id2222"
content: "some content"
})
{
commentId
content
}
}
mutation #2 gives:
{
"data": {
"commentOnEvent": null
}
}
In the React sample created by AWS AppSync creates commentId automatically but I can't recreate that in manually created schema and resource.
I would like to know how I can establish relations on separate types and query them. Has anybody successfully done this??
Starting from the console's “Create resources” functionality lets talk through how this works. Assume we have this schema.
type Comment {
eventId: ID!
commentId: String!
content: String
}
type CommentConnection {
items: [Comment]
nextToken: String
}
input CreateCommentInput {
eventId: ID!
commentId: String!
content: String
}
input CreateEventInput {
id: ID!
name: String
where: String
when: String
description: String
}
input DeleteCommentInput {
eventId: ID!
}
input DeleteEventInput {
id: ID!
}
type Event {
id: ID!
name: String
where: String
when: String
description: String
comments(limit: Int, nextToken: String): CommentConnection
}
type EventConnection {
items: [Event]
nextToken: String
}
type Mutation {
createEvent(input: CreateEventInput!): Event
updateEvent(input: UpdateEventInput!): Event
deleteEvent(input: DeleteEventInput!): Event
createComment(input: CreateCommentInput!): Comment
updateComment(input: UpdateCommentInput!): Comment
deleteComment(input: DeleteCommentInput!): Comment
}
type Query {
getEvent(id: ID!): Event
listEvents(first: Int, after: String): EventConnection
getComment(eventId: ID!): Comment
listComments(first: Int, after: String): CommentConnection
}
type Subscription {
onCreateEvent(
id: ID,
name: String,
where: String,
when: String,
description: String
): Event
#aws_subscribe(mutations: ["createEvent"])
onUpdateEvent(
id: ID,
name: String,
where: String,
when: String,
description: String
): Event
#aws_subscribe(mutations: ["updateEvent"])
onDeleteEvent(
id: ID,
name: String,
where: String,
when: String,
description: String
): Event
#aws_subscribe(mutations: ["deleteEvent"])
onCreateComment(eventId: ID, commentId: String, content: String): Comment
#aws_subscribe(mutations: ["createComment"])
onUpdateComment(eventId: ID, commentId: String, content: String): Comment
#aws_subscribe(mutations: ["updateComment"])
onDeleteComment(eventId: ID, commentId: String, content: String): Comment
#aws_subscribe(mutations: ["deleteComment"])
}
input UpdateCommentInput {
eventId: ID!
commentId: String
content: String
}
input UpdateEventInput {
id: ID!
name: String
where: String
when: String
description: String
}
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
This is what the schema should look like after running Create Resources on the Event and Comment types. When going through the "Create Resources" flow with the Comment type you should choose the eventId as the table's hash key and the commentId as the sort key. For the Event type you can leave "id" as the single hash key. So what did that do for us?
First it created 2 DynamoDB tables to hold our objects of type Event and Comment. It then imported those tables as AppSync data sources and generated new schema parts including input objects, objects, and query and mutation fields and saved them to the schema. It also wired up resolvers specific to the new table you just defined and attached them to the newly generated query and mutation fields that implement common CRUD patterns. Unfortunately this does not yet understand relations so we have to add those ourselves. To do that let's first make the mutation to create relations as you have asked about and for completeness we will do a query as well.
As you have already done, you are going to need to add something like this to your schema
type Mutation {
commentOnEvent(input: CommentOnEventInput!): Comment
}
input CommentOnEventInput {
eventId: ID!
content: String
}
Save the schema and then click "Attach" on the Mutation.commentOnEvent field to add a resolver. Select the CommentTable data source we created earlier and from the mapping template put this:
{
"version" : "2017-02-28",
"operation" : "PutItem",
"key" : {
"eventId": $util.dynamodb.toDynamoDBJson($ctx.args.input.eventId),
"commentId": $util.dynamodb.toDynamoDBJson($util.autoId()),
},
"attributeValues" : $util.dynamodb.toMapValuesJson($ctx.args.input)
}
and for the response mapping template
$util.toJson($context.result)
Click save. Now you should be able to run a query like this:
mutation {
commentOnEvent(input: { eventId: "***", content: "A comment"}) {
eventId
content
}
}
Let's now add away to read data via a relation. E.G. I want to be able to run a query like this:
query {
getEvent(id: "***") {
id
comments(first: 5) {
items {
content
}
}
}
}
To do this lets first add the following parts to the schema.
type Event {
# add this to existing fields
comments(first: Int, after: String): CommentConnection
}
Click save then click "Attach" on the Event.comments field. Select the CommentTable data source again and then provide the following for the request mapping template.
# Event.comments.request.vtl
{
"version" : "2017-02-28",
"operation" : "Query",
"query" : {
"expression": "eventId = :eventId",
"expressionValues" : {
":eventId" : {
"S" : "${ctx.source.id}"
}
}
},
"limit": $util.defaultIfNull(${ctx.args.first}, 20),
"nextToken": $util.toJson($util.defaultIfNullOrBlank($ctx.args.after, null))
}
Notice the $ctx.source.id. Since we are resolving the Event.comments field, the $ctx.source is the instance of the Event type that we are resolving comments for. In effect this makes it so that anywhere we include { comments { ... } in a selection set on the Event type, only comments for the parent event will be fetched. And then you can return the paginated result object.
# Event.comments.response.vtl
# $ctx.result = { items: [...], nextToken: "..." }
$util.toJson($ctx.result)
This should do the trick. Now you can run both these queries and see the results.
mutation {
commentOnEvent(input: { eventId: "***", content: "A comment"}) {
eventId
content
}
}
query {
getEvent(id: "***") {
id
comments(first: 5) {
items {
content
}
}
}
}
Hope this helps.