I'm trying to use GraphQL in iOS with Apollo Client. I have the following mutation:
login(user: String!, password: String!): UserType
and the UserType looks like this:
id: ID
user: String!
password: String!
name: String
lastname: String
email: Email!
groups: [GroupType]
In iOS, I have configured aopllo client as the doc says and is working perfectly, but I don't know how to get access to every field in the response. When the login success I want to read the json I receive as response with the UserType fields, so, I'm doing this:
apolloClient.perform(mutation: loginMutation) {
resultsGQL, error in
...
}
My question is, how can I read every field from resultGQL which belongs to the UserType data defined in my grapql schema?
Regards
The question is not 100% clear, since it is missing some code your mutation: A GraphQL mutation has to return at least one value, which you have to define. Since i'm not sure about your method
login(user: String!, password: String!): UserType
i am giving you a simple example for updating an existing userProfile with a GraphQL mutation and then returning every field being defined in your schema for userType.
Let us assume you have a userType (and therefore know the corresponding userId) and you want to modify the email:
mutation updateUserProfile($id: ID!, $newEmail: String) {
updateUser(id: $id, email: $newEmail) {
id
user
password
name
lastName
email
}
}
As you can see, after executing
updateUser(id: $id, email: $newEmail)
all return values are defined inside the following {...} parentheses and are therefore accessible in your callback variable
resultsGQL
That means:
apolloClient.perform(mutation: loginMutation) { resultsGQL, error in
if let results = resultsGQL?.data {
// "results" can now access all data from userType
}
}
Since you defined all entities of your userType schema to be returned from the mutation, you can access them now in your callback variable.
Related
I'm trying to implement the #auth directive in GraphQL for use with Neo4j as documented here:
https://neo4j.com/docs/graphql-manual/current/auth/auth-directive/
With a jwt token that is taken from firebase, and should have all of the necessary fields, including admin roles
The problem is that whenever I try to use one of the generated queries with the admin bearer token it says "forbidden" when the auth directive is attached to it.
The full discussion of this issue between me and ChatGPT, which includes extensive trial and error that was done before writing this question, logs, code snippets etc, can be found here for reference:
https://firebasestorage.googleapis.com/v0/b/awtsmoos.appspot.com/o/ai%2FBH_1674546675630.html?alt=media&token=17b653c4-5db2-4bca-8bc1-3cc3625e5a6c
Just to summarize some key code parts, I'm trying to follow the setup example like this essentially:
const neoSch = new graphqlNeo.Neo4jGraphQL({
typeDefs: typeDefs,
driver:dr,
resolvers:rez,
plugins: {
auth: new neoGraphQLAuth
.Neo4jGraphQLAuthJWTPlugin({
secret:
secret.private_key.toString()
})
}
})
Here's the schema that it suggested after I gave it some samples, doesn't work:
type Homework {
id: ID #id
title: String!
steps: [Step!]! #relationship(
type: "HAS",
direction: OUT
)
}
extend type Homework #auth(
rules:[
{
allow: CREATE,
roles:[
"ADMIN"
]
}
]
)
extend type Homework #auth(
rules:[
{
allow: READ,
}
]
)
The token itself is getting properly passed in, as discussed at length in the ChatGPT session, I'm not sure what else it is?
Where the secret property is my JSON of a service account taken from firebase, then in my Apollo context I've tried lots of trial and error with no success, in this matter, but here was one implantation I tried:
app.use(
"/etsem",
cors(),
bodyp.json(),
exp4.expressMiddleware(
serv, {
context: async info => {
var rz = {
req:info.req,
headers:info.req.headers
}
return rz;
}
}
)
)
But still I got the forbidden error
When I send only one parameter, I got query result like string, not like string[]. This heppend only from UI swagger, if I send from Postman - it works good.
I just want send from swagger-ui one parammeter and got array of string, not string
How I can fix it? Help me please.
Example1: send one paramenter and in my controller I got string like '25'
Example2: when I send 2 parameters in controller I can see array of strings ('25', '21')
export class List {
#ApiProperty({ isArray: true, type: String, required: false })
#IsOptional()
public categories?: string[];
}
You should try to spread your parameter in a const in services
edit:
I don't know how to explain in formal words, but a array of strings of one item, for JAVASCRIPT, seems with the same thing as one string value.
Because array is not a type, but a form of a type....
So, if you, in your controller, before do anything with it, you redeclare as:
#Get(":id")
findManybyId(#Param("id") id: string[]) {
const idArray = [...id];
return await this.service.findManyById(idArray);
}
It will solve your problem about being an array
old answer:
You should try to change in your controller where you make your input decorator.
in your case, i don't know if you are using ID to get, but you must to do as the example:
#ApiOperation({
summary: "Get many categories by ID",
})
async getMany(
#Param("id") ids: string[],
) {
return await this.categoriesService.getMany(id);
}
when you fill a single category the query param will be translated as a string while when you fill in several categories understand it as a array.
to solve this problem I added in DTO :
#Transform(({ value }) => (Array.isArray(value) ? value : Array(value)))
I force the cast to array
I am using neo4j dB and apollo-graphql server for my test project. I am trying to create a Q&A app where a user could create a question (with unique id) and then it would be answered by other user (similar to stackoverflow but with very minimum features).I want to create relationship between "Questions" node and "Users" node in my neo4j db via mutation.
so, in the mutations I created User Type and Question Type. But when I am trying to create relation between the user (who created question) and the question, the mutation is not working as expected.
Here is the graphql schema...
type Question {
questionID: Int! # unique id for a particular question
title: String! # Question Title
details: String!
createdBy: User!
}
type User {
userID: Int!
name: String!
email: String
questions: [Question] # specific questions related to the user
}
type Mutation {
createUser(name: String!, userID: Int!): [User]
createQuestion(questionID: Int!, title: String!, details: String!,userID: Int! ): Question
And here is the mutation snippet from resolver...
Mutation: {
createQuestion(_,params){
let session = driver.session();
let query = "MERGE (q:Questions {questionID:{questionID},title:
{title},details:{details},userID:{userID}})-[:CREATED_BY]->
(u:Users{userID:{userID}}) RETURN q;"
return session.run(query,params)
.then( result => {
return result.records.map( record => {
return record.get("q").properties
}
)
}
)
},
}
I had already inserted a dummy user with unique userID "1" in neo4j db before running this mutation query. And I ran this "createQuestion" mutation in graphiQL to create a question for the existing user and so I passed userID "1" as argument,
//createQuestion(questionID: Int!, title: String!, details: String!,userID: Int! )
And after running this mutation query, I was hoping to get a relation of [:CREATED_BY] between the existing user and the question created,
But my database now has 2 users with same user id "1" (i.e. one more user with same userid "1" has been inserted and the relationship is now present between this newly inserted user and question node).
What I need is to avoid to create new duplicate user with same id , and rather just to create relationship between the existing user and the question. So could someone please let me know what I have mistaken here ?
You must change your query:
let query = "
MERGE (u:Users{userID:{userID}})
MERGE (q:Questions {questionID:{questionID},title:
{title},details:{details},userID:{userID}})
MERGE (q)-[:CREATED_BY]->(u)
RETURN q;"
As you have the questionID already in place, which i'm guessing is a unique identifier I would change the query to:
let query = "
MERGE (u:Users{userID:{userID}})
MERGE (q:Questions {questionID:{questionID})
ON CREATE SET q.title=
{title},q.details={details}
MERGE (q)-[:CREATED_BY]->(u)
RETURN q;"
Notice I also removed userID property from question as you do not need it as you already create a relationship from User to Question
Edit:
You can solve the problem from the comment in two ways:
by adding UserID property to question:
let query = "
MERGE (u:Users{userID:{userID}})
MERGE (q:Questions {questionID:{questionID},userID:{userID}})
ON CREATE SET q.title= {title},q.details={details},
MERGE (q)-[:CREATED_BY]->(u)
RETURN q;"
Or with local merges:
let query = "
MERGE (u:Users{userID:{userID}})
MERGE (u)<-[:CREATED_BY]-(q:Questions {questionID:{questionID}})
ON CREATE SET q.title= {title},q.details={details},
RETURN q;"
I'm using Sync from HyperOslo i get a simple JSON object:
Printed json object (user) =>
[{
email = "email#email.fr";
name = "Damian Menestrel";
}]
...to convert in Core Data User with the method:
Sync.changes(user , inEntityNamed: "User", dataStack: DataManager.dataStack, completion: { (response ) -> Void in
})
The app crash with this error:
Assertion failure in +[Sync
changes:inEntityNamed:predicate:parent:inContext:dataStack:completion:],
.../Pods/Sync/Source/Sync.m:77
Where this error come from?
My CodeData model is :
User.swift
import Foundation
import CoreData
import UIKit
class User: NSManagedObject {
#NSManaged var email: String?
#NSManaged var name: String?
}
Short answer:
Just add a remoteID attribute to your Core Data model as a primary key and it will work. This will map to the id attribute in your JSON.
Long answer:
Taken from the Primary Key section on Sync's README.
Sync requires your entities to have a primary key, this is important
for diffing otherwise Sync doesn’t know how to differentiate between
entries.
By default Sync uses id from the JSON and remoteID from Core
Data as the primary key. You can mark any attribute as primary key by
adding hyper.isPrimaryKey and the value YES.
For example in our Designer
News
project we have a Comment entity that uses body as the primary
key.
We have an API command like "student/create" to create a new student object. The code looks like this:
def student = new Student(firstName: firstName, lastName: lastName, email: email)
if (! student.validate()) {
response.error = "UNKNOWN_ERROR" // in case we can't find anything better
student.errors.allErrors.each { error ->
// set response.error to an appropriate value
println error
}
} else {
student.save()
}
Our goal is to give a reasonable error message like "EMAIL_DUPLICATE" or "FIRSTNAME_LENGTH" on a validation fail, so we want to test the errors we get against a set of expected errors so we can respond like that.
Here's what we get from that println:
Field error in object 'com.example.Student' on field 'email': rejected value [student#example.com]; codes [com.example.Student.email.unique.error.com.example.Student.email,com.example.Student.email.unique.error.email,com.example.Student.email.unique.error.java.lang.String,com.example.Student.email.unique.error,student.email.unique.error.com.example.Student.email,student.email.unique.error.email,student.email.unique.error.java.lang.String,student.email.unique.error,com.example.Student.email.unique.com.example.Student.email,com.example.Student.email.unique.email,com.example.Student.email.unique.java.lang.String,com.example.Student.email.unique,student.email.unique.com.example.Student.email,student.email.unique.email,student.email.unique.java.lang.String,student.email.unique,unique.com.example.Student.email,unique.email,unique.java.lang.String,unique]; arguments [email,class com.example.Student,student#example.com.org]; default message [Property [{0}] of class [{1}] with value [{2}] must be unique]
How can I figure out that this means the email is already used in the database so that I can tell the API user that?
(to be clear, I want to give a computer-readable message like "EMAIL_DUPLICATE" instead of something like "Property email of class Student with value student#example.com must be unique")
Not sure it would work in more situations than just this one, but does:
println "${error.objectName}_${error.codes[-1]}".toUpperCase()
Get you anywhere near?