update to cache on apollo graphql - javascript

I have a list of contacts where I can add a new contact. As soon a new contact is added, I need to show it on the list of contacts without using refetchQueries. I tried to do this watching on the apollo documentation
addContact(
data: ContactInputData
): ContactPayload
type ContactPayload {
status: Int!
error: ErrorData
data: ContactResponseData
}
type ContactResponseData {
id: String!
}
type ContactInputData {
country: String!
address: String!
company: String!
number: String!
name: String!
email: String!
visibility: Boolean
}
// list of contacts
contacts: ContactListPayload!
type ContactListPayload {
status: Int!
error: ErrorData
data: [Client]!
}
type Client {
id: ID!
email: String!
number: String!
visibility: String!
client_id: String!
country: String!
profile_picture: String!
tags: [Tag]!
notes: [Note]!
}
const formSubmit = async (val: AddContactInputs) => {
console.log('val', val);
const response = await addContact({
variables: {
data: {
...val,
country: '',
address: '',
company: ''
}
},
optimisticResponse: {
__typename: 'Mutation',
addContact: {
__typename: 'ContactPayload',
data: {
// not sure the shape of data
}
}
},
update: (client, { data: { addContact } }) => {
console.log('client', client);
console.log('addContact', addContact);
// if (addContact.status === 201) {}
// read data from our cache
const data: any = client.readQuery({ query: CONTACTS });
console.log('data from cache', data);
// write our data back to cache with the new contacts in it
const newData: any = {
...data,
contacts: { ...data.contacts, data: [...data.contacts.data, addContact.data] }
};
console.log('newData to write', newData);
client.writeQuery({ query: CONTACTS, data: newData });
}
});
console.log('response', response)
};
However, I am confused on the shape of data on optimisticResponse. If you see addContact response model, it just sends id to me. How am i supposed to update the list of contacts where it mainly needs name, email, number?

Related

How do I reference mongoose model to another model?

I have a mongoose schema for stories that looks like this:
{
id: {
type: Number,
default: 0
},
title: {
type: String,
maxLength: 60
},
author: {
userid: {
type: Number
},
username: {
type: String
}
}
chapters: [chapter],
numchapters: {
type: Number,
default: 1
},
favs: {
type: Number,
default: 0
},
completed: {
type: Boolean,
default: false
}
}
What I'm trying to do is reference a document in a separate collection (users), and use the values of its userid and username fields in the author field.
how do I do this?
current code:
storyobj.populate('author', {path: 'author', model: 'users', select: 'userid username'}, (err) => {
if (err) {
console.log(err)
}
})
just in case it's relevant, the structure of the users collection looks like this:
{
username: {
type: String,
},
email: {
type: String,
},
password: {
type: String,
},
userid: {
type: Number
},
isAdmin: {
type: Boolean,
default: false
},
banned: {
type: Boolean,
default: false
}
}
EDIT:
I've changed the author field in the Stories model to look like this:
author: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
}
This is so I tell Mongoose, "Hey, I want this field to reference a user in the User collection".
Here are some more details that I hope will be of help.
Full code:
var storydb = require('../models/stories/story');
var chapterdb = require('../models/stories/chapter');
var userdb = require('../models/user');
const file = JSON.parse(fs.readFileSync('test.json')); // this is a file with the data for the stories I am trying to insert into my database
for (const d in file) {
var storyobj = new storydb({
id: d,
chapters: []
});
for (let e = 0; e < file[d].length; e++) {
var abc = file[d][e];
var updatey = {
chaptertitle: abc.chapter,
chapterid: parseInt(abc.recid),
words: abc.wordcount,
notes: abc.notes,
genre: abc.gid.split(', '),
summary: abc.summary,
hidden: undefined,
loggedinOnly: undefined,
posted: new Date(Date.parse(abc.date)),
bands: abc.bandnames.split(', ')
};
var kbv = getKeyByValue(userlookup, abc.uid);
storyobj.title = abc.title;
storyobj.numchapters = file[d].length;
storyobj.favs = file[d][0].numfavs;
updatey.characters = abc.charid.split(/, |,/g);
storyobj.chapters.push(updatey)
}
storyobj.save();
}
In file, there's a unique ID representing the author of each story. kbv returns the userid associated with that unique ID (note that they're NOT the same).
Now, here's where I'm having trouble:
What I want to do is find a user matching the userid in kbv, and make that the author property in the story model.
The code I'm currently using to try and achieve that:
storydb.findOne({storyobj}, 'author').populate("author", (f) => console.log(f));
const Stories = require("./path/to/model");
Stories
.find({ /* query */ }, { /* projection */ })
.populate("author.username", ["userid", "username"])
.then(/* handle resolve */)
.catch(/* handle rejection */)
For this to work, you have to add a ref key to the userid key in your model, where the ref value is the name of the model it's referencing.
Story.model.js
const StorySchema = new Schema({
author: {
userid: { type: Schema.Types.ObjectId, ref: "users", required: true },
/* other props */
}
/* other props */
});

