How to save dynamic number of variables into database using graphql? - node.js

I'm trying to mutate and query dynamic variables. The user has the choice to add as many variables as they want before sending them off to the server. For example, my app is a productivity app that allows a user to add as many metrics as they want to track their goal so if "Gym" is their goal, the metrics would be "running", "bench press", etc. My problem is, I'm unsure how to save them in the database since there is no pre-configured Schema for these user-created variables.
I've managed to send the variables to the back end using the following:
mutation CreateGoal ($title: String!, $description: String, $metric: [Json!]) {
createGoal(
data: {
title: $title
description: $description
metric: { set: $metric }
}
){
id
}
}
Schema:
type Mutation {
createGoal(data: CreateGoalInput!): Goal!
}
input CreateGoalInput {
title: String!
description: String
metric: GoalCreatemetricInput
}
input GoalCreatemetricInput {
set: [Json!]
}
Once the variables arrive in the resolver, it's in the Json format:
{ set: [ 'running', 'bench press' ] }
Normally, I'd simply save the variables through Prisma:
async createGoal(parent, { data }, { request, prisma }, info) {
const { title, description, metric } = data && data
return prisma.mutation.createGoal({
data: {
user: {
connect: {
email: user.email
}
},
title,
description,
}
}, info)
},
However, since the number of variables are unknown, how do I save 'metric' into my database?
If I were to try the following:
async createGoal(parent, { data }, { request, prisma }, info) {
const { title, description, metric } = data && data
return prisma.mutation.createGoal({
data: {
user: {
connect: {
email: user.email
}
},
title,
description,
metric,
}
}, info)
},
I get the error:
Error: Variable "$_v0_data" got invalid value [ "running", "bench
press" ] at "_v0_data.metric"; Field "0" is not defined by type
GoalCreatemetricInput.
If I were to try:
async createGoal(parent, { data }, { request, prisma }, info) {
const { title, description, metric } = data && data
return prisma.mutation.createGoal({
data: {
user: {
connect: {
email: user.email
}
},
title,
description,
metric: metric.set
}
}, info)
},
I get the error:
Error: Variable "$_v0_data" got invalid value ["running", "bench
press"] at "_v0_data.metric"; Field "0" is not defined by type
GoalCreatemetricInput. Variable "$_v0_data" got invalid value
["Asdfasdf", "Asdfasdf"] at "_v0_data.metric"; Field "1" is not
defined by type GoalCreatemetricInput.

I don't think you need to use the Json scalar at all. It looks like you're trying to pass an array of strings so instead of [Json!] you may just need to use [String!].
input CreateGoalInput {
title: String!
description: String
metric: [String!]
}
Then you should be able to get rid of
input GoalCreatemetricInput {
set: [Json!]
}
Here you should be able to pass the array of strings to the backend:
mutation CreateGoal ($title: String!, $description: String, $metric: [String!]) {
createGoal(
data: {
title: $title
description: $description
metric: $metric
}
){
id
}
}
And in your resolover I think all you need to do is:
async createGoal(parent, { data }, { request, prisma }, info) {
const { title, description, metric } = data && data
return prisma.mutation.createGoal({
data: {
user: {
connect: {
email: user.email
}
},
title,
description,
metric: { set: metric },
}
}, info)
},

Related

node-ews Update email to mark as read

I'm using "node-ews" library version 3.5.0, but when I try to update any property I get the following error:
{
"ResponseMessages":{
"UpdateItemResponseMessage":{
"attributes":{
"ResponseClass":"Error"
},
"MessageText":"An internal server error occurred. The operation failed., Object reference not set to an instance of an object.",
"ResponseCode":"ErrorInternalServerError",
"DescriptiveLinkKey":0,
"Items":null
}
}
}
I'm trying to mark email as read using the following code:
const markFolderAsRead = async (ews, id, changeKey) => {
const args = {
attributes: {
MessageDisposition: "SaveOnly",
},
ItemChanges: {
ItemChange: {
ItemId: {
attributes: {
Id: id,
ChangeKey: changeKey,
},
},
Updates: {
SetItemField: {
FieldURI: {
attributes: {
FieldURI: "message:IsRead",
},
Message: {
IsRead: true,
},
},
},
},
},
},
};
await ews.run("UpdateItem", args).then((result) => {
console.log("email read:", JSON.stringify(result));
});
};
I tried several modifications, including trying to update another fields, but none of it worked.
I followed this documentation: https://learn.microsoft.com/pt-br/exchange/client-developer/web-service-reference/updateitem-operation
And the lib doesn't show any example of it, but when I change the json to a wrong "soap" construction the error show different messages, or even if I do not pass any of the parameters required as "ChangeKey".
So, maybe this error is something relate to microsoft ews soap construction that I'm missing parameters, or so.
Got it working!
My JSON was wrong. The FieldURI was finishing after the message attribute, it should be before.
Correct JSON:
const args = {
attributes: {
MessageDisposition: "SaveOnly",
ConflictResolution: "AlwaysOverwrite",
SendMeetingInvitationsOrCancellations: "SendToNone",
},
ItemChanges: {
ItemChange: {
ItemId: {
attributes: {
Id: id,
ChangeKey: changeKey,
},
},
Updates: {
SetItemField: {
FieldURI: {
attributes: {
FieldURI: "message:IsRead",
},
},
Message: {
IsRead: "true",
},
},
},
},
},
};

How to manage GraphQL child objectType that can be nullable in an output type?

I'm setting up a nodeJS GraphQL API and I'm experimenting a blocking point regarding one of my resource output type.
The feature is a form that contain three different level :
Level 1- formTemplate
Level 2- formItems (templateId, type (video, image, question) - 1-N relation with formTemplate)
Level 3- formQuestions (0-1 relation with formItem if and only if formItems.type is 'question')
My GraphQL resource is returning all the templates in the database so it's an array that for each template is returning all his items and each item of type "question" needs to return an array containing the associated question.
My problem is : I really don't know how to return an empty object type for the formItems where type is different from "question" or if there is a better approach for this kind of situation
I've tried to look at GraphQL directives and inline fragments but I think it really needs to be manage by the backend side because it's transparent for the API consumer.
const formTemplate = new GraphQLObjectType({
name: 'FormTemplate',
fields: () => {
return {
id: {
type: new GraphQLNonNull(GraphQLInt)
},
authorId: {
type: new GraphQLNonNull(GraphQLInt)
},
name: {
type: new GraphQLNonNull(GraphQLString)
},
items: {
type: new GraphQLList(formItem),
resolve: parent => FormItem.findAllByTemplateId(parent.id)
}
}
}
})
const formItem = new GraphQLObjectType({
name: 'FormItem',
fields: () => {
return {
id: {
type: new GraphQLNonNull(GraphQLInt)
},
templateId: {
type: new GraphQLNonNull(GraphQLInt)
},
type: {
type: new GraphQLNonNull(GraphQLString)
},
question: {
type: formQuestion,
resolve: async parent => FormQuestion.findByItemId(parent.id)
}
}
}
})
const formQuestion= new GraphQLObjectType({
name: 'FormQuestion',
fields: () => {
return {
id: {
type: new GraphQLNonNull(GraphQLInt)
},
itemId: {
type: new GraphQLNonNull(GraphQLInt)
},
type: {
type: new GraphQLNonNull(GraphQLString)
},
label: {
type: new GraphQLNonNull(GraphQLString)
}
}
}
})
My GraphQL request :
query {
getFormTemplates {
name
items {
type
question {
label
type
}
}
}
}
What I'm expected is
{
"data": {
"getFormTemplates": [
{
"name": "Form 1",
"items": [
{
"type": "question",
"question": {
"label": "Question 1",
"type": "shortText"
},
{
"type": "rawContent"
"question": {}
}
]
}
]
}
}
I'd design your "level 2" items so that the "type" property corresponded to actual GraphQL types, implementing a common interface. Also, in general, I'd design the schema so that it had actual links to neighboring items and not their identifiers.
So if every form item possibly has an associated template, you can make that be a GraphQL interface:
interface FormItem {
id: ID!
template: FormTemplate
}
Then you can have three separate types for your three kinds of items
# Skipping VideoItem
type ImageItem implements FormItem {
id: ID!
template: FormTemplate
src: String!
}
type QuestionItem implements FormItem {
id: ID!
template: FormTemplate
questions: [FormQuestion!]!
}
The other types you describe would be:
type FormTemplate {
id: ID!
author: Author!
name: String!
items: [FormItem!]!
}
type FormQuestion {
id: ID!
question: Question
type: String!
label: String!
}
The other tricky thing is, since not all form items are questions, you have to specifically mention that you're interested in questions in your query to get the question-specific fields. Your query might look like
query {
getFormTemplates {
name
items {
__typename # a GraphQL builtin that gives the type of this object
... on Question {
label
type
}
}
}
}
The ... on Question syntax is an inline fragment, and you can similarly use it to pick out the fields specific to other kinds of form items.
Thank you David for your answer !
I've figured it out how to solve my problem using inline fragments and UnionTypes that seems to be the most adapted for this use case. Here is the code :
const formItemObjectType = new GraphQLUnionType({
name: 'FormItemObject',
types: [formItemContent, formItemQuestion],
resolveType(parent) {
switch (parent.type) {
case ('question'): return formItemQuestion
default: return formItemContent
}
}
})
and the GraphQL query using inline fragment:
query {
getFormTemplates {
name
items {
...on FormItemContent {
type,
meta
}
...on FormItemQuestion {
type,
meta,
question {
label
}
}
}
}
}

Data null after saving entity with Moongose and GraphQL

When saving an entity with mongoose and graphql the following happens:
First method to save:
create(budgetProps){
const budget = new Budget(budgetProps);
return budget.save();
}
The result is as follows:
{
"data": {
"addBudget": {
"_id": "59fbdefaa7b0a81180dd2c9c",
"tiempoAproximado": 2245.5,
"User": {
"name": null,
"organization": null
},
"Vehicle": {
"name": null,
"type": null
}
}
}
}
Using this method:
create(budgetProps){
const budget = new Budget(budgetProps);
return budget.save().then((res)=>{
Budget.findById(res._id)
.populate('User')
.populate('Vehicle')
.exec((err, newBudget)=> {
return newBudget;
});
});
},
I get the following:
{
"data": {
"addBudget": null
}
}
This is the Schema:
const typeDefs = `
scalar Date
input UserInput {
_id: ID,
name: String,
organization: String,
phones: [String],
emails: [String],
type: String,
password: String,
percentaje: String
}
input VehicleDescriptionInput {
es: String,
en: String
}
input VehicleInput{
_id: ID,
name: String,
passengers: Int,
largeBags: Int,
smallBags: Int,
doors: Int,
type: String,
status: Boolean,
imagesUrls: [String],
description: VehicleDescriptionInput
}
input FinalTotalCostInput {
es: String,
en: String
}
input BudgetTotalCostInput {
es: String,
en: String
}
input BudgetInput {
finalTotalCost: FinalTotalCostInput,
budgetTotalCost: BudgetTotalCostInput,
destinoInicial: String,
destinoFinal: String,
tiempoAproximado: Float,
distancia: Float,
tollCost: Float,
tolls: [String],
budgetDate: Date,
aprove: Boolean,
User: UserInput,
Vehicle: VehicleInput
}
type Mutation {
addBudget(data: BudgetInput): Budget
}
`;
Here is the resolver:
Mutation: {
addBudget: (_, {data}) =>{
return BudgetController.create(data);
}
},
Finally here is the mutation with its variables:
mutation addBudget($budget: BudgetInput) {
addBudget(data: $budget) {
_id
User{
name
organization
}
Vehicle{
name
type
}
}
}
{
"budget": {
"finalTotalCost": {
"es": "100 peso",
"en": "10 dolars"
},
"budgetTotalCost": {
"es": "80 peso",
"en": "8 dolars"
},
"destinoInicial": "Queretaro",
"destinoFinal": "Sonora",
"tiempoAproximado": 2245.5,
"distancia": 100.565,
"tollCost": 20.5,
"tolls": [
"GDL",
"Marina",
"Culap",
"MalageƱa"
],
"budgetDate": "2017/07/21",
"aprove": false,
"User": {
"_id": "59fbcc42aa82460924e5fbad"
},
"Vehicle": {
"_id": "59fbcbe4aa82460924e5fbac"
}
}
}
The entity is stored properly in the database, when Console.log the result of the populated search results are correct then I do not understand what is happening.
You can find the whole app in the following link: GitHub Repo
You're mixing Promises and callbacks. exec() will return a Promise, but only if doesn't have any arguments passed to it. Additionally, you need to return the Promise that's returned by exec().
return budget.save().then((res) => {
return Budget.findById(res._id) // missing return here
.populate('User')
.populate('Vehicle')
.exec() // don't need anything else
})
You can clean this up a little more:
return budget.save()
.then(res => Budget.findById(res._id)
.populate('User')
.populate('Vehicle')
.exec())
If you need to transform the results returned by findById before turning them over to the client:
return budget.save()
.then(res => Budget.findById(res._id)
.populate('User')
.populate('Vehicle')
.exec())
.then(res => {
res.foo = 'Foo'
return res
})

GraphQL - show list or single object

I have managed to get following data through GraphQL:
{
"data": {
"city": {
"name": "Eldorado",
"users": [
{
"username": "lgraham1"
},
{
"username": "ehowell"
},
{
"username": "cbauch"
}
]
}
}
}
I have QueryType, CityType and UserType. In my QueryType I fetch city and display users by GraphQLList(UserType). What should I do if I want to display single user if there is an id provided?
My API looks like this:
all cities:
/cities/
single city:
/cities/:city_id
users for particular city:
/cities/:city_id/users
single user:
/cities/:city_id/users/:user_id
You'll need to add a user query to your main Query object.
Assuming your id is an Integer, you would do this
const Query = new GraphQLObjectType({
name: 'RootQuery',
fields: {
// ...
user: {
type: User,
args: {
id: {
type: new GraphQLNonNull(GraphQLInt)
}
},
resolve: function(rootValue, args) {
return db.users.findOne(args)
}
}
}
})
const Schema = new GraphQLSchema({
query: Query,
// ...
});
Then you can query using
{
user (id: 12345) {
...
}
}
Or you could make a function
query findUser ($id: Int!) {
user (id: $id) {
...
}
}

Unable to fetch list in react relay

I am following schema same as mentioned here
I want to fetch all users so I updated my schema like this
var Root = new GraphQLObjectType({
name: 'Root',
fields: () => ({
user: {
type: userType,
resolve: (rootValue, _) => {
return getUser(rootValue)
}
},
post: {
type: postType,
args: {
...connectionArgs,
postID: {type: GraphQLString}
},
resolve: (rootValue, args) => {
return getPost(args.postID).then(function(data){
return data[0];
}).then(null,function(err){
return err;
});
}
},
users:{
type: new GraphQLList(userType),
resolve: (root) =>getUsers(),
},
})
});
And in database.js
export function getUsers(params) {
console.log("getUsers",params)
return new Promise((resolve, reject) => {
User.find({}).exec({}, function(err, users) {
if (err) {
resolve({})
} else {
resolve(users)
}
});
})
}
I am getting results in /graphql as
{
users {
id,
fullName
}
}
and results as
{
"data": {
"users": [
{
"id": "VXNlcjo1Nzk4NWQxNmIwYWYxYWY2MTc3MGJlNTA=",
"fullName": "Akshay"
},
{
"id": "VXNlcjo1Nzk4YTRkNTBjMWJlZTg1MzFmN2IzMzI=",
"fullName": "jitendra"
},
{
"id": "VXNlcjo1NzliNjcyMmRlNjRlZTI2MTFkMWEyMTk=",
"fullName": "akshay1"
},
{
"id": "VXNlcjo1NzliNjgwMDc4YTYwMTZjMTM0ZmMxZWM=",
"fullName": "Akshay2"
},
{
"id": "VXNlcjo1NzlmMTNkYjMzNTNkODQ0MmJjOWQzZDU=",
"fullName": "test"
}
]
}
}
but If I try to fetch this in view as
export default Relay.createContainer(UserList, {
fragments: {
userslist: () => Relay.QL`
fragment on User #relay(plural: true) {
fullName,
local{
email
},
images{
full
},
currentPostCount,
isPremium,
}
`,
},
});
I am getting error Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.
Please tell me what I am missing .
I tried a lot with and without #relay(plural: true).
Also tried to update schema with arguments as
users:{
type: new GraphQLList(userType),
args: {
names: {
type: GraphQLString,
},
...connectionArgs,
},
resolve: (root, {names}) =>connectionFromArray(getUsers(names)),
},
but I got error Cannot read property 'after' of undefined in implementing react-relay
Thanks in Advance.
Relay currently only supports three types of root fields (see facebook/relay#112):
Root field without arguments, returning a single node:
e.g. { user { id } } returning {"id": "123"}
Root field with one argument, returning a single node:
e.g. { post(id: "456") { id } } returning {"id": "456"}
Root field with one array argument returning an array of nodes with the same size as the argument array (also known as "a plural identifying root field"):
e.g. { users(ids: ["123", "321"]) { id } } returning [{"id": "123"}, {"id": "321"}]
A workaround is to create a root field (often called viewer) returning a node that has those fields. When nested inside the Viewer (or any other node), fields are allowed to have any return type, including a list or connection. When you've wrapped the fields in this object in your GraphQL server, you can query them like this:
{
viewer {
users {
id,
fullName,
}
}
}
The Viewer type is a node type, and since there will just be one instance of it, its id should be a constant. You can use the globalIdField helper to define the id field, and add any other fields you want to query with Relay:
const viewerType = new GraphQLObjectType({
name: 'Viewer',
interfaces: [nodeInterface],
fields: {
id: globalIdField('Viewer', () => 'VIEWER_ID'),
users:{
type: new GraphQLList(userType),
resolve: (viewer) => getUsers(),
},
},
});
On the client you'll need to change the root query in your route to { viewer } and define the fragment on Viewer:
export default Relay.createContainer(UserList, {
fragments: {
viewer: () => Relay.QL`
fragment on Viewer {
users {
fullName,
local {
email,
},
images {
full,
},
currentPostCount,
isPremium,
}
}
`,
},
});

Resources