GraphQL: Updating an array - javascript

I'm having some issues updating an array in the resolver. I'm building with typescript.
Description
I have in the datamodel.graphql for Prisma:
type Service #model {
id: ID! #unique
title: String
content: String
createdAt: DateTime!
updatedAt: DateTime!
comments: [Comment!]! // Line to be seen here
author: User!
offer: Offer
isPublished: Boolean! #default(value: "false")
type: [ServiceType!]!
}
type Comment #model {
id: ID! #unique
author: User! #relation(name: "WRITER")
service: Service!
message: String!
}
The Prisma is connected to the GraphQl server and in this one, I defined the mutation :
commentService(id: String!, comment: String!): Service!
So comes the time for implementing the resolver for the given mutation and I'm doing this :
async commentService(parent, {id, comment}, ctx: Context, info) {
const userId = getUserId(ctx);
const service = await ctx.db.query.service({
where: {id}
});
if (!service) {
throw new Error(`Service not found or you're not the author`)
}
const userComment = await ctx.db.mutation.createComment({
data: {
message: comment,
service: {
connect: {id}
},
author: {
connect: {id:userId}
},
}
});
return ctx.db.mutation.updateService({
where: {id},
data: {
comments: {
connect: {id: userComment.id}
}
}
})
}
The problem :
The only thing I'm receiving when querying the playground is null instead of the comment I've given.
Thanks for reading till so far.

Can you please share code where you expose mutation resolvers? You might get null in response in case you forgot to include commentService resolver in mutation resolver object.
Apart from this, I see one more issue in the code. Since you have relation between Service and Comment, you can use single mutation to create comment and add it in service. You don't need to write two separate mutations in order to achieve that. Your resolver can be changed to be as simple as below:
async commentService(parent, {id, comment}, ctx: Context, info) {
const userId = getUserId(ctx);
return ctx.db.mutation.updateService({
where: {id},
data: {
comments: {
create: {
message: comment,
author: {
connect: {id:userId}
}
}
}
}
})
}
Note that I also removed query to check if service exists before performing update. Reason being, updateService binding call will throw error in case it does not exist, we don't need to explicitly check for that.

If I understood the question correctly, you are calling this commentService mutation and you get null as a result? Following your logic, you should get whatever ctx.db.mutation.updateService resolves with, right? If you expect that to indeed be a Service object, then the only reason why you might not be getting it back is a missing await. You probably needed to write return await ctx.db.mutation.updateService({ ....

Related

NextAuth Credentials, adding more to the user scheme

Nextauth with mysql persisting users.
I'm trying out this NextAuth thing to see if this is something I like. So far so good. There is one thing tho which is buggin me and that would be the user scheme. By default it returns a name, image and the last one I forgot.
I'd like to add more to this scheme and found some ways to do it by looking at google, however those I tried did not work.
One example I found is by extending the model which clearly makes sense...
The issue here is then me, I do not know what to change in the code below to make it work with my NextAuth credentials provider. As shown below, this doesnt work.
projectfolder -> models -> index.js
import User, { UserSchema } from "./User"
export default {
User: {
model: User,
schema: UserSchema
}
}
projectfolder -> models -> user.js
import Adapters from "next-auth/adapters"
// Extend the built-in models using class inheritance
export default class User extends Adapters.TypeORM.Models.User.model {
constructor(name, email, image, emailVerified, roles) {
super(name, email, image, emailVerified)
if (roles) { this.roles = roles}
}
}
export const UserSchema = {
name: "User",
target: User,
columns: {
...Adapters.TypeORM.Models.User.schema.columns,
roles: {
type: "varchar",
nullable: true
},
},
}
In my [...nextauth].js file I have my provider, in this provider i've added an profile() field with the extra fields. This did not solve the issue.
profile(profile) {
return {
name: profile.name,
email: profile.email,
role: profile.role
};
},
Please correct me if I am wrong but if I am using credentials, then I need to replace the "TypeORM" with something else, correct? How about the path for the files, are they correct?
This should clearly be quite easy but am I missing something or am I doing something wrong? I feel like there is a lack of documentation on extending the user model for mysql.
I've doubled checked that the role is being retrieved from the database and then added to the user variable shown here:
async authorize ....
const user = {
name: result.display_name,
role: result.role_name,
email: result.email
}
Although I can see the role being set in the variable with my console.log(), I still cannot access the role and that I suspect is because of the model. How would I resolve this? Thanks a lot in advance.
Any ideas?
----------------------- UPDATES ------------------------
Btw, here is my callback
callbacks: {
async signIn({ user, account, profile, email }) {
console.log("user", user);
return true;
},
},
and this is what it returns (shortened)
token: {
token: { name: 'Firstname Lastname', email: 'test#mail.com' },
user: {
name: 'Firstname Lastname',
role: 'administrator',
email: 'test#mail.com'
},
account: { type: 'credentials', provider: 'credentials' },
isNewUser: false,
iat: 1634193197,
exp: 1636785197
}
I'm new to TypeORM and I am facing the same problems as people here.
What I've done was create a separate Entity which I called users_info to store the other information and retrieve it after signing in.
It looks like this:
import { UserEntity } from './NextAuthEntities';
#Entity({ name: 'users_info' })
export class MemberEntity {
#PrimaryGeneratedColumn('increment')
id!: number;
#OneToOne(() => UserEntity)
#JoinColumn({
name: 'auth_id',
referencedColumnName: 'id',
})
auth_id!: UserEntity;
#Column({ type: 'varchar', nullable: true })
full_name!: string | null;
// etc
}
Then, I created a handshake API route to retrieve users_info if the user is signed-in.
When I added a new #Column on my custom UsersEntity, it threw me an error when I tried to login. It seems like TypeORMLegacyAdapter can't be extended or be different from the default UserEntity.
Hope it helps

