Why does my deleteComment mutation fail to find the entity to be deleted? - graphql

I have inherited a GraphQL/Apollo server and have been tasked with making several changes, one of which is adding Comments and Attachments to an existing Card object type (something similar to Trello). For the purposes of keeping this question simple I will only refer to Comments, assuming that the solution can also be applied to Attachments, which are suffering the same issue.
I have Mutations to Create, Update and Delete Comments, as well as the Repository, Resolver and have defined them in files below. The schema is also below for reference.
The problem being encountered is that the deleteComment mutation is failing to find the Comment to be deleted, returning a GraphQL error Could not find any entity of type "Comment" matching:. I have been able to positively confirm that the correct ID is traced through the entire path to the SQL query. However, the updateComment mutation, which uses the same access method to find the Comment, does successfully find and update the body of the correct Comment. I have been able to produce these results in both my developed frontend, and the Apollo Sandbox.
The only solution I have tried has been to define queries for Comments (both front and back end). This didn't appear to make any difference to the end result.
deleteComment.graphql
mutation DeleteComment($id: String!) {
deleteComment(id: $id) {
id
}
}
Comment.repository.ts
import { EntityRepository } from 'typeorm';
import { FindOneOptions } from 'typeorm/find-options/FindOneOptions';
import { BaseRepository } from './Base.repository';
import { Comment } from '../entities';
#EntityRepository(Comment)
export class CommentRepository extends BaseRepository<Comment> {
/**
* Fetch a comment using its UUID
* #param {string} id - UUID for comment being fetched
* #param {FindOneOptions<Comment>} options - Optional TypeORM find options
* #throws {EntityNotFoundError} if no entities match given UUID
* #returns {Promise<Comment>} Comment entity
*/
findById(id: string, options?: FindOneOptions<Comment>) {
return this.findOneByIdOrThrow(id, { ...options });
}
}
CommentResolver.ts
import { Arg, Mutation, Resolver } from 'type-graphql';
import { getConnection } from 'typeorm';
import { Comment } from '../models/entities';
import { CommentCreatePayload, CommentUpdatePayload } from '../graphql/types/CommentPayload';
import { CommentRepository } from '../models/repositories';
#Resolver()
export class CommentResolver {
private commentRepository: CommentRepository =
getConnection().getCustomRepository(CommentRepository);
#Mutation(() => Comment)
async createComment(
#Arg('commentCreatePayload', () => CommentCreatePayload) data: CommentCreatePayload,
) {
return this.commentRepository.insertWithValidate(data);
}
#Mutation(() => Comment)
async updateComment(
#Arg('id', () => String) id: string,
#Arg('commentUpdatePayload', () => CommentUpdatePayload) patch: CommentUpdatePayload,
): Promise<Comment> {
return this.commentRepository.updateWithValidate(id, patch);
}
#Mutation(() => Comment)
async deleteComment(
#Arg('id', () => String) id: string): Promise<Comment> {
return this.commentRepository.deleteOrThrow(id);
}
}
Comment.entity.ts
import { Entity, Column, ManyToOne } from 'typeorm';
import { ObjectType, Field } from 'type-graphql';
import { Base, Card, User } from './index';
#ObjectType()
#Entity()
export class Comment extends Base {
#Field(() => String)
#Column('text')
body: string;
#Field(() => User)
#ManyToOne(() => User, (user) => user.comments, { onDelete: 'CASCADE' })
user: User | undefined;
#Field(() => String)
#Column('text')
userId: string;
#Field(() => Card)
#ManyToOne(() => Card, (card) => card.comments, { onDelete: 'CASCADE' })
card: Card | undefined;
#Field(() => String)
#Column('text')
cardId: string;
}
schema.graphql (some parts omitted)
type Query {
list(id: String!): List!
card(id: String!): Card!
cardByShortId(shortId: String!): Card!
boards(archived: Boolean = false): [Board!]!
board(id: String!): Board!
boardByShortId(shortId: String!): Board!
archivedSwimlanes(boardId: String!): [Swimlane!]!
archivedLists(boardId: String!): [List!]!
currentUser: User!
swimlane(id: String!): Swimlane!
label(id: String!): Label!
}
type Card {
id: String!
createdAt: DateTime!
updatedAt: DateTime!
deletedAt: DateTime
version: Float!
shortId: String!
name: String!
slug: String!
rank: String!
description: String
startTime: DateTime
endTime: DateTime
ownerId: String!
listSection: ListSection!
listSectionId: String!
comments: [Comment!]!
attachments: [Attachment!]!
labels: [Label!]!
labelIds: [String!]!
users: [User!]!
userIds: [String!]!
}
type Comment {
id: String!
createdAt: DateTime!
updatedAt: DateTime!
deletedAt: DateTime
version: Float!
body: String!
user: User!
userId: String!
card: Card!
cardId: String!
}
type Mutation {
createCard(cardCreatePayload: CardCreatePayload!): Card!
updateCard(cardUpdatePayload: CardUpdatePayload!, id: String!): Card!
addLabelToCard(labelId: String!, id: String!): Card!
removeLabelFromCard(labelId: String!, id: String!): Card!
archiveCard(id: String!): Card!
recoverCard(id: String!): Card!
deleteCard(id: String!): Card!
createComment(commentCreatePayload: CommentCreatePayload!): Comment!
updateComment(commentUpdatePayload: CommentUpdatePayload!, id: String!): Comment!
deleteComment(id: String!): Comment!
}
input CommentCreatePayload {
id: String
body: String
userId: String!
cardId: String!
}
input CommentUpdatePayload {
body: String
}