Dynamic query in mongo and NodeJs asking for fields of documents embedded?

I am trying to make a dynamic query based on multiple selection of the user.
In my application I have the Publication schema that has the Pet schema embedded as follows:
var status = ["public", "private", "deleted"];
var publication_schema = new Schema({
pet:{
type: Schema.Types.ObjectId,
ref: "Pet"
},
status: {
type: String,
enum: status,
default: status[0]
}
});
module.exports = mongoose.model('Publication', publication_schema);
var pet_schema = new Schema({
type: {
type: String,
require: true
},
createdDate: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('Pet', pet_schema);
Insyde an async method I build the query, getting all the user input values from the object filter, also I have the query object where I push the different criteria and use it with an $and
let query = {};
let contentQuery = []
if (filter.public && !filter.private) {
contentQuery.push({ status: { $eq: "public" } });
} else if (filter.privada && !filter.public) {
contentQuery.push({ status: { $eq: "private" } });
}
query = { $and: contentQuery }
try {
const publication = await Publication.find(query).populate('pet');
} catch (e) {
console.log(e)
}
the problem is when I want to add more criteria such as follows:
if (filter.specie) { // for example filter.specie equals 'cat'
contentQuery.push({ pet: { type: { $eq: filter.specie } } });
}
I get the error:
'Cast to ObjectId failed for value "{ type: { \'$eq\': \'cat\' } }" at path "pet" for model "Publication"',
name: 'CastError',
stringValue: '"{ type: { \'$eq\': \'cat\' } }"',
kind: 'ObjectId',
value: { type: { '$eq': 'cat' } },
path: 'pet',
reason: undefined,
model: Model { Publication } }
So. How can I do to query the fields of publication and also the pet fields inside publication?
You can have a look on Populate Query Conditions
Instead of .populate('pet') you could do something like
Publication.find({})
.populate({
path: 'pet',
match: { specie: 'cat'},
// You can select the fields you want from pet, or remove the select attribute to select all
select: 'name -_id',
// Here you could add options (e.g. limit)
options: { limit: 5 }
}).exec();
The above query will get you all Publications with pet.specie equals to 'cat'

Stripe : Error: Received unknown parameter: bank_account[bank_name]

I have been trying to add a bank_name to my Stripe Connect user's external account, but I keep getting an error as if I am misreading the documentation on the function.
Error: Received unknown parameter: bank_account[bank_name]
The documentation shows that I should be able to access the bank_name from the bank_account object, but my error is narrowed down to it being null. My console.log(newValue.externalAccount.bankName) returns the bankName as expected that was entered, so it isn't null going in. Any idea why I am getting this error?
Firebase Function:
exports.createStripeAccount = functions.firestore
.document("users/{userId}")
.onUpdate(async (change, context) => {
const newValue = change.after.data();
const previousValue = change.before.data();
if (newValue.state === "technician" && previousValue.state === "client") {
try {
const account_add_response = await stripe.accounts.create(
{
type: "custom",
country: "US",
requested_capabilities: ["platform_payments"],
email: newValue.email,
tos_acceptance: newValue.stripeTosAcceptance,
business_type: "individual",
business_profile: {
url: newValue.socialLinks.linkedin
},
individual: {
first_name: newValue.firstName,
last_name: newValue.lastName,
gender: newValue.gender,
email: newValue.email,
phone: newValue.phone,
address: {
line1: newValue.address.line1,
line2: newValue.address.line2,
city: newValue.address.city,
state: newValue.address.state,
postal_code: newValue.address.zip,
country: newValue.address.country
},
ssn_last_4: newValue.technician.ssnLast4,
dob: {
day: newValue.dob.day,
month: newValue.dob.month,
year: newValue.dob.year
}
}
},
async function(error, account) {
if (error) {
return console.error(error);
} else {
console.log(
"Writing account.id " + account.id + " to user DB..."
);
console.log("newValue.externalAccount.bankName: " + newValue.externalAccount.bankName)
const bank_add_response = await stripe.accounts.createExternalAccount(
account.id,
{
external_account: {
object: "bank_account",
country: "US",
currency: "USD",
account_holder_name:
newValue.externalAccount.accountHolderName, // Have user input manually, might be different than user's name
account_holder_type: "individual",
bank_name: newValue.externalAccount.bankName,
routing_number: newValue.externalAccount.routingNumber,
account_number: newValue.externalAccount.accountNumber
}
},
function(error, bank_account) {
if (error) {
return console.error(error);
} else {
console.log(
"Writing bank_account.id " +
bank_account.id +
" to user DB..."
);
return admin
.firestore()
.collection("users")
.doc(context.params.userId)
.set(
{
connectId: account.id,
externalAccount: {
bankAccountId: bank_account.id,
bankName: bank_account.bank_name,
last4: bank_account.last4,
}
},
{ merge: true }
);
}
}
);
}
}
);
} catch (error) {
console.log(error);
await change.ref.set(
{ error: userFacingMessage(error) },
{ merge: true }
);
return reportError(error, { user: context.params.userId });
}
}
});
Looks like I misunderstood the purpose of the bank_name field. I thought it was for a custom name the user defines about their bank account, like "Doug's Chase Checkings", but it seems that it's auto generated by Stripe and read only.

How to manage Validation vs raw values in Objection.js

I have this model in Objection.js:
class UserAccess extends Model {
static get tableName() {
return 'user_access'
}
static get jsonSchema() {
return {
type: 'object',
properties: {
id: {
type: 'integer'
},
user_id: {
type: 'integer'
}
timestamp: {
type: 'string',
format: 'date-time'
},
},
additionalProperties: false
}
}
$beforeInsert() {
this.timestamp = this.$knex().raw('now()')
}
static async insert(data) {
const result = await this.query().insert(data)
return result.id
}
I need to insert the database time in timestamp column. When Objection executes the validation, the value of timestamp is an intance of Raw Knex Function:
Raw {
client:
Client_MySQL2 {
config: { client: 'mysql2', connection: [Object] },
connectionSettings:
{ user: '',
password: '',
host: '',
database: '' },
driver:
{ createConnection: [Function],
connect: [Function],
// continues
So when validation is executed, an error is returned, since it isnt a string:
Validation error: "timestamp" should be a string
Is there any way to use the database time and keep the validation?
You have to chain the query builder with a ".then()" to get the query result or use async/await. This might work:
async $beforeInsert() {
this.timestamp = await this.$knex().raw('now()')
}
or:
$beforeInsert() {
this.timestamp = this.$knex().raw('now()').then(function(result) {
console.log(result)
})
}
https://knexjs.org/#Interfaces

Deep query to nested types in GraphQL return NULL

Have a strange problem...
Query to nested types return null.
But, if I return anything in parent type - resolve return right result
My code:
import { GraphQLList, GraphQLString, GraphQLID, GraphQLObjectType, GraphQLSchema } from 'graphql';
import AdminModel from '../models/Admin.model';
const AdminType = new GraphQLObjectType({
name: 'AdminType',
fields: {
_id: { type: GraphQLID },
login: { type: GraphQLString },
password: { type: GraphQLString }
}
});
const AdminRooteType = new GraphQLObjectType({
name: 'AdminRooteType',
fields: {
getAdmins: {
type: new GraphQLList(AdminType),
resolve() {
return AdminModel.find({})
}
}
}
})
export default new GraphQLSchema({
query: new GraphQLObjectType({
name: 'RootQuery',
fields: {
admin: {
type: AdminRooteType,
resolve() {
// EMPTY RESOLVE - EMPTY RESULT
}
}
}
})
});
Query:
{
admin {
getAdmins {
login
}
}
}
Result:
{
"data": {
"admin": null
}
}
If I changed returned value in fields admin in RootQuery:
import { GraphQLList, GraphQLString, GraphQLID, GraphQLObjectType, GraphQLSchema } from 'graphql';
import AdminModel from '../models/Admin.model';
const AdminType = new GraphQLObjectType({
name: 'AdminType',
fields: {
_id: { type: GraphQLID },
login: { type: GraphQLString },
password: { type: GraphQLString }
}
});
const AdminRooteType = new GraphQLObjectType({
name: 'AdminRooteType',
fields: {
getAdmins: {
type: new GraphQLList(AdminType),
resolve() {
return AdminModel.find({})
}
}
}
})
export default new GraphQLSchema({
query: new GraphQLObjectType({
name: 'RootQuery',
fields: {
admin: {
type: AdminRooteType,
#resolve() {#
#// RETURN ANYTHING HERE:#
# return 'foobar'#
}
}
}
})
});
I've got expected result:
{
"data": {
"admin": {
"getAdmins": [
{
"login": "123"
},
{
"login": "12asdf3"
}
]
}
}
}
What is right solution for this issue? (without using dummy values in return)
Thank's a lot!
What you are seeing is the expected behavior. Imagine we have a User type with some fields:
const UserType = new GraphQLObjectType({
name: 'User',
fields: {
id: { type: GraphQLID },
name: { type: GraphQLString },
}
})
And a way to fetch a single user:
const QueryType = new GraphQLObjectType({
name: 'Query',
fields: {
user: {
type: AdminRooteType,
resolve: () => getUser(),
},
},
})
If getUser returns an object representing a User, the resolvers for all the fields (i.e. id and name on the User type will be called.
When those fields (and whatever child fields they might have) resolve, you end up with a User object for the entire user field to return. A response might look something like:
"data": {
"user": {
"id": 1,
"name": "Maria",
"comments": [
{
"id": 1,
// and so on...
}
]
}
}
Now, consider what happens when a user is not found and we return null instead. Our response looks like this:
"data": {
"user": null
}
It doesn't make sense to call any of the resolvers for the User fields. Would you expect the API to still return an id or name in this case? If it did, what values would those fields have? If we just returned a null id and name, how would the client distinguish that object from a User that existed but really did have id and name null values?
The point is, if a field returns a GraphQLObjectType and it resolves to null, none of the resolvers on the GraphQLObjectType will be called.
By unnecessarily nesting your getAdmins field inside another GraphQLObjectType, you're forced to return some kind of object inside the resolver for admin. So you will need to either live with that, or avoid creating an AdminRootType altogether and just put the getAdmins field on your Query type directly, as per convention:
const QueryType = new GraphQLObjectType({
name: 'Query',
fields: {
getAdmins: {
type: new GraphQLList(AdminType),
resolve: () => AdminModel.find({}),
},
},
})

Categories