TypeORM findOne with nested relations

I am having some issues performing a nested find query with TypeORM. Here's the basic code:
const { completionId } = req?.params;
const user = req.user;
const retrievedCompletion = await getRepository(
CompletionGoogleSearch
).findOne({
relations: ['run', 'run.user'],
where: {
id: completionId,
// run: { user: { id: user.id } }, // This is the code that breaks the function
},
});
console.log(retrievedCompletion?.run.user.id);
console.log(user.id);
It looks to me like there's nothing out of order, and that the query should run. Any idea on what I am doing wrong? I know I can get around this issue by writing a querybuilder query or using raw SQL–I am just curious to understand if there's a flaw in my code.
typeorm added the ability to use nested object
userRepository.find({
relations: {
profile: true,
photos: true,
videos: {
videoAttributes: true,
},
},
});
on this way, you can fetch the data without using eager.
You can find more information here
The feature you're asking about doesn't supported on typeorm yet (Feb 2021).
Checkout this issue that was opened on 2018.
the Solution is use eager:true in run.user entity :
#OneToOne(() => User, User=> User.run, {
eager:true
})
user: User;
and next time you search in CompletionGoogleSearch do just relations: ['run'] and user will come with it.

Apollo client and graphQl query error : Variable '$where' expected value of type 'OrganizationWhereUniqueInput

Hi I'm making a backend server with GraphQL, Apollo client & Prisma. I'm trying to write a query where I get organization data back. The user who sends the query should get its organization data back based on their id. When running the query in playground I get this error.
error:
"message": "Variable '$where' expected value of type 'OrganizationWhereUniqueInput!' but got: {\"employees\":{\"id\":\"ckas83z13t9qk0992pucglc4k\"}}. Reason: 'employees' Field 'employees' is not defined in the input type 'OrganizationWhereUniqueInput'. (line 1, column 8):\nquery ($where: OrganizationWhereUniqueInput!) {\n ^",
I don't see what I did wrong. I'm still pretty new to it all. I tried to write the function in Query.js in different ways but no luck. Also, I still find the error messages you get in playground very confusing
schema:
type Query {
getOrganization: Organization!
}
type Organization {
id: ID!
name: String!
country: String!
street: String!
zipCode: Int!
houseNumber: Int!
addings: String
employees: [User!]
}
type User {
id: ID!
firstname:String!
lastname:String!
email: String!
services: [Service!]
organization: Organization!
}
query.js
function getOrganization(parent, args, context, info){
const userId = getUserId(context)
return context.prisma.organization({employees:{id:userId}})
}
// also tried this
/*
function getOrganization(parent, args, context, info){
const userId = getUserId(context)
return context.prisma.organization({where: {employees:{id:userId}}})
}*/
User.js
function services (parent, args, context){
return context.prisma.user({id: parent.id}).services()
}
function organization (parent, args, context){
return context.prisma.user({id: parent.id}).organization()
}
module.exports={
services,
organization
}
Organization.js
function employees(parent, args, context){
return context.prisma.organization({id: parent.id}).employees()
}
module.exports={
employees
}
Could anyone help me see what went wrong?
query in playground:
query{
getOrganization{
name
id
}}
HTTP HEADER:
{
"Authorization": "Bearer {contains user token }"
}
Just use OrganizationWhereInput instead of OrganizationWhereUniqueInput. It will return a list of organisations instead of a single result (might return an empty array), yet it should allow you to search for an organisation using an employee id.