Related

How do I use schema.graphql file as typeDefs?

I am learning GraphQL with Node.js and I am currently using graphql-yoga as a GraphQLServer. Now, I want to seperate Type Definitions and Resolvers so I have used schema.graphql and in there I have all my types defined but now I have no Idea how to use that file as typeDefs in GraphQL Server. I have provided my files below.
index.js
const { createServer } = require('graphql-yoga');
const { resolvers } = require('./template1');
const server = createServer({
schema: {
resolvers,
typeDefs: 'src/schema.graphql'
}
});
server.start(() => {
console.log('GraphQL Server started.');
});
schema.graphql
type Query {
hello: String!
posts: [Post!]!
users: [User!]!
comments: [Comment!]!
}
type Mutation {
signUp(data: SignUpInput): User!
createPost(data: CreatePostInput): Post!
createComment(data: CreateCommentInput): Comment!
deleteUser(id: ID!): User!
deletePost(id: ID!): Post!
deleteComment(id: ID!): Comment!
}
input SignUpInput {
email:String!
username: String!
password: String!
}
input CreatePostInput {
title: String!
author: ID!
}
input CreateCommentInput {
text: String!
author: ID!
post: ID!
}
type Post {
id: ID!
title: String!
author: User!
comments: [Comment!]
}
type User {
id: ID!
email: String!
password: String!
username: String!
posts: [Post!]
comments: [Comment!]
}
type Comment {
id: ID!
text: String!
author: User!
post: Post!
}
and I am getting this Error.
Instead of directly passing the schema use:
typeDefs: fs.readFileSync(
path.join(__dirname, 'schema.graphql'),
'utf8'
)
Thanks this worked for me!
Just added this at the top of the file.
import { readFileSync } from 'node:fs';
import { join } from 'node:path';

graphql trouble accessing items in object

I am still trying to learn graphql and I am having trouble accessing items that are within an object in the database. In my client side code the data for id and createdAt shows up just fine it is just when I add the object that I get the error:
Expected Iterable, but did not find one for field Users.profile
I am not sure what my code is missing:
resolver:
Query: {
getUser(root, args, { userId }) {
const {id } = args;
const user = User.findOne({
id
});
return user;
}
},
schema
const User = `
type User{
id: String!
createdAt: Date
profile: [Profile]
}
type Profile {
name: String!
email: String!
}
extend type Query {
getUser(
id: String!
): User
}
How I am calling it in my client code:
const getUser = gql`
query getUser($id: String!) {
getUser(id: $id) {
id
createdAt
profile {
name
email
}
}
}
`;
This is how it looks in the MongoDB database:
user{
_id: "22222"
createdAt: 11/22/2018
profile:{
name: "Chris"
email: "chris#emample.com"
}
} `
In case it helps someone in future I had to set my objects to JSON to get it to work.
const User = `
type User{
id: String!
createdAt: Date
profile: JSON
}
extend type Query {
getUser(
id: String!
): User
}

How to pass params to child property in GraphQL

i am pretty new to GraphQL, getting to become a huge fan :)
But, something is not clear to me. I am using Prisma with and GraphQL-Yoga with Prisma bindings.
I do not know how to pass params from my graphQL server to sub properties. Don't know if this is clear, but i will show it with code, thats hopefully easier :)
These are my types
type User {
id: ID! #unique
name: String!
posts: [Post!]!
}
type Post {
id: ID! #unique
title: String!
content: String!
published: Boolean! #default(value: "false")
author: User!
}
My schema.graphql
type Query {
hello: String
posts(searchString: String): [Post]
users(searchString: String, searchPostsTitle: String): [User]
me(id: ID): User
}
and my users resolver:
import { Context } from "../../utils";
export const user = {
hello: () => "world",
users: (parent, args, ctx: Context, info) => {
return ctx.db.query.users(
{
where: {
OR: [
{
name_contains: args.searchString
},
{
posts_some: { title_contains: args.searchPostsTitle }
}
]
}
},
info
);
},
me: (parent, args, ctx: Context, info) => {
console.log("parent", parent);
console.log("args", args);
console.log("info", info);
console.log("end_________________");
return ctx.db.query.user({ where: { id: args.id } }, info);
}
};
and my posts resolver
import { Context } from "../../utils";
export const post = {
posts: (parent, args, ctx: Context, info) => {
return ctx.db.query.posts(
{
where: {
OR: [
{
title_contains: args.searchString
},
{
content_contains: args.searchString
}
]
}
},
info
);
}
};
so, now :)
I am able to do the following when i am in the GraphQL playground on my prisma service:
{
user(where: {id: "cjhrx5kaplbu50b751a3at99d"}) {
id
name
posts(first: 1, after: "cjhweuosv5nsq0b75yc18wb2v") {
id
title
content
}
}
}
but i cant do it on the server, if i do something like that.. i am getting the error:
"error": "Response not successful: Received status code 400"
this is what i am trying:
{
me(id: "cjhrx5kaplbu50b751a3at99d") {
id
name
posts(first:1) {
id
title
content
}
}
}
does somebody know how i could do that?
since i have a custom type of user, posts does not have params like the generated one. Either i am using the the generated one, or modifying it to look like this:
type User {
id: ID!
name: String!
posts(where: PostWhereInput, orderBy: PostOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Post!]
}
EDIT 2018 June 4th
# import Post from './generated/prisma.graphql'
type Query {
hello: String
posts(searchString: String): [Post]
users(searchString: String, where: UserWhereInput, orderBy: UserOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [User]
me(id: ID): User
}
type Mutation {
createUser(name: String!): User
createPost(
title: String!
content: String!
published: Boolean!
userId: ID!
): Post
}
I copied the params over from prisma.graphql manually.

Query.products is defined in resolvers but not in schema

Hi I defined rootQuery in Customer schema and then in Product schema I extended query. I wrote resolvers for product schema but then I got following error: Error: Query.products defined in resolvers, but not in schema.
When I move product queries to customer query definition it works.
I dont understand why I'm getting this error. Do I need implement some rootQuery and insert it into typeDefs array and then extend queries in Customer and Product ?
Customer schema
import CustomerPhoto from "./customerPhoto";
const Customer = `
type Customer {
customerID: ID!
firstname: String
lastname: String
phone: String
email: String
CustomerPhoto: CustomerPhoto
}
input CustomerInput {
firstname: String!
lastname: String!
phone: String!
email: String!
}
type Query {
customers(cursor: Int!):[Customer]
customer(id: Int!): Customer
}
type Mutation {
createCustomer(photo: String!, input: CustomerInput): Customer
updateCustomer(customerID: ID!, photo: String, input: CustomerInput): Customer
deleteCustomer(customerID: ID!): Customer
}
`;
export default [Customer, CustomerPhoto];
Product Schema
import ProductPhoto from "./productPhoto";
const Product = `
type Product {
productID: ID!
name: String!
description: String!
pricewithoutdph: Float!
pricewithdph: Float!
barcode: Int!
ProductPhoto: ProductPhoto
}
extend type Query {
products: [Product]
product(productID: ID!): Product
}
`;
export default [Product, ProductPhoto]
Here Im importing both schemas. Is there something missing ?
const schema = makeExecutableSchema({
typeDefs: [...Customer,...Product],
resolvers: merge(CustomerResolvers, ProductResolvers),
logger: {
log: e => {
console.log("schemaError", e);
}
},
resolverValidationOptions: {
requireResolversForNonScalar: true
}
});
Product Resolvers
const ProductResolvers = {
Query: {
products: (_, { cursor }) => {
return models.Product.findAndCountAll({
include: {
model: models.ProductPhoto,
attributes: ["productPhotoID", "photo"],
required: true
},
offset: cursor,
limit: 10,
attributes: ["productID", "name", "description", "pricewithoutdph", "pricewithdph", "barcode"]
}).then(response => {
return response.rows;
});
}
};
export default ProductResolvers;
Customer Resolvers
const CustomerResolvers = {
Query: {
customers: (_, {cursor}) => {
return models.Customer.findAndCountAll({
include: {
model: models.CustomerPhoto,
attributes: ["customerPhotoID", "photo"],
required: true
},
offset: cursor,
limit: 10,
attributes: ["customerID", "firstname", "lastname", "phone", "email"]
}).then(response => {
return response.rows;
});
}
......
}
};

graphql required fields approach

This is my graphql schema, query and mutations.
I marked required fields in my schema with "!"
How I can create mutation to add new client?
Do I really need to write the same required fields again?
Like createClient(contactMethod: String!, hearAbout: String! ......... ): Client
const typeShard = `
type ClientProfile {
name: String!
surname: String!
address: String
language: String!
}
type Client {
_id: String
isEdit: Boolean
createdAt: String
shortId: Int
profile: ClientProfile
comments: String
contactMethod: String!
hearAbout: String!
leadAgentId: String
branchId: String!
}
`;
const queryShard = `
getAllClients: [Client]
`;
const mutationShard = `
removeClient(shortId : Int!): Client
createClient(contactMethod: String!, hearAbout: String! ......... ): Client
`;
const resolvers = {
Query: {
getAllClients: () => MongoClients.find().fetch(),
},
Mutation: {
removeClient(root, { shortId }) {
const client = MongoClients.findOne({ shortId });
if (!client) throw new Error(`Couldn't find client with id ${shortId}`);
MongoClients.remove({ shortId });
return client;
},
createClient: (_, args) => {
return MongoClients.insert(args);
},
},
};
You do not need to write the same fields for every mutation. You could define an input type. Please take a look at this cheat sheet.
So in your case it could look like:
const typeShard = `
type ClientProfile {
name: String!
surname: String!
address: String
language: String!
}
type Client {
_id: String
isEdit: Boolean
createdAt: String
shortId: Int
profile: ClientProfile
comments: String
contactMethod: String!
hearAbout: String!
leadAgentId: String
branchId: String!
}
input ClientInput {
contactMethod: String!
hearAbout: String!
.....
}
`;
const mutationShard = `
removeClient(shortId : Int!): Client
createClient(clientInput: ClientInput!): Client
`;

Resources