Apollo Server, Graphql - Must provide query string

Im not sure what im doing wrong here? I've been stuck now for soem time on getting my mutations to run with my apollo-server-lambda in my serverless setup, my queries works fine bu when i try to run a query like this:
{ "mutation": "{ signIn(username: \"SomeUser\", password: \"SomePassword\" ) { token } }" }
I just get the message: " Must provide query string." status 400.
I've set up my resolver like so:
const resolvers = {
Query: {
users: async (_, args, ctx) => User.load(args, ctx)
},
Mutation: {
signIn: async (_, { username, password }, ctx) => Auth.signIn({ username, password }, ctx)
}
};
For additional infor here is my typeDefs:
const typeDefs = gql`
type User {
id: ID!,
firstname: String,
lastname: String,
username: String,
createdAt: String,
role: String
}
type AuthToken {
token: String
}
type Query {
hello: String,
users(id: Int): [User]
}
type Mutation {
signIn(username: String!, password: String!): AuthToken!
}
`;
I'm using postman to test my graphql endpoint and my content type is application/json
I dont know if any one here can tell me what im doing wrong, i tryed to move it all to Query resolver, and it works replace "mutation" with "query" then but it dosent make sens to me using the "query" here and i guess later on when i actually want to use the Mutation to mutate data i would need this to work anyway?
Can any one tell me where im wrong here?
EDIT
I installed: graphql-playground-middleware-lambda and set up the serverless setup with: https://github.com/prisma/graphql-playground#as-serverless-handler and if i use Graphiql it works as intented, but im still interested if any one knows whats wrong with the json i send via postman?
When sending the request, your request body should be a properly-formatted JSON object, with a query property (and optionally, a variables property if including variables):
{
"query": "<GraphQL Document>",
"variables {},
}
This is the case whether the operation itself is a query or a mutation.
The actual value of the query property above must be a syntactically correct document, as outlined in the GraphQL specification. A document will typically consist of a single operation definition (either a query or a mutation) that includes all the requested fields for that operation. The document will also include fragments, if using any.
An operation definition looks like this:
OperationType [Name] [VariableDefinitions] [Directives] SelectionSet
So you could have a document like this:
mutation SomeMutation {
signIn(username: "SomeUser", password: "SomePassword") {
token
}
}
Here, the type of the operation is mutation, the name is SomeMutation and everything between the outermost set of curly brackets is the selection set. If you had any variables, their types would be declared in parentheses before the selection set.
The operation name is optional, but it's helpful to include it for debugging purposes on the backend. Technically, the operation type can be omitted as well, in which case GraphQL simply assumes the type is a query. For example, this is still a valid document:
{
users {
id
}
}
and is equivalent to
query SomeName {
users {
id
}
}
The former is referred to as query shorthand. Obviously, this cannot be used for mutations, so mutations must always explicitly state their operation type. A complete example:
{
"query": "mutation SomeName ($username: String!, $password: String!) { signIn(username: $username, password: $password) { token } }",
"variables {
"username": "SomeUser",
"password": "SomePassword"
},
}

GraphQL List or single object

I got the following "problem". I am used to having an API like that.
/users
/users/{id}
The first one returns a list of users. The second just a single object. I would like the same with GraphQL but seem to fail. I got the following Schema
var schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
users: {
type: new GraphQLList(userType),
args: {
id: {type: GraphQLString}
},
resolve: function (_, args) {
if (args.id) {
return UserService.findOne(args.id).then(user => [user]);
} else {
return UserService.find()
}
}
}
}
})
});
How can I modify the type of users to either return a List OR a single object?
You shouldn't use one field for different purposes. Instead of that, make two fields. One for single object and another for list of objects. It's better practice and better for testing
fields: {
user: {
type: userType,
description: 'Returns a single user',
args: {
id: {type: GraphQLString}
},
resolve: function (_, args) {
return UserService.findOne(args.id);
}
},
users: {
type: new GraphQLList(userType),
description: 'Returns a list of users',
resolve: function () {
return UserService.find()
}
}
}
The above answer is correct, the usual approach is to add singular and plural form of queries. However, in large schema, this can duplicate a lot of logic and can be abstracted a little bit for example with Node interface and node, nodes queries. But the nodes query is usually applied with ids as argument (in Relay viz node Fields), but you can build your own abstracted way for fetching so that you have just nodes with some argument for type and based on that you can say what type of list to fetch. However, the simpler approach is to just duplicate the logic for every type and use singular and plural form of query and do the same type of queries as above or in this code snippet for every type. For more detail explanation on implementing GraphQL list modifiers in queries or even as an input for mutations. I just published the article on that.

Categories